网络编程:网络超时检测(select poll setsockopt alarm)

一、网络超时检测

1.1 概念

阻塞:

以读阻塞为例,如果缓冲区中有内容,则程序正常执行,

如果缓冲区没有内容,程序会一直阻塞,直到有内容,读取内容继续向下运行。

非阻塞:

以读阻塞为例,如果缓冲区中有内容,则程序正常执行,

如果缓冲区没有内容,程序会立即返回,然后继续向下运行。

超时检测:

介于阻塞和非阻塞之间,需要设置一定的时间,如果时间到达之前,缓冲区中没有数据

程序会阻塞,如果时间到了,缓冲区中还没有数据,则程序立即返回,继续向下执行。

二超时检测的方法:

2.3以下所用程序的客户端:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#define N 128

#define ERRLOG(msg) do{\
    printf("%s,%s,[%d]\n", __FILE__, __func__, __LINE__);\
    perror(msg);\
    exit(-1);\
}while(0)

int main(int argc, char const *argv[])
{
    if (argc != 3){
        ERRLOG("输入参数错误\n");
    }

    int socketfd;
    if (-1 == (socketfd = socket(AF_INET, SOCK_STREAM, 0))){
        ERRLOG("socket fd error");
    }

    struct sockaddr_in clientaddr;
    clientaddr.sin_family = AF_INET;
    clientaddr.sin_addr.s_addr = inet_addr(argv[1]);
    clientaddr.sin_port = htons(atoi(argv[2]));
    socklen_t clientaddr_len = sizeof(clientaddr);

    if (-1 == connect(socketfd,(struct sockaddr*)&clientaddr, clientaddr_len)){
        ERRLOG("connect error");
    } 

    printf("连接成功...\n");

    char buff[N];
    while (1){
        memset(buff, 0, sizeof(buff));

        printf("请输入>>");
        fgets(buff, N, stdin);
        buff[strlen(buff) -1] = '\0';

        if (-1 == send(socketfd, buff, strlen(buff), 0)){
            ERRLOG("send error");
        }

        if (strncmp("quit", buff, 5) == 0){
            break;
        }
        memset(buff, 0, sizeof(buff));
        if (-1 == recv(socketfd, buff, sizeof(buff), 0)){
            ERRLOG("recv error");
        }

        printf("%s\n", buff);
    }

    close(socketfd);
    return 0;
}

2.2select函数自带超时检测

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/select.h>
#define N 128

#define ERRLOG(msg) do{\
    printf("%s,%s,[%d]\n", __FILE__, __func__, __LINE__);\
    perror(msg);\
    exit(-1);\
}while(0)

int main(int argc, char const *argv[])
{
    if (argc != 3){
        printf("输入格式错误\n");
        exit(-1);
    }

    int socketfd;

    if (-1 == (socketfd = socket(AF_INET, SOCK_STREAM, 0))){
        ERRLOG("socketfd erro");
    }

    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t serveraddr_len = sizeof(serveraddr);

    if (-1 == bind(socketfd, (struct sockaddr*)&serveraddr, serveraddr_len)){
        ERRLOG("bind error");
    }
    
    if (-1 == listen(socketfd, 5)){
        ERRLOG("listen error");
    }

    int nbytes;
    int ret;
    int i;
    int maxfd = 0;
    int acceptfd;
    char buff[128] = {0};

    fd_set readfs;
    fd_set readfs_temp;
    FD_ZERO(&readfs);
    FD_ZERO(&readfs_temp);

    FD_SET(socketfd, &readfs);
    maxfd = maxfd > socketfd ? maxfd: socketfd;
    
    struct timeval tm;
    while (1){
        readfs_temp = readfs;
        tm.tv_sec = 5;
        tm.tv_usec = 0;
        if (-1 == (ret = select(maxfd + 1, &readfs_temp, NULL, NULL, &tm))){
            ERRLOG("select error");
        } else if (ret == 0){
            printf("timeout.....\n");
        } else {
            for (i = 3; i < maxfd + 1 && ret != 0; i++){
                if (FD_ISSET(i, &readfs_temp)){
                    if (i == socketfd){
                        if (-1 == (acceptfd = accept(i, NULL, NULL))){
                            ERRLOG("acceptfd error");
                        }
                            printf("111111111\n");
                        FD_SET(acceptfd, &readfs);

                        maxfd = maxfd > acceptfd ? maxfd : acceptfd;
                    } else {
                        memset(buff, 0, sizeof(buff));

                        if (-1 == (nbytes = recv(i, buff, sizeof(buff), 0))){
                            ERRLOG("recv error");
                        } else if(nbytes == 0){
                            printf("断开链接...\n");
                            FD_CLR(i, &readfs);
                            close(i);
                            continue;
                        }

                        if (!strncmp("quit", buff, 5)){
                            printf("退出链接...\n");
                            FD_CLR(i, &readfs);
                            close(i);
                            continue;
                        }

                        printf("%s\n", buff);

                        strcat(buff, "hhhh");

                        if (-1 == send(i, buff, sizeof(buff), 0)){
                            ERRLOG("send error");
                        }
                    }
                    ret--;
                }
            }
        }   
    }
    close(socketfd);
    return 0;
}

2.2poll函数也自带超时器

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <poll.h>

#define ERRLOG(msg) do{\
        printf("%s %s(%d):", __FILE__, __func__, __LINE__);\
        perror(msg);\
        exit(-1);\
    }while(0)

#define N 128


int main(int argc, char const *argv[])
{
    if (argc != 3){
        printf("输入过程\n");
        exit(-1);
    }

    int socketfd;
    if (-1 == (socketfd = socket(AF_INET, SOCK_STREAM, 0))){
        ERRLOG("socket error");
    }

    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));
    socklen_t serveraddr_len = sizeof(serveraddr);

    if (-1 == bind(socketfd, (struct sockaddr*)&serveraddr, serveraddr_len)){
        ERRLOG("bind error");
    }

    if (-1 == listen(socketfd, 5)){
        ERRLOG("listen error");
    }

    int maxfd = 0;
    int ret = 0;
    int nbytes = 0;
    int i;
    int j;
    int acceptfd;
    char buff[128];

    struct pollfd my_fds[1024];
    for (i = 0; i < 1024; i++){
        my_fds[i].fd = -1;
    }

    my_fds[0].fd = socketfd;
    my_fds[0].events |= POLLIN;
    maxfd = maxfd > socketfd ? maxfd : socketfd;

    while (1){
        if (-1 == (ret = poll(my_fds, maxfd, 5000))){

        } else if (ret == 0){
            printf("time out...\n");
        } else {
            for (i = 0; i <= maxfd && ret != 0; i++){
                if (my_fds[i].revents & POLLIN != 0){
                    if (my_fds[i].fd == socketfd){ 
                        if (-1 == (acceptfd = accept(socketfd, NULL, NULL))){
                            ERRLOG("accepfd error");
                        }

                        printf("客户端[%d]连接了\n", acceptfd);

                        maxfd = maxfd > acceptfd ? maxfd : acceptfd;

                        for (j = 0; j < 1024 ; j++){
                            if (my_fds[j].fd == -1){
                                my_fds[j].fd = acceptfd;
                                my_fds[j].events = POLLIN;

                                break;                                
                            }
                        }

                        if (j == 1024){
                            printf("满了\n");
                            close(acceptfd);
                        }
                    }else {
                        memset(buff, 0, sizeof(buff));

                        if (-1 == (nbytes = recv(my_fds[i].fd, buff, 128, 0))){
                            ERRLOG("recv error");
                        }else if (nbytes == 0){
                            printf("断开连接...\n");
                            close(my_fds[i].fd);
                            my_fds[i].fd = -1;
                            continue;
                        }

                        if (!strncmp("quit", buff, 5)){
                            printf("退出连接..\n");
                            printf("断开连接...\n");
                            close(my_fds[i].fd);
                            my_fds[i].fd = -1;
                            continue;
                        }

                        printf("%s\n", buff);

                        strcat(buff, "jhhh");

                        if (-1 == send(my_fds[i].fd, buff, sizeof(buff), 0)){
                            ERRLOG("send errro");
                        }
                    }
                }
            }
        }
    }


    close(socketfd);
    return 0;
}

2.3使用 setsockopt 函数实现超时检测

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#include <sys/time.h>
#include <errno.h>

#define ERRLOG(msg) do{\
        printf("%s %s(%d):", __FILE__, __func__, __LINE__);\
        perror(msg);\
        exit(-1);\
    }while(0)

#define N 128

typedef struct _MSG{
    int acceptfd;
    struct sockaddr_in clientaddr;
}msg_t;

void *deal_read_write(void *arg){
    msg_t msg = *(msg_t *)arg;
    char buff[N] = {0};
    int nbytes = 0;
    printf("客户端 [%s:%d] 连接了\n", inet_ntoa(msg.clientaddr.sin_addr), ntohs(msg.clientaddr.sin_port));
    
    //收发数据
    while(1){
        memset(buff, 0, N);
        //接受数据
        //由已经设置过超时时间的sockfd产生的accpetfd会继承超时属性
        //如果想设置的超时时间是一样的,直接使用即可
        //如果不想设置一样,也可以对每个acceptfd单独调用 setsockopt 进行设置
        if(-1 == (nbytes = recv(msg.acceptfd, buff, 128, 0))){
            if(errno == EAGAIN){
                printf("recv timeout..\n");
                close(msg.acceptfd);
                break;
            }
            perror("recv error");
            break;
        }else if(0 == nbytes){
            printf("[%s:%d]:断开了连接...\n", inet_ntoa(msg.clientaddr.sin_addr), ntohs(msg.clientaddr.sin_port));
            break;
        }
        if(!strncmp(buff, "quit", 5)){
            printf("[%s:%d]:退出了...\n", inet_ntoa(msg.clientaddr.sin_addr), ntohs(msg.clientaddr.sin_port));
            break;
        }
        //打印数据
        printf("[%s:%d]:[%s]\n", inet_ntoa(msg.clientaddr.sin_addr), ntohs(msg.clientaddr.sin_port), buff);
        //组装应答
        strcat(buff, "--hqyj");
        //发送应答
        if(-1 == send(msg.acceptfd, buff, N, 0)){
            ERRLOG("send error");
        }
    }
    close(msg.acceptfd);
    pthread_exit(NULL);
}

int main(int argc, const char *argv[]){
    if(3 != argc){
        printf("Usage: %s <IP> <port>\n", argv[0]);
        return -1;
    }

    //创建流式套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd){
        ERRLOG("socket error");
    }

    //填充服务器网络信息结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    //网络字节序的端口号 8888  9999  6789 等 都可以
    serveraddr.sin_port = htons(atoi(argv[2]));
    //网络字节序的IP地址,IP地址不能乱填
    //自己的主机ifconfig 查到的ip地址是多少就填多少
    //如果本机测试使用 也可以填写 127.0.0.1
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    
    socklen_t serveraddr_len = sizeof(serveraddr);

    //设置允许端口复用
    int on = 1;
    if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))){
        perror("setsockopt error");
    }

    //将套接字和网络信息结构体绑定
    if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
        ERRLOG("bind error");
    }

    //将套接字设置成被动监听状态
    if(-1 == listen(sockfd, 5)){
        ERRLOG("listen error");
    }

    //设置sockfd接收超时时间为 5s
    struct timeval tm;
    tm.tv_sec = 5;
    tm.tv_usec = 0;
    if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tm, sizeof(tm))){
        ERRLOG("setsockopt error");
    }

    //定义一个保存客户端信息的结构体
    struct sockaddr_in clientaddr;
    memset(&clientaddr, 0, sizeof(clientaddr));
    socklen_t clientaddr_len = sizeof(clientaddr);

    msg_t msg;
    pthread_t tid = 0;

    //阻塞等待客户端连接
    int acceptfd = 0;
    while(1){
        if(-1 == (acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len))){
            if(errno == EAGAIN){
                printf("5秒内没有客户端连接到服务器..\n");
                continue;
            }
            ERRLOG("acceptfd error");
        }
        memset(&msg, 0, sizeof(msg));
        msg.clientaddr = clientaddr;
        msg.acceptfd = acceptfd;
        //有客户端连接就创建线程专门处理读写
        if(0 != pthread_create(&tid, NULL, deal_read_write, (void *)&msg)){
            ERRLOG("pthread_create error");
        }

        //设置线程分离属性 由操作系统回收线程的资源
        pthread_detach(tid);
    }

    close(sockfd);

    return 0;
}

2.4使用 alarm 闹钟实现超时检测

2.4.1 概念

alarm是一个函数,可以通过他设置一个时间,当时间到达的时候,会给进程发一个

SIGALRM 信号。

使用alarm函数设置完超时时间后,进程会继续执行,直到设置的时间到了,

进程收到 SIGALRM 信号

进程对该信号默认的处理方式是 终止进程,我们的是服务器程序,不能使用默认的方式

所以需要对 该信号进行一个捕捉的操作。

如果使用 捕捉的操作,当信号产生时,回去执行信号处理函数,执行完信号处理函数

进程会继续向下执行,这种属性,叫做信号的自重启属性。

我们想要使用该信号实现超时检测,也就是说,执行完信号处理函数,不应该直接向下运行

而是应该给我们返回一个错误,也就是说,需要关闭信号的自重启属性。

2.4.2关闭自重启属性的方式,使用 sigaction 函数

struct sigaction action;
//获取旧的行为
sigaction(SIGALRM, NULL, &action);
//取消自重启属性
action.sa_flags &= (~SA_RESTART);
//指定信号处理函数
action.sa_handler = my_function;
//再将行为重新设置回去
sigaction(SIGALRM, &action, NULL);

2.4.3代码实现

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <errno.h>

#define ERRLOG(msg) do{\
	printf("%s %s(%d):", __FILE__, __func__, __LINE__);\
	perror(msg);\
	exit(-1);\
}while(0)

#define N 128

void my_function(int x){
	//什么都不用作  我们只是通过这种方法捕获信号
	printf("helle\n");
}

int main(int argc, const char *argv[]){
	if(3 != argc){
		printf("Usage: %s <IP> <port>\n", argv[0]);
		return -1;
	}

	//创建流式套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == sockfd){
		ERRLOG("socket error");
	}

	//填充服务器网络信息结构体
	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	//网络字节序的端口号 8888  9999  6789 等 都可以
	serveraddr.sin_port = htons(atoi(argv[2]));
	//网络字节序的IP地址,IP地址不能乱填
	//自己的主机ifconfig 查到的ip地址是多少就填多少
	//如果本机测试使用 也可以填写 127.0.0.1
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);

	socklen_t serveraddr_len = sizeof(serveraddr);

	//设置端口复用
	int on = 1;
	if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))){
		perror("setsockopt error");
	}

	//将套接字和网络信息结构体绑定
	if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
		ERRLOG("bind error");
	}

	//将套接字设置成被动监听状态
	if(-1 == listen(sockfd, 5)){
		ERRLOG("listen error");
	}

	//定义一个保存客户端信息的结构体
	struct sockaddr_in clientaddr;
	memset(&clientaddr, 0, sizeof(clientaddr));
	socklen_t clientaddr_len = sizeof(clientaddr);

	char buff[N] = {0};
	int nbytes = 0;

	//关闭SIGALRM信号的自重启属性
	struct sigaction action;
	//获取旧的行为
	sigaction(SIGALRM, NULL, &action);
	//取消自重启属性
	action.sa_flags &= (~SA_RESTART);
	//指定信号处理函数
	action.sa_handler = my_function;
	//再将行为重新设置回去
	sigaction(SIGALRM, &action, NULL);

	//阻塞等待客户端连接
	int acceptfd = 0;
	while(1){
		alarm(5);
		if(-1 == (acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len))){
			if(errno == EINTR){
				printf("accept timeout..\n");
				continue;
			}
			ERRLOG("acceptfd error");
		}
		printf("客户端 [%s:%d] 连接了\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

		//收发数据
		while(1){
			memset(buff, 0, N);
			//接受数据
			alarm(5);
			if(-1 == (nbytes = recv(acceptfd, buff, 128, 0))){
				if(errno == EINTR){
					printf("recv timeout..\n");
					break;
				}
				ERRLOG("recv error");
			}else if(0 == nbytes){
				printf("[%s:%d]:断开了连接...\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
				break;
			}
			if(!strncmp(buff, "quit", 5)){
				printf("[%s:%d]:退出了...\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
				break;
			}
			//打印数据
			printf("[%s:%d]:[%s]\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buff);
			//组装应答
			strcat(buff, "--hqyj");
			//发送应答
			if(-1 == send(acceptfd, buff, N, 0)){
				ERRLOG("send error");
			}
		}
		//关闭套接字
		close(acceptfd);
	}

	//这句代码一般不会执行到
	//服务器程序一般不会主动退出
	close(sockfd);

	return 0;
}

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值