tcp socket server端

能同时处理多个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返回的已连接套接字就是不相同的(同一个进程中就不会存在相同的套接字)

发布了95 篇原创文章 · 获赞 14 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ding283595861/article/details/104390861