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