从服务端多进程模型理解TCP连接

时间:2020-12-31 14:59:38

前面写了二篇关于TCP连接建立与listen(),accept()函数调用关系的文章:

之前对listen、accpet函数理解误区----《Linux高性能服务器编程》读书笔记

listen函数与TCP连接建立过程的关系

今天再补充下多进程模型服务端程序TCP连接建立过程,

服务端代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>

#define IP "127.0.0.1"
#define PORT 9000
#define WORKER 4

int worker( int listenfd, int idx )
{
	while( 1 )
	{
		printf( "I am worker[%d] begin to accept connection\n", idx );
		struct sockaddr_in cli_addr;
		socklen_t cliaddr_len = sizeof( cli_addr );
		int clifd = accept( listenfd, ( sockaddr* )&cli_addr, &cliaddr_len );
		if( clifd != -1 )
		{
			printf( "worker[%d] pid[%d] accept a connect from client[%s:%d] success\n", idx, getpid(),
				inet_ntoa( cli_addr.sin_addr ), ntohs( cli_addr.sin_port ) );
				
		}
		else 
		{
			printf( "worker[%d] accept a connect failed err[%s]\n", idx, strerror( errno ) );
		}
	}
	
	return 0;
}

int main( int argc, char* argv[] )
{
	int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
	assert( listenfd != -1 );
	
	struct sockaddr_in svr_addr;
	bzero( &svr_addr, sizeof( svr_addr ) );
	svr_addr.sin_family = AF_INET;
	inet_pton( AF_INET, IP, &svr_addr.sin_addr );
	svr_addr.sin_port = htons( PORT );
	
	int ret = bind( listenfd, (  const struct sockaddr* )&svr_addr, sizeof( svr_addr ) );
	assert( ret != -1 );
	
	ret = listen( listenfd, 5 );
	assert( ret != -1 );
	printf( "svr pid[%d] listen[%s:%d] success\n", getpid(), IP, PORT );
	
	for( int i = 0; i < WORKER; i++ )
	{
		pid_t pid = fork();
		printf( "create worker[%d]\n", i );
		if( pid == 0 )	//child process
		{
			worker( listenfd, i );
		}
		else if( pid > 0 )
		{
			printf( "pid[%d]\n", pid );
		}
		else
		{
			printf( "fork err[%s]\n", strerror( errno ) );
		}
	}
	
	int status;
	wait( &status );
	
	return 0;
}

主进程监听9000端口,子进程accept客户端连接。

从服务端多进程模型理解TCP连接

从服务端多进程模型理解TCP连接

打开两个客户端连接9000端口,然后查看连接:

从服务端多进程模型理解TCP连接

主进程14573监听9000(listenfd), 子进程14574和14575跟客户端建立TCP连接。

综合之前2篇文章总结TCP连接建立过程:

1)服务端主进程调用listen()函数开始监听服务端口,内核建立SYN队列(未完成握手队列)和ACCEPT队列(已完成握手队列)。

2)客户端调connect发起连接,三次握手完成后,已完成连接放入ACCEPT队列。

3)服务端(可能是master或worker)调用accept从ACCEPT队列取出连接,并创建一个新的代表连接双方的socket。服务端

与客户端建立连接的进程就是对应调用accept返回成功的进程。

之前对listen、accpet函数理解误区----《Linux高性能服务器编程》读书笔记文章中,服务端没有调用accept(),此时TCP连接虽然建立(三次握手完成),但无法完成数据交互。因为连接的服务器一端没有指定具体进程,如图:

从服务端多进程模型理解TCP连接

红色标识的连接信息中没有进程ID