在上一篇程序框架中,解决了子进程退出,父进程继续存在的功能,但是多条客户端连接如果同一时间并行退出,
导致服务器端多个子进程同一时间全部退出,而SIGCHLD是不可靠信号,同时来多条信号可能无法处理,导致出现僵尸进程,
如果使用while循环wait又会阻塞父进程,这里采取waitpid()函数来解决这个问题。
//辅助类实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/wait.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "pub.h"
ssize_t readn(int fd, const void *buf, ssize_t count)
{
if (buf == NULL)
{
printf("readn() params is not correct !
");
return -1;
}
//定义剩余字节数
ssize_t lread = count;
//定义辅助指针变量
char *pbuf = (char *) buf;
//定义每次读取的字节数
ssize_t nread = 0;
while (lread > 0)
{
nread = read(fd, pbuf, lread);
if (nread == -1)
{
//read是可中断睡眠函数,需要屏蔽信号
if (errno == EINTR)
continue;
perror("read() err");
return -1;
} else if (nread == 0)
{
printf("peer read socket is closed !
");
//返回已经读取的字节数
return count - lread;
}
//重置剩余字节数
lread -= nread;
//辅助指针后移
pbuf += nread;
}
return count;
}
ssize_t writen(int fd, const void *buf, ssize_t count)
{
if (buf == NULL)
{
printf("writen() params is not correct !
");
return -1;
}
//定于剩余字节数
ssize_t lwrite = count;
//定义每次写入字节数
ssize_t nwrite = 0;
//定义辅助指针变量
char *pbuf = (char *) buf;
while (lwrite > 0)
{
nwrite = write(fd, pbuf, lwrite);
if (nwrite == -1)
{
if (errno == EINTR)
continue;
perror("write() err");
return -1;
} else if (nwrite == 0)
{
printf("peer write socket is closed !
");
return count - lwrite;
}
//重置剩余字节数
lwrite -= nwrite;
//辅助指针变量后移
pbuf += nwrite;
}
return count;
}
ssize_t recv_peek(int fd, const void *buf, ssize_t count)
{
if (buf == NULL)
{
printf("recv_peek() params is not correct !
");
return -1;
}
ssize_t ret = 0;
while (1)
{
//此处有多少读取多少,不一定ret==count
ret = recv(fd, (void *) buf, count, MSG_PEEK);
if (ret == -1 && errno == EINTR)
continue;
return ret;
}
return -1;
}
ssize_t mreadline(int fd, const void *buf, ssize_t count)
{
//定义剩余字节数
ssize_t lread = count;
//定义每次读取的字节数
ssize_t nread = 0;
//定义辅助指针变量
char *pbuf = (char *) buf;
int i = 0, ret = 0;
while (1)
{
nread = recv_peek(fd, pbuf, count);
if (nread == -1)
{
perror("recv_peek() err");
return -1;
} else if (nread == 0)
{
//注意:这里一个客户端有两个进程,也就是套接字会关闭两次,会向服务器发送两次FIN信号
printf("peer socket is closed !
");
return -1;
}
for (i = 0; i < nread; i++)
{
if (pbuf[i] == '
')
{
//这是一段报文
memset(pbuf, 0, count);
//从socket缓存区读取i+1个字节
ret = readn(fd, pbuf, i + 1);
if (ret != i + 1)
return -1;
return ret;
}
}
//如果当前socket缓存区中没有
,
//那么先判断自定义buf是否还有空间,如果没有空间,直接退出
//如果有空间,先将当前socket缓存区中的数据读出来,放入buf中,清空socket缓存
//继续recv,判断下一段报文有没有
if (lread >= count)
{
printf("自定义buf太小了!
");
return -1;
}
//读取当前socket缓存
ret = readn(fd, pbuf, nread);
if (ret != nread)
return -1;
lread -= nread;
pbuf += nread;
}
return -1;
}
void handler(int sign)
{
if (sign == SIGCHLD)
{
int mypid=0;
//WNOHANG 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若结束,则返回该子进程的ID。
while((mypid=waitpid(-1,NULL,WNOHANG))>0)
{
printf("子进程pid=%d
",mypid);
}
//wait(NULL);
}
}
int server_socket()
{
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1)
{
perror("socket() err");
return -1;
}
//reuseaddr
int optval = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))
== -1)
{
perror("setsockopt() err");
return -1;
}
//bind
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(listenfd, (struct sockaddr *) &addr, sizeof(addr)) == -1)
{
perror("bind() err");
return -1;
}
//listen
if (listen(listenfd, SOMAXCONN) == -1)
{
perror("listen()err");
return -1;
}
pid_t pid = 0;
//忽略SIGCHLD信号
//signal(SIGCHLD,SIG_IGN);
//安装信号
if (signal(SIGCHLD, handler) == SIG_ERR)
{
printf("signal() failed !
");
return -1;
}
while (1)
{
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn = accept(listenfd, (struct sockaddr *) &peeraddr, &peerlen);
printf("accept by %s
", inet_ntoa(peeraddr.sin_addr));
if (conn == -1)
{
perror("accept() err");
return -1;
}
pid = fork();
if (pid == -1)
{
perror("fork() err");
return -1;
}
//子进程接收数据
if (pid == 0)
{
//关闭监听套接字
close(listenfd);
char buf[1024] = { 0 };
int ret = 0;
while (1)
{
ret = mreadline(conn, buf, 1024);
if (ret == -1)
{
close(conn);
return -1;
}
//打印客户端数据
fputs(buf, stdout);
//把数据返回给客户端
writen(conn, buf, ret);
memset(buf, 0, sizeof(buf));
}
} else if (pid > 0)
{
close(conn);
}
}
return 0;
}
int client_say(int fd)
{
int ret = 0;
char buf[1024] = { 0 };
while (fgets(buf, 1024, stdin) != NULL)
{
//发送数据
ret = writen(fd, buf, strlen(buf));
if (ret != strlen(buf))
return -1;
memset(buf, 0, sizeof(buf));
ret = mreadline(fd, buf, sizeof(buf));
if (ret == -1)
{
return -1;
}
fputs(buf,stdout);
memset(buf, 0, sizeof(buf));
}
return 0;
}
int client_socket()
{
int sockarr[10] = { 0 };
int i = 0;
//同时创建5个连接
for (i = 0; i < 5; i++)
{
sockarr[i] = socket(AF_INET, SOCK_STREAM, 0);
if (sockarr[i] == -1)
{
perror("socket() err");
return -1;
}
//bind
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sockarr[i], (struct sockaddr *) &addr, sizeof(addr)) == -1)
{
perror("connect() err");
return -1;
}
//获取本机地址
struct sockaddr_in myaddr;
socklen_t mylen = sizeof(myaddr);
if (getsockname(sockarr[i], (struct sockaddr *) &myaddr, &mylen) == -1)
{
perror("getsockname() err");
return -1;
}
printf("本次连接地址:%s
",inet_ntoa(myaddr.sin_addr));
}
client_say(sockarr[0]);
return 0;
}