Linux下网络通信中的超时设置(C语言 客户端/服务器实现)
1. 网络超时检测
1. 超时设置:为某个操作(如连接、请求、响应等)设定一个最大的等待时间,超出这个时间没有得到预期结果,就认为操作失败或超时,从而触发相应的处理(如报错、重试、放弃等)
1. 在网络通信中,很多操作会使得进程阻塞,比如TCP套接字中的recv/accept/connect函数,UDP套接字中的recvfrom函数等
2. 超时检测的必要性:避免进程在没有数据时无限制的阻塞,当设定的时间到时,进程从原操作返回继续运行
3. 本文介绍两种超时设置的函数用来解决客户端/服务器中的阻塞:setsockopt()、sigaction()
2. 方法1:setsockopt
(1)使用方法(简易流程)
struct timeval {
long tv_sec; //单位秒
long tv_usec; //单位微妙
};
struct timeval tv = {2}; //设置超时时长2s
setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); //设置接收超时
while (1) {
ret = recv(connfd, );
if (ret < 0) { //recv函数超时返回-1
continue; //超时设置的关键语句,不会直接结束进程,而是让进程返回继续执行
}
}
(2)代码实现
这里用客户端/服务器的形式来展示超时设置效果
在服务器端server.c的代码中,我们在与客户端进行通信前,进行超时设置
以下是使用setsockopt函数进行超时设置时,服务器端的完整代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <fcntl.h>int main(int argc, char *argv[])
{ //1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("socket");return -1;}printf("socket create success\n");//2.绑定本机地址和端口struct sockaddr_in srvaddr;memset(&srvaddr, 0, sizeof(srvaddr));srvaddr.sin_family = AF_INET;srvaddr.sin_port = htons(60621);srvaddr.sin_addr.s_addr = inet_addr(INADDR_ANY);if(0 > bind(sockfd, (struct sockaddr *)&srvaddr, sizeof(srvaddr))){perror("bind");return -1;}printf("bind success\n");//3.设置监听套接字if(0 > listen(sockfd, 1)){perror("listen");return -1;}printf("listen success\n");//4.接收客户端的连接, 并生成通信套接字int connfd = accept(sockfd, NULL, NULL);if(0 > connfd){perror("accept");return -1;}printf("accept success\n");//超时设置struct timeval tv = {3};//设置接收超时setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));//5.与客户端通信:接收客户端的数据并打印int ret;char buf[1024];while(1){ret = recv(connfd, buf, sizeof(buf), 0);if(0 > ret){perror("recv");continue; //超时等待}else if(0 == ret){printf("server close\n");break;}printf("recv: %s\n", buf);}//6.关闭套接字close(sockfd);close(connfd);return 0;
}
3. 方法2:sigaction
(1)使用方法(简易流程)
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
作用: 检查和改变信号动作
参数:
signum --- 信号
act --- 设置信号处理动作
old_act --- 获取信号处理动作
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
返回值:
成功: 0
失败: -1, 并设置errno
例:设置定时器(timer), 捕捉SIGALRM信号
参考代码如下
void handler(int signo) { return; } //一旦进程收到这个信号,执行完信号处理函数之后,下一个函数就会直接返回,不阻塞在那里
struct sigaction act;
sigaction(SIGALRM, NULL, &act); //获取信号原来的act
act.sa_handler = handler;
act.sa_flags &= ~SA_RESTART; //自动重启
sigaction(SIGALRM, &act, NULL); //设置信号新的act
while (1) {
alarm(5);
if (recv(,,,) < 0) ……
}
(2)代码实现
这里同样用客户端/服务器的形式来展示超时设置效果
在服务器端server.c的代码中,我们在与客户端进行通信前,进行超时设置
以下是使用sigaction函数进行超时设置时,服务器端的完整代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>void handler(int sig)
{printf("timeout...\n");
}
int main(int argc, char *argv[])
{ //1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("socket");return -1;}printf("socket create success\n");//2.绑定本机地址和端口struct sockaddr_in srvaddr;memset(&srvaddr, 0, sizeof(srvaddr));srvaddr.sin_family = AF_INET;srvaddr.sin_port = htons(60621);srvaddr.sin_addr.s_addr = inet_addr(INADDR_ANY);if(0 > bind(sockfd, (struct sockaddr *)&srvaddr, sizeof(srvaddr))){perror("bind");return -1;}printf("bind success\n");//3.设置监听套接字if(0 > listen(sockfd, 1)){perror("listen");return -1;}printf("listen success\n");//4.接收客户端的连接, 并生成通信套接字int connfd = accept(sockfd, NULL, NULL);if(0 > connfd){perror("accept");return -1;}printf("accept success\n");//超时设置struct sigaction act;sigaction(SIGALRM, NULL, &act);act.sa_handler = handler;act.sa_flags &= ~SA_RESTART;sigaction(SIGALRM, &act, NULL);//5.与客户端通信:接收客户端的数据并打印int ret;char buf[1024];while(1){alarm(3); //设置超时时间为3sret = recv(connfd, buf, sizeof(buf), 0)if(0 > ret){perror("recv");continue; //超时等待}else if(0 == ret){printf("server close\n");break;}printf("recv: %s\n", buf);}//6.关闭套接字close(sockfd);close(connfd);return 0;
}
4. 客户端的搭建
(1)客户端完整代码
这里提供与上方服务器端代码对应的客户端完整代码如下(以上两种超时设置方法对应的客户端代码相同)
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>int main(int argc, char *argv[])
{ //创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(0 > sockfd){perror("socket");return -1;}printf("socket create success\n");//主动连接服务器struct sockaddr_in srvaddr;memset(&srvaddr, 0, sizeof(srvaddr));srvaddr.sin_family = AF_INET;srvaddr.sin_port = htons(60621);srvaddr.sin_addr.s_addr = inet_addr("192.168.2.154");if(0 > connect(sockfd, (struct sockaddr *)&srvaddr, sizeof(srvaddr))){perror("connect");return -1;}//与服务器通信:从键盘输入字符串发送给服务器int ret;char buf[1024];while(1){printf("send: ");fgets(buf, sizeof(buf), stdin);buf[strlen(buf)-1] = '\0';if(0 > send(sockfd, buf, sizeof(buf), 0)){perror("send");break;}//设置客户端退出条件if(strcmp(buf, "exit") == 0)break;}//关闭套接字close(sockfd);return 0;
}
(2)编译时注意事项
执行前的准备工作:
方法1
1. 我们在第一个终端编译 gcc server.c -o s //重命名执行文件名
2. 在第二个终端编译 gcc client.c -o c
(为什么要重命名执行文件名?这是因为如果像平常一样进行编译,两个程序编译后生成同一个文件名(a.out),互相覆盖,无法同时执行,所以,通常我们会用 -o 指定不同的输出文件名)
3. 在第一个终端执行 ./s
4. 在第二个终端执行 ./c
方法2
1. 创建一个makefile文件,在里面写入:
all:gcc server.c -o sgcc client.c -o c
clean:rm s c
2. 接下来与方法1相同,在两个终端上分别执行 ./s 和 ./c 即可
5. 总结
1. 对运行结果分析我们可以得知,在设置的超时时间到时,进程就会从原操作返回继续运行,而不是一直阻塞等待
2. 超时设置的好处:
(1)设置了超时后,如果一定时间内没有响应,程序可以主动放弃等待,避免永久阻塞,保证程序能够继续运行或进行错误处理
(2)当请求超时时,可以及时给用户反馈,比如提示 “ 请求超时,请重试 ”
(3)超时后能及时释放连接、线程、内存等资源,让它们可以被其他请求复用,提升系统整体的资源利用率和并发能力
3. 在网络编程中,超时设置是不可或缺的核心机制之一,对其他网络编程知识感兴趣的同学,欢迎浏览主页其他相关文章!
感谢观看!如有疑问欢迎提出!
----香菜小猫祝这位uu天天开心----