能同时处理多个client端的链接,每一个client端的链接,server都会fork一个子进程处理。主进程目的一直监听新进入的client,具体的事情,是放到子进程种处理
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<signal.h>
#include <sys/wait.h>
#define MAXLINE 1024
#define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); } while (0)
void do_service(int connfd)
{
char recvbuf[MAXLINE];
printf("###creat new thread####\n");
while(1)
{
memset(recvbuf,0,sizeof(recvbuf));
printf("wait for client send data: \n");
int ret = recv(connfd, recvbuf, MAXLINE, 0);
//int ret = read(connfd, recvbuf, sizeof(recvbuf));
if (ret == 0)
{
printf("client close\n");
break;
}else if(ret == -1){
ERR_EXIT("read error");
}
printf("####connfd: %d\n", connfd);
printf("recv msg from client: %s\n", recvbuf);
//send msg to client
printf("send ack to client###\n");
//write(connfd, "###server receive ok###", strlen(sendbuf));
send(connfd, "###server receive ok###", MAXLINE, 0 );
if (strcmp(recvbuf,"quit") == 0)
{
printf("child process shutdown\n");
break;
}
}
close(connfd);
}
void sig_chld(int signo)
{
pid_t pid;
int stat;
//pid = wait(&stat);
while((pid = waitpid(-1, &stat, WNOHANG)) >0){
printf("child %d terminated\n",pid);
}
//可以捕获子进程退出的原因?
if(WIFEXITED(stat)){
printf("child process exits,stat=%d\n",WEXITSTATUS(stat));
}
if(WIFSIGNALED(stat)){
printf("child process is killed by signal %d\n", WTERMSIG(stat));
}
return;
}
int main(int argc, char **argv)
{
signal(SIGCHLD, sig_chld);
int listenfd, connfd;
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
ERR_EXIT("socket error");
}
struct sockaddr_in servadd;
memset(&servadd, 0, sizeof(servadd));
servadd.sin_family = AF_INET;
servadd.sin_port = htons(49160);
servadd.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listenfd, (struct sockaddr *)&servadd, sizeof(servadd))<0)
{
ERR_EXIT("bind error");
}
//cd proc/sys/net/core ,查看默认somaxconn为128
if (listen(listenfd, SOMAXCONN) < 0)
{
ERR_EXIT("listen error");
}
//传出参数
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
pid_t pid;
while(1)
{
printf("wait new client to connect.....\n");
if ((connfd = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen)) < 0)
{
if(errno == EINTR){
printf("server continue\n");
continue;
}else{
ERR_EXIT("accept error");
}
}
printf("recv connect ip=%s, port=%d, connect:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), connfd);
pid = fork();
if (pid == -1){
ERR_EXIT("fork error");
}else if (pid == 0)
{
//子进程
close(listenfd);
do_service(connfd);
exit(EXIT_SUCCESS);
}else{
close(connfd);
}
}
return 0;
}
server端如果不注册signal(SIGCHLD, sig_chld); 当client端非法退出的时候,会产生僵尸进程,当多个client异常退出的时候,就会产生多个僵尸进程,最终可能导致系统奔溃,如截图所示,server端产生了三个僵尸进程.
不同的系统pts带的参数不一样,可以ps查看当前进程对应的参数
ps -t pts/0 -o pid,ppid,tty,stat,args,wchan
ps -t pts/4 -o pid,ppid,tty,stat,args,wchan
现在有一个疑问:
同时开启两个或者多个client的时候,为什么server端 accept返回的值 connfd 都为4 ? 正常的理解,内核会维护一个已链接socket的表,每个client 对应的connfd,都应该是唯一的。 哪位大神能回复一下,谢谢
—>这个问题大概明白了,如果同时启动两个client连接server.通过ps -at 就可以看到有5个进程在运行,如图所示:
4457: 是首先启动的server.
当第一个client连接server的时候,又会fork出一个子进程,也就是看到的client pid
4459 ,server pid 4460 。
4459对应的fd如下:
4460对应的fd如下:
从上图可以看出,4460对应的connfd就是4 .
当第二个client连接server的时候,又会fork出一个子进程,也就是看到的client pid
4587 ,server pid 4588 .
4587对应的fd如下:
4588对应的fd如下:
从上图可以看出,4588对应的connfd就是4 .
调用accept返回的fd都为4,最终会被子进程复制一份,然后应用在不同的子进程中,而父类会关闭此连接套接字(connfd),各个子进程对应的pid 不一样,所以从上图可以看出,4460和4588是两个不同的server进程,对应的连接套接字都是从父类继承过来的,所以准确的讲。是内核维护了多个进程的已连接套接字. 所以,套接字不存在重复的说法, 如果select +accept的方式处理连接套接字,多个client连接server,这个时候,accept返回的已连接套接字就是不相同的(同一个进程中就不会存在相同的套接字)