Linux:多进程、多线程服务器的实现解析(有图有代码有真相!!!)
一、問題引入
阻塞型的網(wǎng)絡(luò)編程接口
幾乎所有的程序員第一次接觸到的網(wǎng)絡(luò)編程都是從 listen()、send()、recv()等接口開始的。使用這些接口可以很方便的構(gòu)建服務(wù)器 /客戶機的模型。
我們假設(shè)希望建立一個簡單的服務(wù)器程序,實現(xiàn)向單個客戶機提供類似于“一問一答”的內(nèi)容服務(wù)。
我們注意到,大部分的 socket接口都是阻塞型的。所謂阻塞型接口是指系統(tǒng)調(diào)用(一般是 IO接口)不返回調(diào)用結(jié)果并讓當前線程一直阻塞,只有當該系統(tǒng)
調(diào)用獲得結(jié)果或者超時出錯時才返回。實際上,除非特別指定,幾乎所有的 IO接口 (包括 socket 接口 )都是阻塞型的。這給網(wǎng)絡(luò)編程帶來了一個很大的問題,如在調(diào)用 send()的同時,線程將被
阻塞,在此期間,線程將無法執(zhí)行任何運算或響應(yīng)任何的網(wǎng)絡(luò)請求。這給多客戶機、多業(yè)務(wù)邏輯的網(wǎng)絡(luò)編程帶來了挑戰(zhàn)。這時,很多程序員可能會選擇多線程的方式來解決這個問題。
二、多進程多線程
應(yīng)對多客戶機的網(wǎng)絡(luò)應(yīng)用,最簡單的解決方式是在服務(wù)器端使用多線程(或多進程)。多線程(或多進程)的目的是讓每個連接都擁有獨立的線程(或進
程),這樣任何一個連接的阻塞都不會影響其他的連接。具體使用多進程還是多線程,并沒有?一個特定的模式。傳統(tǒng)意義上,進程的開
銷要遠遠大于線程,所以,如果需要同時為較多的客戶機提供服務(wù),則不推薦使用多進程;如果單個服務(wù)執(zhí)行體需要消耗較多的 CPU 資源,譬如需要進行
大規(guī)模或長時間的數(shù)據(jù)運算或文件訪問,則進程較為安全。通常,使用pthread_create () 創(chuàng)建新線程,fork() 創(chuàng)建新進程。
我們假設(shè)對上述的服務(wù)器 / 客戶機模型,提出更高的要求,即讓服務(wù)器同時為多個客戶機提供一問一答的服務(wù)。于是有了如上的模型。
在上述的線程 / 時間圖例中,主線程持續(xù)等待客戶端的連接請求,如果有連接,則創(chuàng)建新線程,并在新線程中提供為前例同樣的問答服務(wù)。
很多初學者可能不明白為何一個 socket 可以 accept 多次。實際上,socket的設(shè)計者可能特意為多客戶機的情況留下了伏筆,讓 accept() 能夠返回一個新
的 socket。下面是 accept 接口的原型:輸入?yún)?shù) sockfd 是從 socket(),bind() 和 listen() 中沿用下來的 socket 句柄
值。執(zhí)行完 bind() 和 listen() 后,操作系統(tǒng)已經(jīng)開始在指定的端口處監(jiān)聽所有的連接請求,如果有請求,則將該連接請求加入請求隊列。調(diào)用 accept() 接口
正是從 socket s 的請求隊列抽取第一個連接信息,創(chuàng)建?一個與 socked同類的新的 socket 返回句柄。新的 socket 句柄即是后續(xù) read() 和 recv() 的輸入?yún)?br /> 數(shù)。如果請求隊列當前沒有請求,則 accept() 將進?入阻塞狀態(tài)直到有請求進入隊列。
1、多進程服務(wù)器、客戶端實現(xiàn)簡單通信
fork_server.c:#include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/types.h> #include<sys/socket.h> #include<arpa/inet.h> #include<netinet/in.h> static int startup(const char *_ip,int _port) {int sock=socket(AF_INET,SOCK_STREAM,0);if(sock<0){perror("socket");return 3; }struct sockaddr_in local;local.sin_family=AF_INET;local.sin_port=htons(_port);local.sin_addr.s_addr=inet_addr(_ip);if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){perror("bind");return 4;}if(listen(sock,10)<0){perror("listen");return 5;} } static void usage(const char *proc) {printf("usage:[local_ip] [local_port]",proc); } int main(int argc,char *argv[]) {if(argc!=3){usage(argv[0]);printf("usage");return 1;}int listen_sock=startup(argv[1],atoi(argv[2]));struct sockaddr_in peer;socklen_t len=sizeof(peer);while(1){int new_sock=accept(listen_sock,(struct sockaddr*)&peer,&len);if(new_sock>0){pid_t id=fork();if(id==0){//childclose(listen_sock);char buf[1024];while(1){ssize_t s=read(new_sock,buf,sizeof(buf)-1);if(s>0){buf[s]=0;printf("client say#%s\n",buf);write(new_sock,buf,strlen(buf));}else if(s==0){printf("client quick\n");}else{break;}}close(new_sock);exit(1);}else{//fatherclose(new_sock);if(fork()>0){exit(0);}}}else{perror("new_sock");return 2;}}return 0; }fork_client.c:#include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/types.h> #include<sys/socket.h> #include<arpa/inet.h> #include<netinet/in.h> static void usage(const char *proc) {printf("usage:[server_ip] [server_port]",proc); } int main(int argc,char *argv[]) {int sock=socket(AF_INET,SOCK_STREAM,0);if(sock<0){perror("socket");return 1;}struct sockaddr_in server;server.sin_family=AF_INET;server.sin_port=htons(atoi(argv[2]));server.sin_addr.s_addr=inet_addr(argv[1]);if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0){perror("connect");return 2;}char buf[1024];while(1){printf("please enter:");fflush(stdout);ssize_t s=read(0,buf,sizeof(buf)-1);if(s>0){buf[s-1]=0;write(sock,buf,strlen(buf));ssize_t _s=read(sock,buf,sizeof(buf)-1);if(_s>0){buf[_s]=0;printf("server echo:%s\n",buf);}}}close(sock);return 0; }
2、多線程實現(xiàn)簡單服務(wù)器(遠程登陸:telnet)
thread_server.c#include<stdio.h> #include<pthread.h> #include<stdlib.h> #include<sys/types.h> #include<sys/socket.h> #include<arpa/inet.h> #include<netinet/in.h> #include<string.h> void* handleRequest(void* arg) {char buf[10240];int sock=(int)arg;while(1){ssize_t s=read(sock,buf,sizeof(buf)-1);//successif(s>0){buf[s]=0;printf("%s\n",buf);const char *msg= "HTTP/1.1 200 OK\r\n\r\n<html><h1>This is title</h1></html>\r\n";write(sock,msg,strlen(msg));break;}else if(s==0) {printf("client is quit!\n");break;}else{perror("read");break;}}close(sock); } int startup(const char *_ip,int _port) {//create socketint sock=socket(AF_INET,SOCK_STREAM,0);if(sock<0){perror("socket");return 2;}int flag=1;setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));//bindstruct sockaddr_in local;local.sin_family=AF_INET;local.sin_port=htons(_port);local.sin_addr.s_addr=inet_addr(_ip);if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){perror("bind");return 3;}//listenif(listen(sock,10)<0){perror("listen");return 4;}return sock; } static void usage(char *proc) {printf("usage:%s [server_ip] [server_port]",proc); } int main(int argc, char *argv[]) {if(argc!=3){usage(argv[0]);return 1;}int listen_sock=startup(argv[1],atoi(argv[2]));struct sockaddr_in peer;socklen_t len=sizeof(peer);while(1){int new_sock=accept(listen_sock,(struct sockaddr*)&peer,&len); if(new_sock<0){perror("accept");continue;}printf("client ip:%s,port:%d\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));pthread_t id;pthread_create(&id,NULL,handleRequest,(void*)new_sock);pthread_detach(id);}return 0; }
總結(jié)
以上是生活随笔為你收集整理的Linux:多进程、多线程服务器的实现解析(有图有代码有真相!!!)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据有序_Redis实战(3)-数据结构
- 下一篇: 最近想入门股票,请问要如何操作?转载(博