一、问题引入
阻塞型的网络编程接口
几乎所有的程序员第一次接触到的网络编程都是从 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;
}