当我们用socket进行编程的时候,细节上都是选择一个AF_LOCAL
,AF_INET
再根据相应的类型填充地址,其实根据通信需求,有几种简单的服务模型可供选用,掌握了这些框架再结合socket高度的抽象,可以为我们编写简单的服务器程序提供指导
循环服务
用户请求服务需要排队,服务器一次只能服务一个客户,服务完才能对下一个客户进行服务。ATM机就是这个1vs1模型。udp服务器也经常使用这个模型
//模型伪代码
main{
//获得侦听文件描述符
listenfd=socket();
//准备地址addr
//(listenfd,100);
while(1){将服务器的addr和listenfd绑定
bind(listenfd,addr)
//开始侦听,设置缓冲队列长度
listen(listenfd,100);
while(1){
//如果侦听到任务就获取sockfd
int sockfd = accept(listenfd); //listenfd默认是阻塞IO,没任务时进程会sleep
//通信...
close(sockfd);
}
}
多进程并发服务
多进程只是放listen到请求的时候,不是在原进程中处理请求,而是在子进程中处理,父进程继续侦听请求。多进程的好处父进程不必等待子进程处理完一个请求才能获取下一个请求,而是只要有请求就fork一个子进程处理,这样就可以实现并发服务器。多进程并发的更实际的方案是使用进程池来实现。
//模型伪代码
main{
//获得侦听文件描述符
listenfd=socket();
//准备地址addr
//将服务器的addr和listenfd绑定
bind(listenfd,addr)
//开始侦听,设/准备地址add置缓冲队列长度
listen(listenfd,100);
while(1){
//如果侦听到任务就获取sockfd
int sockfd = accept(listenfd); //listenfd默认是阻塞IO,没任务时进程会sleep
pid=fork();
if(0 == pid){
//子进程一定要首先关闭listenfd,防止和父进程一起侦听
//也可以在父进程socket(,,SOCK_CLOEXEC);
close(listenfd);
while(1){
ret=read(sockfd);
if(0 == ret) break;
//通信...
}
exit(0);
}
}
}
多线程并发服务
使用多线程实现并发和多进程类似,但是创建一个线程的开销比创建一个进程小得多,需要注意的是使用多线程需要做好对临界资源的保护。实际操作经常使用线程池来实现多线程并发服务。
//模型伪代码
void* communicate(void* arg){
int sockfd=(int)arg;
while(1){
ret=read(sockfd);
if(0 == ret) break;
//通信...
}
}
main{
//获得侦听文件描述符
listenfd=socket();
//准备地址addr
//将服务器的addr和listenfd绑定
bind(listenfd,addr)
//开始侦听,设置缓冲队列长度
listen(listenfd,100);
while(1){
//如果侦听到任务就获取sockfd
//listenfd默认是阻塞IO,没任务时进程会sleep
int sockfd = accept(listenfd);
pthread_create(tid,communicate,(void*)&sockfd);
}
}
}
I/O多路复用并发服务
I/O多路复用实现并发服务我已经在Linux I/O多路复用一文中举出了详细的例子,其实质就是将udp服务器的循环模型用在tcp上。
//模型伪代码
main{
//获得侦听文件描述符
listenfd=socket();
//准备地址addr
//将服务器的addr和listenfd绑定
bind(listenfd,addr)
//开始侦听,设置缓冲队列长度
listen(listenfd,100);
//准备侦听对象,和相应的触发事件的集合
monitor_set_1[i]={fds...} //监控I/O有数据流入
monitor_set_2[i]={fds...} //监控I/O变得可写
while(1){
//监控对象,如果有事件发生就返回
poll(monitor_set_1,monitor_set_2)
for(n=0;n<maxfd;n++){ //poll返回,说明有(一些)事件被触发,依次处理这些触发了事件的文件描述符
if(一个fd有数据流入){
if(是listenfd有数据流入){
//获取sockfd
int sockfd = accept(listenfd)
//将这个sockfd加入监听有数据流入可写的集合
add_mem(moniter_set_1,sockfd)
}else { //不是listenfd有数据流入,而是之前加入的sockfd有数据流入
//读取信息
read(fd,&msg)
//将其挪入加入监控可写事件的集合
add_mem(monitor_set_1,fd)
}
}else{ //一个fd变得可写了
//写入信息
write(fd,&msg)
//将其挪入监控可读的集合
add_mem(monitor_set_1,fd)
}
}
}
}
}