目录
多进程实现TCP并发
无超时检测版:多进程实现TCP并发
多进程TCP并发-使用alarm闹钟实现网络超时检测
当使用alarm()闹钟函数设置一定的时间后,代码继续向下执行,当设定的时间到了, 会产生 SIGALRM 信号。
网络超时检测概念
详情见:使用select函数实现超时检测
recv 、send
SIGALRM 信号
该信号用于 通知 进程 定时器 时间 已到
默认操作:终止进程
信号的自重启属性
对SIGALRM信号进行捕捉,一旦产生信号,就执行对应的信号处理函数,
信号处理函数执行之后,程序会回到产生信号前的状态继续执行,
这种属性叫做信号的自重启属性。
如果要实现超时检测,就应该关掉这种属性,让信号处理函数执行之后,返回一个错误,而不是继续向下执行。
使用 sigaction 函数设置信号的行为
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
参数:
signum:
信号的编号,除了 SIGKILL 和 SIGSTOP 都可以
act:
信号的行为,如果是获取信号的行为,可以传NULL
oldact:
信号旧的行为,如果是设置信号的行为,可以传NULL
struct sigaction {
void (*sa_handler)(int); //信号处理函数
//SIG_DFL 默认
//SIG_IGN 忽略
void (*sa_sigaction)(int, siginfo_t *, void *);//信号处理函数
sigset_t sa_mask;//关于阻塞的掩码
int sa_flags;//信号的属性 按位或操作
//SA_RESTART 表示自重启属性
//关闭自重启属性后 调用失败会产生 EINTR 错误!!!!!!!!!!!!!!!!!!!!!
void (*sa_restorer)(void);
};
返回值:
成功 0
失败 -1
alarm
1.在利用 alarm() 函数实现网络超时检测时,相比较于select、setsockopt方式的特点是:
- 每调用alarm()函数一次,函数只会执行一次,并且只对最近的一个阻塞函数有效
2.当执行alarm() 时,在设定时间内 还会执行下面的函数,直到到达时间后,会进行sigaction 信号处理函数
3.执行 alarm() 后,在到达设定的时间时,系统会认定alarm()最近的一个阻塞函数为错误,让其返回值小于0;
- sigaction 关闭自重启属性后 调用失败会产生 EINTR 错误
代码实现
服务器—01server.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
#define ERRLOG(errmsg) \
do \
{ \
printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
perror(errmsg); \
exit(-1); \
} while (0)
//sigaction信号处理
void deal_signal(int a)
{
//什么都不用做
printf("信号处理\n");
}
//创建套接字-填充服务器网络信息结构体-绑定-监听
int socket_bind_listen(const char *argv[]);
//signal信号处理函数
void signal_handle(int signo);
int main(int argc, const char *argv[])
{
//检测命令行参数个数
if (3 != argc)
{
printf("Usage : %s <IP> <PORT>\n", argv[0]);
exit(-1);
}
//创建套接字-填充服务器网络信息结构体-绑定-监听
int sockfd = socket_bind_listen(argv);
//用来保存客户端信息的结构体
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(client_addr));
socklen_t client_addr_len = sizeof(client_addr);
char buff[128] = {0};
int accept_fd; //文件描述符,专门用于和该客户端通信
int ret = 0;
while (1)
{
//阻塞等待客户端连接--一旦有客户端连接就会解除阻塞
int accept_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
if (-1 == accept_fd)
ERRLOG("accept error");
// inet_ntoa32位网络字节序二进制地址转换成点分十进制的字符串
// ntohs将无符号2字节整型 网络-->主机
printf("客户端 (%s:%d) 连接了\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
//创建子进程
pid_t pid;
pid = fork();
if (pid < 0)
{
ERRLOG("fork error");
}
else if (pid == 0)
{
//这里是子进程执行
while (1)
{
alarm(5); //设置超时5s!!!!!!!!!!!!!!!!!!!!
if (0 > (ret = recv(accept_fd, buff, sizeof(buff), 0)))
{
if (errno == EINTR)//!!!!!!!!!!!!!!!!!!
{
printf("recv超时,等待重新连接....\n");
//结束本次循环
break;
}
perror("recv error");
break;
}
else if (0 == ret) //客户端侧CTRL+C
{
printf("客户端 (%s:%d) 断开连接\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
break;
}
else
{
if (0 == strcmp(buff, "quit"))
{
printf("客户端 (%s:%d) 退出了\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
break;
}
printf("客户端 (%s:%d) 发来数据:[%s]\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buff);
//组装回复给客户端的应答
strcat(buff, "---996");
//回复应答
if (0 > (ret = send(accept_fd, buff, sizeof(buff), 0)))
{
perror("send error");
break;
}
}
}
//关闭该客户端的套接字
close(accept_fd);
exit(EXIT_SUCCESS);
}
else
{
//这里是父进程执行
//当子进程结束的时候,父进程会收到一个SIGCHLD的信号
if (signal(SIGCHLD, signal_handle) == SIG_ERR)
perror("signal error");
//父进程不用,可以关闭
close(accept_fd);
}
}
//关闭监听套接字 一般不关闭
close(sockfd);
return 0;
}
//信号处理函数
void signal_handle(int signo)
{
wait(NULL);
printf("父进程以阻塞的方式回收了子进程的资源\n");
}
//创建套接字-填充服务器网络信息结构体-绑定-监听
int socket_bind_listen(const char *argv[])
{
// 1.创建套接字 //IPV4 //TCP
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
ERRLOG("socket error");
// 2.填充服务器网络信息结构体
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr)); //清空
server_addr.sin_family = AF_INET; // IPV4
//端口号 填 8888 9999 6789 ...都可以
// atoi字符串转换成整型数
// htons将无符号2字节整型 主机-->网络
server_addr.sin_port = htons(atoi(argv[2]));
// ip地址 要么是当前Ubuntu主机的IP地址 或者
//如果本地测试的化 使用 127.0.0.1 也可以
// inet_addr字符串转换成32位的网络字节序二进制值
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
//结构体长度
socklen_t server_addr_len = sizeof(server_addr);
/*----------------------------------------------------------------------------*/
//关闭信号的自重启属性
struct sigaction action;
// 获取信号的行为
sigaction(SIGALRM, NULL, &action);
action.sa_handler = deal_signal; //指定信号处理函数;deal_signal函数要自定义
action.sa_flags = action.sa_flags & (~SA_RESTART); //关闭自重启属性
//设置信号的行为
sigaction(SIGALRM, &action, NULL);
/*----------------------------------------------------------------------------*/
// 3.将套接字和网络信息结构体绑定
if (-1 == bind(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
ERRLOG("bind error");
//将套接字设置成被动监听状态
if (-1 == listen(sockfd, 10))
ERRLOG("listen error");
return sockfd;
}
客户端—02client.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define ERRLOG(errmsg) \
do \
{ \
printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
perror(errmsg); \
exit(-1); \
} while (0)
int main(int argc, const char *argv[])
{
//检测命令行参数个数
if (3 != argc)
{
printf("Usage : %s <IP> <PORT>\n", argv[0]);
exit(-1);
}
// 1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
ERRLOG("socket error");
// 2.填充服务器网络信息结构体
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
//端口号 填 8888 9999 6789 ...都可以
server_addr.sin_port = htons(atoi(argv[2]));
// ip地址 要么是当前Ubuntu主机的IP地址 或者
//如果本地测试的化 使用 127.0.0.1 也可以
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
//结构体长度
socklen_t server_addr_len = sizeof(server_addr);
//与服务器建立连接
if (-1 == connect(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
ERRLOG("connect error");
printf("---连接服务器成功---\n");
char buff[128] = {0};
while (1)
{
scanf("%s", buff);
int ret = 0;
if (0 > (ret = send(sockfd, buff, sizeof(buff), 0)))
ERRLOG("send error");
if (0 == strcmp(buff, "quit"))
break;
if (0 > (ret = recv(sockfd, buff, sizeof(buff), 0)))
{
ERRLOG("recv error");
}
else if(0==ret)
{
//说明超时了 服务器已经把客户端踢掉了
//客户端5秒没有给服务器发数据
//服务器break之后关闭套接字
printf("由于你长时间没有说话,服务器已经把你踢掉了...\n");
break;
}
printf("收到服务器回复:[%s]\n", buff);
}
//关闭套接字
close(sockfd);
return 0;
}
执行结果
5秒不发数据
注意
使用 sigaction 函数设置信号的行为,关闭信号的自重启属性
应该插放在 socket创建套接字 - bind将套接字和网络信息结构体绑定 之间
1.创建套接字
socket()
2.填充服务器网络信息结构体
struct sockaddr_in
----------------------------------------------------------
3.关闭信号的自重启属性
struct sigaction action;
// 获取信号的行为
sigaction(SIGALRM, NULL, &action);
action.sa_handler = deal_signal; //指定信号处理函数;deal_signal函数要自定义
action.sa_flags = action.sa_flags & (~SA_RESTART); //关闭自重启属性
//设置信号的行为
sigaction(SIGALRM, &action, NULL);
----------------------------------------------------------
4.将套接字和网络信息结构体绑定
bind()