• 网络编程(五)


    网络服务器超时检测

    网络通信中,很多操作会使得进程阻塞
    TCP套接字中的recv/accept/connect
    UDP套接字中的recvfrom
    超时检测的必要性:
    1、避免进程在没有数据时无限制地阻塞
    2、当设定的时间到时,进程从原操作返回继续运行

    使用select进行超时检测

    如果该服务器使用select实现的,可以使用select的最后一个参数做超时检测

    #include
    #include
    #include
    int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
    //参数1:需要监控的最大文件描述符的个数,总数
    //参数2:指针指向读文件描述符集合表
    //参数3:指针指向写文件描述符集合表
    //参数4:指针指向异常文件描述符集合表
    //参数5:① ② ③
    //①NULL—>将select变成阻塞函数
    //②0—>仅仅检测文件描述符的状态,然后立即返回,不会置位
    //③时间值—>表示select的监控超时时间。时间到达之前select监控到有IO通道活跃,select返回通道路数并置位;超时时间到,select没有监控到任何IO通道有数据,那么select返回0
    //返回值:成功返回监控到的有数据发生的通道个数;没有活跃则没有返回,则一直阻塞,失败返回-1

    *第四个参数struct timeval timeout
    在这里插入图片描述
    在这里插入图片描述

    套接字属性

    socket—>有socket层,IP层,tcp/udp层属性

    getsockopt:获取socket软通道的某项属性值

    #include /* See NOTES */
    #include
    int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
    //参数1:文件描述符–>标识一个网络软通道
    //参数2:level指定控制套接字的层次(SOL_SOCKET,IPPROTO_TCP,IPPROTO_IP)
    //参数3:要设置的属性项,根据参数2决定属性项
    //参数4:参数3对应的属性值的指针
    //参数5:指向参数4大小的指针

    setsockopt:设置socket软通道的某项属性值**(socket建立之后就可使用)

    #include /* See NOTES */
    #include
    int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
    //参数1:文件描述符–>标识一个网络软通道
    //参数2:level指定控制套接字的层次(SOL_SOCKET,IPPROTO_TCP,IPPROTO_IP)
    //参数3:要设置的属性项,根据参数2决定属性项
    //参数4:参数3对应的属性值的指针
    //参数5:指向参数4大小
    //返回值:成功返回0,失败返回-1,并更新errno

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    //超时检测发送方代码
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(int argc, const char *argv[])
    {
    	//1,创建套接字
    	int socketFd = socket(AF_INET, SOCK_DGRAM, 0);
    	if(socketFd < 0){
    		perror("socket udp client error");
    		return -1;
    	}
    	printf("socket udp client ok!\n");
    	//2,设置服务器的IP 地址和端口
    	struct sockaddr_in addr;
    	bzero(&addr, sizeof(addr));
    	//赋值
    	addr.sin_family = AF_INET;
    	addr.sin_port = htons(8888);//接收方的端口
    	addr.sin_addr.s_addr = inet_addr("224.0.2.88");//组播地址
    	//允许组播
    	int opt = 1;
    	setsockopt(socketFd, IPPROTO_IP, IP_MULTICAST_IF, &opt, sizeof(opt));
    	//3,通信
    	char buf[256] = {0};
    	while(1)
    	{
    		bzero(buf, sizeof(buf));
    		printf("推送一条组播消息:");
    		fgets(buf, sizeof(buf), stdin);//从标准输入流中获取数据
    		if(0 == strncasecmp("quit", buf, 4))
    		{
    			printf("结束!\n");
    			bzero(buf, sizeof(buf));
    			sendto(socketFd, buf, strlen(buf), 0, \
    			(struct sockaddr *)&addr, sizeof(addr));
    			break;
    		}
    	//发送消息
    		sendto(socketFd, buf, strlen(buf), 0, \
    		(struct sockaddr *)&addr, sizeof(addr));
    		printf("send multicast message ok!\n");
    	}
    	//关闭套接字
    	close(socketFd);
    	return 0;
    }
    
    //超时检测接收方代码
    recvfrom进行超时检测
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(int argc, const char *argv[])
    {
    	//1,创建套接字
    	int socketFd = socket(AF_INET, SOCK_DGRAM, 0);
    	printf("socket server ok!\n");
    	//2,绑定接收方的IP 地址和端口
    	struct sockaddr_in addr;
    	bzero(&addr,sizeof(addr));
    	addr.sin_family = AF_INET;
    	addr.sin_port = htons(8888);//指定的接收方端口,必须和发送方一样
    	addr.sin_addr.s_addr = htonl(INADDR_ANY);//任意地址
    	bind(socketFd, (struct sockaddr *)&addr, sizeof(addr));
    	printf("bind ok!\n");
    	//3,加入到多播组中
    	struct ip_mreq ipaddr;
    	bzero(&ipaddr, sizeof(ipaddr));
    	ipaddr.imr_multiaddr.s_addr = inet_addr("224.0.2.88");
    	ipaddr.imr_interface.s_addr = htonl(INADDR_ANY);
    	setsockopt(socketFd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &ipaddr,
    	sizeof(ipaddr));
    	char buf[256] = {0};
    	//4,接收多播组中的消息
    	//定义存储对方地址信息的结构变量
    	struct sockaddr_in otherAddr;
    	bzero(&otherAddr, sizeof(otherAddr));
    	int len = sizeof(otherAddr);
    	while(1)
    	{
    		bzero(buf, sizeof(buf));
    		//设置超时检测
    		struct timeval timeout;
    		bzero(&timeout, sizeof(timeout));
    		timeout.tv_sec = 3;
    		//设置
    		setsockopt(socketFd, SOL_SOCKET, SO_RCVTIMEO,
    		&timeout,sizeof(timeout));
    		//注意:udp服务器一定要存储发送方的地址信息(没有建立过连接,每一次
    		//的发送,都需要知道对方的地址信息!!!)
    		int ret = recvfrom(socketFd, buf, sizeof(buf), 0, \
    		(struct sockaddr *)&otherAddr, &len);
    		if(ret < 0)
    		{
    			//判断错误码是否为EAGAIN
    			if(errno == EAGAIN)
    			{
    				printf("已超时3秒...");
    			}
    			else
    			{
    				perror("recvfrom error");
    				break;
    			}
    		}
    		else if(0 == ret){
    			printf("组中停止消息的发送~\n");
    			break;
    		}
    		else
    		{
    			printf("接收的组播消息:%s\n",buf);
    		}
    	}
    	//5,离开多播组
    	setsockopt(socketFd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &ipaddr,
    	sizeof(ipaddr));
    	//6,关闭套接字
    	close(socketFd);
    	return 0;
    }
    

    信号

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    //信号处理函数:
    void handler_func(int signum)
    {
    ;
    }
    //信号行为结构体
    struct sigaction {
    void (*sa_handler)(int);//信号处理函数,和signal一样的
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;//标志,可以设置为取消自重启属性
    void (*sa_restorer)(void);
    };

    更改信号默认操作:

    signal():信号处理函数===set

    signal函数原型:
    #include
    void (*signal(int signum, void (*handler)(int)))(int);
    优化原型:看的更清楚。
    //给函数指针类型取别名
    #include
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);

    功能:进行信号安装
    参数:
    参数1:需要安装的信号名称
    参数2:对于该信号的处理函数(该函数是用户自定义的,且返回值类型void ,形参是一个int的函数的地址)
    PS:参数2的位置可以传入:忽略(SIG_IGN) 执行缺省操作(SIG_DFL),信号处理函数的地址
    返回值:成功安装完毕的信号处理函数的地址。

    sigaction():信号处理函数===set/get

    sigaction(信号,处理动作结构,处理动作结构)
    ① sigaction(SIGALRM, NULL, &act) //获取SIGALRM信号对应的默认处理信息结构
    ① sigaction(SIGALRM, &act, NULL)//设置SIGALRM信号对应的新的处理信息结构

    在这里插入图片描述
    在这里插入图片描述

    #include
    unsigned int alarm(unsigned int seconds);
    //参数:秒数
    //当定时器指定的时间到了时,它就向进程发送SIGALARM信号,信号的默认操作是结束进程.
    //注意:每个进程只能有一个闹钟时间。如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,则该闹钟时间的剩余时间值作为本次alarm函数调用的值返回,以前登记的闹钟时间则被新值代换。如果有以前登记的尚未超过的闹钟时间,而新设的闹钟时间值为0,则取消以前的闹钟时间,其剩余时间值仍作为函数的返回值。

    在这里插入图片描述
    在这里插入图片描述

    //alarm定时器接收方超时检测代码
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    //信号处理函数
    void handler_func(int signum)
    {
    	printf("已超时...\n");
    }
    int main(int argc, const char *argv[])
    {
    	//1,创建套接字
    	int socketFd = socket(AF_INET, SOCK_DGRAM, 0);
    	printf("socket server ok!\n");
    	//2,绑定接收方的IP 地址和端口
    	struct sockaddr_in addr;
    	bzero(&addr,sizeof(addr));
    	addr.sin_family = AF_INET;
    	addr.sin_port = htons(8888);//指定的接收方端口,必须和发送方一样
    	addr.sin_addr.s_addr = htonl(INADDR_ANY);//任意地址
    	bind(socketFd, (struct sockaddr *)&addr, sizeof(addr));
    	printf("bind ok!\n");
    	//3,加入到多播组中
    	struct ip_mreq ipaddr;
    	bzero(&ipaddr, sizeof(ipaddr));
    	ipaddr.imr_multiaddr.s_addr = inet_addr("224.0.2.88");
    	ipaddr.imr_interface.s_addr = htonl(INADDR_ANY);
    	setsockopt(socketFd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &ipaddr,
    	sizeof(ipaddr));
    	char buf[256] = {0};
    	//4,接收多播组中的消息
    	//定义存储对方地址信息的结构变量
    	struct sockaddr_in otherAddr;
    	bzero(&otherAddr, sizeof(otherAddr));
    	int len = sizeof(otherAddr);
    	//第一步:先获取SIGALRM信号的旧行为
    	struct sigaction act;
    	bzero(&act, sizeof(act));
    	sigaction(SIGALRM, NULL, &act);
    	//第二步:设置处理函数+取消recvfrom的自重启属性
    	act.sa_handler = &handler_func;
    	//act.sa_flags |= SA_RESTART;//启动函数的自重启属性:eg:recvfrom:自重启属性再次陷入阻塞
    	act.sa_flags &= ~SA_RESTART;//取消recvfrom再次陷入阻塞
    	//第三步:将设置完毕的新行为设置给SIGALRM信号
    	sigaction(SIGALRM, &act, NULL);
    	while(1)
    	{
    		bzero(buf, sizeof(buf));
    		//使用sigaction函数捕捉SIGALRM信号,给他设置一个处理函数和
    		//取消recv函数自带的自重启属性
    		//设置超时检测
    		alarm(5);//秒到了,会产生SIGALRM信号
    		//注意:udp服务器一定要存储发送方的地址信息(没有建立过连接,每一次
    		//的发送,都需要知道对方的地址信息!!!)
    		int ret = recvfrom(socketFd, buf, sizeof(buf), 0, \
    		(struct sockaddr *)&otherAddr, &len);
    		if(ret < 0)
    		{
    			perror("recvfrom error");//perror:interrupt syatem call-->中断系统调用
    		//break;加了break此时会直接跳出while循环,不加则继续进行等待接收(超时检
    		测)
    		}
    		else if(0 == ret){
    			printf("组中停止消息的发送~\n");
    			break;
    		}
    		else
    		{
    			printf("接收的组播消息:%s\n",buf);
    		}
    	}
    	//5,离开多播组
    	setsockopt(socketFd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &ipaddr,
    	sizeof(ipaddr));
    	//6,关闭套接字
    	close(socketFd);
    	return 0;
    }
    
  • 相关阅读:
    跟误告警说再见,Smart Metrics 帮你用算法配告警
    【JAVA程序设计】基于JAVA的坦克大战小游戏--入门级小游戏
    PAT 1143 Lowest Common Ancestor
    CNVD-2021-26422:亿邮电子邮件系统moni_detail.do远程命令执行漏洞复现 [附POC]
    电脑提示msvcr120.dll丢失怎样修复
    六、K8S之StatefulSet
    甘特图来啦,项目管理神器,模板直接用
    2 快速上手使用Paimon数据湖
    R语言使用plot函数可视化数据散点图,使用lines函数在可视化图像中添加线条、使用lwd参数自定义线条的粗细
    Dubbo服务提供者如何优雅升级?
  • 原文地址:https://blog.csdn.net/xuezhe_____/article/details/139415888