Linux 网络编程详解七(并发僵尸进程处理)
在上一篇程序框架中,解决了子进程退出,父进程继续存在的功能,但是多条客户端连接如果同一时间并行退出,
导致服务器端多个子进程同一时间全部退出,而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 !\n");
return -;
}
//定义剩余字节数
ssize_t lread = count;
//定义辅助指针变量
char *pbuf = (char *) buf;
//定义每次读取的字节数
ssize_t nread = ;
while (lread > )
{
nread = read(fd, pbuf, lread);
if (nread == -)
{
//read是可中断睡眠函数,需要屏蔽信号
if (errno == EINTR)
continue;
perror("read() err");
return -;
} else if (nread == )
{
printf("peer read socket is closed !\n");
//返回已经读取的字节数
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 !\n");
return -;
}
//定于剩余字节数
ssize_t lwrite = count;
//定义每次写入字节数
ssize_t nwrite = ;
//定义辅助指针变量
char *pbuf = (char *) buf;
while (lwrite > )
{
nwrite = write(fd, pbuf, lwrite);
if (nwrite == -)
{
if (errno == EINTR)
continue;
perror("write() err");
return -;
} else if (nwrite == )
{
printf("peer write socket is closed !\n");
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 !\n");
return -;
}
ssize_t ret = ;
while ()
{
//此处有多少读取多少,不一定ret==count
ret = recv(fd, (void *) buf, count, MSG_PEEK);
if (ret == - && errno == EINTR)
continue;
return ret;
}
return -;
} ssize_t mreadline(int fd, const void *buf, ssize_t count)
{
//定义剩余字节数
ssize_t lread = count;
//定义每次读取的字节数
ssize_t nread = ;
//定义辅助指针变量
char *pbuf = (char *) buf;
int i = , ret = ;
while ()
{
nread = recv_peek(fd, pbuf, count);
if (nread == -)
{
perror("recv_peek() err");
return -;
} else if (nread == )
{
//注意:这里一个客户端有两个进程,也就是套接字会关闭两次,会向服务器发送两次FIN信号
printf("peer socket is closed !\n");
return -;
}
for (i = ; i < nread; i++)
{
if (pbuf[i] == '\n')
{
//这是一段报文
memset(pbuf, , count);
//从socket缓存区读取i+1个字节
ret = readn(fd, pbuf, i + );
if (ret != i + )
return -;
return ret;
}
}
//如果当前socket缓存区中没有\n,
//那么先判断自定义buf是否还有空间,如果没有空间,直接退出
//如果有空间,先将当前socket缓存区中的数据读出来,放入buf中,清空socket缓存
//继续recv,判断下一段报文有没有\n
if (lread >= count)
{
printf("自定义buf太小了!\n");
return -;
}
//读取当前socket缓存
ret = readn(fd, pbuf, nread);
if (ret != nread)
return -;
lread -= nread;
pbuf += nread;
}
return -;
} void handler(int sign)
{
if (sign == SIGCHLD)
{
int mypid=;
//WNOHANG 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若结束,则返回该子进程的ID。
while((mypid=waitpid(-,NULL,WNOHANG))>)
{
printf("子进程pid=%d\n",mypid);
}
//wait(NULL);
}
} int server_socket()
{
int listenfd = socket(AF_INET, SOCK_STREAM, );
if (listenfd == -)
{
perror("socket() err");
return -;
}
//reuseaddr
int optval = ;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))
== -)
{
perror("setsockopt() err");
return -;
}
//bind
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(listenfd, (struct sockaddr *) &addr, sizeof(addr)) == -)
{
perror("bind() err");
return -;
}
//listen
if (listen(listenfd, SOMAXCONN) == -)
{
perror("listen()err");
return -;
}
pid_t pid = ;
//忽略SIGCHLD信号
//signal(SIGCHLD,SIG_IGN);
//安装信号
if (signal(SIGCHLD, handler) == SIG_ERR)
{
printf("signal() failed !\n");
return -;
}
while ()
{
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn = accept(listenfd, (struct sockaddr *) &peeraddr, &peerlen);
printf("accept by %s\n", inet_ntoa(peeraddr.sin_addr));
if (conn == -)
{
perror("accept() err");
return -;
}
pid = fork();
if (pid == -)
{
perror("fork() err");
return -;
}
//子进程接收数据
if (pid == )
{
//关闭监听套接字
close(listenfd);
char buf[] = { };
int ret = ;
while ()
{
ret = mreadline(conn, buf, );
if (ret == -)
{
close(conn);
return -;
}
//打印客户端数据
fputs(buf, stdout);
//把数据返回给客户端
writen(conn, buf, ret);
memset(buf, , sizeof(buf));
}
} else if (pid > )
{
close(conn);
}
}
return ;
} int client_say(int fd)
{
int ret = ;
char buf[] = { };
while (fgets(buf, , stdin) != NULL)
{
//发送数据
ret = writen(fd, buf, strlen(buf));
if (ret != strlen(buf))
return -;
memset(buf, , sizeof(buf));
ret = mreadline(fd, buf, sizeof(buf));
if (ret == -)
{
return -;
}
fputs(buf,stdout);
memset(buf, , sizeof(buf));
}
return ;
} int client_socket()
{
int sockarr[] = { };
int i = ;
//同时创建5个连接
for (i = ; i < ; i++)
{
sockarr[i] = socket(AF_INET, SOCK_STREAM, );
if (sockarr[i] == -)
{
perror("socket() err");
return -;
}
//bind
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sockarr[i], (struct sockaddr *) &addr, sizeof(addr)) == -)
{
perror("connect() err");
return -;
}
//获取本机地址
struct sockaddr_in myaddr;
socklen_t mylen = sizeof(myaddr);
if (getsockname(sockarr[i], (struct sockaddr *) &myaddr, &mylen) == -)
{
perror("getsockname() err");
return -;
}
printf("本次连接地址:%s\n",inet_ntoa(myaddr.sin_addr));
}
client_say(sockarr[]);
return ;
}
Linux 网络编程详解七(并发僵尸进程处理)的更多相关文章
- TCP/UDP Linux网络编程详解
本文主要记录TCP/UDP网络编程的基础知识,采用TCP/UDP实现宿主机和目标机之间的网络通信. 内容目录 1. 目标2.Linux网络编程基础2.1 嵌套字2.2 端口2.3 网络地址2.3.1 ...
- Linux 网络编程详解六(多进程服务器僵尸进程解决方案)
小结:在点对点p2p程序中,服务器端子程序退出,子进程会主动发送信号,关闭父进程,但是这种模式导致服务器只能支持一个客户端连接,本章节中使用新的框架,子进程退出,不主动发送信号关闭父进程,而是父进程安 ...
- Linux 网络编程详解九
TCP/IP协议中SIGPIPE信号产生原因 .假设客户端socket套接字close(),会给服务器发送字节段FIN: .服务器接收到FIN,但是没有调用close(),因为socket有缓存区,所 ...
- Linux 网络编程详解二(socket创建流程、多进程版)
netstat -na | grep " --查看TCP/IP协议连接状态 //socket编程提高版--服务器 #include <stdio.h> #include < ...
- Linux 网络编程详解一(IP套接字结构体、网络字节序,地址转换函数)
IPv4套接字地址结构 struct sockaddr_in { uint8_t sinlen;(4个字节) sa_family_t sin_family;(4个字节) in_port_t sin_p ...
- Linux 网络编程详解十一
/** * read_timeout - 读超时检测函数,不含读操作 * @fd:文件描述符 * @wait_seconds:等待超时秒数,如果为0表示不检测超时 * 成功返回0,失败返回-1,超时返 ...
- Linux 网络编程详解十
select int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *tim ...
- Linux 网络编程详解五(TCP/IP协议粘包解决方案二)
ssize_t recv(int s, void *buf, size_t len, int flags); --与read相比,只能用于网络套接字文件描述符 --当flags参数的值设置为MSG_P ...
- Linux 网络编程详解四(流协议与粘包)
TCP/IP协议是一种流协议,流协议是字节流,只有开始和结束,包与包之间没有边界,所以容易产生粘包,但是不会丢包. UDP/IP协议是数据报,有边界,不存在粘包,但是可能丢包. 产生粘包问题的原因 . ...
随机推荐
- 基于分布式、服务化的maven项目文件规划
引言 此文不是纯粹介绍maven概念,而是介绍一个具体的maven项目文件规划 这个规划可能适合于研发比较复杂的业务,这些业务有分布式和服务化的需要. 这个规划能够解决因为分布式和服务化要求而引起的项 ...
- 2014.1.23 Discuz论坛迁移+VPS配置手记
虽说这也不是我第一次转移这个论坛了,但毕竟还是第一次自己配置VPS,写点东西记一下 一:关于VPS的配置 1.用TeamViewer连接服务器 这个VPS的IDC自己带有一个远程控制的页面,用浏览器打 ...
- 双十一来了,别让你的mongodb宕机了
好久没过来吹牛了,前段时间一直赶项目,没有时间来更新博客,项目也终于赶完了,接下来就要面临双十一这场惊心动魄的处女秀考验, 我们项目中会有一个wcf集群,而集群地址则放在mongodb中,所以mong ...
- 说下查询动作 Pivot
上一篇说了一下查询5步走~然后就几天_(:з」∠)_ ~今天继续说一下其中 表里面操作符里面的 Pivot ~ Pivot 在实现行转列的时候灰常有用.通常一个例子 ), ),LoginTime TI ...
- Office——EXCEL 打开自动修改 关闭自动保存
==================================声明================================== 本文版权归作者所有 未经作者授权 请勿转载 保留法律追究的 ...
- 【mysql】数据库使用的一些规范
一.MySQL存在的问题 优化器对复杂SQL支持不好 对SQL标准支持不好 大规模集群方案不成熟,主要指中间件 ID生成器,全局自增ID 异步逻辑复制,数据安全性问题 Online DDL HA方案不 ...
- 如何获得浏览器localStorage的剩余容量
一.如何获取localStorage的剩余容量 在H5大行其道的今天,localStorage(本地存储)对每一个前断攻城师来说都不太陌生.同时localStorage也给我们带来了极大的便利,不用于 ...
- VIM使用技巧总结
一.vim使用的基本配置 1: set nu //设置行号 2: set ts=4 //设置tab为4个空格大小 3: set expandtab //设置用空格代替tab 4: set ai //设 ...
- Java enum的用法详解
(转自:http://www.cnblogs.com/happyPawpaw/archive/2013/04/09/3009553.html) 用法一:常量 在JDK1.5 之前,我们定义常量都是: ...
- centos---无线上网的电脑所安装的虚拟机网络设置
1.共享设置 找到本地的无线网卡,右键---属性---共享,设置如下 2.修改VMware Network Adapter VMnet1 选中VMware Network Adapter VMnet1 ...