前面写了二篇关于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