不得不说的一个概念性问题:阻塞与非阻塞
在阻塞服务中,当服务器运行到accept语句而没有客户连接服务请求到来,那么会发生什么情况?这时服务器就会停止在accept语句上等待连接服务请求的到来;同样,当程序运行到接收数据语句recv时,如果没有数据可以读取,则程序同样会停止在接收语句上。这种情况称为阻塞(blocking)。
但如果你希望服务器仅仅注意检查是否有客户在等待连接,有就接受连接;否则就继续做其他事情,则可以通过将socket设置为非阻塞方式来实现:非阻塞socket在没有客户在等待时就使accept调用立即返回。
通过设置socket为非阻塞方式,可以实现“轮询”若干socket。当企图从一个没有数据等待处理的非阻塞socket读入数据时,函数将立即返回,并且返回值置为-1,并且errno置为EWOULDBLOCK。但是这种“轮询”会使CPU处于忙等待方式,从而降低性能。考虑到这种情况,假设你希望服务器监听连接服务请求的同时从已经建立的连接读取数据,你也许会想到用一个accept语句和多个recv()语句,但是由于accept及recv都是会阻塞的,所以这个想法显然不会成功。
调用非阻塞的socket会大大地浪费系统资源。而调用select()会有效地解决这个问题,它允许你把进程本身挂起来,而同时使系统内核监听所要求的一组文件描述符的任何活动,只要确认在任何被监控的文件描述符上出现活动,select()调用将返回指示该文件描述符已准备好的信息,从而实现了为进程选出随机的变化,而不必由进程本身对输入进行测试而浪费CPU开销。
其次,并发服务器,在上述cycletcpserver.c中,由于使用了fork技术也可以称之为并发服务器,但这种服务器并不是真正意义上的IO多路复用的并发服务器,并且由于没有处理阻塞问题,实际应用有各种各样的问题。
一个典型IO多路复用的单进程并发服务器流程如下:
/*IO多路复用并发服务流程图*/
下面是一个演示IO多路复用的源程序,是一个端口转发程序,但它的用处相当大,实际应用中的各类代理软件或端口映射软件都是基于这样的代码的,比如Windows下的WinGate、WinProxy等都是在此基础上实现的。源代码如下:
/*----------------------源代码开始--------------------------------------------*/
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/types.h>
#include<string.h>
#include<signal.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<errno.h>
staticintforward_port;
#undefmax
#definemax(x,y)((x)>(y)?(x):(y))
/*************************关于本文档************************************
*filename:tcpforwardport.c
*purpose:演示了select的用法,这是一个极好的代理软件核心,专门作端口映射用
*tidiedby:zhoulifa(zhoulifa@163.com)周立发(http://zhoulifa.bokee.com)
Linux爱好者Linux知识传播者SOHO族开发者最擅长C语言
*datetime:2006-07-0519:00:00
*Note:任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
*但请遵循GPL
*Thanksto:PaulSheer感谢PaulSheer在select_tut的man手册里提供了这份源代码
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
*********************************************************************/
staticintlisten_socket(intlisten_port){
structsockaddr_ina;
ints;
intyes;
if((s=socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
return-1;
}
yes=1;
if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes))<
0){
perror("setsockopt");
close(s);
return-1;
}
memset(&a,0,sizeof(a));
a.sin_port=htons(listen_port);
a.sin_family=AF_INET;
if(bind(s,(structsockaddr*)&a,sizeof(a))<0){
perror("bind");
close(s);
return-1;
}
printf("acceptingconnectionsonport%d\n",(int)listen_port);
listen(s,10);
returns;
}
staticintconnect_socket(intconnect_port,char*address){
structsockaddr_ina;
ints;
if((s=socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
close(s);
return-1;
}
memset(&a,0,sizeof(a));
a.sin_port=htons(connect_port);
a.sin_family=AF_INET;
if(!inet_aton(address,(structin_addr*)&a.sin_addr.s_addr)){
perror("badIPaddressformat");
close(s);
return-1;
}
if(connect(s,(structsockaddr*)&a,sizeof(a))<0){
perror("connect()");
shutdown(s,SHUT_RDWR);
close(s);
return-1;
}
returns;
}
#defineSHUT_FD1{\
if(fd1>=0){\
shutdown(fd1,SHUT_RDWR);\
close(fd1);\
fd1=-1;\
}\
}
#defineSHUT_FD2{\
if(fd2>=0){\
shutdown(fd2,SHUT_RDWR);\
close(fd2);\
fd2=-1;\
}\
}
#defineBUF_SIZE1024
intmain(intargc,char**argv){
inth;
intfd1=-1,fd2=-1;
charbuf1[BUF_SIZE],buf2[BUF_SIZE];
intbuf1_avail,buf1_written;
intbuf2_avail,buf2_written;
if(argc!=4){
fprintf(stderr,"Usage\n\tfwd\n");
exit(1);
}
signal(SIGPIPE,SIG_IGN);
forward_port=atoi(argv[2]);
/*建立监听socket*/
h=listen_socket(atoi(argv[1]));
if(h<0)exit(1);
for(;;){
intr,nfds=0;
fd_setrd,wr,er;
FD_ZERO(&rd);
FD_ZERO(&wr);
FD_ZERO(&er);
FD_SET(h,&rd);
/*把监听socket和可读socket三个一起放入select的可读句柄列表里*/
nfds=max(nfds,h);
if(fd1>0&&buf1_avail<BUF_SIZE){
FD_SET(fd1,&rd);
nfds=max(nfds,fd1);
}
if(fd2>0&&buf2_avail<BUF_SIZE){
FD_SET(fd2,&rd);
nfds=max(nfds,fd2);
}
/*把可写socket两个一起放入select的可写句柄列表里*/
if(fd1>0&&buf2_avail-buf2_written>0){
FD_SET(fd1,&wr);
nfds=max(nfds,fd1);
}
if(fd2>0&&buf1_avail-buf1_written>0){
FD_SET(fd2,&wr);
nfds=max(nfds,fd2);
}
/*把有异常数据的socket两个一起放入select的异常句柄列表里*/
if(fd1>0){
FD_SET(fd1,&er);
nfds=max(nfds,fd1);
}
if(fd2>0){
FD_SET(fd2,&er);
nfds=max(nfds,fd2);
}
/*开始select*/
r=select(nfds+1,&rd,&wr,&er,NULL);
if(r==-1&&errno==EINTR)continue;
if(r<0){
perror("select()");
exit(1);
}
/*处理新连接*/
if(FD_ISSET(h,&rd)){
unsignedintl;
structsockaddr_inclient_address;
memset(&client_address,0,l=sizeof(client_address));
r=accept(h,(structsockaddr*)&client_address,&l);
if(r<0){
perror("accept()");
}else{
/*关闭原有连接,把新连接作为fd1,同时连接新的目标fd2*/
SHUT_FD1;
SHUT_FD2;
buf1_avail=buf1_written=0;
buf2_avail=buf2_written=0;
fd1=r;
fd2=connect_socket(forward_port,argv[3]);
if(fd2<0){
SHUT_FD1;
}else
printf("connectfrom%s\n",inet_ntoa(client_address.sin_addr));
}
}
/*NB:readoobdatabeforenormalreads*/
if(fd1>0)
if(FD_ISSET(fd1,&er)){
charc;
errno=0;
r=recv(fd1,&c,1,MSG_OOB);
if(r<1){
SHUT_FD1;
}else
send(fd2,&c,1,MSG_OOB);
}
if(fd2>0)
if(FD_ISSET(fd2,&er)){
charc;
errno=0;
r=recv(fd2,&c,1,MSG_OOB);
if(r<1){
SHUT_FD1;
}else
send(fd1,&c,1,MSG_OOB);
}
/*NB:readdatafromfd1*/
if(fd1>0)
if(FD_ISSET(fd1,&rd)){
r=read(fd1,buf1+buf1_avail,BUF_SIZE-buf1_avail);
if(r<1){
SHUT_FD1;
}else
buf1_avail+=r;
}
/*NB:readdatafromfd2*/
if(fd2>0)
if(FD_ISSET(fd2,&rd)){
r=read(fd2,buf2+buf2_avail,BUF_SIZE-buf2_avail);
if(r<1){
SHUT_FD2;
}else
buf2_avail+=r;
}
/*NB:writedatatofd1*/
if(fd1>0)
if(FD_ISSET(fd1,&wr)){
r=write(fd1,buf2+buf2_written,buf2_avail-buf2_written);
if(r<1){
SHUT_FD1;
}else
buf2_written+=r;
}
/*NB:writedatatofd1*/
if(fd2>0)
if(FD_ISSET(fd2,&wr)){
r=write(fd2,buf1+buf1_written,buf1_avail-buf1_written);
if(r<1){
SHUT_FD2;
}else
buf1_written+=r;
}
/*checkifwritedatahascaughtreaddata*/
if(buf1_written==buf1_avail)buf1_written=buf1_avail=0;
if(buf2_written==buf2_avail)buf2_written=buf2_avail=0;
/*onesidehasclosedtheconnection,keepwritingtotheothersideuntilempty*/
if(fd1<0&&buf1_avail-buf1_written==0){
SHUT_FD2;
}
if(fd2<0&&buf2_avail-buf2_written==0){
SHUT_FD1;
}
}
return0;
}
/*----------------------源代码结束--------------------------------------------*/
Linux下TCP网络服务器实现源代码2
精选 转载
提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章
-
Linux服务器PBS作业提交脚本介绍与代码
本文介绍在Linux服务器中,通过PBS(Portable Batch System)作业管理系统脚本的方式,提交任务到服务器队列,并执行任务的方法~
Linux 服务器 任务队列 PBS脚本 作业提交