前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >linux 网络编程之信号机制

linux 网络编程之信号机制

作者头像
一灰灰blog
发布2018-02-06 14:40:36
7930
发布2018-02-06 14:40:36
举报
文章被收录于专栏:小灰灰小灰灰

调试输出函数

这里利用宏来实现debug的相关输出

代码语言:javascript
复制
int dbg_level = -1;
#define print_dbg(level, ...)						\
	({								\
	if (level <= dbg_level) {					\
		fprintf(stderr, "%s, %u, %s: ",				\
			__FILE__, __LINE__, __func__);			\
		fprintf(stderr, ##__VA_ARGS__);				\
	}								\
	})

基本的socket编程

前面有讲过基本的网络编程,主要是利用socket的相关函数进行实现,其大体框架如下

代码语言:javascript
复制
int sockfd = socket(PF_INET, SOCK_STREAM, 0); // 创建套接字
if(sockfd < 0)
{
    print_dbg(0, "create socket error\n");
    return;
}

struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr) < 0)
{
    print_dbg(0, "Failed to bind port:%d\n", port);
    return;
}

if(listen(sockfd, SOMAXCONN) < 0)
{
    print_dbg(0, "Listen error\n");
    return;
}

while(1)
{
    clientfd = accept(sockfd, (struct sockaddr *)&client_addr, NULL);
    if(clientfd < 0)
    {
        if(error == EINTR) // 信号中断处理
            continue;
        else
        {
            print_dbg(0, "accept dumpt\n");
            break;
        }
    }
    
    int pid = fork();
    if(pid < 0)
    {
        print_dbg(0, "create child process error\n");
        close(clientfd);
        continue;
    }
    else if(pid == 0)
    {
        ...
        exit(0);
    }
    ...
}

扩充的socket编程

  1.  首先是对socket的一些可能出现错误的函数进行再封装处理,比如accept可能出现多种错误,若只是简单的退出,则对服务器的安全性将有很大的挑战;这里对于最常见的信号中断处理,进行了再次封装

代码语言:javascript
复制
 clientfd = accept(sockfd, (struct sockaddr *)&client_addr, NULL);
    if(clientfd < 0)
    {
        if(error == EINTR) // 信号中断处理
            continue;
        else
        {
            print_dbg(0, "accept dumpt\n");
            break;
        }
    }

从上面的代码可以看出,当错误号为EINTR时,忽略掉再次进入循环;再例如出现EPERM,防火墙问题时,也可以做出相应的提示

2. 除了accept之外,对于send,recv函数同样需要再次封装处理,以确保能正确的接收到数据和正确的发送完数据.下面给出了最常见的错误处理,特别是对于信号中断,一般都有做出要求

代码语言:javascript
复制
char client_ip[16];
int recvn(int fd, char *buf, size_t len, int flag)
{
	int size = recv(fd, buf, len, flag);
	if (size < 0)
	{
		if (errno == EINTR)
			return recvn(fd, buf, len, flag);
		else
		{
			record_log("receive data from ip:%s error\n", client_ip);
			print_dbg(0, "receive data from ip:%s error\n", client_ip);
			return -errno;
		}
	}
	return size;
}

int sendn(int fd, const char *buf, size_t len, int flag)
{
	int size = send(fd, buf, len, flag);
	if (size < 0)
		if (errno == EINTR)
			return sendn(fd, buf, len, flag);
		else
		    goto ERROR;
	if(size != len)
	    goto ERROR;
	    
	return size;
	
	ERROR:
        record_log("send data to ip:%s error\n", client_ip);
	print_dbg(0, "receive data from ip:%s error\n", client_ip);
	return -errno;		
}

信号与进程通信

如果我们考虑的服务器是只允许顺序的请求,即一个请求处理完毕之后再响应另一个请求;若当前正在处理一个client的请求时,此时接受到其他client的请求,这里可以利用信号来实现屏蔽处理。

思路:利用全局变量busy来决定在while(1)循环中是否接受客户端处理请求

        当busy为1时,表示不再接受client请求;当busy为0时,表示可以接受client请求,并标记busy为1;

        当与client建立连接的子进程结束后,要求将busy设置为0

        故流程为:

代码语言:javascript
复制
signal(SIGCHLD, signa_handler); // 注册信号处理函数
while(1)
{
    clientfd = accept(...);
    ...
    
    if(busy == 1)
    {
        print_dbg(0, "socket busy\n");
        close(clientfd);
        continue;
    }
    busy = 1;
    int pid = fork();
    if(pid < 0) {}
    else if(pid == 0) 
    {
        child_process(clientfd);
        exit(0); // 求捕获子进程结束信号,在处理函数中将busy复位为0
    }
}

这里主要简单的使用signal函数结合waitpid来实现这个要求

1. signal函数

函数原型为: 

代码语言:javascript
复制
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

2. 结合本博客主题,这里简单说明本函数具体使用的方式,首先是建一个信号处理函数,用于处理信号发生时所需要做的操作,具体代码如下:

代码语言:javascript
复制
int busy = 0;
static void signal_handler(int sig)
{
	int stat;
	pid_t pid;

	while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
		;
	busy = 0;
}

3. waitpid 来处理子进程结束问题

    函数原型,

代码语言:javascript
复制
#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int * status,int options);

其中参数参考百科,但需要注意的是上面的代码,在信号处理函数中,下面这行是保障子进程结束不会僵死的关键

代码语言:javascript
复制
while ((pid = waitpid(-1, &stat, WNOHANG)) > 0);

参数 status 可以设成 NULL。参数 pid 为欲等待的子进程识别码,

其他数值意义如下:

pid<-1 等待进程组识别码为 pid 绝对值的任何子进程。

pid=-1 等待任何子进程,相当于 wait()。

pid=0 等待进程组识别码与目前进程相同的任何子进程。

pid>0 等待任何子进程识别码为 pid 的子进程。

参数options提供了一些额外的选项来控制waitpid,参数 option 可以为 0 或可以用"|"运算符把它们连接起来使用,比如:

ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);

如果我们不想使用它们,也可以把options设为0,如:

ret=waitpid(-1,NULL,0);

WNOHANG 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若结束,则返回该子进程的ID。

WUNTRACED 若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会。WIFSTOPPED(status)宏确定返回值是否对应与一个暂停子进程。

最后源代码可以参考:https://github.com/liuyueyi/sa/blob/master/server.c

测试

服务器首先接受一个R\T的字符,用于判断是接受数据还是发送数据

  1. 启动服务器
  2. 开启一个终端输入: nc 127.0.0.1 10033 输入: R [换行] 其他的一些数据
  3. 再开启一个终端输入:  nc 127.0.0.1 10033
  4.  服务器提示socket busy,并关闭连接
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 调试输出函数
  • 基本的socket编程
  • 扩充的socket编程
  • 信号与进程通信
  • 测试
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档