﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-&lt;font color="#ff8000"&gt;&amp;nbsp&amp;nbsp&amp;nbspC++&amp;nbsp技术中心&lt;/font&gt;-随笔分类-Windows 网络编程</title><link>http://www.cppblog.com/API/category/16122.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 12 Dec 2017 03:06:12 GMT</lastBuildDate><pubDate>Tue, 12 Dec 2017 03:06:12 GMT</pubDate><ttl>60</ttl><item><title>套接字read/write返回值</title><link>http://www.cppblog.com/API/archive/2017/12/12/215420.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Tue, 12 Dec 2017 02:32:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2017/12/12/215420.html</guid><wfw:comment>http://www.cppblog.com/API/comments/215420.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2017/12/12/215420.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/215420.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/215420.html</trackback:ping><description><![CDATA[<p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">1、阻塞模式与非阻塞模式下recv的返回值各代表什么意思？有没有区别？（就我目前了解阻塞与非阻塞recv返回值没有区分，都是&nbsp;&lt;0：出错，=0：连接关闭，&gt;0接收到数据大小，特别：返回值&nbsp;&lt;0时并且(errno&nbsp;==&nbsp;EINTR&nbsp;||&nbsp;errno&nbsp;==&nbsp;EWOULDBLOCK&nbsp;||&nbsp;errno&nbsp;==&nbsp;EAGAIN)的情况下认为连接是正常的，继续接收。只是阻塞模式下recv会阻塞着接收数据，非阻塞模式下如果没有数据会返回，不会阻塞着读，因此需要&nbsp;循环读取</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">2、阻塞模式与非阻塞模式下write的返回值各代表什么意思？有没有区别？</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">阻塞与非阻塞write返回值没有区分，都是&nbsp;&lt;0：出错，=0：连接关闭，&gt;0发送数据大小，特别：返回值&nbsp;&lt;0时并且(errno&nbsp;==&nbsp;EINTR&nbsp;||&nbsp;errno&nbsp;==&nbsp;EWOULDBLOCK&nbsp;||&nbsp;errno&nbsp;==&nbsp;EAGAIN)的情况下认为连接是正常的，继续发送。只是阻塞模式下write会阻塞着发送数据，非阻塞模式下如果暂时无法发送数据会返回，不会阻塞着&nbsp;write，因此需要循环发送</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">3、阻塞模式下read返回值&nbsp;&lt;&nbsp;0&nbsp;&amp;&amp;&nbsp;errno&nbsp;!=&nbsp;EINTR&nbsp;&amp;&amp;&nbsp;errno&nbsp;!=&nbsp;EWOULDBLOCK&nbsp;&amp;&amp;&nbsp;errno&nbsp;!=&nbsp;EAGAIN时，连接异常，需要关闭，read返回值&nbsp;&lt;&nbsp;0&nbsp;&amp;&amp;&nbsp;(errno&nbsp;==&nbsp;EINTR&nbsp;||&nbsp;errno&nbsp;==&nbsp;EWOULDBLOCK&nbsp;||&nbsp;errno&nbsp;==&nbsp;EAGAIN)时表示没有数据，需要继续接收，如果返回值大于0表示接送到数据。&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">非阻塞模式下read返回值&nbsp;&lt;&nbsp;0表示没有数据，=&nbsp;0表示连接断开，&gt;&nbsp;0表示接收到数据。&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">这2种模式下的返回值是不是这么理解，有没有跟详细的理解或跟准确的说明？&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">4、阻塞模式与非阻塞模式下是否send返回值&nbsp;&lt;&nbsp;0&nbsp;&amp;&amp;&nbsp;(errno&nbsp;==&nbsp;EINTR&nbsp;||&nbsp;errno&nbsp;==&nbsp;EWOULDBLOCK&nbsp;||&nbsp;errno&nbsp;==&nbsp;EAGAIN)表示暂时发送失败，需要重试，如果send返回值&nbsp;&lt;=&nbsp;0,&nbsp;&amp;&amp;&nbsp;errno&nbsp;!=&nbsp;EINTR&nbsp;&amp;&amp;&nbsp;errno&nbsp;!=&nbsp;EWOULDBLOCK&nbsp;&amp;&amp;&nbsp;errno&nbsp;!=&nbsp;EAGAIN时，连接异常，需要关闭，如果send返回值&nbsp;&gt;&nbsp;0则表示发送了数据？send的返回值是否这么理解，阻塞模式与非阻塞模式下send返回值=0是否都是发送失败，还是那个模式下表示暂时不可发送，需要&nbsp;重发？</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">1.&nbsp;send函数</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">int&nbsp;send(&nbsp;SOCKET&nbsp;s,&nbsp;const&nbsp;char&nbsp;FAR&nbsp;*buf,&nbsp;int&nbsp;len,&nbsp;int&nbsp;flags&nbsp;);&nbsp;&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">不论是客户端还是服务器端应用程序都用send函数来向TCP连接的另一端发送数据。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">客户端程序一般用send函数向服务器发送请求，而服务器则通常用send函数来向客户程序发送应答。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">该函数的：</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第一个参数指定发送端套接字描述符；</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第二个参数指明一个存放应用程序要发送数据的缓冲区；</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第三个参数指明实际要发送的数据的字节数；</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第四个参数一般置0。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">这里只描述同步Socket的send函数的执行流程。当调用该函数时，send先比较待发送数据的长度len和套接字s的发送缓冲的长度，如果len大于s的发送缓冲区的长度，该函数返回SOCKET_ERROR；如果len小于或者等于s的发送缓冲区的长度，那么send先检查协议&nbsp;是否正在发送s的发送缓冲中的数据，如果是就等待协议把数据发送完，如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据，那么&nbsp;send就比较s的发送缓冲区的剩余空间和len，如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完，如果len小于剩余&nbsp;空间大小send就仅仅把buf中的数据copy到剩余空间里（<span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #000000;">注意并不是send</span>把s的发送缓冲中的数据传到连接的另一端的，而是协议传的，send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里<span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #000000;">）。</span>如果send函数copy数据成功，就返回实际copy的字节数，如果send在copy数据时出现错误，那么send就返回SOCKET_ERROR；如果send在等待协议传送数据时网络断开的话，那么send函数也返回SOCKET_ERROR。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;"><span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #000000;">要注意send</span>函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了，但是此时这些数据并不一定马上被传到连接的另一端<span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #000000;">。</span>如果协议在后续的传送过程中出现网络错误的话，那么下一个Socket函数就会返回SOCKET_ERROR。（每一个除send外的Socket函数在执&nbsp;行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续，如果在等待时出现网络错误，那么该Socket函数就返回&nbsp;SOCKET_ERROR）</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">注意：在Unix系统下，如果send在等待协议传送数据时网络断开的话，调用send的进程会接收到一个SIGPIPE信号，进程对该信号的默认处理是进程终止。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">Send函数的返回值有三类：</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">（1）返回值=0：</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">（2）返回值&lt;0：发送失败，错误原因存于全局变量errno中</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">（3）返回值&gt;0：表示发送的字节数（实际上是拷贝到发送缓冲中的字节数）</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">错误代码：</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EBADF&nbsp;参数s&nbsp;非合法的socket处理代码。<br style="box-sizing: border-box;" />EFAULT&nbsp;参数中有一指针指向无法存取的内存空间<br style="box-sizing: border-box;" />ENOTSOCK&nbsp;参数s为一文件描述词，非socket。<br style="box-sizing: border-box;" />EINTR&nbsp;被信号所中断。<br style="box-sizing: border-box;" />EAGAIN&nbsp;此操作会令进程阻断，但参数s的socket为不可阻断。<br style="box-sizing: border-box;" />ENOBUFS&nbsp;系统的缓冲内存不足<br style="box-sizing: border-box;" />ENOMEM&nbsp;核心内存不足<br style="box-sizing: border-box;" />EINVAL&nbsp;传给系统调用的参数不正确。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">2.&nbsp;&nbsp;recv函数</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">int&nbsp;recv(&nbsp;SOCKET&nbsp;s,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;char&nbsp;FAR&nbsp;*buf,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;len,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;flags&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;);&nbsp;&nbsp;&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">不论是客户端还是服务器端应用程序都用recv函数从TCP连接的另一端接收数据。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">该函数的：</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第一个参数指定接收端套接字描述符；</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第二个参数指明一个缓冲区，该缓冲区用来存放recv函数接收到的数据；</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第三个参数指明buf的长度；</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第四个参数一般置0。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时，recv先等待s的发送缓冲&nbsp;中的数据被协议传送完毕，如果协议在传送s的发送缓冲中的数据时出现网络错误，那么recv函数返回SOCKET_ERROR，如果s的发送缓冲中没有数&nbsp;据或者数据被协议成功发送完毕后，recv先检查套接字s的接收缓冲区，如果s接收缓冲区中没有数据或者协议正在接收数据，那么recv就一直等待，只到&nbsp;协议把数据接收完毕。当协议把数据接收完毕，recv函数就把s的接收缓冲中的数据copy到buf中（<span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #000000;">注意协议接收到的数据可能大于buf</span>的长度，所以&nbsp;在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据，真正的接收数据是协议来完成的），recv函数返回其实际copy的字节数。如果recv在copy时出错，那么它返回SOCKET_ERROR；如果recv函数在等待协议接收数据时网络中断了，那么它返回0。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">注意：在Unix系统下，如果recv函数在等待协议接收数据时网络断开了，那么调用recv的进程会接收到一个SIGPIPE信号，进程对该信号的默认处理是进程终止。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">默认情况下socket是阻塞的。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">阻塞与非阻塞recv返回值没有区别，都是：</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&lt;0&nbsp;出错</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">=0&nbsp;对方调用了close&nbsp;API来关闭连接</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&gt;0&nbsp;接收到的数据大小，</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">特别地：返回值&lt;0时并且(errno&nbsp;==&nbsp;EINTR&nbsp;||&nbsp;errno&nbsp;==&nbsp;EWOULDBLOCK&nbsp;||&nbsp;errno&nbsp;==&nbsp;EAGAIN)的情况下认为连接是正常的，继续接收。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">只是阻塞模式下recv会一直阻塞直到接收到数据，非阻塞模式下如果没有数据就会返回，不会阻塞着读，因此需要循环读取）。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">返回说明：&nbsp;&nbsp;&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">（1）成功执行时，返回接收到的字节数。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">（2）若另一端已关闭连接则返回0，这种关闭是对方主动且正常的关闭</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">（3）失败返回-1，errno被设为以下的某个值&nbsp;&nbsp;&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EAGAIN：套接字已标记为非阻塞，而接收操作被阻塞或者接收超时</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EBADF：sock不是有效的描述词</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">ECONNREFUSE：远程主机阻绝网络连接</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EFAULT：内存空间访问出错</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EINTR：操作被信号中断</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EINVAL：参数无效</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">ENOMEM：内存不足</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">ENOTCONN：与面向连接关联的套接字尚未被连接上</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">ENOTSOCK：sock索引的不是套接字</p><img src ="http://www.cppblog.com/API/aggbug/215420.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2017-12-12 10:32 <a href="http://www.cppblog.com/API/archive/2017/12/12/215420.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Echo Server based on libevent </title><link>http://www.cppblog.com/API/archive/2015/05/03/210531.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Sun, 03 May 2015 12:28:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2015/05/03/210531.html</guid><wfw:comment>http://www.cppblog.com/API/comments/210531.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2015/05/03/210531.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/210531.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/210531.html</trackback:ping><description><![CDATA[<div></div><div>出处：Blog of Felix021</div><div>时间：Sat, 25 Feb 2012 00:43:26 +0000</div><div>作者：felix021</div><div>地址：http://www.felix021.com/blog/read.php?2068</div><div></div><div>内容：</div><div>花了两天的时间在libevent上，想总结下，就以写简单tutorial的方式吧，貌似没有一篇简单的说明，让人马上就能上手用的。</div><div></div><div>首先给出官方文档吧： http://libevent.org ，首页有个Programming with Libevent，里面是一节一节的介绍libevent，但是感觉信息量太大了，而且还是英文的-。-（当然，如果想好好用libevent，看看还是很有必要的），还有个Reference，大致就是对各个版本的libevent使用doxgen生成的文档，用来查函数原型和基本用法什么的。</div><div></div><div>下面假定已经学习过基本的socket编程（socket,bind,listen,accept,connect,recv,send,close），并且对异步/callback有基本认识。</div><div></div><div>基本的socket编程是阻塞/同步的，每个操作除非已经完成或者出错才会返回，这样对于每一个请求，要使用一个线程或者单独的进程去处理，系统资源没法支撑大量的请求（所谓c10k problem?），例如内存：默认情况下每个线程需要占用2～8M的栈空间。posix定义了可以使用异步的select系统调用，但是因为其采用了轮询的方式来判断某个fd是否变成active，效率不高[O(n)]，连接数一多，也还是撑不住。于是各系统分别提出了基于异步/callback的系统调用，例如Linux的epoll，BSD的kqueue，Windows的IOCP。由于在内核层面做了支持，所以可以用O(1)的效率查找到active的fd。基本上，libevent就是对这些高效IO的封装，提供统一的API，简化开发。</div><div></div><div>libevent大概是这样的：</div><div></div><div>&nbsp; &nbsp; 默认情况下是单线程的（可以配置成多线程，如果有需要的话），每个线程有且只有一个event_base，对应一个struct event_base结构体（以及附于其上的事件管理器），用来schedule托管给它的一系列event，可以和操作系统的进程管理类比，当然，要更简单一点。当一个事件发生后，event_base会在合适的时间（不一定是立即）去调用绑定在这个事件上的函数（传入一些预定义的参数，以及在绑定时指定的一个参数），直到这个函数执行完，再返回schedule其他事件。//创建一个event_base</div><div>struct event_base *base = event_base_new();</div><div>assert(base != NULL);</div><div></div><div></div><div>&nbsp; &nbsp; event_base内部有一个循环，循环阻塞在epoll/kqueue等系统调用上，直到有一个/一些事件发生，然后去处理这些事件。当然，这些事件要被绑定在这个event_base上。每个事件对应一个struct event，可以是监听一个fd或者POSIX信号量之类（这里只讲fd了，其他的看manual吧）。struct event使用event_new来创建和绑定，使用event_add来启用：//创建并绑定一个event</div><div>struct event *listen_event;</div><div>//参数：event_base, 监听的fd，事件类型及属性，绑定的回调函数，给回调函数的参数</div><div>listen_event = event_new(base, listener, EV_READ|EV_PERSIST, callback_func, (void*)base);</div><div>//参数：event，超时时间(struct timeval *类型的，NULL表示无超时设置)</div><div>event_add(listen_event, NULL);</div><div></div><div>&nbsp; &nbsp; 注：libevent支持的事件及属性包括(使用bitfield实现，所以要用 | 来让它们合体)</div><div>&nbsp; &nbsp; (a) EV_TIMEOUT: 超时</div><div>&nbsp; &nbsp; (b) EV_READ: 只要网络缓冲中还有数据，回调函数就会被触发</div><div>&nbsp; &nbsp; (c) EV_WRITE: 只要塞给网络缓冲的数据被写完，回调函数就会被触发</div><div>&nbsp; &nbsp; (d) EV_SIGNAL: POSIX信号量，参考manual吧</div><div>&nbsp; &nbsp; (e) EV_PERSIST: 不指定这个属性的话，回调函数被触发后事件会被删除</div><div>&nbsp; &nbsp; (f) EV_ET: Edge-Trigger边缘触发，参考EPOLL_ET</div><div></div><div></div><div>&nbsp; &nbsp; 然后需要启动event_base的循环，这样才能开始处理发生的事件。循环的启动使用event_base_dispatch，循环将一直持续，直到不再有需要关注的事件，或者是遇到event_loopbreak()/event_loopexit()函数。//启动事件循环</div><div>event_base_dispatch(base);</div><div></div><div></div><div>&nbsp; &nbsp; 接下来关注下绑定到event的回调函数callback_func：传递给它的是一个socket fd、一个event类型及属性bit_field、以及传递给event_new的最后一个参数（去上面几行回顾一下，把event_base给传进来了，实际上更多地是分配一个结构体，把相关的数据都撂进去，然后丢给event_new，在这里就能取得到了）。其原型是：typedef void(* event_callback_fn)(evutil_socket_t sockfd, short event_type, void *arg)</div><div></div><div></div><div>&nbsp; &nbsp; 对于一个服务器而言，上面的流程大概是这样组合的：</div><div>&nbsp; &nbsp; 1. listener = socket()，bind()，listen()，设置nonblocking(POSIX系统中可使用fcntl设置，windows不需要设置，实际上libevent提供了统一的包装evutil_make_socket_nonblocking)</div><div>&nbsp; &nbsp; 2. 创建一个event_base</div><div>&nbsp; &nbsp; 3. 创建一个event，将该socket托管给event_base，指定要监听的事件类型，并绑定上相应的回调函数(及需要给它的参数)。对于listener socket来说，只需要监听EV_READ|EV_PERSIST</div><div>&nbsp; &nbsp; 4. 启用该事件</div><div>&nbsp; &nbsp; 5. 进入事件循环</div><div>&nbsp; &nbsp; ---------------</div><div>&nbsp; &nbsp; 6. (异步) 当有client发起请求的时候，调用该回调函数，进行处理。</div><div></div><div>&nbsp; &nbsp; 问题：为什么不在listen完马上调用accept，获得客户端连接以后再丢给event_base呢？这个问题先想想噢。</div><div></div><div>&nbsp; &nbsp; 回调函数要做什么事情呢？当然是处理client的请求了。首先要accept，获得一个可以与client通信的sockfd，然后&#8230;&#8230;调用recv/send吗？错！大错特错！如果直接调用recv/send的话，这个线程就阻塞在这个地方了，如果这个客户端非常的阴险（比如一直不发消息，或者网络不好，老是丢包），libevent就只能等它，没法处理其他的请求了&#8212;&#8212;所以应该创建一个新的event来托管这个sockfd。</div><div></div><div>&nbsp; &nbsp; 在老版本libevent上的实现，比较罗嗦[如果不想详细了解的话，看下一部分]。</div><div>&nbsp; &nbsp; 对于服务器希望先从client获取数据的情况，大致流程是这样的：</div><div>&nbsp; &nbsp; 1. 将这个sockfd设置为nonblocking</div><div>&nbsp; &nbsp; 2. 创建2个event:</div><div>&nbsp; &nbsp; &nbsp; &nbsp; event_read，绑上sockfd的EV_READ|EV_PERSIST，设置回调函数和参数（后面提到的struct）</div><div>&nbsp; &nbsp; &nbsp; &nbsp; event_write，绑上sockfd的EV_WRITE|EV_PERSIST，设置回调函数和参数（后面提到的struct）</div><div>&nbsp; &nbsp; 3. 启用event_read事件</div><div>&nbsp; &nbsp; ------</div><div>&nbsp; &nbsp; 4. (异步) 等待event_read事件的发生, 调用相应的回调函数。这里麻烦来了：回调函数用recv读入的数据，不能直接用send丢给sockfd了事&#8212;&#8212;因为sockfd是nonblocking的，丢给它的话，不能保证正确（为什么呢？）。所以需要一个自己管理的缓存用来保存读入的数据中（在accept以后就创建一个struct，作为第2步回调函数的arg传进来），在合适的时间（比如遇到换行符）启用event_write事件【event_add(event_write, NULL)】，等待EV_WRITE事件的触发</div><div>&nbsp; &nbsp; ------</div><div>&nbsp; &nbsp; 5. (异步) 当event_write事件的回调函数被调用的时候，往sockfd写入数据，然后删除event_write事件【event_del(event_write)】，等待event_read事件的下一次执行。</div><div>&nbsp; &nbsp; 以上步骤比较晦涩，具体代码可参考官方文档里面的【Example: A low-level ROT13 server with Libevent】</div><div></div><div></div><div>&nbsp; &nbsp; 由于需要自己管理缓冲区，且过程晦涩难懂，并且不兼容于Windows的IOCP，所以libevent2开始，提供了bufferevent这个神器，用来提供更加优雅、易用的API。struct bufferevent内建了两个event(read/write)和对应的缓冲区【struct evbuffer *input, *output】，并提供相应的函数用来操作缓冲区（或者直接操作bufferevent）。每当有数据被读入input的时候，read_cb函数被调用；每当output被输出完的时候，write_cb被调用；在网络IO操作出现错误的情况（连接中断、超时、其他错误），error_cb被调用。于是上一部分的步骤被简化为：</div><div>&nbsp; &nbsp; 1. 设置sockfd为nonblocking</div><div>&nbsp; &nbsp; 2. 使用bufferevent_socket_new创建一个struct bufferevent *bev，关联该sockfd，托管给event_base</div><div>&nbsp; &nbsp; 3. 使用bufferevent_setcb(bev, read_cb, write_cb, error_cb, (void *)arg)将EV_READ/EV_WRITE对应的函数</div><div>&nbsp; &nbsp; 4. 使用bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST)来启用read/write事件</div><div>&nbsp; &nbsp; ------</div><div>&nbsp; &nbsp; 5. (异步)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; 在read_cb里面从input读取数据，处理完毕后塞到output里(会被自动写入到sockfd)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; 在write_cb里面（需要做什么吗？对于一个echo server来说，read_cb就足够了）</div><div>&nbsp; &nbsp; &nbsp; &nbsp; 在error_cb里面处理遇到的错误</div><div>&nbsp; &nbsp; *. 可以使用bufferevent_set_timeouts(bev, struct timeval *READ, struct timeval *WRITE)来设置读写超时, 在error_cb里面处理超时。</div><div>&nbsp; &nbsp; *. read_cb和write_cb的原型是</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;void read_or_write_callback(struct bufferevent *bev, void *arg)</div><div>&nbsp; &nbsp; &nbsp; &nbsp;error_cb的原型是</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;void error_cb(struct bufferevent *bev, short error, void *arg) //这个是event的标准回调函数原型</div><div>&nbsp; &nbsp; &nbsp; &nbsp;可以从bev中用libevent的API提取出event_base、sockfd、input/output等相关数据，详情RTFM~</div><div>&nbsp; &nbsp;&nbsp;</div><div></div><div>&nbsp; &nbsp; 于是代码简化到只需要几行的read_cb和error_cb函数即可：</div><div>void read_cb(struct bufferevent *bev, void *arg) {</div><div>&nbsp; &nbsp; char line[256];</div><div>&nbsp; &nbsp; int n;</div><div>&nbsp; &nbsp; evutil_socket_t fd = bufferevent_getfd(bev);</div><div>&nbsp; &nbsp; while (n = bufferevent_read(bev, line, 256), n &gt; 0)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; bufferevent_write(bev, line, n);</div><div>}</div><div></div><div>void error_cb(struct bufferevent *bev, short event, void *arg) {</div><div>&nbsp; &nbsp; bufferevent_free(bev);</div><div>}</div><div></div><div></div><div>&nbsp; &nbsp; 于是一个支持大并发量的echo server就成型了！下面附上无注释的echo server源码，110行，多抄几遍，就能完全弄懂啦！更复杂的例子参见官方文档里面的【Example: A simpler ROT13 server with Libevent】</div><div>#include &lt;stdio.h&gt;</div><div>#include &lt;stdlib.h&gt;</div><div>#include &lt;errno.h&gt;</div><div>#include &lt;assert.h&gt;</div><div></div><div>#include &lt;event2/event.h&gt;</div><div>#include &lt;event2/bufferevent.h&gt;</div><div></div><div>#define LISTEN_PORT 9999</div><div>#define LISTEN_BACKLOG 32</div><div></div><div>void do_accept(evutil_socket_t listener, short event, void *arg);</div><div>void read_cb(struct bufferevent *bev, void *arg);</div><div>void error_cb(struct bufferevent *bev, short event, void *arg);</div><div>void write_cb(struct bufferevent *bev, void *arg);</div><div></div><div>int main(int argc, char *argv[])</div><div>{</div><div>&nbsp; &nbsp; int ret;</div><div>&nbsp; &nbsp; evutil_socket_t listener;</div><div>&nbsp; &nbsp; listener = socket(AF_INET, SOCK_STREAM, 0);</div><div>&nbsp; &nbsp; assert(listener &gt; 0);</div><div>&nbsp; &nbsp; evutil_make_listen_socket_reuseable(listener);</div><div></div><div>&nbsp; &nbsp; struct sockaddr_in sin;</div><div>&nbsp; &nbsp; sin.sin_family = AF_INET;</div><div>&nbsp; &nbsp; sin.sin_addr.s_addr = 0;</div><div>&nbsp; &nbsp; sin.sin_port = htons(LISTEN_PORT);</div><div></div><div>&nbsp; &nbsp; if (bind(listener, (struct sockaddr *)&amp;sin, sizeof(sin)) &lt; 0) {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; perror("bind");</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return 1;</div><div>&nbsp; &nbsp; }</div><div></div><div>&nbsp; &nbsp; if (listen(listener, LISTEN_BACKLOG) &lt; 0) {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; perror("listen");</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return 1;</div><div>&nbsp; &nbsp; }</div><div></div><div>&nbsp; &nbsp; printf ("Listening...\n");</div><div></div><div>&nbsp; &nbsp; evutil_make_socket_nonblocking(listener);</div><div></div><div>&nbsp; &nbsp; struct event_base *base = event_base_new();</div><div>&nbsp; &nbsp; assert(base != NULL);</div><div>&nbsp; &nbsp; struct event *listen_event;</div><div>&nbsp; &nbsp; listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);</div><div>&nbsp; &nbsp; event_add(listen_event, NULL);</div><div>&nbsp; &nbsp; event_base_dispatch(base);</div><div></div><div>&nbsp; &nbsp; printf("The End.");</div><div>&nbsp; &nbsp; return 0;</div><div>}</div><div></div><div>void do_accept(evutil_socket_t listener, short event, void *arg)</div><div>{</div><div>&nbsp; &nbsp; struct event_base *base = (struct event_base *)arg;</div><div>&nbsp; &nbsp; evutil_socket_t fd;</div><div>&nbsp; &nbsp; struct sockaddr_in sin;</div><div>&nbsp; &nbsp; socklen_t slen = sizeof(sin);</div><div>&nbsp; &nbsp; fd = accept(listener, (struct sockaddr *)&amp;sin, &amp;slen);</div><div>&nbsp; &nbsp; if (fd &lt; 0) {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; perror("accept");</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return;</div><div>&nbsp; &nbsp; }</div><div>&nbsp; &nbsp; if (fd &gt; FD_SETSIZE) { //这个if是参考了那个ROT13的例子，貌似是官方的疏漏，从select-based例子里抄过来忘了改</div><div>&nbsp; &nbsp; &nbsp; &nbsp; perror("fd &gt; FD_SETSIZE\n");</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return;</div><div>&nbsp; &nbsp; }</div><div></div><div>&nbsp; &nbsp; printf("ACCEPT: fd = %u\n", fd);</div><div></div><div>&nbsp; &nbsp; struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);</div><div>&nbsp; &nbsp; bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);</div><div>&nbsp; &nbsp; bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST);</div><div>}</div><div></div><div>void read_cb(struct bufferevent *bev, void *arg)</div><div>{</div><div>#define MAX_LINE &nbsp; &nbsp;256</div><div>&nbsp; &nbsp; char line[MAX_LINE+1];</div><div>&nbsp; &nbsp; int n;</div><div>&nbsp; &nbsp; evutil_socket_t fd = bufferevent_getfd(bev);</div><div></div><div>&nbsp; &nbsp; while (n = bufferevent_read(bev, line, MAX_LINE), n &gt; 0) {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; line[n] = '\0';</div><div>&nbsp; &nbsp; &nbsp; &nbsp; printf("fd=%u, read line: %s\n", fd, line);</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; bufferevent_write(bev, line, n);</div><div>&nbsp; &nbsp; }</div><div>}</div><div></div><div>void write_cb(struct bufferevent *bev, void *arg) {}</div><div></div><div>void error_cb(struct bufferevent *bev, short event, void *arg)</div><div>{</div><div>&nbsp; &nbsp; evutil_socket_t fd = bufferevent_getfd(bev);</div><div>&nbsp; &nbsp; printf("fd = %u, ", fd);</div><div>&nbsp; &nbsp; if (event &amp; BEV_EVENT_TIMEOUT) {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; printf("Timed out\n"); //if bufferevent_set_timeouts() called</div><div>&nbsp; &nbsp; }</div><div>&nbsp; &nbsp; else if (event &amp; BEV_EVENT_EOF) {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; printf("connection closed\n");</div><div>&nbsp; &nbsp; }</div><div>&nbsp; &nbsp; else if (event &amp; BEV_EVENT_ERROR) {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; printf("some other error\n");</div><div>&nbsp; &nbsp; }</div><div>&nbsp; &nbsp; bufferevent_free(bev);</div><div>}</div><div></div><div></div><img src ="http://www.cppblog.com/API/aggbug/210531.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2015-05-03 20:28 <a href="http://www.cppblog.com/API/archive/2015/05/03/210531.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>libevent windows编译</title><link>http://www.cppblog.com/API/archive/2015/02/15/209824.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Sun, 15 Feb 2015 05:14:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2015/02/15/209824.html</guid><wfw:comment>http://www.cppblog.com/API/comments/209824.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2015/02/15/209824.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/209824.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/209824.html</trackback:ping><description><![CDATA[<div>1. 下载libevent库<br />2.本人用vs2013编译，所有需要修改<br />
<p style="white-space: normal; text-transform: none; word-spacing: 0px; color: rgb(0,0,0); padding-bottom: 0px; padding-top: 0px; font: 14px/20px Verdana, Arial, Helvetica, sans-serif; padding-left: 0px; margin: 10px auto; letter-spacing: normal; padding-right: 0px; background-color: rgb(255,255,255); text-indent: 0px; -webkit-text-stroke-width: 0px"><span style="padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; padding-right: 0px">在以下</span>3<span style="padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; padding-right: 0px">个文件开头修改&#8220;</span>#define _WIN32_WINNT 0x0603<span style="padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; padding-right: 0px">&#8221;</span></p>
<p style="white-space: normal; text-transform: none; word-spacing: 0px; color: rgb(0,0,0); padding-bottom: 0px; padding-top: 0px; font: 14px/20px Verdana, Arial, Helvetica, sans-serif; padding-left: 0px; margin: 10px auto; letter-spacing: normal; padding-right: 0px; background-color: rgb(255,255,255); text-indent: 0px; -webkit-text-stroke-width: 0px">libevent-2.0.22-stable\event_iocp.c</p>
<p style="white-space: normal; text-transform: none; word-spacing: 0px; color: rgb(0,0,0); padding-bottom: 0px; padding-top: 0px; font: 14px/20px Verdana, Arial, Helvetica, sans-serif; padding-left: 0px; margin: 10px auto; letter-spacing: normal; padding-right: 0px; background-color: rgb(255,255,255); text-indent: 0px; -webkit-text-stroke-width: 0px">libevent-2.0.22-stable\evthread_win32.c</p>
<p style="white-space: normal; text-transform: none; word-spacing: 0px; color: rgb(0,0,0); padding-bottom: 0px; padding-top: 0px; font: 14px/20px Verdana, Arial, Helvetica, sans-serif; padding-left: 0px; margin: 10px auto; letter-spacing: normal; padding-right: 0px; background-color: rgb(255,255,255); text-indent: 0px; -webkit-text-stroke-width: 0px">libevent-2.0.22-stable\listener.c<br /><br />3.设置nmake的环境<br />VC6&nbsp; prefix\Microsoft Visual Studio\VC98\Bin\VCVARS32.BAT<br />VC8&nbsp; prefix\Microsoft Visual Studio 8\VC\bin\vcvars32.bat<br />VC9&nbsp; prefix\Microsoft Visual Studio 9.0\VC\bin\vcvars32.bat<br />我这里是vs2013，执行D:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\vcvars32.bat<br />注意：这些批处理文件只会在当前进程中设置（局部的）环境变量，也就是说：<br />1. 用cmd（或者command）打开的命令行窗口中， 运行某个vcvar32.bat一次。<br />那么当前命令行窗口中就可以正常使用cl，直到关闭。<br /><br />4.<span style="white-space: normal; text-transform: none; word-spacing: 0px; float: none; color: rgb(51,51,51); font: 13px/19px verdana, sans-serif; display: inline !important; letter-spacing: normal; background-color: rgb(255,255,255); text-indent: 0px; -webkit-text-stroke-width: 0px">使用VC的nmake -f Makefile.nmake即可编译32位release模式。</span><br style="white-space: normal; text-transform: none; word-spacing: 0px; color: rgb(51,51,51); font: 13px/19px verdana, sans-serif; letter-spacing: normal; background-color: rgb(255,255,255); text-indent: 0px; -webkit-text-stroke-width: 0px" /><span style="white-space: normal; text-transform: none; word-spacing: 0px; float: none; color: rgb(51,51,51); font: 13px/19px verdana, sans-serif; display: inline !important; letter-spacing: normal; background-color: rgb(255,255,255); text-indent: 0px; -webkit-text-stroke-width: 0px">如果要求编译64位的版本，需要在Makefile.nmake中添加一个LIBFLAGS选项 /MACHINE:X64</span><br style="white-space: normal; text-transform: none; word-spacing: 0px; color: rgb(51,51,51); font: 13px/19px verdana, sans-serif; letter-spacing: normal; background-color: rgb(255,255,255); text-indent: 0px; -webkit-text-stroke-width: 0px" /><span style="white-space: normal; text-transform: none; word-spacing: 0px; float: none; color: rgb(51,51,51); font: 13px/19px verdana, sans-serif; display: inline !important; letter-spacing: normal; background-color: rgb(255,255,255); text-indent: 0px; -webkit-text-stroke-width: 0px">如果要加调试信息，可以在 CFLAGS中加入/Zi，32位加调试选项是 CFLAGS中加/ZI，当然要调整优化选项/Ox</span><br /></p></div><img src ="http://www.cppblog.com/API/aggbug/209824.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2015-02-15 13:14 <a href="http://www.cppblog.com/API/archive/2015/02/15/209824.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>tcp连接探测Keepalive和心跳包</title><link>http://www.cppblog.com/API/archive/2013/08/13/202516.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Tue, 13 Aug 2013 01:03:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2013/08/13/202516.html</guid><wfw:comment>http://www.cppblog.com/API/comments/202516.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2013/08/13/202516.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/202516.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/202516.html</trackback:ping><description><![CDATA[<span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;采用TCP连接的C/S模式软件，连接的双方在连接空闲状态时，如果任意一方意外崩溃、当机、网线断开或路由器故障，另一方无法得知TCP连接已经失效，除非继续在此连接上发送数据导致错误返回。很多时候，这不是我们需要的。我们希望服务器端和客户端都能及时有效地检测到连接失效，然后优雅地完成一些清理工作并把错误报告给用户。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>如何及时有效地检测到一方的非正常断开，一直有两种技术可以运用。一种是由TCP协议层实现的Keepalive，另一种是由应用层自己实现的心跳包</span>。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>TCP默认并不开启Keepalive功能，因为开启Keepalive功能需要消耗额外的宽带和流量，尽管这微不足道，但在按流量计费的环境下增加了费用，另一方面，Keepalive设置不合理时可能会因为短暂的网络波动而断开健康的TCP连接。并且，默认的Keepalive超时需要7,200,000 milliseconds，即2小时，探测次数为5次。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>对于Win2K/XP/2003，可以从下面的注册表项找到影响整个系统所有连接的keepalive参数：</span><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Tcpip/Parameters]</span><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "<span>KeepAliveTime&#8221;=dword:006ddd00</span><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"<span>KeepAliveInterval"=dword:000003e8</span>&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "<span>MaxDataRetries"="5"<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>对于实用的程序来说，2小时的空闲时间太长。因此，我们需要手工开启Keepalive功能并设置合理的Keepalive参数。 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 4px; background-color: #eeeeee; padding-left: 4px; width: 98%; padding-right: 5px; font-size: 13px; word-break: break-all; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 4px"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /><span style="color: #008000">//</span><span style="color: #008000">&nbsp;开启KeepAlive</span><span style="color: #008000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /></span><span style="color: #000000">BOOL&nbsp;bKeepAlive&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;TRUE;<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /></span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;nRet&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;::setsockopt(socket_handle,&nbsp;SOL_SOCKET,&nbsp;SO_KEEPALIVE,&nbsp;(</span><span style="color: #0000ff">char</span><span style="color: #000000">*</span><span style="color: #000000">)</span><span style="color: #000000">&amp;</span><span style="color: #000000">bKeepAlive,&nbsp;</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(bKeepAlive));<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /></span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(nRet&nbsp;</span><span style="color: #000000">==</span><span style="color: #000000">&nbsp;SOCKET_ERROR)<br /><img id="Codehighlighter1_172_188_Open_Image" onclick="this.style.display='none'; Codehighlighter1_172_188_Open_Text.style.display='none'; Codehighlighter1_172_188_Closed_Image.style.display='inline'; Codehighlighter1_172_188_Closed_Text.style.display='inline';" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockStart.gif"><img style="display: none" id="Codehighlighter1_172_188_Closed_Image" onclick="this.style.display='none'; Codehighlighter1_172_188_Closed_Text.style.display='none'; Codehighlighter1_172_188_Open_Image.style.display='inline'; Codehighlighter1_172_188_Open_Text.style.display='inline';" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ContractedBlock.gif"></span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_172_188_Closed_Text"><img alt="" src="http://www.cppblog.com/Images/dot.gif" /></span><span id="Codehighlighter1_172_188_Open_Text"><span style="color: #000000">{<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" /></span><span style="color: #0000ff">return</span><span style="color: #000000">&nbsp;FALSE;<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockEnd.gif" />}</span></span><span style="color: #000000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /></span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;设置KeepAlive参数</span><span style="color: #008000"><br /><img id="Codehighlighter1_233_235_Open_Image" onclick="this.style.display='none'; Codehighlighter1_233_235_Open_Text.style.display='none'; Codehighlighter1_233_235_Closed_Image.style.display='inline'; Codehighlighter1_233_235_Closed_Text.style.display='inline';" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockStart.gif"><img style="display: none" id="Codehighlighter1_233_235_Closed_Image" onclick="this.style.display='none'; Codehighlighter1_233_235_Closed_Text.style.display='none'; Codehighlighter1_233_235_Open_Image.style.display='inline'; Codehighlighter1_233_235_Open_Text.style.display='inline';" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ContractedBlock.gif"></span><span style="color: #000000">tcp_keepalive&nbsp;alive_in&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_233_235_Closed_Text"><img alt="" src="http://www.cppblog.com/Images/dot.gif" /></span><span id="Codehighlighter1_233_235_Open_Text"><span style="color: #000000">{</span><span style="color: #000000">0</span><span style="color: #000000">}</span></span><span style="color: #000000">;<br /><img id="Codehighlighter1_264_266_Open_Image" onclick="this.style.display='none'; Codehighlighter1_264_266_Open_Text.style.display='none'; Codehighlighter1_264_266_Closed_Image.style.display='inline'; Codehighlighter1_264_266_Closed_Text.style.display='inline';" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockStart.gif"><img style="display: none" id="Codehighlighter1_264_266_Closed_Image" onclick="this.style.display='none'; Codehighlighter1_264_266_Closed_Text.style.display='none'; Codehighlighter1_264_266_Open_Image.style.display='inline'; Codehighlighter1_264_266_Open_Text.style.display='inline';" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ContractedBlock.gif">tcp_keepalive&nbsp;alive_out&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_264_266_Closed_Text"><img alt="" src="http://www.cppblog.com/Images/dot.gif" /></span><span id="Codehighlighter1_264_266_Open_Text"><span style="color: #000000">{</span><span style="color: #000000">0</span><span style="color: #000000">}</span></span><span style="color: #000000">;<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" />alive_in.keepalivetime&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="color: #000000">5000</span><span style="color: #000000">;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;开始首次KeepAlive探测前的TCP空闭时间</span><span style="color: #008000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /></span><span style="color: #000000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" />alive_in.keepaliveinterval&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="color: #000000">1000</span><span style="color: #000000">;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;两次KeepAlive探测间的时间间隔</span><span style="color: #008000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /></span><span style="color: #000000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" />alive_in.onoff&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;TRUE;<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" />unsigned&nbsp;</span><span style="color: #0000ff">long</span><span style="color: #000000">&nbsp;ulBytesReturn&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="color: #000000">0</span><span style="color: #000000">;<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" />nRet&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;WSAIoctl(socket_handle,&nbsp;SIO_KEEPALIVE_VALS,&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">alive_in,&nbsp;</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(alive_in),<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /></span><span style="color: #000000">&amp;</span><span style="color: #000000">alive_out,&nbsp;</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(alive_out),&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">ulBytesReturn,&nbsp;NULL,&nbsp;NULL);<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /></span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(nRet&nbsp;</span><span style="color: #000000">==</span><span style="color: #000000">&nbsp;SOCKET_ERROR)<br /><img id="Codehighlighter1_611_629_Open_Image" onclick="this.style.display='none'; Codehighlighter1_611_629_Open_Text.style.display='none'; Codehighlighter1_611_629_Closed_Image.style.display='inline'; Codehighlighter1_611_629_Closed_Text.style.display='inline';" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockStart.gif"><img style="display: none" id="Codehighlighter1_611_629_Closed_Image" onclick="this.style.display='none'; Codehighlighter1_611_629_Closed_Text.style.display='none'; Codehighlighter1_611_629_Open_Image.style.display='inline'; Codehighlighter1_611_629_Open_Text.style.display='inline';" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ContractedBlock.gif"></span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_611_629_Closed_Text"><img alt="" src="http://www.cppblog.com/Images/dot.gif" /></span><span id="Codehighlighter1_611_629_Open_Text"><span style="color: #000000">{<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" /></span><span style="color: #0000ff">return</span><span style="color: #000000">&nbsp;FALSE;<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" /><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" /><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockEnd.gif" />}</span></span><span style="color: #000000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /></span></div></span><br />
<p><span>开启Keepalive选项之后，对于使用IOCP模型的服务器端程序来说，一旦检测到连接断开，GetQueuedCompletionStatus函数将立即返回FALSE，使得服务器端能及时清除该连接、释放该连接相关的资源。对于使用select模型的客户端来说，连接断开被探测到时，以recv目的阻塞在socket上的select方法将立即返回SOCKET_ERROR，从而得知连接已失效，客户端程序便有机会及时执行清除工作、提醒用户或重新连接。<br /></span></p>
<p>&nbsp;</p>
<p><span>另一种技术，由应用程序自己发送心跳包来检测连接的健康性。客户端可以在一个Timer中或低级别的线程中定时向发服务器发送一个短小精悍的包，并等待服务器的回应。客户端程序在一定时间内没有收到服务器回应即认为连接不可用，同样，服务器在一定时间内没有收到客户端的心跳包则认为客户端已经掉线。<br /><br /><br /></span></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++</span></p>
<p><span>　windows下此处的&#8221;非正常断开&#8221;指TCP连接不是以优雅的方式断开,如网线故障等物理链路的原因,还有突然主机断电等原因.</span></p>
<p><span>有两种方法可以检测:</span></p>
<p><span>1.TCP连接双方定时发握手消息 </span></p>
<p><span></span>
<p><br />2.利用TCP协议栈中的KeepAlive探测<br />第二种方法简单可靠,只需对TCP连接两个Socket设定KeepAlive探测,<br />所以本文只讲第二种方法在Linux,Window2000下的实现(在其它的平台上没有作进一步的测试)</p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 4px; background-color: #eeeeee; padding-left: 4px; width: 98%; padding-right: 5px; font-size: 13px; word-break: break-all; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 4px"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /><span style="color: #000000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" />Windows&nbsp;2000平台下&nbsp;头文件<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" />#include&nbsp;</span><span style="color: #000000">&lt;</span><span style="color: #000000">mstcpip.h</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /></span><span style="color: #008000">//</span><span style="color: #008000">定义结构及宏<br />/*</span><span style="color: #008000"><br /><img id="Codehighlighter1_72_133_Open_Image" onclick="this.style.display='none'; Codehighlighter1_72_133_Open_Text.style.display='none'; Codehighlighter1_72_133_Closed_Image.style.display='inline'; Codehighlighter1_72_133_Closed_Text.style.display='inline';" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockStart.gif"><img style="display: none" id="Codehighlighter1_72_133_Closed_Image" onclick="this.style.display='none'; Codehighlighter1_72_133_Closed_Text.style.display='none'; Codehighlighter1_72_133_Open_Image.style.display='inline'; Codehighlighter1_72_133_Open_Text.style.display='inline';" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ContractedBlock.gif"></span><span style="color: #0000ff">struct</span><span style="color: #000000">&nbsp;TCP_KEEPALIVE&nbsp;</span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_72_133_Closed_Text"><img alt="" src="http://www.cppblog.com/Images/dot.gif" /></span><span id="Codehighlighter1_72_133_Open_Text"><span style="color: #000000">{<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" />u_longonoff;<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" />u_longkeepalivetime;<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" />u_longkeepaliveinterval;<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockEnd.gif" />}</span></span><span style="color: #000000">&nbsp;;<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" />*/<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp; tcp_keepalive live,liveout;&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;live.keepaliveinterval=5000; //每5秒发一次探测报文,发5次没有回应，就断开<br />&nbsp;&nbsp;&nbsp;&nbsp;live.keepalivetime=30000;//超过30s没有数据，就发送控测包<br />&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;live.onoff=TRUE;&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;int Opt = 1;<br />&nbsp;&nbsp;&nbsp;&nbsp;int iRet = setsockopt(Accept,SOL_SOCKET,SO_KEEPALIVE,(char *)&amp;Opt,sizeof(int));&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;if(iRet == 0){<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DWORD dw;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(::WSAIoctl(Accept,SIO_KEEPALIVE_VALS,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;live,sizeof(live),&amp;liveout,sizeof(liveout),<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;dw,NULL,NULL)== SOCKET_ERROR){<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /></span></div>
<p><br /><br /><br /><br />ACE下代码 //by rainfish blog.csdn.net/bat603</p>
<p>int Opt = 1;<br />//在测试过程中，发现检测的次数是5次，即下面的设置中，从最近一次消息开始计算的10秒后，每次间隔5秒，连续发送5次，即35秒发现网络断了<br />tcp_keepalive live,liveout; <br />live.keepaliveinterval=5000; //每次检测的间隔 （单位毫秒）<br />live.keepalivetime=10000; //第一次开始发送的时间（单位毫秒）<br />live.onoff=TRUE; <br />int iRet = stream.set_option(SOL_SOCKET,SO_KEEPALIVE,&amp;Opt,sizeof(int)); <br />if(iRet == 0){ <br />DWORD dw;<br />//此处显示了在ACE下获取套接字的方法，即句柄的(SOCKET)化就是句柄<br />if(WSAIoctl((SOCKET)h,SIO_KEEPALIVE_VALS,&amp;live,sizeof(live),<br />&amp;liveout,sizeof(liveout),&amp;dw,NULL,NULL)== SOCKET_ERROR){<br />//Delete Client <br />return; <br />} <br />} </p>
<p><br /></p>
<p>Linux平台下</p>
<p><br />#include "/usr/include/linux/tcp.h"<br />#include "/usr/include/linux/socket.h"<br />////KeepAlive实现，单位秒<br />//下面代码要求有ACE,如果没有包含ACE,则请把用到的ACE函数改成linux相应的接口<br />int keepAlive = 1;//设定KeepAlive<br />int keepIdle = 5;//开始首次KeepAlive探测前的TCP空闭时间<br />int keepInterval = 5;//两次KeepAlive探测间的时间间隔<br />int keepCount = 3;//判定断开前的KeepAlive探测次数<br />if(setsockopt(s,SOL_SOCKET,SO_KEEPALIVE,(void*)&amp;keepAlive,sizeof(keepAlive)) == -1)<br />{<br />ACE_DEBUG ((LM_INFO,<br />ACE_TEXT ("(%P|%t) setsockopt SO_KEEPALIVE error!/n")));<br />}</p>
<p>if(setsockopt(s,SOL_TCP,TCP_KEEPIDLE,(void *)&amp;keepIdle,sizeof(keepIdle)) == -1)<br />{<br />ACE_DEBUG ((LM_INFO,<br />ACE_TEXT ("(%P|%t) setsockopt TCP_KEEPIDLE error!/n")));<br />}</p>
<p>if(setsockopt(s,SOL_TCP,TCP_KEEPINTVL,(void *)&amp;keepInterval,sizeof(keepInterval)) == -1)<br />{<br />ACE_DEBUG ((LM_INFO,<br />ACE_TEXT ("(%P|%t) setsockopt TCP_KEEPINTVL error!/n")));<br />}</p>
<p>if(setsockopt(s,SOL_TCP,TCP_KEEPCNT,(void *)&amp;keepCount,sizeof(keepCount)) == -1)<br />{<br />ACE_DEBUG ((LM_INFO,<br />ACE_TEXT ("(%P|%t)setsockopt TCP_KEEPCNT error!/n")));<br />}</p>
<p>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++</p></span></span><img src ="http://www.cppblog.com/API/aggbug/202516.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2013-08-13 09:03 <a href="http://www.cppblog.com/API/archive/2013/08/13/202516.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>UDP,TCP打洞技术</title><link>http://www.cppblog.com/API/archive/2012/08/24/188130.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Fri, 24 Aug 2012 06:18:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2012/08/24/188130.html</guid><wfw:comment>http://www.cppblog.com/API/comments/188130.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2012/08/24/188130.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/188130.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/188130.html</trackback:ping><description><![CDATA[内容概述：在p2p通信领域中，由NAT(Network Address Translation，网络地址转换)引起的问题已经众所周知了,它会导致在NAT内部的p2p客户端在无论以何种有效的公网ip都无法访问的问题。虽然目前已经发展出多种穿越NAT的技术,但相关的技术文档却很少，用来证明这些技术的稳定性和优点的实际数据更少。本文的目的在于描述和分析在实际中运用得最广泛、最可靠同时也是最简单的一种NAT穿越技术，该技术通常被称为&#8220;打洞&#8221;技术。目前，&#8220;打洞&#8221;技术已经在UDP通信领域中得到了广泛的理解和应用，在此，也将讨论如何利用它实现可靠的p2p的TCP流通信。在收集了大量的&#8220;打洞&#8221;技术可以穿越的NAT设备和网络的数据以后，我们发现82%的已测 NAT设备支持UDP形式的&#8220;打洞&#8221;穿越，64%的已测NAT设备支持TCP流形式的&#8220;打洞&#8221;穿越。由于重量级p2p应用程序(如，VOIP、BT、在线游戏等)的用户需求量持续上升，并且该事实也已经引起了NAT设备生产厂商的广泛关注，因此，我们认为未来会有越来越多的NAT设备提供对&#8220;打洞&#8221;穿越技术的支持。 
<p>1、介绍</p>
<p>用户量高速增长以及大量安全问题的巨大压力迫使Internet技术不断向前发展，但是这些新兴的技术很大程度地增加了应用程序开发的成本和复杂性。Internet最初的地址体系是每个节点有一个唯一不变的全局地址，可以通过该地址直接与任何其它的节点进行通信，而现如今，该地址体系已经被新的实际上广泛使用的地址体系所替换，新的地址体系是由全局地址域和通过NAT接入全局地址域的大量私有地址域组成。在新的地址体系中（如图1所示），只有在 &#8220;main&#8221;全局地址域中的节点可以在网络中很容易地与任何其它的拥有全局地址的节点通信，因为该节点拥有全局的、唯一的、可路由的地址。在私有网络中的节点可以与在同一个私有网络中的其它节点进行通信，并且在通常情况下可以向全局地址中的某个&#8220;著名&#8221;的节点发起TCP连接或发送UDP数据包。NAT设备在此扮演的角色就是为从内网向公网发起的连接的节点分配临时的转发session，将来自内网的数据包的地址和端口转换为公网的地址和端口，将来自公网的数据包的地址和端口转换为内网的端口和地址，同时NAT将屏蔽所有未经授权的来自公网的数据包。</p>
<div>
<div><img border="0" alt="" src="http://www.ppcn.net/upload/2008-12/081208225549421.png" /> </div></div>
<div>新的Internet地址体系非常适合于&#8220;客户端/服务器&#8221;这样的通信模式，一个典型的C/S通信模式是：客户端在内网（私有地址域），服务器在公网（全局地址域），通过NAT将内网和公网连接起来。这种地址体系使得在不同内网（私有地址域）中的两个节点很难直接通信，而这恰恰是p2p应用 (如，电话会议或在线游戏)中最基本的要求。很显然，我们需要一种方法即使在NAT设备存在的前提下，仍然能够无障碍地实现p2p通信。</div>
<div></div>
<div>在不同内网的两个节点之间建立p2p连接的最有效的方法就是&#8220;打洞&#8221;。该技术在基于UDP的应用程序中得到了广泛的应用，同样的，该技术也可以用于基于TCP的应用程序。有趣的是，与&#8220;打洞&#8221;字面上的意思刚好相反，该技术不会影响到内网的安全。事实上，&#8220;打洞&#8221;技术使得p2p软件的绝大部分功能都在NAT设备默认的安全策略的控制之下，这些都由NAT设备建立的session来管理。本文阐述了适用于UDP和TCP的&#8220;打洞&#8221;技术，并详细描述了重要&#8220;打洞&#8221;过程中，应用程序和NAT设备之间的行为。</div>
<div></div>
<div>不幸的是，由于NAT设备的响应和行为不是标准的，所以没有任何技术可以穿越现有的所有NAT设备。本文提供了一些在现有NAT设备上进行&#8220;打洞&#8221;的实验结果。我们收集的数据来自于互联网上使用了&#8220;NAT Check&#8221;工具并在大量不同生产厂商的NAT设备上进行&#8220;打洞&#8221;实验的用户。由于数据是来自于一个叫做&#8220;self-selecting&#8221;的用户社区，或许不会完全代表在Internet上真正部署和使用的NAT设备，但是结果无论如何还是很令人兴奋的。</div>
<div></div>
<div>在做基本的&#8220;打洞&#8221;操作评估的时候，我们应该指出在现有的NAT设备&#8220;打洞&#8221;的复杂度上，不同的复杂度会有不同的结果。但目前我们把讨论的重点集中于开发最简单的，可以应用于任何网络拓扑结构的、稳定的、有正确NAT响应的NAT设备上的&#8220;打洞&#8221;技术。我们有意避免使用一些&#8220;聪明的小把戏&#8221;通过欺骗某些NAT设备来达到短期内穿越较多的NAT设备，但从长期来看会引起网络未知错误的技术。</div>
<div></div>
<div>尽管引入IPv6会极大地增加互联网的地址空间，从而减少对于NAT设备的需求量，但短期内IPv6确实增加了对NAT设备的需求量，因为 NAT设备本身提供了一种方便的方法进行IPv4与IPv6地址域转换。另外私有网络上建立匿名和加密访问节点也有利于组织机构的安全性以及不受外界干扰，这些都意味着NAT还将存在相当长的一段时间。同样，防火墙技术也不会由于有了足够的ip地址而消失，IPv6的防火墙仍然会默认丢掉所有未经授权的数据包，仍然可以让在IPv6环境下工作的应用程序&#8220;打洞&#8221;。</div>
<div></div>
<div>本文接下来的部分按照如下的方式组织：第二章介绍基本的NAT穿越概念和术语；第三章介绍UDP&#8220;打洞&#8221;过程；第四章介绍TCP&#8220;打洞&#8221;过程；第五章介绍支持&#8220;打洞&#8221;的NAT设备必须具有那些特性；第六章介绍我们在目前流行的NAT设备上的&#8220;打洞&#8221;实验结果；第七章讨论相关的网络问题；第八章全文总结以及结束语。</div>
<div></div>
<div>2、基本概念本节介绍了本文使用到的基本的NAT术语，着重描述了适用于UDP和TCP两种协议的通用的NAT穿越技术。</div>
<div></div>
<div>2.1、NAT术语</div>
<div>本文绝大部分术语和分类来自于RFC 2663定义，另外一些来自于较新的RFC 3489中的定义。</div>
<div>理解session是很重要的。一个TCP或UDP的session endpoint是由一个IP地址，端口号组成，每个session是由两个session endpoint构成。从内网节点的角度来看，一个session由4部分组成分别为：本地IP，本地端口，远端IP，远端端口。session的方向通常代表了数据包的初始流动的方向；对于TCP来说就是SYN包的流向，对于UDP来说就是第一个用户数据包的流向。</div>
<div></div>
<div>NAT有很多种，但最普遍的一种类型叫做&#8220;传统&#8221;NAT，或者&#8220;向外&#8221;NAT。他们在内网和公网之间提供了一个&#8220;不对称&#8221;桥的映射。&#8220;向外&#8221;NAT在默认情况下只允许向外的session穿越NAT：</div>
<div>从外向内的的数据包都会被丢弃掉，除非NAT设备事先已经定义了这些从外向内的数据包是已存在的内网session的一部分。</div>
<div>&#8220;外向&#8221;NAT会造成p2p协议的混乱，因为当p2p的双方决定向在不同NAT后面的对方开始通信的时候，无论哪一方试图初始化一个session，另一方的NAT都会拒绝这个请求。NAT穿越的核心思想就是让p2p的双方的NAT看上去都是&#8220;向外&#8221;的NAT。</div>
<div></div>
<div>&#8220;向外&#8221;NAT有两种类型：（1）&#8220;基础&#8221;NAT，该NAT只转换IP地址，不转换端口号。（2）NAPT(Network Address/Port Translation)NAPT转换整个session endpoints。由于NAPT允许内网的多个节点通过共享的方式使用同一个的公共的IP地址，因此，支持NAPT的NAT设备才会越来越多。尽管本文通篇讨论的内容都是基于支持NAPT的NAT设备的，但这些规律和技术同样适用于&#8220;基础&#8221;NAT。</div>
<div></div>
<div>2.2 转发方式</div>
<div></div>
<div>最可靠但同时也是效率最低的p2p穿越NAT进行通信的方法是采用类似C/S方式的转发。假定两个节点A和B每个节点都有向外的TCP或UDP 连接，联入公共的已知服务器S，S的公网IP地址是18.181.0.31，端口号是1234（如图2所示）,每个客户端位于不同的私有内网中，并且它们的NAT设备妨碍了客户端之间直接的p2p连接。做为对直连方案的替代方案，两个客户端可以利用公共的服务器S进行消息的转发。例如，A为了将消息送给 B，A只需将消息发给S，然后由S转发给B，这一过程将使用A与B事先与S建立好的连接。</div>
<div>
<div><img border="0" alt="" src="http://www.ppcn.net/upload/2008-12/081208225549422.png" /> </div></div>
<div>转发方式通常只能在双方客户端都连接到服务器的时候有效。这种方式的缺点在于，它假定服务器的处理能力和网络带宽以及通信延迟都是理想的情况下，不会受到客户端个数的影响。但是，由于没有其它的方法能够像转发方式那样，可以穿越现存的所有NAT设备，因此在构建高可靠性的p2p系统的时候，通过服务器转发的方式依旧是一个非常有用的保证系统可靠性的方法。TURN协议定义了如何实现安全的转发方式。</div>
<div></div>
<div>2.3 反向连接方式</div>
<div></div>
<div>一些p2p的应用程序采用了直接但是有所限制的技术来实现NAT穿越，该技术叫做&#8220;反向连接&#8221;，这是用于当两个节点联入服务器S的