Linux:多进程、多线程服务器的实现解析(有图有代码有真相!!!)
一、問題引入
阻塞型的網絡編程接口
幾乎所有的程序員第一次接觸到的網絡編程都是從 listen()、send()、recv()等接口開始的。使用這些接口可以很方便的構建服務器 /客戶機的模型。
我們假設希望建立一個簡單的服務器程序,實現向單個客戶機提供類似于“一問一答”的內容服務。
我們注意到,大部分的 socket接口都是阻塞型的。所謂阻塞型接口是指系統調用(一般是 IO接口)不返回調用結果并讓當前線程一直阻塞,只有當該系統
調用獲得結果或者超時出錯時才返回。實際上,除非特別指定,幾乎所有的 IO接口 (包括 socket 接口 )都是阻塞型的。這給網絡編程帶來了一個很大的問題,如在調用 send()的同時,線程將被
阻塞,在此期間,線程將無法執行任何運算或響應任何的網絡請求。這給多客戶機、多業務邏輯的網絡編程帶來了挑戰。這時,很多程序員可能會選擇多線程的方式來解決這個問題。
二、多進程多線程
應對多客戶機的網絡應用,最簡單的解決方式是在服務器端使用多線程(或多進程)。多線程(或多進程)的目的是讓每個連接都擁有獨立的線程(或進
程),這樣任何一個連接的阻塞都不會影響其他的連接。具體使用多進程還是多線程,并沒有?一個特定的模式。傳統意義上,進程的開
銷要遠遠大于線程,所以,如果需要同時為較多的客戶機提供服務,則不推薦使用多進程;如果單個服務執行體需要消耗較多的 CPU 資源,譬如需要進行
大規模或長時間的數據運算或文件訪問,則進程較為安全。通常,使用pthread_create () 創建新線程,fork() 創建新進程。
我們假設對上述的服務器 / 客戶機模型,提出更高的要求,即讓服務器同時為多個客戶機提供一問一答的服務。于是有了如上的模型。
在上述的線程 / 時間圖例中,主線程持續等待客戶端的連接請求,如果有連接,則創建新線程,并在新線程中提供為前例同樣的問答服務。
很多初學者可能不明白為何一個 socket 可以 accept 多次。實際上,socket的設計者可能特意為多客戶機的情況留下了伏筆,讓 accept() 能夠返回一個新
的 socket。下面是 accept 接口的原型:輸入參數 sockfd 是從 socket(),bind() 和 listen() 中沿用下來的 socket 句柄
值。執行完 bind() 和 listen() 后,操作系統已經開始在指定的端口處監聽所有的連接請求,如果有請求,則將該連接請求加入請求隊列。調用 accept() 接口
正是從 socket s 的請求隊列抽取第一個連接信息,創建?一個與 socked同類的新的 socket 返回句柄。新的 socket 句柄即是后續 read() 和 recv() 的輸入參
數。如果請求隊列當前沒有請求,則 accept() 將進?入阻塞狀態直到有請求進入隊列。
1、多進程服務器、客戶端實現簡單通信
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、多線程實現簡單服務器(遠程登陸: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; }
總結
以上是生活随笔為你收集整理的Linux:多进程、多线程服务器的实现解析(有图有代码有真相!!!)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据有序_Redis实战(3)-数据结构
- 下一篇: linux 其他常用命令