您的位置:首页 > 编程语言

孙鑫 第十五/十六课之五 基于消息异步套接字编程

2012-12-05 17:19 141 查看
声明
由于接触套接字编程不久,而且异步套接字编程更是刚刚接触,在对照学习完孙鑫老师这节课的视频自己总结的时候有个重大误区,那就是以为孙鑫老师讲的就是近乎全部的异步套接字编程知识,可是没有注意到一个大前提----基于消息的!!! 通过群友和度娘得知:异步套接字有六种I/O模型,my god ! 六种有木有!!! 而基于消息的仅仅是其中一种! 其中一种有木有!!!
孙老师的视频里讲WSASocket函数的时候,最后一个参数dwFlags一句话带过,说以后要讲,还有PPT里说的WSARecvFrom和WSASendto中最后两个参数, 差一点就误导了我! 他应该这样做:如果要讲基于消息的套接字编程干脆就用socket函数而别用WSASocket函数,因为WSASocket函数中后三个参数没有使用,其效果和socket函数完全一样,这样再讲基于消息的就不容易迷糊。

如果WSASocket函数中设置了WSA_FLAG_OVERLAPPED重叠操作标识,那么就可用WSASend, WSASendto, WSARecv, WSARecvFrom, WSAIoctl来进行异步重叠操作,当这些函数完成时通过回调函数通知用户。
重叠操作是实现异步操作的一种方式。

说明
所谓异步,就是指不用等函数体的代码执行完毕再返回,而是函数一经调用马上返回继续往下执行。 我们知道在网络编程中,基于TCP的套接字服务器端的accept函数,在客户端连接请求到来之前,服务器端该accept函数所在的线程会阻塞在此处,直到有连接请求到来才能往下执行,如果阻塞的线程多了,那么OS可能会限制新线程的创建,总之这是一种性能不高的编程方式。 而异步方式下,accept函数一经调用马上返回,不会造成所在线程的阻塞,通过注册网络事件的方式,等到有请求到来的时候去执行相应的消息函数处理连接请求。

相关函数
①WSASocket
SOCKET WSASocket(
int af, //必须为AF_INET
int type, //哪种套接字,SOCK_STREAM 或 SOCK_DGRAM
int protocol, //协议,可为0,自动选择合适协议
LPWSAPROTOCOL_INFO lpProtocolInfo, //WSAPROTOCOL_INFO结构体指针,如果为NULL则按照前三个参数设置,如果不为NULL则忽略前三个参数,按照此参数的设置
GROUP g, //套接组口描述符? 保留,为0即可
DWORD dwFlags //标识
);
函数说明:该函数的第三个参数是一个指向WSAPROTOCOL_INFO结构体的指针,如果为NULL则用前三个参数设置该套接字,如果不为NULL则忽略前三个参数,用此参数设置该套接字。
该函数最后一个参数dwFlags,dwFlags中有一个很重要的标识WSA_FLAG_OVERLAPPED(重叠标识),如果设置了这个标识则该套接字为重叠套接字,可用WSASend, WSASendto, WSARecv, WSARecvFrom, WSAIoctl来进行异步重叠操作。

eg.
SOCKET sock = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, 0); //UDP, 非重叠

②WSAAsyncSelect
int WSAAsyncSelect(
SOCKET s, //套接字
HWND hWnd, //网络事件发送时消息传递给的窗口句柄,继承自CWnd类的子类都有m_hWnd句柄
unsigned int wMsg, //用户自定义消息,其消息处理函数用来对网络事件发生时的处理
long lEvent //可以注册的网络事件
);
函数说明:该函数可以注册网络事件,当网络事件发生时调用响应的消息处理函数进行处理。
当有错误发生时返回SOCKET_ERROR,可用WSAGetLastError()函数获取错误代码。
可以注册的网络事件有:
FD_READ FD_WRITE FD_ACCEPT FD_CONNECT FD_CLOSE FD_OOB FD_QOS FD_GROUP_QOS
FD_ROUTING_INTERFACE_CHANGE FD_ADDRESS_LIST_CHANGE

eg.
if( SOCKET_ERROR == WSAAsyncSelect(sock, m_hWnd, WM_SOCK, FD_READ)) //假设消息WM_SOCK对应的消息处理函数 // 已经存在, 注册网络读事件
{
CString str;
str.Format("%d", WSAGetLastError()); //获得错误代码
MessageBox(str);
}

③WSARecvFrom
int WSARecvFrom(
SOCKET s, // [in]
LPWSABUF lpBuffers, //[in / out] WSABUF类型结构体指针,可以是一个WSABUF结构体数组,如 WSABUF wsaBuf[5];
DWORD dwBufferCount, //[in] 上一个参数中WSABUF结构体的个数,1个或者多个(数组元素个数)
LPDWORD lpNumberOfBytesRecvd, //[out] 本次接收到的字节数
LPDWORD lpFlags, //[in / out]指向标志位的指针
struct sockaddr FAR* lpFrom, //对方地址端口结构体
LPINT lpFromlen, // [int / out] 前一个参数结构体大小,必须初始化
LPWSAOVERLAPPED lpOverlapped, //指向WSAOVERLAPPED结构体的指针,如果WSASocket中没有设置重叠标识则忽略此参数
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //指向接收操作完成时调用的完成例程的指针,也就是操作完成时调用的回调函数地址,如果WSASocket中没有设置重叠标识则忽略此参数
);
eg.
//假设SOCKADDR_IN addrCandidate已存在
WSABUF wsaBuf[5];
DWORD numberOfBytes = 0;
DWORD flags = 0; //为0即可
int len = sizeof(SOCKADDR);
WSARecvFrom(sock, wsaBuf, sizeof(wsaBuf) / sizeof(WSABUF), &numberOfBytes, &flags, (SOCKADDR*)&addrCandidate, &len, NULL, NULL);

④WSASendTo

int WSASendTo(
SOCKET s, // [in]
LPWSABUF lpBuffers, //[in / out] WSABUF类型结构体指针,可以是一个WSABUF结构体数组,如 WSABUF wsaBuf[5];
DWORD dwBufferCount, //[in] 上一个参数中WSABUF结构体的个数,1个或者多个(数组元素个数)
LPDWORD lpNumberOfBytesRecvd, //[out] 本次发送的字节数
DWORD dwFlags, //[in]标志位
const struct sockaddr FAR* lpTo, //发送到的地址端口结构体
int iTolen, // [int] 前一个参数的长度,一般为SIZEOF(SOCKADDR)
LPWSAOVERLAPPED lpOverlapped, //指向WSAOVERLAPPED结构体的指针,如果WSASocket中没有设置重叠标识则忽略此参数
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //指向接收操作完成时调用的完成例程的指针,也就是操作完成时调用的回调函数地址,如果WSASocket中没有设置重叠标识则忽略此参数
);

该函数同WSARecvFrom类似
eg.
//假设SOCKADDR_IN addrCandidate已存在
WSABUF wsaBuf[5];
DWORD numberOfBytes = 0;
DWORD flags = 0; //为0即可
WSASentTo(sock, wsaBuf, sizeof(wsaBuf) / sizeof(WASBUF), &numberOfBytes, flags, (SOCKADDR*)&addrCandidate, sizeof(SOCKADDR), NULL, NULL);

NOTE:
WSARecvFrom和WSASendTo中第五个参数的区别:前者是指针类型,后者是DWORD类型。
如果WSASocket函数中没有设置重叠操作标识,那么函数③④可用recvfrom和sendto代替,效果一样。

下篇文章讲贴一个基于消息的UDP套接字编程简单实例。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: