并发服务器
pid_t pid; int listenfd,connfd; listenfd=socket(.....); bind(listenfd,....); listen(listenfd,LISTENQ); for(;;) { connfd=accept(listenfd,...); if((pid=fork())==0) { close(listenfd); /* child closes listening sockfd */ doit(connfd); /* process the request */ close(connfd); /* done with this client */ exit(0); /* child terminates */ } close(connfd); /* parent close connected sockfd */ }
当一个连接建立时,accept返回,服务器接着fork,然后由子进程服务客户(通过已连接套接字connfd),父进程则等待另一个连接。既然新的客户由子进程服务,父进程就关闭已连接套接字。
doit是我们假设执行服务客户所需要的所有操作。当该函数返回时,我们在子进程中显式的关闭已连接套接字。这一点并非必要,因为下一个语句就是exit,而进程终止处理的部分工作就是关闭所有由内核打开的描述符。
一个TCP套接字调用close会导致发送一个FIN,随后是正常的TCP连接终止序列。但是上述代码中父进程队connfd调用close没有终止他与客户的连接。每个文件或套接字都有一个引用计数。引用计数在文件表项中维护,它是当打开着的引用文件或套接字的描述符的个数。上述代码中,socket返回与listenfd关联的文件表项引用计数值为1.accept返回后与connfd关联的文件表项的引用计数值也为1.然而fork返回后,这两个描述符就在父进程与子进程间共享,因此与这两个套接字相关联的文件表项各自的访问计数值均为2.这么一来,当父进程关闭connfd是,他只是把相应的引用计数从2减为1.该套接字真正的清理和资源释放要等到其引用计数值达到0时才发生。这会在稍后子进程也关闭connfd时发生。
上述代码我们用图示演示:

服务器阻塞于accept调用且来自客户的请求到达时客户和服务器的状态

从accept返回后,我们立即有了上图的状态,连接被内核接受,新的套接字conned被创建。这是一个已连接套接字,可由此跨连接读写数据。

并发服务器的下雨不是调用fork

此时listened和conned这两个描述符都在父进程和子进程之间共享(被复制)
在下一步就是由父进程关闭已连接套接字,由子进程关闭监听套接字
这是这两个套接字所期望的最终状态,子进程处理与客户的连接,父进程则可以在监听套接字上再次调用accept来处理下一个客户连接。
#include <unistd.h>
int close(int sockfd);
返回:成功返回0,出错返回-1
close一个TCP套接字的默认行为是吧该套接字标记成已关闭,然后立即返回到调用进程。该套接字描述符不能在由调用进程使用,有就是说不能再作为read或write的第一个参数。
然而TCP将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常TCP连接终止序列。
浙公网安备 33010602011771号