多进程实现TCP并发服务器使用alarm闹钟实现网络超时检测-传输层

多进程实现TCP并发

在这里插入图片描述

无超时检测版:多进程实现TCP并发

多进程TCP并发-使用alarm闹钟实现网络超时检测

当使用alarm()闹钟函数设置一定的时间后,代码继续向下执行,当设定的时间到了, 会产生 SIGALRM 信号。

网络超时检测概念

详情见:使用select函数实现超时检测

recv 、send

详情见:多线程实现TCP并发服务器使用setsockopt设置超时检测

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() 函数实现网络超时检测时,相比较于selectsetsockopt方式的特点是:

  • 每调用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()

6.非原创

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值