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
#include
#include
#include
#include
#include
#include
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
");
//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
");
//3.设置监听套接字
if(0 > listen(sockfd, 1))
{
perror("listen");
return -1;
}
printf("listen success
");
//4.接收客户端的连接, 并生成通信套接字
int connfd = accept(sockfd, NULL, NULL);
if(0 > connfd)
{
perror("accept");
return -1;
}
printf("accept success
");
//超时设置
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
");
break;
}
printf("recv: %s
", 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
#include
#include
#include
#include
#include
#include
#include
void handler(int sig)
{
printf("timeout...
");
}
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
");
//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
");
//3.设置监听套接字
if(0 > listen(sockfd, 1))
{
perror("listen");
return -1;
}
printf("listen success
");
//4.接收客户端的连接, 并生成通信套接字
int connfd = accept(sockfd, NULL, NULL);
if(0 > connfd)
{
perror("accept");
return -1;
}
printf("accept success
");
//超时设置
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); //设置超时时间为3s
ret = recv(connfd, buf, sizeof(buf), 0)
if(0 > ret)
{
perror("recv");
continue; //超时等待
}
else if(0 == ret)
{
printf("server close
");
break;
}
printf("recv: %s
", buf);
}
//6.关闭套接字
close(sockfd);
close(connfd);
return 0;
}
4. 客户端的搭建
(1)客户端完整代码
这里提供与上方服务器端代码对应的客户端完整代码如下(以上两种超时设置方法对应的客户端代码相同)
#include
#include
#include
#include
#include
#include
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
");
//主动连接服务器
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] = ' ';
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 s
gcc client.c -o c
clean:
rm s c
2. 接下来与方法1相同,在两个终端上分别执行 ./s 和 ./c 即可
5. 总结
1. 对运行结果分析我们可以得知,在设置的超时时间到时,进程就会从原操作返回继续运行,而不是一直阻塞等待
2. 超时设置的好处:
(1)设置了超时后,如果一定时间内没有响应,程序可以主动放弃等待,避免永久阻塞,保证程序能够继续运行或进行错误处理
(2)当请求超时时,可以及时给用户反馈,比如提示 “ 请求超时,请重试 ”
(3)超时后能及时释放连接、线程、内存等资源,让它们可以被其他请求复用,提升系统整体的资源利用率和并发能力
3. 在网络编程中,超时设置是不可或缺的核心机制之一,对其他网络编程知识感兴趣的同学,欢迎浏览主页其他相关文章!
感谢观看!如有疑问欢迎提出!
----香菜小猫祝这位uu天天开心----







