Linux:多进程、多线程服务器的实现解析(有图有代码有真相!!!)

来源:互联网 发布:知乎童谣事件文章 编辑:程序博客网 时间:2024/06/06 11:44

一、问题引入

阻塞型的网络编程接口


几乎所有的程序员第一次接触到的网络编程都是从 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)   {     //child close(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   {     //father close(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 socket  int 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));  //bind  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 3;  }  //listen  if(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;}






阅读全文
0 0