前面写了二篇关于TCP连接建立与listen(),accept()函数调用关系的文章:
之前对listen、accpet函数理解误区----《Linux高性能服务器编程》读书笔记
今天再补充下多进程模型服务端程序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客户端连接。
打开两个客户端连接9000端口,然后查看连接:
主进程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连接虽然建立(三次握手完成),但无法完成数据交互。因为连接的服务器一端没有指定具体进程,如图:
红色标识的连接信息中没有进程ID