日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

UNIX网络编程笔记(3):简单的并发服务器

發布時間:2023/11/30 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 UNIX网络编程笔记(3):简单的并发服务器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
上一講中的簡單時間獲取服務器是一個迭代服務器,對于獲取時間來說夠用了。迭代服務器有這樣的特點:同一時間只能給一個客戶服務。也就是說,如果某一時刻服務器與某個客戶正在連接,其它客戶必須等到上一個客戶與服務器斷開連接后才能連接成功。這對于需要花長時間處理客戶請求的服務器來說并不適用。
解決辦法是將服務器改為并發服務器。這樣,即使有一個客戶正在和服務器連接,其它的客戶也能與服務器建立連接獲得服務。最簡單的辦法就是fork函數。

1、fork函數

fork函數是UNIX中派生進程的唯一方法,定義在<unistd.h>頭文件中:

pid_t fork(void);這個函數的奇特之處在于一次調用,兩次返回。在父進程中返回子進程的ID,在子進程中返回0。所以,我們可以通過返回值判斷當前進程是父進程還是子進程。

fork函數在子進程中返回0而不是父進程ID的原因是:任何子進程只有一個父進程,而這個父進程的ID可以通過函數getppid獲得;但是對于父進程,可以有多個子進程,所以父進程無法通過函數獲得子進程ID,如果父進程想跟蹤所有子進程的ID,那么父進程必須在每次調用fork時記住子進程ID。

2、并發服務器

上面介紹了fork函數,可以通過調用fork函數達到并發的效果。下面是典型的并發服務器框架:

pid_t pid; int listenfd,connfd; listenfd=socket(...); bind(listenfd,...); listen(listenfd,...); for(;;) {connfd=accept(listenfd,...);if((pid=fork)==0){close(listenfd);//關閉listenfddoit(connfd);//處理請求close(connfd);//關閉子進程connfdexit(0);//子進程退出}close(onnfd);//關閉父進程connfd }這個框架中究竟發生了什么?可以用下面的圖示展示出來。

(1)服務器調用socket、bind和listen函數后,處于監聽狀態,等待客戶發送連接請求,這個時候服務器還沒有調用accept:


(2)這時服務器只有一個套接字描述符listenfd,即監聽描述符。當服務器中accept函數返回時,就有了兩個套接字描述符listenfd和connfd,套接字描述符connfd是一個連接描述符,可以進行讀寫操作:


(3)這個時候客戶與服務器建立了連接。隨后服務器調用fork函數,自己變成父進程,復制自己形成一個子進程。由于兩個進程相同,所以客戶與兩個進程的兩個connfd都處于連接狀態:


(4)然后,父進程中關閉連接套接字connfd,子進程關閉監聽套接字listenfd:


這就達到了我們希望的狀態,由子進程處理客戶請求,父進程繼續監聽。

需要注意的是,close函數會導致發送一個FIN,隨后TCP連接應該終止。那為什么父進程中close(connfd)后沒有終止與客戶的連接呢?每個文件描述符都有一個引用計數,是當前打開著的引用該文件的描述符的個數,只有引用計數為0時文件才會關閉。而fork函數執行后,對connfd套接字的引用計數是2,父進程關閉connfd后引用計數變為1,所以不會關閉連接。然而在子進程中,關閉connfd后引用計數變為0,會關閉與客戶的連接。

3、時間獲取程序的改進

這里我們只需要改進服務器程序,將它變成一個并發服務器。代碼如下:

#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;}} }這里我們為了增加服務器處理每個請求的時間,加了sleep(5),會看到,服務器在處理一個客戶的請求時,也會與另一個客戶建立連接處理請求。運行結果如下:

(1)打開服務器:


(2)客戶1連接(進程ID是3594):


(3)客戶2連接(進程ID是3596):


(4)下圖是服務器處理情況:


可以看到,在客戶1(進程ID是3594)仍在sleep的時候,服務器接受了客戶2(進程ID是3596)的連接。

總結

以上是生活随笔為你收集整理的UNIX网络编程笔记(3):简单的并发服务器的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。