﻿<?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++博客-每天早晨叫醒你的不是闹钟,而是梦想-随笔分类-网络</title><link>http://www.cppblog.com/mmdengwo/category/16586.html</link><description /><language>zh-cn</language><lastBuildDate>Thu, 19 May 2011 23:53:33 GMT</lastBuildDate><pubDate>Thu, 19 May 2011 23:53:33 GMT</pubDate><ttl>60</ttl><item><title>封包和拆包 </title><link>http://www.cppblog.com/mmdengwo/archive/2011/05/13/146310.html</link><dc:creator>沛沛</dc:creator><author>沛沛</author><pubDate>Fri, 13 May 2011 05:26:00 GMT</pubDate><guid>http://www.cppblog.com/mmdengwo/archive/2011/05/13/146310.html</guid><wfw:comment>http://www.cppblog.com/mmdengwo/comments/146310.html</wfw:comment><comments>http://www.cppblog.com/mmdengwo/archive/2011/05/13/146310.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/mmdengwo/comments/commentRss/146310.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/mmdengwo/services/trackbacks/146310.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 作者:fengge8ylf&nbsp; 博客:http://blog.csdn.net/fengge8ylf对于基于TCP开发的通讯程序,有个很重要的问题需要解决,就是封包和拆包.自从我从事网络通讯编程工作以来(大概有三年的时间了),我一直在思索和改进封包和拆包的方法.下面就针对这个问题谈谈我的想法,抛砖引玉.若有不对,不妥之处,恳求大家指正.在此先谢过大家了.一.为什么基于TCP的通讯程...&nbsp;&nbsp;<a href='http://www.cppblog.com/mmdengwo/archive/2011/05/13/146310.html'>阅读全文</a><img src ="http://www.cppblog.com/mmdengwo/aggbug/146310.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/mmdengwo/" target="_blank">沛沛</a> 2011-05-13 13:26 <a href="http://www.cppblog.com/mmdengwo/archive/2011/05/13/146310.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>WinSock学习笔记 </title><link>http://www.cppblog.com/mmdengwo/archive/2011/05/03/145571.html</link><dc:creator>沛沛</dc:creator><author>沛沛</author><pubDate>Tue, 03 May 2011 07:44:00 GMT</pubDate><guid>http://www.cppblog.com/mmdengwo/archive/2011/05/03/145571.html</guid><wfw:comment>http://www.cppblog.com/mmdengwo/comments/145571.html</wfw:comment><comments>http://www.cppblog.com/mmdengwo/archive/2011/05/03/145571.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/mmdengwo/comments/commentRss/145571.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/mmdengwo/services/trackbacks/145571.html</trackback:ping><description><![CDATA[1、读取当前错误值：每次发生错误时，如果要对具体问题进行处理，那么就应该调用这个函数取得错误代码。
<pre>      int  WSAGetLastError(void );
#define h_errno   WSAGetLastError()
</pre>
错误值请自己阅读Winsock2.h。<br><br>2、将主机的unsigned long值转换为网络字节顺序(32位)：为什么要这样做呢？因为不同的计算机使用不同的字节顺序存储数据。因此任何从Winsock函数对IP地址和端口号的引用和传给Winsock函数的IP地址和端口号均时按照网络顺序组织的。<br>
<pre>      u_long  htonl(u_long hostlong);
举例：htonl(0)=0
htonl(80)= 1342177280
</pre>
3、将unsigned long数从网络字节顺序转换位主机字节顺序，是上面函数的逆函数。
<pre>      u_long  ntohl(u_long netlong);
举例：ntohl(0)=0
ntohl(1342177280)= 80
</pre>
4、将主机的unsigned short值转换为网络字节顺序(16位)：原因同2：
<pre>      u_short  htons(u_short hostshort);
举例：htonl(0)=0
htonl(80)= 20480
</pre>
5、将unsigned short数从网络字节顺序转换位主机字节顺序，是上面函数的逆函数。
<pre>      u_short  ntohs(u_short netshort);
举例：ntohs(0)=0
ntohsl(20480)= 80
</pre>
6、将用点分割的IP地址转换位一个in_addr结构的地址，这个结构的定义见笔记(一)，实际上就是一个unsigned long值。计算机内部处理IP地址可是不认识如192.1.8.84之类的数据。
<pre>      unsigned long  inet_addr( const char FAR * cp );
举例：inet_addr("192.1.8.84")=1409810880
inet_addr("127.0.0.1")= 16777343
</pre>
如果发生错误，函数返回INADDR_NONE值。<br><br>7、将网络地址转换位用点分割的IP地址，是上面函数的逆函数。
<pre>      char FAR *  inet_ntoa( struct in_addr in );
举例：char * ipaddr=NULL;
char addr[20];
in_addr inaddr;
inaddr. s_addr=16777343;
ipaddr= inet_ntoa(inaddr);
strcpy(addr,ipaddr); </pre>
这样addr的值就变为127.0.0.1。<br>注意意不要修改返回值或者进行释放动作。如果函数失败就会返回NULL值。<br><br>8、获取套接字的本地地址结构：
<pre>      int  getsockname(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
s为套接字
name为函数调用后获得的地址值
namelen为缓冲区的大小。
</pre>
9、获取与套接字相连的端地址结构：<br>
<pre>      int  getpeername(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
s为套接字
name为函数调用后获得的端地址值
namelen为缓冲区的大小。
</pre>
10、获取计算机名：<br>
<pre>      int  gethostname( char FAR * name, int namelen );
name是存放计算机名的缓冲区
namelen是缓冲区的大小
用法：
char szName[255];
memset(szName,0,255);
if(gethostname(szName,255)==SOCKET_ERROR)
{
//错误处理
}
返回值为：szNmae="xiaojin"
</pre>
11、根据计算机名获取主机地址：
<pre>      struct hostent FAR *  gethostbyname( const char FAR * name );
name为计算机名。
用法：
hostent * host;
char* ip;
host= gethostbyname("xiaojin");
if(host-&gt;h_addr_list[0])
{
struct in_addr addr;
memmove(&amp;addr, host-&gt;h_addr_list[0]，4);
//获得标准IP地址
ip=inet_ ntoa (addr);
}
返回值为：hostent-&gt;h_name="xiaojin"
hostent-&gt;h_addrtype=2    //AF_INET
hostent-&gt;length=4
ip="127.0.0.1"
</pre>
<img src="http://www.vckbase.com/document/image/paragraph.gif"><strong> Winsock 的I/O操作：</strong><br><br>1、 两种I/O模式
<ul>
    <li>阻塞模式：执行I/O操作完成前会一直进行等待，不会将控制权交给程序。套接字 默认为阻塞模式。可以通过多线程技术进行处理。
    <li>非阻塞模式：执行I/O操作时，Winsock函数会返回并交出控制权。这种模式使用 起来比较复杂，因为函数在没有运行完成就进行返回，会不断地返回 WSAEWOULDBLOCK错误。但功能强大。 </li>
</ul>
为了解决这个问题，提出了进行I/O操作的一些I/O模型,下面介绍最常见的三种：<br><br>2、select模型：<br><br>　　通过调用select函数可以确定一个或多个套接字的状态，判断套接字上是否有数据，或<br>者能否向一个套接字写入数据。
<pre>      int  select( int nfds, fd_set FAR * readfds, fd_set FAR * writefds,&nbsp;
fd_set FAR *exceptfds, const struct timeval FAR * timeout );
</pre>
◆先来看看涉及到的结构的定义：<br>a、 d_set结构：<br>
<pre>#define FD_SETSIZE 64?
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;      </pre>
fd_count为已设定socket的数量<br>fd_array为socket列表，FD_SETSIZE为最大socket数量，建议不小于64。这是微软建<br>议的。<br><br>B、timeval结构：
<pre>struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
</pre>
tv_sec为时间的秒值。<br>tv_usec为时间的毫秒值。<br>这个结构主要是设置select()函数的等待值，如果将该结构设置为(0,0)，则select()函数<br>会立即返回。<br><br>◆再来看看select函数各参数的作用：
<ol>
    <li>nfds：没有任何用处，主要用来进行系统兼容用，一般设置为0。<br>
    <li>readfds：等待可读性检查的套接字组。<br>
    <li>writefds；等待可写性检查的套接字组。<br>
    <li>exceptfds：等待错误检查的套接字组。<br>
    <li>timeout：超时时间。<br>
    <li>函数失败的返回值：调用失败返回SOCKET_ERROR,超时返回0。 </li>
</ol>
readfds、writefds、exceptfds三个变量至少有一个不为空，同时这个不为空的套接字组<br>种至少有一个socket，道理很简单，否则要select干什么呢。 举例：测试一个套接字是否可读：
<pre>fd_set fdread;
//FD_ZERO定义
// #define FD_ZERO(set) (((fd_set FAR *)(set))-&gt;fd_count=0)
FD_ZERO(&amp;fdread);
FD_SET(s,&amp;fdread)； //加入套接字，详细定义请看winsock2.h
if(select(0,%fdread,NULL,NULL,NULL)&gt;0
{
//成功
if(FD_ISSET(s,&amp;fread) //是否存在fread中，详细定义请看winsock2.h
{
//是可读的
}
}</pre>
<p align=left>◆I/O操作函数：主要用于获取与套接字相关的操作参数。 </p>
<pre> int  ioctlsocket(SOCKET s, long cmd, u_long FAR * argp );     </pre>
s为I/O操作的套接字。<br>cmd为对套接字的操作命令。<br>argp为命令所带参数的指针。<br><br>常见的命令：
<pre>//确定套接字自动读入的数据量
#define FIONREAD _IOR(''''f'''', 127, u_long) /* get # bytes to read */
//允许或禁止套接字的非阻塞模式，允许为非0，禁止为0
#define FIONBIO _IOW(''''f'''', 126, u_long) /* set/clear non-blocking i/o */
//确定是否所有带外数据都已被读入
#define SIOCATMARK _IOR(''''s'''', 7, u_long) /* at oob mark? */
</pre>
3、WSAAsynSelect模型：<br>WSAAsynSelect模型也是一个常用的异步I/O模型。应用程序可以在一个套接字上接收以<br>WINDOWS消息为基础的网络事件通知。该模型的实现方法是通过调用WSAAsynSelect函<br>数 自动将套接字设置为非阻塞模式，并向WINDOWS注册一个或多个网络时间，并提供一<br>个通知时使用的窗口句柄。当注册的事件发生时，对应的窗口将收到一个基于消息的通知。<br>
<pre>      int  WSAAsyncSelect( SOCKET s, HWND hWnd, u_int wMsg, long lEvent);       </pre>
s为需要事件通知的套接字<br>hWnd为接收消息的窗口句柄<br>wMsg为要接收的消息<br>lEvent为掩码，指定应用程序感兴趣的网络事件组合，主要如下：
<pre>#define FD_READ_BIT 0
#define FD_READ (1 &lt;&lt; FD_READ_BIT)
#define FD_WRITE_BIT 1
#define FD_WRITE (1 &lt;&lt; FD_WRITE_BIT)
#define FD_OOB_BIT 2
#define FD_OOB (1 &lt;&lt; FD_OOB_BIT)
#define FD_ACCEPT_BIT 3
#define FD_ACCEPT (1 &lt;&lt; FD_ACCEPT_BIT)
#define FD_CONNECT_BIT 4
#define FD_CONNECT (1 &lt;&lt; FD_CONNECT_BIT)
#define FD_CLOSE_BIT 5
#define FD_CLOSE (1 &lt;&lt; FD_CLOSE_BIT)
</pre>
用法：要接收读写通知：
<pre>int nResult= WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE)；
if(nResult==SOCKET_ERROR)
{
//错误处理
}
</pre>
取消通知：<br>
<pre>      int nResult= WSAAsyncSelect(s,hWnd,0，0)；
</pre>
当应用程序窗口hWnd收到消息时，wMsg.wParam参数标识了套接字，lParam的低字标明<br>了网络事件，高字则包含错误代码。<br><br>4、WSAEventSelect模型<br>WSAEventSelect模型类似WSAAsynSelect模型，但最主要的区别是网络事件发生时会被发<br>送到一个事件对象句柄，而不是发送到一个窗口。<br><br>使用步骤如下：<br>a、 创建事件对象来接收网络事件：<br>
<pre>#define WSAEVENT HANDLE
#define LPWSAEVENT LPHANDLE
WSAEVENT WSACreateEvent( void );
</pre>
该函数的返回值为一个事件对象句柄，它具有两种工作状态：已传信(signaled)和未传信<br>(nonsignaled)以及两种工作模式：人工重设(manual reset)和自动重设(auto reset)。默认未<br>未传信的工作状态和人工重设模式。<br><br>b、将事件对象与套接字关联，同时注册事件，使事件对象的工作状态从未传信转变未<br>已传信。<br>
<pre>      int  WSAEventSelect( SOCKET s,WSAEVENT hEventObject,long lNetworkEvents );  </pre>
s为套接字<br>hEventObject为刚才创建的事件对象句柄<br>lNetworkEvents为掩码，定义如上面所述<br><br>c、I/O处理后，设置事件对象为未传信
<pre>BOOL WSAResetEvent( WSAEVENT hEvent );</pre>
<p>Hevent为事件对象<br><br>成功返回TRUE，失败返回FALSE。<br><br>d、等待网络事件来触发事件句柄的工作状态：</p>
<pre>DWORD WSAWaitForMultipleEvents( DWORD cEvents,
const WSAEVENT FAR * lphEvents, BOOL fWaitAll,
DWORD dwTimeout, BOOL fAlertable );</pre>
<p>lpEvent为事件句柄数组的指针<br>cEvent为为事件句柄的数目，其最大值为WSA_MAXIMUM_WAIT_EVENTS&nbsp;<br>fWaitAll指定等待类型：TRUE：当lphEvent数组重所有事件对象同时有信号时返回；<br>FALSE：任一事件有信号就返回。<br>dwTimeout为等待超时（毫秒）<br>fAlertable为指定函数返回时是否执行完成例程<br><br>对事件数组中的事件进行引用时，应该用WSAWaitForMultipleEvents的返回值，减去<br>预声明值WSA_WAIT_EVENT_0，得到具体的引用值。例如：</p>
<pre>nIndex=WSAWaitForMultipleEvents(&#8230;);
MyEvent=EventArray[Index- WSA_WAIT_EVENT_0];</pre>
<p>e、判断网络事件类型：</p>
<pre>int WSAEnumNetworkEvents( SOCKET s,
WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );</pre>
<p>s为套接字<br>hEventObject为需要重设的事件对象<br>lpNetworkEvents为记录网络事件和错误代码，其结构定义如下：</p>
<pre>typedef struct _WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;</pre>
<p>f、关闭事件对象句柄：</p>
<pre>BOOL WSACloseEvent(WSAEVENT hEvent);</pre>
<p>调用成功返回TRUE，否则返回FALSE。<br></p>
<img src ="http://www.cppblog.com/mmdengwo/aggbug/145571.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/mmdengwo/" target="_blank">沛沛</a> 2011-05-03 15:44 <a href="http://www.cppblog.com/mmdengwo/archive/2011/05/03/145571.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Socket 编程</title><link>http://www.cppblog.com/mmdengwo/archive/2011/04/29/145307.html</link><dc:creator>沛沛</dc:creator><author>沛沛</author><pubDate>Fri, 29 Apr 2011 03:18:00 GMT</pubDate><guid>http://www.cppblog.com/mmdengwo/archive/2011/04/29/145307.html</guid><wfw:comment>http://www.cppblog.com/mmdengwo/comments/145307.html</wfw:comment><comments>http://www.cppblog.com/mmdengwo/archive/2011/04/29/145307.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/mmdengwo/comments/commentRss/145307.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/mmdengwo/services/trackbacks/145307.html</trackback:ping><description><![CDATA[<p>Socket（套接字）</p>
<p>◆先看定义： <br>typedef unsigned int u_int;<br>typedef u_int SOCKET;◆Socket相当于进行网络通信两端的插座，只要对方的Socket和自己的Socket有通信联接，双方就可以发送和接收数据了。其定义类似于文件句柄的定义。</p>
<p>◆Socket有五种不同的类型：</p>
<p>1、流式套接字(stream socket)<br>定义：</p>
<p>#define SOCK_STREAM 1 流式套接字提供了双向、有序的、无重复的以及无记录边界的数据流服务，适合处理大量数据。它是面向联结的，必须建立数据传输链路，同时还必须对传输的数据进行验证，确保数据的准确性。因此，系统开销较大。</p>
<p>2、 数据报套接字(datagram socket)</p>
<p>定义：</p>
<p>#define SOCK_DGRAM 2 数据报套接字也支持双向的数据流，但不保证传输数据的准确性，但保留了记录边界。由于数据报套接字是无联接的，例如广播时的联接，所以并不保证接收端是否正在侦听。数据报套接字传输效率比较高。</p>
<p>3、原始套接字(raw-protocol interface)</p>
<p>定义：</p>
<p>#define SOCK_RAW 3 原始套接字保存了数据包中的完整IP头，前面两种套接字只能收到用户数据。因此可以通过原始套接字对数据进行分析。<br>其它两种套接字不常用，这里就不介绍了。</p>
<p>◆Socket开发所必须需要的文件(以WinSock V2.0为例)：</p>
<p>头文件：Winsock2.h</p>
<p>库文件：WS2_32.LIB</p>
<p>动态库：W32_32.DLL</p>
<p>&nbsp;一些重要的定义</p>
<p>1、数据类型的基本定义：这个大家一看就懂。</p>
<p>typedef unsigned char u_char;<br>typedef unsigned short u_short;<br>typedef unsigned int u_int;<br>typedef unsigned long u_long;2、 网络地址的数据结构，有一个老的和一个新的的，请大家留意，如果想知道为什么，<br>请发邮件给Bill Gate。其实就是计算机的IP地址，不过一般不用用点分开的IP地<br>址，当然也提供一些转换函数。</p>
<p>◆ 旧的网络地址结构的定义，为一个4字节的联合：</p>
<p>struct in_addr {<br>union {<br>struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;<br>struct { u_short s_w1,s_w2; } S_un_w;<br>u_long S_addr;<br>} S_un;<br>#define s_addr S_un.S_addr /* can be used for most tcp &amp; ip code */<br>//下面几行省略,反正没什么用处。<br>};其实完全不用这么麻烦，请看下面:</p>
<p>◆ 新的网络地址结构的定义：<br>非常简单，就是一个无符号长整数 unsigned long。举个例子：IP地址为127.0.0.1的网络地址是什么呢？请看定义：</p>
<p>#define INADDR_LOOPBACK 0x7f0000013、 套接字地址结构</p>
<p>(1)、sockaddr结构：</p>
<p>struct sockaddr {<br>u_short sa_family; /* address family */<br>char sa_data[14]; /* up to 14 bytes of direct address */<br>};sa_family为网络地址类型，一般为AF_INET，表示该socket在Internet域中进行通信，该地址结构随选择的协议的不同而变化，因此一般情况下另一个与该地址结构大小相同的sockaddr_in结构更为常用，sockaddr_in结构用来标识TCP/IP协议下的地址。换句话说，这个结构是通用socket地址结构，而下面的sockaddr_in是专门针对Internet域的socket地址结构。</p>
<p>(2)、sockaddr_in结构</p>
<p>struct sockaddr_in {<br>short sin_family;<br>u_short sin_port;<br>struct in_addr sin_addr;<br>char sin_zero[8];<br>};sin _family为网络地址类型，必须设定为AF_INET。sin_port为服务端口，注意不要使用已固定的服务端口，如HTTP的端口80等。如果端口设置为0，则系统会自动分配一个唯一端口。sin_addr为一个unsigned long的IP地址。sin_zero为填充字段，纯粹用来保证结构的大小。</p>
<p>◆ 将常用的用点分开的IP地址转换为unsigned long类型的IP地址的函数：</p>
<p>unsigned long inet_addr(const char FAR * cp )用法：</p>
<p>unsigned long addr=inet_addr("192.1.8.84")◆ 如果将sin_addr设置为INADDR_ANY，则表示所有的IP地址，也即所有的计算机。</p>
<p>#define INADDR_ANY (u_long)0x000000004、 主机地址：</p>
<p>先看定义：</p>
<p>struct hostent {<br>char FAR * h_name; /* official name of host */<br>char FAR * FAR * h_aliases; /* alias list */<br>short h_addrtype; /* host address type */<br>short h_length; /* length of address */<br>char FAR * FAR * h_addr_list; /* list of addresses */<br>#define h_addr h_addr_list[0] /* address, for backward compat */<br>};<br>h_name为主机名字。<br>h_aliases为主机别名列表。<br>h_addrtype为地址类型。<br>h_length为地址类型。<br>h_addr_list为IP地址，如果该主机有多个网卡，就包括地址的列表。另外还有几个类似的结构，这里就不一一介绍了。</p>
<p>5、 常见TCP/IP协议的定义：</p>
<p>#define IPPROTO_IP 0 <br>#define IPPROTO_ICMP 1 <br>#define IPPROTO_IGMP 2 <br>#define IPPROTO_TCP 6 <br>#define IPPROTO_UDP 17 <br>#define IPPROTO_RAW 255 具体是什么协议，大家一看就知道了。</p>
<p>&nbsp;套接字的属性</p>
<p>为了灵活使用套接字，我们可以对它的属性进行设定。</p>
<p>1、 属性内容：</p>
<p>//允许调试输出<br>#define SO_DEBUG 0x0001 /* turn on debugging info recording */<br>//是否监听模式<br>#define SO_ACCEPTCONN 0x0002 /* socket has had listen() */<br>//套接字与其他套接字的地址绑定<br>#define SO_REUSEADDR 0x0004 /* allow local address reuse */<br>//保持连接<br>#define SO_KEEPALIVE 0x0008 /* keep connections alive */<br>//不要路由出去<br>#define SO_DONTROUTE 0x0010 /* just use interface addresses */<br>//设置为广播<br>#define SO_BROADCAST 0x0020 /* permit sending of broadcast msgs */<br>//使用环回不通过硬件<br>#define SO_USELOOPBACK 0x0040 /* bypass hardware when possible */<br>//当前拖延值<br>#define SO_LINGER 0x0080 /* linger on close if data present */<br>//是否加入带外数据<br>#define SO_OOBINLINE 0x0100 /* leave received OOB data in line */<br>//禁用LINGER选项<br>#define SO_DONTLINGER (int)(~SO_LINGER)<br>//发送缓冲区长度<br>#define SO_SNDBUF 0x1001 /* send buffer size */<br>//接收缓冲区长度<br>#define SO_RCVBUF 0x1002 /* receive buffer size */<br>//发送超时时间<br>#define SO_SNDTIMEO 0x1005 /* send timeout */<br>//接收超时时间<br>#define SO_RCVTIMEO 0x1006 /* receive timeout */<br>//错误状态<br>#define SO_ERROR 0x1007 /* get error status and clear */<br>//套接字类型<br>#define SO_TYPE 0x1008 /* get socket type */2、 读取socket属性：</p>
<p>int getsockopt(SOCKET s, int level, int optname, char FAR * optval, int FAR * optlen)s为欲读取属性的套接字。level为套接字选项的级别，大多数是特定协议和套接字专有的。如IP协议应为 IPPROTO_IP。</p>
<p>optname为读取选项的名称<br>optval为存放选项值的缓冲区指针。<br>optlen为缓冲区的长度用法：</p>
<p>int ttl=0; //读取TTL值<br>int rc = getsockopt( s, IPPROTO_IP, IP_TTL, (char *)&amp;ttl, sizeof(ttl));<br>//来自MS platform SDK 20033、 设置socket属性：</p>
<p>int setsockopt(SOCKET s,int level, int optname,const char FAR * optval, int optlen)s为欲设置属性的套接字。<br>level为套接字选项的级别，用法同上。<br>optname为设置选项的名称<br>optval为存放选项值的缓冲区指针。<br>optlen为缓冲区的长度</p>
<p>用法：</p>
<p>int ttl=32; //设置TTL值<br>int rc = setsockopt( s, IPPROTO_IP, IP_TTL, (char *)&amp;ttl, sizeof(ttl)); 套接字的使用步骤</p>
<p>1、启动Winsock：对Winsock DLL进行初始化，协商Winsock的版本支持并分配必要的<br>资源。（服务器端和客户端）</p>
<p>int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData )</p>
<p>wVersionRequested为打算加载Winsock的版本，一般如下设置：<br>wVersionRequested=MAKEWORD(2,0)<br>或者直接赋值：wVersionRequested=2</p>
<p>LPWSADATA为初始化Socket后加载的版本的信息,定义如下：<br>typedef struct WSAData {<br>WORD wVersion;<br>WORD wHighVersion;<br>char szDescription[WSADESCRIPTION_LEN+1];<br>char szSystemStatus[WSASYS_STATUS_LEN+1];<br>unsigned short iMaxSockets;<br>unsigned short iMaxUdpDg;<br>char FAR * lpVendorInfo;<br>} WSADATA, FAR * LPWSADATA;如果加载成功后数据为：</p>
<p>wVersion＝2表示加载版本为2.0。<br>wHighVersion＝514表示当前系统支持socket最高版本为2.2。<br>szDescription="WinSock 2.0"<br>szSystemStatus="Running"表示正在运行。<br>iMaxSockets＝0表示同时打开的socket最大数，为0表示没有限制。<br>iMaxUdpDg＝0表示同时打开的数据报最大数，为0表示没有限制。<br>lpVendorInfo没有使用，为厂商指定信息预留。该函数使用方法：</p>
<p>WORD wVersion=MAKEWORD(2,0);<br>WSADATA wsData;<br>int nResult= WSAStartup(wVersion,&amp;wsData);<br>if(nResult !=0)<br>{<br>//错误处理<br>}2、创建套接字：（服务器端和客户端）</p>
<p>SOCKET socket( int af, int type, int protocol );<br>af为网络地址类型，一般为AF_INET，表示在Internet域中使用。<br>type为套接字类型，前面已经介绍了。<br>protocol为指定网络协议，一般为IPPROTO_IP。用法：</p>
<p>SOCKET sock=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);<br>if(sock==INVALID_SOCKET)<br>{<br>//错误处理<br>}3、套接字的绑定：将本地地址绑定到所创建的套接字上。（服务器端和客户端）</p>
<p>int bind( SOCKET s, const struct sockaddr FAR * name, int namelen )<br>s为已经创建的套接字。<br>name为socket地址结构，为sockaddr结构，如前面讨论的，我们一般使用sockaddr_in<br>结构，在使用再强制转换为sockaddr结构。<br>namelen为地址结构的长度。<br>用法：</p>
<p>sockaddr_in addr;<br>addr. sin_family=AF_INET;<br>addr. sin_port= htons(0); //保证字节顺序<br>addr. sin_addr.s_addr= inet_addr("192.1.8.84")<br>int nResult=bind(s,(sockaddr*)&amp;addr,sizeof(sockaddr));<br>if(nResult==SOCKET_ERROR)<br>{<br>//错误处理<br>}4、 套接字的监听：（服务器端）</p>
<p>int listen(SOCKET s, int backlog )s为一个已绑定但未联接的套接字。<br>backlog为指定正在等待联接的最大队列长度，这个参数非常重要，因为服务器一般可<br>以提供多个连接。<br>用法：</p>
<p>int nResult=listen(s,5) //最多5个连接<br>if(nResult==SOCKET_ERROR)<br>{<br>//错误处理<br>}5、套接字等待连接:：（服务器端）</p>
<p>SOCKET accept( SOCKET s, struct sockaddr FAR * addr, int FAR * addrlen )s为处于监听模式的套接字。<br>sockaddr为接收成功后返回客户端的网络地址。<br>addrlen为网络地址的长度。</p>
<p>用法：</p>
<p>sockaddr_in addr;<br>SOCKET s_d=accept(s,(sockaddr*)&amp;addr,sizeof(sockaddr));<br>if(s==INVALID_SOCKET)<br>{<br>//错误处理<br>}6、套接字的连结：将两个套接字连结起来准备通信。（客户端）</p>
<p>int connect(SOCKET s, const struct sockaddr FAR * name, int namelen )s为欲连结的已创建的套接字。<br>name为欲连结的socket地址。<br>namelen为socket地址的结构的长度。</p>
<p>用法：</p>
<p>sockaddr_in addr;<br>addr. sin_family=AF_INET;<br>addr. sin_port=htons(0); //保证字节顺序<br>addr. sin_addr.s_addr= htonl(INADDR_ANY) //保证字节顺序<br>int nResult=connect(s,(sockaddr*)&amp;addr,sizeof(sockaddr));<br>if(nResult==SOCKET_ERROR)<br>{<br>//错误处理<br>}7、套接字发送数据：（服务器端和客户端）</p>
<p>int send(SOCKET s, const char FAR * buf, int len, int flags )s为服务器端监听的套接字。<br>buf为欲发送数据缓冲区的指针。<br>len为发送数据缓冲区的长度。<br>flags为数据发送标记。<br>返回值为发送数据的字符数。</p>
<p>◆这里讲一下这个发送标记，下面8中讨论的接收标记也一样：</p>
<p>flag取值必须为0或者如下定义的组合：0表示没有特殊行为。</p>
<p>#define MSG_OOB 0x1 /* process out-of-band data */<br>#define MSG_PEEK 0x2 /* peek at incoming message */<br>#define MSG_DONTROUTE 0x4 /* send without using routing tables */<br>MSG_OOB表示数据应该带外发送，所谓带外数据就是TCP紧急数据。<br>MSG_PEEK表示使有用的数据复制到缓冲区内，但并不从系统缓冲区内删除。<br>MSG_DONTROUTE表示不要将包路由出去。</p>
<p>用法：</p>
<p>char buf[]="xiaojin";<br>int nResult=send(s,buf,strlen(buf));<br>if(nResult==SOCKET_ERROR)<br>{<br>//错误处理<br>}8、 套接字的数据接收：（客户端）</p>
<p>int recv( SOCKET s, char FAR * buf, int len, int flags )s为准备接收数据的套接字。<br>buf为准备接收数据的缓冲区。<br>len为准备接收数据缓冲区的大小。<br>flags为数据接收标记。<br>返回值为接收的数据的字符数。</p>
<p>用法：</p>
<p>char mess[1000];<br>int nResult =recv(s,mess,1000,0);<br>if(nResult==SOCKET_ERROR)<br>{<br>//错误处理<br>}9、中断套接字连接：通知服务器端或客户端停止接收和发送数据。（服务器端和客户端）</p>
<p>int shutdown(SOCKET s, int how)s为欲中断连接的套接字。<br>How为描述禁止哪些操作，取值为：SD_RECEIVE、SD_SEND、SD_BOTH。</p>
<p>#define SD_RECEIVE 0x00<br>#define SD_SEND 0x01<br>#define SD_BOTH 0x02用法：</p>
<p>int nResult= shutdown(s,SD_BOTH);<br>if(nResult==SOCKET_ERROR)<br>{<br>//错误处理<br>}10、 关闭套接字：释放所占有的资源。（服务器端和客户端）</p>
<p>int closesocket( SOCKET s )s为欲关闭的套接字。</p>
<p>用法：</p>
<p>int nResult=closesocket(s);<br>if(nResult==SOCKET_ERROR)<br>{<br>//错误处理<br>}<br>&nbsp; </p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/coffeemay/archive/2006/08/05/1023149.aspx">http://blog.csdn.net/coffeemay/archive/2006/08/05/1023149.aspx</a></p>
<img src ="http://www.cppblog.com/mmdengwo/aggbug/145307.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/mmdengwo/" target="_blank">沛沛</a> 2011-04-29 11:18 <a href="http://www.cppblog.com/mmdengwo/archive/2011/04/29/145307.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>最通俗浅显的“IO模式”解析</title><link>http://www.cppblog.com/mmdengwo/archive/2011/04/14/144255.html</link><dc:creator>沛沛</dc:creator><author>沛沛</author><pubDate>Thu, 14 Apr 2011 14:33:00 GMT</pubDate><guid>http://www.cppblog.com/mmdengwo/archive/2011/04/14/144255.html</guid><wfw:comment>http://www.cppblog.com/mmdengwo/comments/144255.html</wfw:comment><comments>http://www.cppblog.com/mmdengwo/archive/2011/04/14/144255.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/mmdengwo/comments/commentRss/144255.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/mmdengwo/services/trackbacks/144255.html</trackback:ping><description><![CDATA[[源] ：http://hi.baidu.com/firebird/blog/item/f592b3193a02814542a9adeb.html<br><br>&nbsp;<font size=2>&nbsp;&nbsp;&nbsp; 一：select模型<br>&nbsp;&nbsp;&nbsp; 二：WSAAsyncSelect模型<br>&nbsp;&nbsp;&nbsp; 三：WSAEventSelect模型<br>&nbsp;&nbsp;&nbsp; 四：Overlapped I/O 事件通知模型<br>&nbsp;&nbsp;&nbsp; 五：Overlapped I/O 完成例程模型<br>&nbsp;&nbsp;&nbsp; 六：IOCP模型</font><br><br>原文名：《基于Delphi的Socket&nbsp;I/O模型全接触&nbsp;》 <br>老陈有一个在外地工作的女儿，不能经常回来，老陈和她通过信件联系。他们的信会被邮递员投递到他们的信箱里。&nbsp; <br><br>这和Socket模型非常类似。下面我就以老陈接收信件为例讲解Socket&nbsp;I/O模型。&nbsp; <br><br>一：select模型&nbsp; <br><br>老陈非常想看到女儿的信。以至于他每隔10分钟就下楼检查信箱，看是否有女儿的信，在这种情况下，&#8220;下楼检查信箱&#8221;然后回到楼上耽误了老陈太多的时间，以至于老陈无法做其他工作。&nbsp; <br><br>select模型和老陈的这种情况非常相似：周而复始地去检查......如果有数据......接收/发送.......&nbsp; <br><br>使用线程来select应该是通用的做法：&nbsp; <br><br>procedure&nbsp;TListenThread.Execute;&nbsp; <br>var&nbsp; <br>　&nbsp;addr&nbsp;:&nbsp;TSockAddrIn;&nbsp; <br>　&nbsp;fd_read&nbsp;:&nbsp;TFDSet;&nbsp; <br>　&nbsp;timeout&nbsp;:&nbsp;TTimeVal;&nbsp; <br>　&nbsp;ASock,&nbsp; <br>　&nbsp;MainSock&nbsp;:&nbsp;TSocket;&nbsp; <br>　&nbsp;len,&nbsp;i&nbsp;:&nbsp;Integer;&nbsp; <br>begin&nbsp; <br>　&nbsp;MainSock&nbsp;:=&nbsp;socket(&nbsp;AF_INET,&nbsp;SOCK_STREAM,&nbsp;IPPROTO_TCP&nbsp;);&nbsp; <br>　&nbsp;addr.sin_family&nbsp;:=&nbsp;AF_INET;&nbsp; <br>　&nbsp;addr.sin_port&nbsp;:=&nbsp;htons(5678);&nbsp; <br>　&nbsp;addr.sin_addr.S_addr&nbsp;:=&nbsp;htonl(INADDR_ANY);&nbsp; <br>　&nbsp;bind(&nbsp;MainSock,&nbsp;@addr,&nbsp;sizeof(addr)&nbsp;);&nbsp; <br>　&nbsp;listen(&nbsp;MainSock,&nbsp;5&nbsp;);&nbsp; <br><br>　&nbsp;while&nbsp;(not&nbsp;Terminated)&nbsp;do&nbsp; <br>　&nbsp;begin&nbsp; <br>　　&nbsp;FD_ZERO(&nbsp;fd_read&nbsp;);&nbsp; <br>　　&nbsp;FD_SET(&nbsp;MainSock,&nbsp;fd_read&nbsp;);&nbsp; <br>　　&nbsp;timeout.tv_sec&nbsp;:=&nbsp;0;&nbsp; <br>　　&nbsp;timeout.tv_usec&nbsp;:=&nbsp;500;&nbsp; <br>　　&nbsp;if&nbsp;select(&nbsp;0,&nbsp;@fd_read,&nbsp;nil,&nbsp;nil,&nbsp;@timeout&nbsp;)&nbsp;&gt;&nbsp;0&nbsp;then&nbsp;//至少有1个等待Accept的connection&nbsp; <br>　　&nbsp;begin&nbsp; <br>　　　&nbsp;if&nbsp;FD_ISSET(&nbsp;MainSock,&nbsp;fd_read&nbsp;)&nbsp;then&nbsp; <br>　　　&nbsp;begin&nbsp; <br>　　　&nbsp;for&nbsp;i:=0&nbsp;to&nbsp;fd_read.fd_count-1&nbsp;do&nbsp;//注意，fd_count&nbsp;&lt;=&nbsp;64， <br>&nbsp;&nbsp;&nbsp;也就是说select只能同时管理最多64个连接&nbsp; <br>　　　&nbsp;begin&nbsp; <br>　　　　&nbsp;len&nbsp;:=&nbsp;sizeof(addr);&nbsp; <br>　　　　&nbsp;ASock&nbsp;:=&nbsp;accept(&nbsp;MainSock,&nbsp;addr,&nbsp;len&nbsp;);&nbsp; <br>　　　　&nbsp;if&nbsp;ASock&nbsp;&lt;&gt;&nbsp;INVALID_SOCKET&nbsp;then&nbsp; <br>　　　　　&nbsp;....//为ASock创建一个新的线程，在新的线程中再不停地select&nbsp; <br>　　　　&nbsp;end;&nbsp; <br>　　　&nbsp;end;&nbsp;　　&nbsp; <br>　　&nbsp;end;&nbsp; <br>　&nbsp;end;&nbsp;//while&nbsp;(not&nbsp;self.Terminated)&nbsp; <br><br>　&nbsp;shutdown(&nbsp;MainSock,&nbsp;SD_BOTH&nbsp;);&nbsp; <br>　&nbsp;closesocket(&nbsp;MainSock&nbsp;);&nbsp; <br>end; <br>&nbsp; <br><br><br>二：WSAAsyncSelect模型&nbsp; <br><br>后来，老陈使用了微软公司的新式信箱。这种信箱非常先进，一旦信箱里有新的信件，盖茨就会给老陈打电话：喂，大爷，你有新的信件了！从此，老陈再也不必频繁上下楼检查信箱了，牙也不疼了，你瞅准了，蓝天......不是，微软......&nbsp; <br><br>微软提供的WSAAsyncSelect模型就是这个意思。&nbsp; <br><br>WSAAsyncSelect模型是Windows下最简单易用的一种Socket&nbsp;I/O模型。使用这种模型时，Windows会把网络事件以消息的形势通知应用程序。&nbsp; <br><br>首先定义一个消息标示常量：&nbsp; <br><br>const&nbsp;WM_SOCKET&nbsp;=&nbsp;WM_USER&nbsp;+&nbsp;55; <br>&nbsp; <br><br><br>再在主Form的private域添加一个处理此消息的函数声明：&nbsp; <br><br>private&nbsp; <br>procedure&nbsp;WMSocket(var&nbsp;Msg:&nbsp;TMessage);&nbsp;message&nbsp;WM_SOCKET; <br>&nbsp; <br>　&nbsp; <br><br>然后就可以使用WSAAsyncSelect了：&nbsp; <br><br>var&nbsp; <br>　&nbsp;addr&nbsp;:&nbsp;TSockAddr;&nbsp; <br>　&nbsp;sock&nbsp;:&nbsp;TSocket;&nbsp; <br><br>　&nbsp;sock&nbsp;:=&nbsp;socket(&nbsp;AF_INET,&nbsp;SOCK_STREAM,&nbsp;IPPROTO_TCP&nbsp;);&nbsp; <br>　&nbsp;addr.sin_family&nbsp;:=&nbsp;AF_INET;&nbsp; <br>　&nbsp;addr.sin_port&nbsp;:=&nbsp;htons(5678);&nbsp; <br>　&nbsp;addr.sin_addr.S_addr&nbsp;:=&nbsp;htonl(INADDR_ANY);&nbsp; <br>　&nbsp;bind(&nbsp;m_sock,&nbsp;@addr,&nbsp;sizeof(SOCKADDR)&nbsp;);&nbsp; <br><br>　&nbsp;WSAAsyncSelect(&nbsp;m_sock,&nbsp;Handle,&nbsp;WM_SOCKET,&nbsp;FD_ACCEPT&nbsp;or&nbsp;FD_CLOSE&nbsp;);&nbsp; <br><br>　&nbsp;listen(&nbsp;m_sock,&nbsp;5&nbsp;);&nbsp; <br>　&nbsp;.... <br>&nbsp; <br><br><br>应用程序可以对收到WM_SOCKET消息进行分析，判断是哪一个socket产生了网络事件以及事件类型：&nbsp; <br><br>procedure&nbsp;TfmMain.WMSocket(var&nbsp;Msg:&nbsp;TMessage);&nbsp; <br>var&nbsp; <br>　&nbsp;sock&nbsp;:&nbsp;TSocket;&nbsp; <br>　&nbsp;addr&nbsp;:&nbsp;TSockAddrIn;&nbsp; <br>　&nbsp;addrlen&nbsp;:&nbsp;Integer;&nbsp; <br>　&nbsp;buf&nbsp;:&nbsp;Array&nbsp;[0..4095]&nbsp;of&nbsp;Char;&nbsp; <br>begin&nbsp; <br>　&nbsp;//Msg的WParam是产生了网络事件的socket句柄，LParam则包含了事件类型&nbsp; <br>　&nbsp;case&nbsp;WSAGetSelectEvent(&nbsp;Msg.LParam&nbsp;)&nbsp;of&nbsp; <br>　&nbsp;FD_ACCEPT&nbsp;:&nbsp; <br>　　&nbsp;begin&nbsp; <br>　　　&nbsp;addrlen&nbsp;:=&nbsp;sizeof(addr);&nbsp; <br>　　　&nbsp;sock&nbsp;:=&nbsp;accept(&nbsp;Msg.WParam,&nbsp;addr,&nbsp;addrlen&nbsp;);&nbsp; <br>　　　&nbsp;if&nbsp;sock&nbsp;&lt;&gt;&nbsp;INVALID_SOCKET&nbsp;then&nbsp; <br>　　　　&nbsp;WSAAsyncSelect(&nbsp;sock,&nbsp;Handle,&nbsp;WM_SOCKET,&nbsp;FD_READ&nbsp;or&nbsp;FD_WRITE&nbsp;or&nbsp;FD_CLOSE&nbsp;);&nbsp; <br>　　&nbsp;end;&nbsp; <br><br>　　&nbsp;FD_CLOSE&nbsp;:&nbsp;closesocket(&nbsp;Msg.WParam&nbsp;);&nbsp; <br>　　&nbsp;FD_READ&nbsp;:&nbsp;recv(&nbsp;Msg.WParam,&nbsp;buf[0],&nbsp;4096,&nbsp;0&nbsp;);&nbsp; <br>　　&nbsp;FD_WRITE&nbsp;:&nbsp;;&nbsp; <br>　&nbsp;end;&nbsp; <br>end; <br>&nbsp; <br><br><br>三：WSAEventSelect模型&nbsp; <br><br>后来，微软的信箱非常畅销，购买微软信箱的人以百万计数......以至于盖茨每天24小时给客户打电话，累得腰酸背痛，喝蚁力神都不好使。微软改进了他们的信箱：在客户的家中添加一个附加装置，这个装置会监视客户的信箱，每当新的信件来临，此装置会发出&#8220;新信件到达&#8221;声，提醒老陈去收信。盖茨终于可以睡觉了。&nbsp; <br><br>同样要使用线程：&nbsp; <br><br>procedure&nbsp;TListenThread.Execute;&nbsp; <br>var&nbsp; <br>　&nbsp;hEvent&nbsp;:&nbsp;WSAEvent;&nbsp; <br>　&nbsp;ret&nbsp;:&nbsp;Integer;&nbsp; <br>　&nbsp;ne&nbsp;:&nbsp;TWSANetworkEvents;&nbsp; <br>　&nbsp;sock&nbsp;:&nbsp;TSocket;&nbsp; <br>　&nbsp;adr&nbsp;:&nbsp;TSockAddrIn;&nbsp; <br>　&nbsp;sMsg&nbsp;:&nbsp;String;&nbsp; <br>　&nbsp;Index,&nbsp; <br>　&nbsp;EventTotal&nbsp;:&nbsp;DWORD;&nbsp; <br>　&nbsp;EventArray&nbsp;:&nbsp;Array&nbsp;[0..WSA_MAXIMUM_WAIT_EVENTS-1]&nbsp;of&nbsp;WSAEVENT;&nbsp; <br>begin&nbsp; <br>　&nbsp;...socket...bind...&nbsp; <br>　&nbsp;hEvent&nbsp;:=&nbsp;WSACreateEvent();&nbsp; <br>　&nbsp;WSAEventSelect(&nbsp;ListenSock,&nbsp;hEvent,&nbsp;FD_ACCEPT&nbsp;or&nbsp;FD_CLOSE&nbsp;);&nbsp; <br>　&nbsp;...listen...&nbsp; <br><br>　&nbsp;while&nbsp;(&nbsp;not&nbsp;Terminated&nbsp;)&nbsp;do&nbsp; <br>　&nbsp;begin&nbsp; <br>　　&nbsp;Index&nbsp;:=&nbsp;WSAWaitForMultipleEvents(&nbsp;EventTotal,&nbsp;@EventArray[0],&nbsp;FALSE,&nbsp; <br>&nbsp;&nbsp;WSA_INFINITE,&nbsp;FALSE&nbsp;);&nbsp; <br>　　&nbsp;FillChar(&nbsp;ne,&nbsp;sizeof(ne),&nbsp;0&nbsp;);&nbsp; <br>　　&nbsp;WSAEnumNetworkEvents(&nbsp;SockArray[Index-WSA_WAIT_EVENT_0],&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;EventArray <br>&nbsp;&nbsp;[Index-WSA_WAIT_EVENT_0],&nbsp;@ne&nbsp;);&nbsp; <br><br>　　&nbsp;if&nbsp;(&nbsp;ne.lNetworkEvents&nbsp;and&nbsp;FD_ACCEPT&nbsp;)&nbsp;&gt;&nbsp;0&nbsp;then&nbsp; <br>　　&nbsp;begin&nbsp; <br>　　　&nbsp;if&nbsp;ne.iErrorCode[FD_ACCEPT_BIT]&nbsp;&lt;&gt;&nbsp;0&nbsp;then&nbsp; <br>　　　　&nbsp;continue;&nbsp; <br><br>　　　&nbsp;ret&nbsp;:=&nbsp;sizeof(adr);&nbsp; <br>　　　&nbsp;sock&nbsp;:=&nbsp;accept(&nbsp;SockArray[Index-WSA_WAIT_EVENT_0],&nbsp;adr,&nbsp;ret&nbsp;);&nbsp; <br>　　　&nbsp;if&nbsp;EventTotal&nbsp;&gt;&nbsp;WSA_MAXIMUM_WAIT_EVENTS-1&nbsp;then <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//这里WSA_MAXIMUM_WAIT_EVENTS同样是64&nbsp; <br>　　　&nbsp;begin&nbsp; <br>　　　　&nbsp;closesocket(&nbsp;sock&nbsp;);&nbsp; <br>　　　　&nbsp;continue;&nbsp; <br>　　　&nbsp;end;&nbsp; <br><br>　　　&nbsp;hEvent&nbsp;:=&nbsp;WSACreateEvent();&nbsp; <br>　　　&nbsp;WSAEventSelect(&nbsp;sock,&nbsp;hEvent,&nbsp;FD_READ&nbsp;or&nbsp;FD_WRITE&nbsp;or&nbsp;FD_CLOSE&nbsp;);&nbsp; <br>　　　&nbsp;SockArray[EventTotal]&nbsp;:=&nbsp;sock;&nbsp; <br>　　　&nbsp;EventArray[EventTotal]&nbsp;:=&nbsp;hEvent;&nbsp; <br>　　　&nbsp;Inc(&nbsp;EventTotal&nbsp;);&nbsp; <br>　　&nbsp;end;&nbsp; <br><br>　　&nbsp;if&nbsp;(&nbsp;ne.lNetworkEvents&nbsp;and&nbsp;FD_READ&nbsp;)&nbsp;&gt;&nbsp;0&nbsp;then&nbsp; <br>　　&nbsp;begin&nbsp; <br>　　　&nbsp;if&nbsp;ne.iErrorCode[FD_READ_BIT]&nbsp;&lt;&gt;&nbsp;0&nbsp;then&nbsp; <br>　　　　&nbsp;continue;&nbsp; <br>　　　　&nbsp;FillChar(&nbsp;RecvBuf[0],&nbsp;PACK_SIZE_RECEIVE,&nbsp;0&nbsp;);&nbsp; <br>　　　　&nbsp;ret&nbsp;:=&nbsp;recv(&nbsp;SockArray[Index-WSA_WAIT_EVENT_0],&nbsp;RecvBuf[0],&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;PACK_SIZE_RECEIVE,&nbsp;0&nbsp;);&nbsp; <br>　　　　&nbsp;......&nbsp; <br>　　　&nbsp;end;&nbsp; <br>　　&nbsp;end;&nbsp; <br>end; <br>&nbsp; <br><br><br><br>四：Overlapped&nbsp;I/O&nbsp;事件通知模型&nbsp; <br><br>后来，微软通过调查发现，老陈不喜欢上下楼收发信件，因为上下楼其实很浪费时间。于是微软再次改进他们的信箱。新式的信箱采用了更为先进的技术，只要用户告诉微软自己的家在几楼几号，新式信箱会把信件直接传送到用户的家中，然后告诉用户，你的信件已经放到你的家中了！老陈很高兴，因为他不必再亲自收发信件了！&nbsp; <br><br>Overlapped&nbsp;I/O&nbsp;事件通知模型和WSAEventSelect模型在实现上非常相似，主要区别在"Overlapped&#8221;，Overlapped模型是让应用程序使用重叠数据结构(WSAOVERLAPPED)，一次投递一个或多个Winsock&nbsp;I/O请求。这些提交的请求完成后，应用程序会收到通知。什么意思呢？就是说，如果你想从socket上接收数据，只需要告诉系统，由系统为你接收数据，而你需要做的只是为系统提供一个缓冲区~~~~~&nbsp; <br><br>Listen线程和WSAEventSelect模型一模一样，Recv/Send线程则完全不同：&nbsp; <br><br>procedure&nbsp;TOverlapThread.Execute;&nbsp; <br>var&nbsp; <br>　&nbsp;dwTemp&nbsp;:&nbsp;DWORD;&nbsp; <br>　&nbsp;ret&nbsp;:&nbsp;Integer;&nbsp; <br>　&nbsp;Index&nbsp;:&nbsp;DWORD;&nbsp; <br>begin&nbsp; <br>　&nbsp;......&nbsp; <br><br>　&nbsp;while&nbsp;(&nbsp;not&nbsp;Terminated&nbsp;)&nbsp;do&nbsp; <br>　&nbsp;begin&nbsp; <br>　　&nbsp;Index&nbsp;:=&nbsp;WSAWaitForMultipleEvents <br>&nbsp;&nbsp;&nbsp;(&nbsp;FLinks.Count,&nbsp;@FLinks.Events[0],&nbsp;FALSE,&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;RECV_TIME_OUT,&nbsp;FALSE&nbsp;);&nbsp; <br>　　&nbsp;Dec(&nbsp;Index,&nbsp;WSA_WAIT_EVENT_0&nbsp;);&nbsp; <br>　　&nbsp;if&nbsp;Index&nbsp;&gt;&nbsp;WSA_MAXIMUM_WAIT_EVENTS-1&nbsp;then&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;//超时或者其他错误&nbsp; <br>　　　&nbsp;continue;&nbsp; <br><br>　　&nbsp;WSAResetEvent <br>&nbsp;&nbsp;&nbsp;(&nbsp;FLinks.Events[Index]&nbsp;);&nbsp; <br>　　&nbsp;WSAGetOverlappedResult(&nbsp;FLinks.Sockets[Index],&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FLinks.pOverlaps[Index],&nbsp;@dwTemp,&nbsp;FALSE,FLinks. <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pdwFlags[Index]^&nbsp;);&nbsp; <br><br>　　&nbsp;if&nbsp;dwTemp&nbsp;=&nbsp;0&nbsp;then&nbsp;//连接已经关闭&nbsp; <br>　　&nbsp;begin&nbsp; <br>　　　&nbsp;......&nbsp; <br>　　　&nbsp;continue;&nbsp; <br>　　&nbsp;end&nbsp;else&nbsp; <br>　&nbsp;begin&nbsp; <br>　　&nbsp;fmMain.ListBox1.Items.Add(&nbsp;FLinks.pBufs[Index]^.buf&nbsp;);&nbsp; <br>　&nbsp;end;&nbsp; <br><br>　&nbsp;//初始化缓冲区&nbsp; <br>　&nbsp;FLinks.pdwFlags[Index]^&nbsp;:=&nbsp;0;&nbsp; <br>　&nbsp;FillChar(&nbsp;FLinks.pOverlaps[Index]^,&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;sizeof(WSAOVERLAPPED),&nbsp;0&nbsp;);&nbsp; <br>　&nbsp;FLinks.pOverlaps[Index]^. <br>&nbsp;&nbsp;&nbsp;hEvent&nbsp;:=&nbsp;FLinks.Events[Index];&nbsp; <br>　&nbsp;FillChar(&nbsp;FLinks.pBufs[Index]^.buf^,&nbsp; <br>&nbsp;&nbsp;&nbsp;BUFFER_SIZE,&nbsp;0&nbsp;);&nbsp; <br><br>　&nbsp;//递一个接收数据请求&nbsp; <br>　&nbsp;WSARecv(&nbsp;FLinks.Sockets[Index],&nbsp;FLinks.pBufs[Index],&nbsp;1, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FLinks.pdwRecvd[Index]^,&nbsp;FLinks.pdwFlags[Index]^,&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FLinks.pOverlaps[Index],&nbsp;nil&nbsp;);&nbsp; <br>end;&nbsp; <br>end; <br>&nbsp; <br><br><br>五：Overlapped&nbsp;I/O&nbsp;完成例程模型&nbsp; <br><br>老陈接收到新的信件后，一般的程序是：打开信封----掏出信纸----阅读信件----回复信件......为了进一步减轻用户负担，微软又开发了一种新的技术：用户只要告诉微软对信件的操作步骤，微软信箱将按照这些步骤去处理信件，不再需要用户亲自拆信/阅读/回复了！老陈终于过上了小资生活！&nbsp; <br><br>Overlapped&nbsp;I/O&nbsp;完成例程要求用户提供一个回调函数，发生新的网络事件的时候系统将执行这个函数：&nbsp; <br><br>procedure&nbsp;WorkerRoutine(&nbsp;const&nbsp;dwError,&nbsp;cbTransferred&nbsp;:&nbsp;DWORD;&nbsp; <br>const&nbsp; <br>lpOverlapped&nbsp;:&nbsp;LPWSAOVERLAPPED;&nbsp;const&nbsp;dwFlags&nbsp;:&nbsp;DWORD&nbsp;);&nbsp;stdcall; <br>&nbsp; <br><br><br>然后告诉系统用WorkerRoutine函数处理接收到的数据：&nbsp; <br><br>WSARecv(&nbsp;m_socket,&nbsp;@FBuf,&nbsp;1,&nbsp;dwTemp,&nbsp;dwFlag,&nbsp;@m_overlap,&nbsp;WorkerRoutine&nbsp;);&nbsp; <br>　　&nbsp;然后......没有什么然后了，系统什么都给你做了！微软真实体贴！&nbsp; <br><br>while&nbsp;(&nbsp;not&nbsp;Terminated&nbsp;)&nbsp;do//这就是一个Recv/Send线程要做的事情......什么都不用做啊！！！&nbsp; <br>begin&nbsp; <br>　&nbsp;if&nbsp;SleepEx(&nbsp;RECV_TIME_OUT,&nbsp;True&nbsp;)&nbsp;=&nbsp;WAIT_IO_COMPLETION&nbsp;then&nbsp;//&nbsp; <br>　&nbsp;begin&nbsp; <br>　　&nbsp;;&nbsp; <br>　&nbsp;end&nbsp;else&nbsp; <br>　&nbsp;begin&nbsp; <br>　　&nbsp;continue;&nbsp; <br>　&nbsp;end;&nbsp; <br>end; <br>&nbsp; <br><br><br>六：IOCP模型&nbsp; <br><br>微软信箱似乎很完美，老陈也很满意。但是在一些大公司情况却完全不同！这些大公司有数以万计的信箱，每秒钟都有数以百计的信件需要处理，以至于微软信箱经常因超负荷运转而崩溃！需要重新启动！微软不得不使出杀手锏......&nbsp; <br><br>微软给每个大公司派了一名名叫&#8220;Completion&nbsp;Port&#8221;的超级机器人，让这个机器人去处理那些信件！&nbsp; <br><br>&#8220;Windows&nbsp;NT小组注意到这些应用程序的性能没有预料的那么高。特别的，处理很多同时的客户请求意味着很多线程并发地运行在系统中。因为所有这些线程都是可运行的[没有被挂起和等待发生什么事]，Microsoft意识到NT内核花费了太多的时间来转换运行线程的上下文[Context]，线程就没有得到很多CPU时间来做它们的工作。大家可能也都感觉到并行模型的瓶颈在于它为每一个客户请求都创建了一个新线程。创建线程比起创建进程开销要小，但也远不是没有开销的。我们不妨设想一下：如果事先开好N个线程，让它们在那hold[堵塞]，然后可以将所有用户的请求都投递到一个消息队列中去。然后那N个线程逐一从消息队列中去取出消息并加以处理。就可以避免针对每一个用户请求都开线程。不仅减少了线程的资源，也提高了线程的利用率。理论上很不错，你想我等泛泛之辈都能想出来的问题，Microsoft又怎会没有考虑到呢?&#8221;-----摘自nonocast的《理解I/O&nbsp;Completion&nbsp;Port》&nbsp; <br><br>先看一下IOCP模型的实现：&nbsp; <br><br>//创建一个完成端口&nbsp; <br>FCompletPort&nbsp;:=&nbsp;CreateIoCompletionPort(&nbsp;INVALID_HANDLE_VALUE,&nbsp;0,0,0&nbsp;);&nbsp; <br><br>//接受远程连接，并把这个连接的socket句柄绑定到刚才创建的IOCP上&nbsp; <br>AConnect&nbsp;:=&nbsp;accept(&nbsp;FListenSock,&nbsp;addr,&nbsp;len);&nbsp; <br>CreateIoCompletionPort(&nbsp;AConnect,&nbsp;FCompletPort,&nbsp;nil,&nbsp;0&nbsp;);&nbsp; <br><br>//创建CPU数*2&nbsp;+&nbsp;2个线程&nbsp; <br>for&nbsp;i:=1&nbsp;to&nbsp;si.dwNumberOfProcessors*2+2&nbsp;do&nbsp; <br>begin&nbsp; <br>　&nbsp;AThread&nbsp;:=&nbsp;TRecvSendThread.Create(&nbsp;false&nbsp;);&nbsp; <br>　&nbsp;AThread.CompletPort&nbsp;:=&nbsp;FCompletPort;//告诉这个线程，你要去这个IOCP去访问数据&nbsp; <br>end; <br>&nbsp; <br><br><br>就这么简单，我们要做的就是建立一个IOCP，把远程连接的socket句柄绑定到刚才创建的IOCP上，最后创建n个线程，并告诉这n个线程到这个IOCP上去访问数据就可以了。&nbsp; <br><br>再看一下TRecvSendThread线程都干些什么：&nbsp; <br><br>procedure&nbsp;TRecvSendThread.Execute;&nbsp; <br>var&nbsp; <br>　&nbsp;......&nbsp; <br>begin&nbsp; <br>　&nbsp;while&nbsp;(not&nbsp;self.Terminated)&nbsp;do&nbsp; <br>　&nbsp;begin&nbsp; <br>　　&nbsp;//查询IOCP状态（数据读写操作是否完成）&nbsp; <br>　　&nbsp;GetQueuedCompletionStatus(&nbsp;CompletPort,&nbsp;BytesTransd,&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;CompletKey,&nbsp;POVERLAPPED(pPerIoDat),&nbsp;TIME_OUT&nbsp;);&nbsp; <br><br>　　&nbsp;if&nbsp;BytesTransd&nbsp;&lt;&gt;&nbsp;0&nbsp;then&nbsp; <br>　　　&nbsp;....;//数据读写操作完成&nbsp; <br>　　&nbsp; <br>　　　&nbsp;//再投递一个读数据请求&nbsp; <br>　　　&nbsp;WSARecv(&nbsp;CompletKey,&nbsp;@(pPerIoDat^.BufData),&nbsp;1,&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;BytesRecv,&nbsp;Flags,&nbsp;@(pPerIoDat^.Overlap),&nbsp;nil&nbsp;);&nbsp; <br>　　&nbsp;end;&nbsp; <br>end; <br>&nbsp; <br><br><br>读写线程只是简单地检查IOCP是否完成了我们投递的读写操作，如果完成了则再投递一个新的读写请求。&nbsp; <br><br>应该注意到，我们创建的所有TRecvSendThread都在访问同一个IOCP（因为我们只创建了一个IOCP），并且我们没有使用临界区！难道不会产生冲突吗？不用考虑同步问题吗？&nbsp; <br><br>这正是IOCP的奥妙所在。IOCP不是一个普通的对象，不需要考虑线程安全问题。它会自动调配访问它的线程：如果某个socket上有一个线程A正在访问，那么线程B的访问请求会被分配到另外一个socket。这一切都是由系统自动调配的，我们无需过问。
<img src ="http://www.cppblog.com/mmdengwo/aggbug/144255.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/mmdengwo/" target="_blank">沛沛</a> 2011-04-14 22:33 <a href="http://www.cppblog.com/mmdengwo/archive/2011/04/14/144255.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>