文章目录
- 思路
- 问题
- 多进程并发回环服务器代码
- 客户端代码
思路
每当一个客户端连接服务器后,创建一个子进程负责与该客户端通信,客户端断开连接之后,服务器回收子进程资源。
问题
问题1:父进程阻塞在等待连接(
accept()
)处,不能在父进程回收资源,可以使用信号SIGCHLD
进行软中断回调处理,当子进程结束后会产生SIGCHLD
信号,信号触发回调函数,进程子进程资源回收,父进程阻塞在accept()
函数时遇到软中断就会产生EINTR
错误信号,这就需要处理accept
函数返回值为-1
时的错误,判断错误号errno
,若errno == EINTR
则继续等待客户端连接进入accept阻塞。
问题2:由于多个进程同时退出时也仅有一个
SIGCHLD
信号,所以在有SIGCHLD
信号时,回调函数内就要循环执行释放子进程资源,直到子进程资源释放完成。
信号的注册,以及回调函数的编写:
//子进程回收回调函数
void recvChild(int arg)
{while(1){int ret = waitpid(-1, NULL, WNOHANG);if(ret > 0){printf("recv child, the num is:%d\n", ret);}else if(ret == 0){//还有子进程}else if(ret == -1){//没有子进程了break;}}
}//注册信号,解决子进程的回收问题struct sigaction act;act.sa_flags = 0;sigemptyset(&act.sa_mask);act.sa_handler = recvChild;sigaction(SIGCHLD, &act, NULL);
问题3:在使用客户端发送数据的时候,是先使用
write
发送数据,然后通过read
读取数据,该读取是阻塞的,所以一开始没有数据时是一直阻塞的,回环服务器接收到数据回传给客户端,这样客户端和服务器同时进行read
时,就会出现都阻塞的状态。
可以设置客户端先发送,再读取,客户端发送后,数据经过服务器回传,客户端收到数据后,再进行下一次发送,若有一次数据丢失则无法进行数据发送,有一直阻塞在接收的风险。
也可以在客户端设置两个进程,一个发送进程,一个接收进程,这样就可以解决。
问题4:接收数据中没有结束符’\0’,在`printf %s时,会导致数据错误(数据先长后短,打印的会包含上次数据),注意结束符的位置,strlen计算到结束符之前。
通过在接收的字符串后补上结束符处理
或者接收数据前对接收数组进行清空处理
********////接收数据没有字符结束符,无法判断数据结束,//使用printf%s出现问题:可以将末尾增加字符结束符/0,也可以使用数据初始化(浪费资源)
//memset(recv, 0, 1024);
int len = read(cfd, recv, 1024);
recv[len] = 0;
多进程并发回环服务器代码
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <wait.h>
#include <errno.h>void recvChild(int arg)
{while(1){int ret = waitpid(-1, NULL, WNOHANG);if(ret > 0){printf("recv child, the num is:%d\n", ret);}else if(ret == 0){//还有子进程}else if(ret == -1){//没有子进程了break;}}
}int main()
{//注册信号,解决子进程的回收问题struct sigaction act;act.sa_flags = 0;sigemptyset(&act.sa_mask);act.sa_handler = recvChild;sigaction(SIGCHLD, &act, NULL);//socketint lfd = socket(PF_INET, SOCK_STREAM, 0);if(lfd == -1){perror("socket");exit(-1);}//bindstruct sockaddr_in saddr;saddr.sin_family = AF_INET;inet_pton(AF_INET, "192.168.1.108", &saddr.sin_addr.s_addr);//saddr.sin_addr.s_addr = INADDR_ANY;saddr.sin_port = htons(9999);int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));if(ret == -1){perror("bind");exit(-1);}//listenret = listen(lfd, 2);if(ret == -1){perror("listen");exit(-1);}int prosess_num = 0;while(1){//acceptstruct sockaddr_in caddr;socklen_t len = sizeof(caddr);int cfd = accept(lfd, (struct sockaddr *)&caddr, &len);if(cfd == -1){if(errno == EINTR) continue;perror("accept");exit(-1);}//childprosess_num++;pid_t fd = fork();if(fd == -1){perror("fork");exit(-1);}if(fd == 0){char cip[16];printf("the process %d link success!\n", prosess_num);inet_ntop(AF_INET, &caddr.sin_addr, cip, sizeof(cip));printf("client IP:%s, Port:%d\n\n", cip, ntohs(caddr.sin_port));char recv[1025];while(1){********//
//接收数据没有字符结束符,无法判断数据结束,
//使用printf%s出现问题:可以将末尾增加字符结束符/0,也可以使用数据初始化(浪费资源)//memset(recv, 0, 1024);int len = read(cfd, recv, 1024);recv[len] = 0;if(len == -1){perror("read");exit(-1);}else if(len > 0){if(strcmp(recv, "break\r\n") == 0) break;write(cfd, recv, len+1);printf("IP:%s Port:%d: %s", cip, ntohs(caddr.sin_port), recv);}else{printf("client is closed...\n");break;}}printf("the process %d, IP:%s, port:%d, will close!\n", prosess_num, cip, ntohs(caddr.sin_port));close(cfd);exit(0);}}close(lfd); return 0;
}
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <fcntl.h>int main()
{int lfd = socket(PF_INET, SOCK_STREAM, 0);if(lfd == -1){perror("socket");exit(-1);}struct sockaddr_in saddr;saddr.sin_family = AF_INET;int len = sizeof(saddr);inet_pton(AF_INET, "192.168.1.108", &saddr.sin_addr.s_addr);saddr.sin_port = htons(9999);int ret = connect(lfd, (struct sockaddr *)&saddr, sizeof(saddr));if(ret == -1){perror("connect");exit(-1);}printf("client link success!\n");//由于读操作会阻塞,客户端需要先发送数据,若先读取数据,一个进程就会阻塞住,一个进程就要先发数据,如果一次数据没有接收到,则会阻塞住。//可以采用两个进程,发、收互不影响pid_t pid = fork();if(pid==0){char rbuf[1024];while(1){//memset(rbuf, 0, 1024);int lent = read(lfd, rbuf, 1024);if(lent > 0){printf("send: %s", rbuf);}else if(lent == -1) perror("read");}}else if(pid > 0){int i = 0;char sbuf[1024];while(1){ i++;if(i > 255) i = 0;sprintf(sbuf, "the num is %d\n", i);write(lfd, sbuf,strlen(sbuf));sleep(1);}}close(lfd);return 0;
}