一、网络超时检测
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;
}