这里利用宏来实现debug的相关输出
git
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的相关函数进行实现,其大致框架以下
github
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); } ... }
1. 首先是对socket的一些可能出现错误的函数进行再封装处理,好比accept可能出现多种错误,若只是简单的退出,则对服务器的安全性将有很大的挑战;这里对于最多见的信号中断处理,进行了再次封装编程
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函数一样须要再次封装处理,以确保能正确的接收到数据和正确的发送完数据.下面给出了最多见的错误处理,特别是对于信号中断,通常都有作出要求服务器
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;
socket
当与client创建链接的子进程结束后,要求将busy设置为0
函数
故流程为: 测试
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来实现这个要求
signal函数
函数原型为:
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
结合本博客主题,这里简单说明本函数具体使用的方式,首先是建一个信号处理函数,用于处理信号发生时所须要作的操做,具体代码以下:
int busy = 0; static void signal_handler(int sig) { int stat; pid_t pid; while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) ; busy = 0; }
waitpid 来处理子进程结束问题
函数原型,
#include<sys/types.h> #include<sys/wait.h> pid_t waitpid(pid_t pid,int * status,int options);
其中参数参考百科,但须要注意的是上面的代码,在信号处理函数中,下面这行是保障子进程结束不会僵死的关键
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的字符,用于判断是接受数据仍是发送数据
启动服务器
开启一个终端输入: nc 127.0.0.1 10033
输入: R [换行] 其余的一些数据
再开启一个终端输入: nc 127.0.0.1 10033