??xml version="1.0" encoding="utf-8" standalone="yes"?> 我们看一个实例:(x)
把这两天做Proactor的一些经验和?j)得写一下,可能?x)给一些h帮助?br /> Proactor是异步模式的|络处理器,ACE中叫?#8220;前摄?#8221;?br /> 先讲几个概念Q?br /> 前摄器(ProactorQ-异步的事件多路分d、处理器Q是核心(j)处理cR启动后?个线E组成(你不需要关?j)这三个U程Q我只是让你知道一下有q回事存在)(j)?br /> 接受器(AcceptorQ-用于服务端,监听在一个端口上Q接受用L(fng)h?br /> q接器(ConnectorQ-用于客户端,去连接远E的监听。当?dng)如果q程是ACE写的Q就是Acceptor?br /> 异步模式Q即非阻塞模式。网l的传输速度一般来讲ؓ(f)10Mbps?00Mbps?000Mbps。拿千兆|来_(d)实际的传输速度?000Mbps/8大概?28KB左右。我们的CPU一般ؓ(f)P4 3.0GHZQ如果是32位的处理器,一U钟大概可以处理6G的字节,那么Q?28KB的网l速度是远q及(qing)不上处理器的速度的。网l发送数据是一位一位发送出ȝQ如果CPU{在q里Q发送完成函数才l束Q那么,处理器浪费了(jin)大量旉在网l传输上?br /> 操作pȝ提供?jin)异步的模式来传输网l数据,工作模式卻I(x)应用E序把要发送的数据交给操作pȝQ操作系l把数据攑֜pȝ~冲区后告诉应用程序OK?jin),我帮你发Q应用程序该q嘛q嘛厅R操作系l发送完成后Q会(x)l应用系l一个回执,告诉应用E序Q刚才那个包发送完成了(jin)Q?br /> 举个例子Q你有几邮件和包裹要发Q最有效率的办法是什么?你把邮g和包裹及(qing)交给dQdMM_(d)好了(jin)Q你帮你发,你忙dQ然后你d作了(jin)。过?jin)一?x),dMM打电(sh)话告诉你Q?#8220;刚才我叫快递公司的人来?jin),把你的包裹发出去了(jin)。邮局的h也来?jin),取走了(jin)邮Ӟ攑ֿ?j)好了(jin)”。同P如果你知道今天会(x)有包Ҏ(gu)Q比如你在淘宝上购物?jin),你能成天{在dQ你应该告诉dMMQ?#8220;今天可能有我的一个快递,你帮我收一下,晚上请你肯d基!”。MMQ?#8220;看在肯得基的面子上,帮你收了(jin)”。某个时_(d)MM打电(sh)话来?jin)?x)“帅哥Q你的包裹到?jin),我帮你签收?jin)Q快来拿吧?#8221;
因ؓ(f)操作pȝ是很有效率的Q所有,他在后台收发是很快的。应用程序也很简单。Proactor是q种异步模式的。Proactor是dMMQACE_Service_Handle是d代ؓ(f)收发邮g的公司流E?/p>
//***********************************************************
class TPTCPAsynchServerImpl : public ACE_Service_Handler
{
public:
TPTCPAsynchServerImpl(void);
~TPTCPAsynchServerImpl(void);
virtual void open (ACE_HANDLE handle, ACE_Message_Block &message_block);
virtual void handle_read_stream (const ACE_Asynch_Read_Stream::Result &result);
virtual void handle_write_stream (const ACE_Asynch_Write_Stream::Result &result);
virtual void handle_time_out (const ACE_Time_Value &tv, const void *act=0);
private:
int initiate_read_stream (const ACE_Asynch_Read_Stream::Result &result);
ACE_Asynch_Read_Stream rs_;
ACE_Asynch_Write_Stream ws_;
};
q个例子从ACE_Service_Handlerl承q来QACE_Service_Handle主要是定义?jin)一些回调函数?br />1?virtual void open (ACE_HANDLE handle, ACE_Message_Block &message_block);
当有客户端连接上来,q接建立成功后Proactor?x)调用这个方法?/p>
2?virtual void handle_read_stream (const ACE_Asynch_Read_Stream::Result &result);
当用戯ȝ数据d?jin)后Q调用这个方?/p>
3、virtual void handle_write_stream (const ACE_Asynch_Write_Stream::Result &result);
当用戯写的数据在网卡上发送成功后QProactor?x)回调这个方?/p>
4?virtual void handle_time_out (const ACE_Time_Value &tv, const void *act=0);
当用戯定的旉到期?jin),q个Ҏ(gu)?x)被调用?/p>
q跟和dMM的联l方法是不是一L(fng)Q?/p>
对还~点东西Q缺怎么向dMM交待d的方法。下面看看:(x)
首先Q创Z个监听器?/p>
ACE_Asynch_Acceptor<TPTCPAsynchServerImpl> acceptor_;
看到没,是我们刚才写的c,因ؓ(f)他承了(jin)回调接口Qƈ实现?jin)自已的代码Q模板中ACE_Asynch_Acceptor?x)在合适的时候回调这些方法?/p>
//创徏一个地址对象
ACE_INET_Addr addr(port, ip);
acceptor_.open (addr, 8 * 1024, 1);
Open后,开始监听了(jin)。其它的Q向Proactor注册一些事件的事模板类中都替你做了(jin)Q你不需要做很多事?br /> 那么Q已l开始监听了(jin)Q我的程序从哪里开始呢Q对于一个服务程序来ԌE序是被用户的连接驱动的Q一个用L(fng)序想和通讯Q必d创徏q接Q就是Socket中的connect操作。这个操作Proactor?x)替我们做一些工作,当连接创建完成后Q上面讲的OpenҎ(gu)?x)被调用Q我们看看OpenҎ(gu)中都有些什么代码:(x)
我们看到Q首先创Z(jin)两个,是前面cd义中定义的一个异步写,一个异步读。以后对|络的读和写通过q两个流q行。我q给Z(jin)一D读客户端地址和端口的代码。然后是d客户Connect可能附带的数据,那段代码不用看懂Q以后用照抄就行。然后就?/p>
q段代码使用LMD|据。这D代码就是向dMM交待Q我要收包裹Q收好了(jin)叫我Q?br />也就是说Q这D代?9%的可能是M出数据的Q只是向Proactor注册ȝ事gQ具体的{待、读取操作由P(pn)roactor读,d?jin),回调Handle_Read_StreamҎ(gu)。ACE_Message_Block是消息块Q数据就是存攑֜消息块中的?br />下面看看Handle_Read_StreamҎ(gu)的代码:(x)
q个函数被调用,p明有数据已经d?jin),包裹已经在d?jin)。Proactor比dMMq好Q给你送上门了(jin)Q数据就在Result里,上面演示?jin)Result中的数据。然后把消息块释放了(jin)Q然后调用initiate_read_streaml箋(hu)监听|络上可能到来的数据。看看initiate_read_stream好了(jin)Q?/p>
代码很简单,是创徏一个新的消息块Q然后用读注册一个读消息可以了(jin)?/p>
到此为止QProactor的读程很清楚了(jin)吧?
下面再说一个写程?/p>
写流E其实更单,在Q意想向客L(fng)写数据的地方Q调用相应代码就行了(jin)Q比如,我们提供?jin)SendDataҎ(gu)来发送数据,在Q意想发送数据的地方调用SendDatap?jin),SendData的代码如下:(x)
单说Q就是创Z(jin)一个消息块Q把用户数据拯q来Q然后调用写WS向Proactor发送一个Write事g可以了(jin)Q发送成功后QHandle_write_handle?x)被调用Q看一下:(x)
代码中用了(jin)result中发数据Q然后把消息块释放了(jin)Q就q么单?/p>
////////////////////////////////////////////////////////////////////////////////////////////////////
q是单的proactor用法Q当?dng)复杂也基本就q样用。所谓不基本的不是Proactor的内容,而是服务器编E本w的ȝ(ch)。比如说Q多个连接的理、重发机制、发送队列等{,q都不是ACE的内宏V这些要大家自己思考了(jin)Qƈd?/p>
在这里,我要说几个重要的问题Q连接的理。Acceptor是一个类Q但是在每一个连接,Proactor都用?jin)某U办法创Z(jin)一个实例,所以,q接理的群集类一定不能在AcceptorcMQ不然得到的l果是始终只有一条记录。因为每个Acceptor都有一个实例,实例对应一个连接,集cM每个实例一个了(jin)。要采取的方法是一个全局的容器对象就可以?jin)。比如我q个c:(x)
我用ACE的Singleton模板创徏q个c,每一个Acceptor要用ConnectionMapQ都使用q里的_connectionsQ方法如?Q?br /> GlobleSingleton::instance()->connection.bind()......
q个问题可是我花费了(jin)2天时间找出来的,怽同仁不可不戒啊,l点掌声Q)(j)
好处
转自Q?a >http://blog.csdn.net/hguisu/article/details/7453390
1. 概念理解
在进行网l编E时Q我们常常见?span style="margin: 0px; padding: 0px; font-family: Arial, Helvetica, sans-serif, ?hu)? font-size: 12px; line-height: 16px; text-indent: 24px;">同步(Sync)/异步(Async)Q阻?Block)/非阻?Unblock)四种调用方式Q?/span>
同步Q?/span>
所谓同步,是在发Z个功能调用时Q在没有得到l果之前Q该调用׃q回?/span>也就是必M件一件事?/strong>,{前一件做完了(jin)才能做下一件事?/span>
例如普?span style="margin: 0px; padding: 0px; font-family: 'Times New Roman';">B/S模式Q同步)(j)Q提交请?span style="margin: 0px; padding: 0px; font-family: 'Times New Roman';">->{待服务器处?span style="margin: 0px; padding: 0px; font-family: 'Times New Roman';">->处理完毕q回 q个期间客户端浏览器不能qQ何事
异步Q?/span>
异步的概念和同步相对。当一个异步过E调用发出后Q调用者不能立d到结果。实际处理这个调用的部g在完成后Q通过状态、通知和回调来通知调用者?/span>
例如 ajaxhQ?/span>异步Q?/span>: h通过事g触发->服务器处理(q是览器仍然可以作其他事情Q?/span>->处理完毕
d
d调用是指调用l果q回之前Q当前线E会(x)被挂PU程q入非可执行状态,在这个状态下Qcpu不会(x)l线E分配时间片Q即U程暂停q行Q。函数只有在得到l果之后才会(x)q回?/span>
有h也许?x)把d调用和同步调用等同v来,实际上他是不同的。对于同步调用来_(d)很多时候当前线E还是激zȝQ只是从逻辑上当前函数没有返回而已?/span> 例如Q我们在socket中调用recv函数Q如果缓冲区中没有数据,q个函数׃(x)一直等待,直到有数据才q回。而此Ӟ当前U程q(sh)(x)l箋(hu)处理各种各样的消息?/span>
非阻?/strong>
非阻塞和d的概늛对应Q指在不能立d到结果之前,该函C?x)阻塞当前线E,而会(x)立刻q回?/span>
对象的阻塞模式和d函数调用
对象是否处于d模式和函数是不是d调用有很强的相关性,但是q不是一一对应的。阻塞对象上可以有非d的调用方式,我们可以通过一定的API去轮询状 态,在适当的时候调用阻塞函敎ͼ可以避免阻塞。而对于非d对象Q调用特D的函数也可以进入阻塞调用。函?/span>select是q样的一个例子?/span>
1. 同步Q就是我调用一个功能,该功能没有结束前Q我ȝl果?/span>
2. 异步Q就?span style="margin: 0px; padding: 0px; line-height: 20px;">我调用一个功能,不需要知道该功能l果Q该功能有结果后通知我(回调通知Q?/span>
3. dQ? 是调用我(函数Q,?span style="margin: 0px; padding: 0px; line-height: 20px;">Q函敎ͼ(j)没有接收完数据或者没有得到结果之前,我不?x)返回?/span>
4. 非阻塞, 是调用?span style="margin: 0px; padding: 0px; line-height: 20px;">Q函敎ͼ(j)Q我Q函敎ͼ(j)立即q回Q?span style="margin: 0px; padding: 0px; line-height: 20px;">通过select通知调用?/span>
同步IO和异步IO的区别就在于Q?span style="margin: 0px; padding: 0px; color: #ff0000;">数据拯的时候进E是否阻塞!
dIO和非dIO的区别就在于Q?/span>应用E序的调用是否立卌回!
对于举个单c/s 模式Q?/span>
同步和异?d和非d,有些L(fng),其实它们完全不是一回事,而且它们修饰的对象也不相同?br style="margin: 0px; padding: 0px;" />d和非d是指当进E访问的数据如果未qA,q程是否需要等?单说q相当于函数内部的实现区?/span>,也就是未qA时是直接q回q是{待qA;
而同步和异步?/span>指访问数据的机制,同步一般指dhq等待I/O操作完毕的方?当数据就l后在读写的时候必阻?区别qA与读写二个阶D?同步的读写必阻?,异步则指dh数据后便可以l箋(hu)处理其它d,随后{待I/O,操作完毕的通知,q可以ɘq程在数据读写时也不d?{待"通知")
1. Linux下的五种I/O模型
1)dI/OQblocking I/OQ?br style="margin: 0px; padding: 0px;" />2)非阻塞I/O Qnonblocking I/OQ?/span>
3) I/O复用(select 和poll) QI/O multiplexingQ?/span>
4)信号驱动I/O Qsignal driven I/O (SIGIO)Q?/span>
5)异步I/O Qasynchronous I/O (the POSIX aio_functions)Q?/span>
前四U都是同步,只有最后一U才是异步IO?/p>
介:(x)q程?/span>一直阻?/span>Q直到数据拷贝完?/span>
应用E序调用一个IO函数Q导致应用程序阻塞,{待数据准备好?如果数据没有准备好,一直等?#8230;.数据准备好了(jin)Q从内核拯到用L(fng)?IO函数q回成功指示?/span>
dI/O模型图:(x)在调用recv()/recvfromQ)(j)函数Ӟ发生在内怸{待数据和复制数据的q程?/span>
当调用recv()函数Ӟpȝ首先查是否有准备好的数据。如果数据没有准备好Q那么系l就处于{待状态。当数据准备好后Q将数据从系l缓冲区复制到用L(fng)_(d)然后该函数返回。在套接应用E序中,当调用recv()函数Ӟ未必用户I间已l存在数据,那么此时recv()函数׃(x)处于{待状态?br style="margin: 0px; padding: 0px;" />
当用socket()函数和W(xu)SASocket()函数创徏套接字时Q默认的套接字都是阻塞的。这意味着当调用Windows Sockets API不能立即完成ӞU程处于{待状态,直到操作完成?/span>
q不是所有Windows Sockets API以阻塞套接字为参数调用都?x)发生阻塞。例如,以阻塞模式的套接字ؓ(f)参数调用bind()、listen()函数Ӟ函数?x)立卌回。将可能d套接字的Windows Sockets API调用分ؓ(f)以下四种:
1Q输入操作:(x) recv()、recvfrom()、WSARecv()和W(xu)SARecvfrom()函数。以d套接字ؓ(f)参数调用该函数接收数据。如果此时套接字~冲区内没有数据可读Q则调用U程在数据到来前一直睡眠?/span>
2Q输出操作:(x) send()、sendto()、WSASend()和W(xu)SASendto()函数。以d套接字ؓ(f)参数调用该函数发送数据。如果套接字~冲区没有可用空_(d)U程?x)一直睡眠,直到有空间?/span>
3Q接受连接:(x)accept()和W(xu)SAAcept()函数。以d套接字ؓ(f)参数调用该函敎ͼ{待接受Ҏ(gu)的连接请求。如果此时没有连接请求,U程׃(x)q入睡眠状态?/span>
4Q外?gu)接?x)connect()和W(xu)SAConnect()函数。对于TCPq接Q客L(fng)以阻塞套接字为参敎ͼ调用该函数向服务器发赯接。该函数在收到服务器的应{前Q不?x)返回。这意味着TCPq接M(x){待臛_到服务器的一ơ往q时间?/span>
使用d模式的套接字Q开发网l程序比较简单,Ҏ(gu)实现。当希望能够立即发送和接收数据Q且处理的套接字数量比较?yu)的情况下,使用d模式来开发网l程序比较合适?/span>
d模式套接字的不表现为,在大量徏立好的套接字U程之间q行通信时比较困难。当使用“生?消费?#8221;模型开发网l程序时Qؓ(f)每个套接字都分别分配一个读U程、一个处理数据线E和一个用于同步的事gQ那么这h疑加大系l的开销。其最大的~点是当希望同时处理大量套接字时Q将无从下手Q其扩展性很?/span>
我们把一个SOCKET接口讄为非d是告诉内核Q当所h的I/O操作无法完成Ӟ不要进E睡眠,而是q回一个错误。这h们的I/O操作函数不断的试数据是否已经准备好,如果没有准备好,l箋(hu)试Q直到数据准备好为止。在q个不断试的过E中Q会(x)大量的占用CPU的时间?/span>
?span style="margin: 0px; padding: 0px; font-size: 14px;">SOCKET讄为非d模式Q即通知pȝ内核Q在调用Windows Sockets APIӞ不要让线E睡眠,而应该让函数立即q回。在q回Ӟ该函数返回一个错误代码。图所C,一个非d模式套接字多ơ调用recv()函数的过E。前三次调用recv()函数Ӟ内核数据q没有准备好。因此,该函数立卌回WSAEWOULDBLOCK错误代码。第四次调用recv()函数Ӟ数据已经准备好,被复制到应用E序的缓冲区中,recv()函数q回成功指示Q应用程序开始处理数据?/span>
当用socket()函数和W(xu)SASocket()函数创徏套接字时Q默认都是阻塞的。在创徏套接字之后,通过调用ioctlsocket()函数Q将该套接字讄为非d模式。Linux下的函数?fcntl().
套接字设|ؓ(f)非阻塞模式后Q在调用Windows Sockets API函数Ӟ调用函数?x)立卌回。大多数情况下,q些函数调用都会(x)调用“p|”Qƈq回WSAEWOULDBLOCK错误代码。说明请求的操作在调用期间内没有旉完成。通常Q应用程序需要重复调用该函数Q直到获得成功返回代码?/span>
需要说明的是ƈ非所有的Windows Sockets API在非d模式下调用,都会(x)q回WSAEWOULDBLOCK错误。例如,以非d模式的套接字为参数调用bind()函数Ӟ׃?x)返回该错误代码。当?dng)在调用WSAStartup()函数时更不会(x)q回该错误代码,因ؓ(f)该函数是应用E序W一调用的函敎ͼ当然不会(x)q回q样的错误代码?/span>
要将套接字设|ؓ(f)非阻塞模式,除了(jin)使用ioctlsocket()函数之外Q还可以使用WSAAsyncselect()和W(xu)SAEventselect()函数。当调用该函数时Q套接字?x)自动地讄为非d方式?br style="margin: 0px; padding: 0px;" />
׃使用非阻塞套接字在调用函数时Q会(x)l常q回WSAEWOULDBLOCK错误。所以在M时候,都应仔细(g)查返回代码ƈ作好?#8220;p|”的准备。应用程序连l不断地调用q个函数Q直到它q回成功指示为止。上面的E序清单中,在While循环体内不断地调用recv()函数Q以d1024个字节的数据。这U做法很费pȝ资源?/span>
要完成这L(fng)操作Q有Z用MSG_PEEK标志调用recv()函数查看~冲Z是否有数据可诅R同Pq种Ҏ(gu)也不好。因做法对系l造成的开销是很大的Qƈ且应用程序至要调用recv()函数两次Q才能实际地d数据。较好的做法是,使用套接字的“I/O模型”来判断非d套接字是否可d写?/span>
非阻塞模式套接字与阻塞模式套接字相比Q不Ҏ(gu)使用。用非d模式套接字,需要编写更多的代码Q以便在每个Windows Sockets API函数调用中,Ҏ(gu)到的WSAEWOULDBLOCK错误q行处理。因此,非阻塞套接字便显得有些难于用?/span>
但是Q非d套接字在控制建立的多个连接,在数据的收发量不均,旉不定Ӟ明显h优势。这U套接字在用上存在一定难度,但只要排除了(jin)q些困难Q它在功能上q是非常强大的。通常情况下,可考虑使用套接字的“I/O模型”Q它有助于应用程序通过异步方式Q同时对一个或多个套接字的通信加以理?/span>
介:(x)主要是select和epollQ对一个IO端口Q两ơ调用,两次q回Q比dIOq没有什么优性;关键是能实现同时对多个IO端口q行监听Q?/span>
I/O复用模型?x)用到select、poll、epoll函数Q这几个函数也会(x)使进E阻塞,但是和阻塞I/O所不同的的Q这两个函数可以同时d多个I/O操作。而且可以同时对多个读操作Q多个写操作的I/O函数q行(g),直到有数据可L可写Ӟ才真正调用I/O操作函数?/span>
介:(x)两次调用Q两ơ返回;
首先我们允许套接口进行信号驱动I/O,q安装一个信号处理函敎ͼq程l箋(hu)q行q不d。当数据准备好时Q进E会(x)收到一个SIGIO信号Q可以在信号处理函数中调用I/O操作函数处理数据?/span>
介:(x)数据拯的时候进E无需d?/span>
当一个异步过E调用发出后Q调用者不能立d到结果。实际处理这个调用的部g在完成后Q通过状态、通知和回调来通知调用者的输入输出操作
同步IO引vq程dQ直至IO操作完成?br style="margin: 0px; padding: 0px;" />异步IO不会(x)引vq程d?br style="margin: 0px; padding: 0px;" />IO复用是先通过select调用d?br style="margin: 0px; padding: 0px;" />
1. select、poll、epoll?br style="margin: 0px; padding: 0px;" />
epoll跟select都能提供多\I/O复用的解x(chng)案。在现在的Linux内核里有都能够支持,其中epoll是Linux所Ҏ(gu)Q而select则应该是POSIX所规定Q一般操作系l均有实?/p>
selectQ?/span>
select本质上是通过讄或者检查存放fd标志位的数据l构来进行下一步处理。这h带来的缺Ҏ(gu)Q?/p>
1?单个q程可监视的fd数量被限Ӟ卌监听端口的大有限?br style="margin: 0px; padding: 0px;" />
一般来说这个数目和pȝ内存关系很大Q具体数目可以cat /proc/sys/fs/file-max察看?2位机默认?024个?4位机默认?048.
2?对socketq行扫描时是U性扫描,即采用轮询的Ҏ(gu)Q效率较低:(x)
当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调?不管哪个Socket是活跃的,都遍历一遍。这?x)浪费很多CPU旉。如果能l套接字注册某个回调函数Q当他们z跃Ӟ自动完成相关操作Q那避免了(jin)轮询Q这正是epoll与kqueue做的?/p>
3、需要维护一个用来存攑֤量fd的数据结构,q样?x)得用L(fng)间和内核I间在传递该l构时复制开销?/p>
pollQ?/span>
poll本质上和select没有区别Q它?yu)用户传入的数组拯到内核空_(d)然后查询每个fd对应的设备状态,如果讑֤qA则在讑֤{待队列中加入一ƈl箋(hu)遍历Q如果遍历完所有fd后没有发现就l设备,则挂起当前进E,直到讑֤qA或者主动超Ӟ被唤醒后它又要再ơ遍历fd。这个过E经历了(jin)多次无谓的遍历?/p>
它没有最大连接数的限Ӟ原因是它是基于链表来存储的,但是同样有一个缺点:(x)
1、大量的fd的数l被整体复制于用h和内核地址I间之间Q而不这L(fng)复制是不是有意义?nbsp; 2、pollq有一个特Ҏ(gu)“水^触发”Q如果报告了(jin)fd后,没有被处理,那么下次poll时会(x)再次报告该fd?/p>epoll:
epoll支持水^触发和边~触发,最大的特点在于边缘触发Q它只告诉进E哪些fd刚刚变(sh)ؓ(f)需态,q且只会(x)通知一ơ。还有一个特Ҏ(gu)Qepoll使用“事g”的就l通知方式Q通过epoll_ctl注册fdQ一旦该fdqAQ内核就?x)采用类似callback的回调机制来Ȁz该fdQepoll_wait便可以收到通知
epoll的优点:(x)
1、支持一个进E所能打开的最大连接数
select | 单个q程所能打开的最大连接数有FD_SETSIZE宏定义,其大是32个整数的大小Q在32位的机器上,大小是32*32Q同?4位机器上FD_SETSIZE?2*64Q,当然我们可以对进行修改,然后重新~译内核Q但是性能可能?x)受到?jing)响,q需要进一步的试?/p> |
poll | poll本质上和select没有区别Q但是它没有最大连接数的限Ӟ原因是它是基于链表来存储?/p> |
epoll | 虽然q接数有上限Q但是很大,1G内存的机器上可以打开10万左右的q接Q?G内存的机器可以打开20万左右的q接 |
2、FD剧增后带来的IO效率问题
select | 因ؓ(f)每次调用旉?x)对q接q行U性遍历,所以随着FD的增加会(x)造成遍历速度慢的“U性下降性能问题”?/p> |
poll | 同上 |
epoll | 因ؓ(f)epoll内核中实现是Ҏ(gu)每个fd上的callback函数来实现的Q只有活跃的socket才会(x)d调用callbackQ所以在z跃socket较少的情况下Q用epoll没有前面两者的U性下降的性能问题Q但是所有socket都很z跃的情况下Q可能会(x)有性能问题?/p> |
3?消息传递方?/p>
select | 内核需要将消息传递到用户I间Q都需要内核拷贝动?/p> |
poll | 同上 |
epoll | epoll通过内核和用L(fng)间共享一块内存来实现的?/p> |
ȝQ?/span>
lgQ在选择selectQpollQepoll时要Ҏ(gu)具体的用场合以?qing)这三种方式的自w特炏V?/p>
1、表面上看epoll的性能最好,但是在连接数ƈ且连接都十分z跃的情况下Qselect和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调?/p>
2?span style="margin: 0px; padding: 0px; color: #000000; background-color: #ffcc00;">select低效是因为每ơ它都需要轮询。但低效也是相对的,视情况而定Q也可通过良好的设计改?/span>
面向q接?/span>Socket通信是基?/span>TCP的。网l中的两个进E以客户?/span>/服务器模式进行通信
服务器程序要先于客户机程序启动,每个步骤中调用的Socket函数如下Q?/span>
Q?/span>1Q调?/span>WSAStartup()函数加蝲Windows Sockets动态库Q然后调?/span>socket()函数创徏一个流式套接字Q返回套接字?/span>s?/span>
Q?/span>2Q调?/span>bind()函数套接字sl定C个已知的地址Q通常为本?/span>IP地址?/span>
Q?/span>3Q调?/span>listen()函数套接字s讄Z听模式,准备好接收来自各个客h的连接请求?/span>
Q?/span>4Q调?/span>accept()函数{待接受客户端的q接h?/span>
Q?/span>5Q如果接收到客户端的hQ则accept()函数q回Q得到新的套接字ns?/span>
Q?/span>6Q调?/span>recv()函数接收来自客户端的数据Q调?/span>send()函数向客L(fng)发送数据?/span>
Q?/span>7Q与客户端的通信l束后,服务器程序可以调?/span>shutdown()函数通知Ҏ(gu)不再发送或接收数据Q也可以由客L(fng)E序断开q接。断开q接后,服务器进E调?/span>closesocket()函数关闭套接?/span>ns。此后服务器E序q回W?/span>4步,l箋(hu){待客户端进E的q接?/span>
Q?/span>8Q如果要退出服务器E序Q则调用closesocket()函数关闭最初的套接?/span>s?/span>
客户端程序在每一步骤中用的函数如下Q?/span>
Q?/span>1Q调?/span>WSAStartup()函数加蝲Windows Sockets动态库Q然后调?/span>socket()函数创徏一个流式套接字Q返回套接字?/span>s?/span>
Q?/span>2Q调?/span>connect()函数套接字sq接到服务器?/span>
Q?/span>3Q调?/span>send()函数向服务器发送数据,调用recv()函数接收来自服务器的数据?/span>
Q?/span>4Q与服务器的通信l束后,客户端程序可以调?/span>shutdown()函数通知Ҏ(gu)不再发送或接收数据Q也可以由服务器E序断开q接。断开q接后,客户端进E调?/span>closesocket()函数关闭套接字?/span>
通过一个控制台应用E序实例来演C初始化Windows Socketsq输出得到的版本信息?br />
注释1Q?/p>
字段wVersionQWindows Sockets DLL期望调用者用的Windows Sockets规范的版本,为WORDcd。高?sh)字节中存储副版本号Q地位字节中存储ȝ本号。调用LOBYTE()函数可以q回WORDcd数据的地位字节,从而获取主版本P调用HIBYTE()函数可以q回WORDcd数据的高?sh)字节,从而获取副版本受?/p>
注释2Q?/p>
字段wHighVersionQWindows Sockets DLL 可以支持的Windows Sockets 规范的最高版本?/p>
注释3Q?/p>
字段szDescriptionQ以nulll尾的ASCLL字符丌ӀWindows Sockets DLL对Windows Sockets实现的描q复制到该字W串中,最多可以包?56个字W?/p>
注释4Q?/p>
字段szSystemStatusQ以nulll尾的ASCLL字符ԌW(xu)indows Sockets DLL有关状态或配置信息复制到该字符串中?/p>
其他字段?qing)其含义Q?/p>
字段iMaxSocketsQ单个进E可以打开的最大Socket数量。Windows Sockets可以提供一个全局的SocketQؓ(f)每个q程分配Socket资源。程序员可以使用该数字作为Windows Sockets是否可以被应用程序用的原始依据?/p>
字段iMaxUdpDgQWindows Sockets应用E序能够发送或接受的最大UDP数据包大,单位为字节。如果实现方式没有限Ӟ则iMaxUdpDg{于0?/p>
字段lpVendorInfoQ指向销售商数据l构的指针?/p>
一?/span>Socket单介l?/span>
Socket的中文翻译是套接字,它是TCP/IP|络环境下应用程序与底层通信驱动E序之间q行的开发接口,它可以将应用E序与具体的TCP/IP隔离开来,使得应用E序不需要了(jin)?/span>TCP/IP的具体细节,p够实现数据传输?/span>
关于Socket需要了(jin)解的q有很多Q我在随后的章节里陆箋(hu)写上?/span>
二?/span>Socket应用E序框架
q里先声明一下,我用的是32?/span>win7pȝQ?/span>vs2010~译器?/span>
首先新徏一个没有预~译头的Win32控制台应用程序?/span>
应用E序框架如下Q?br />
注释1Q?/p>
我们在vs中一般用Winsock2实现|络通信功能Q需要引q头文gwinsock2.h和库文gws2_32.lib?/p>
注释2Q?/p>
WSADATAl构体中主要包含?jin)系l所支持的Winsock版本信息?/p>
注释3“
WSAStartup()函数用于初始化Windows SocketsQƈq回WSADATAl构体。只有调用WSAStartup()函数后,应用E序才能调用其他Windows Sockets API函数Q实现网l通信?/p>
W一个参数是版本PW二个参数用于接收版本信息?/p>
如果函数执行成功则会(x)q回0?/p>
注释4Q?/p>
最后应该做的一些清理工作?/p>