思路
每当一个客户端连接服务器后,创建一个子进程负责与该客户端通信,客户端断开连接之后,服务器回收子进程资源。
问题
问题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);
//socket
int lfd = socket(PF_INET, SOCK_STREAM, 0);
if(lfd == -1)
{
perror("socket");
exit(-1);
}
//bind
struct 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);
}
//listen
ret = listen(lfd, 2);
if(ret == -1)
{
perror("listen");
exit(-1);
}
int prosess_num = 0;
while(1)
{
//accept
struct 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);
}
//child
prosess_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;
}