UNIX网络编程笔记(3):简单的并发服务器
解決辦法是將服務(wù)器改為并發(fā)服務(wù)器。這樣,即使有一個(gè)客戶正在和服務(wù)器連接,其它的客戶也能與服務(wù)器建立連接獲得服務(wù)。最簡單的辦法就是fork函數(shù)。
1、fork函數(shù)
fork函數(shù)是UNIX中派生進(jìn)程的唯一方法,定義在<unistd.h>頭文件中:
pid_t fork(void);這個(gè)函數(shù)的奇特之處在于一次調(diào)用,兩次返回。在父進(jìn)程中返回子進(jìn)程的ID,在子進(jìn)程中返回0。所以,我們可以通過返回值判斷當(dāng)前進(jìn)程是父進(jìn)程還是子進(jìn)程。fork函數(shù)在子進(jìn)程中返回0而不是父進(jìn)程ID的原因是:任何子進(jìn)程只有一個(gè)父進(jìn)程,而這個(gè)父進(jìn)程的ID可以通過函數(shù)getppid獲得;但是對(duì)于父進(jìn)程,可以有多個(gè)子進(jìn)程,所以父進(jìn)程無法通過函數(shù)獲得子進(jìn)程ID,如果父進(jìn)程想跟蹤所有子進(jìn)程的ID,那么父進(jìn)程必須在每次調(diào)用fork時(shí)記住子進(jìn)程ID。
2、并發(fā)服務(wù)器
上面介紹了fork函數(shù),可以通過調(diào)用fork函數(shù)達(dá)到并發(fā)的效果。下面是典型的并發(fā)服務(wù)器框架:
pid_t pid; int listenfd,connfd; listenfd=socket(...); bind(listenfd,...); listen(listenfd,...); for(;;) {connfd=accept(listenfd,...);if((pid=fork)==0){close(listenfd);//關(guān)閉listenfddoit(connfd);//處理請(qǐng)求close(connfd);//關(guān)閉子進(jìn)程connfdexit(0);//子進(jìn)程退出}close(onnfd);//關(guān)閉父進(jìn)程connfd }這個(gè)框架中究竟發(fā)生了什么?可以用下面的圖示展示出來。(1)服務(wù)器調(diào)用socket、bind和listen函數(shù)后,處于監(jiān)聽狀態(tài),等待客戶發(fā)送連接請(qǐng)求,這個(gè)時(shí)候服務(wù)器還沒有調(diào)用accept:
(2)這時(shí)服務(wù)器只有一個(gè)套接字描述符listenfd,即監(jiān)聽描述符。當(dāng)服務(wù)器中accept函數(shù)返回時(shí),就有了兩個(gè)套接字描述符listenfd和connfd,套接字描述符connfd是一個(gè)連接描述符,可以進(jìn)行讀寫操作:
(3)這個(gè)時(shí)候客戶與服務(wù)器建立了連接。隨后服務(wù)器調(diào)用fork函數(shù),自己變成父進(jìn)程,復(fù)制自己形成一個(gè)子進(jìn)程。由于兩個(gè)進(jìn)程相同,所以客戶與兩個(gè)進(jìn)程的兩個(gè)connfd都處于連接狀態(tài):
(4)然后,父進(jìn)程中關(guān)閉連接套接字connfd,子進(jìn)程關(guān)閉監(jiān)聽套接字listenfd:
這就達(dá)到了我們希望的狀態(tài),由子進(jìn)程處理客戶請(qǐng)求,父進(jìn)程繼續(xù)監(jiān)聽。
需要注意的是,close函數(shù)會(huì)導(dǎo)致發(fā)送一個(gè)FIN,隨后TCP連接應(yīng)該終止。那為什么父進(jìn)程中close(connfd)后沒有終止與客戶的連接呢?每個(gè)文件描述符都有一個(gè)引用計(jì)數(shù),是當(dāng)前打開著的引用該文件的描述符的個(gè)數(shù),只有引用計(jì)數(shù)為0時(shí)文件才會(huì)關(guān)閉。而fork函數(shù)執(zhí)行后,對(duì)connfd套接字的引用計(jì)數(shù)是2,父進(jìn)程關(guān)閉connfd后引用計(jì)數(shù)變?yōu)?,所以不會(huì)關(guān)閉連接。然而在子進(jìn)程中,關(guān)閉connfd后引用計(jì)數(shù)變?yōu)?,會(huì)關(guān)閉與客戶的連接。
3、時(shí)間獲取程序的改進(jìn)
這里我們只需要改進(jìn)服務(wù)器程序,將它變成一個(gè)并發(fā)服務(wù)器。代碼如下:
#include <sys/socket.h> #include <string.h> #include <strings.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <time.h> #include <unistd.h> #define MAXLINE 1024int main(int argc,char *argv[]) {int listenfd;struct sockaddr_in servaddr;char buff[MAXLINE];time_t ticks;if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0){printf("socket error\n");return 0;}bzero(&servaddr,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(5000);servaddr.sin_addr.s_addr=htonl(INADDR_ANY);if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0){printf("bind error\n");return 0;}if(listen(listenfd,5)<0){printf("listen error\n");return 0;}int connfd;socklen_t len;struct sockaddr_in cliaddr;pid_t pid;for(;;){len=sizeof(cliaddr);if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&len))<0){printf("accept error\n");return 0;}if((pid=fork())==0){if(close(listenfd)<0){printf("close listenfd error\n");return 0;}printf("[PID]%ld Receive a connection from:%s.%d\n",(long)getpid(),inet_ntop(AF_INET,&cliaddr.sin_addr,buff,sizeof(buff)),ntohs(cliaddr.sin_port));ticks=time(NULL);snprintf(buff,sizeof(buff),"%.24s\r\n",ctime(&ticks));if(write(connfd,buff,strlen(buff))<0){printf("write error\n");return 0;}printf("[PID]%ld sleep 5s.\n",(long)getpid());sleep(5);printf("[PID]%ld sleep done.\n",(long)getpid());if(close(connfd)<0){printf("close child connfd error\n");return 0;}return 0;}if(close(connfd)<0){printf("close parent connfd error\n");return 0;}} }這里我們?yōu)榱嗽黾臃?wù)器處理每個(gè)請(qǐng)求的時(shí)間,加了sleep(5),會(huì)看到,服務(wù)器在處理一個(gè)客戶的請(qǐng)求時(shí),也會(huì)與另一個(gè)客戶建立連接處理請(qǐng)求。運(yùn)行結(jié)果如下:(1)打開服務(wù)器:
(2)客戶1連接(進(jìn)程ID是3594):
(3)客戶2連接(進(jìn)程ID是3596):
(4)下圖是服務(wù)器處理情況:
可以看到,在客戶1(進(jìn)程ID是3594)仍在sleep的時(shí)候,服務(wù)器接受了客戶2(進(jìn)程ID是3596)的連接。
總結(jié)
以上是生活随笔為你收集整理的UNIX网络编程笔记(3):简单的并发服务器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 巫蛊笔记剧情介绍
- 下一篇: UNIX网络编程笔记(4):简单的回射程