Socket C++ TCP阻塞 非阻塞总结
更新时间:2024-05-06 11:48:01 阅读量: 综合文库 文档下载
- socket推荐度:
- 相关推荐
0、可运行实例及基本知识
1、如何设置socket函数的非阻塞调用? 2、深入 CSocket 编程之阻塞和非阻塞模式 3、SOCKET类的设计和实现
服务器
#include \#include
#pragma comment(lib, \#define SERVPORT 7861 /*服务器监听端口号*/ #define MAXDATASIZE 100 #define BACKLOG 10 using namespace std;
std::vector
DWORD WINAPI qtPingServerThreadFunc(LPVOID lpThreadParameter); int _tmain(int argc, _TCHAR* argv[]) {
}
if (LOBYTE(wsaData.wVersion)!= 2 || HIBYTE(wsaData.wVersion) != 0) SOCKET sockfd; /*sock_fd:监听socket;client_fd:数据传输socket */
struct sockaddr_in my_addr; /* 本机地址信息*/ struct sockaddr_in remote_addr; /* 客户端地址信息*/ char szMsg[]=\; WORD wVersionRequested; WSADATA wsaData; int err;
wVersionRequested = MAKEWORD(2,0);
err = WSAStartup(wVersionRequested,&wsaData); if (0 != err) {
cout<<\; return 0;
{ }
sockfd = socket(AF_INET,SOCK_STREAM,0); if (INVALID_SOCKET == sockfd) { }
/*sockfd = socket(AF_INET, SOCK_STREAM, 0);*/ if (sockfd == -1) { }
my_addr.sin_family=AF_INET; my_addr.sin_port=htons(SERVPORT); my_addr.sin_addr.s_addr = INADDR_ANY; memset( &(my_addr.sin_zero),0,8);
if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1) { }
int n = 0; while(1) {
if (listen(sockfd, BACKLOG) != -1) {
int sin_size = sizeof(struct sockaddr_in);
SOCKET nSocket = accept(sockfd, (struct sockaddr *) &remote_addr, &sin_size); std::vector
client_fd.push_back(nSocket);
printf( \a connection from %s\\n\, inet_ntoa(remote_addr.sin_addr)); DWORD dwPingThreadID;
HANDLE hPingHandle = CreateThread(0, 0, qtPingServerThreadFunc, 0, 0,
perror( \出错!\); //exit(1);
perror( \创建出错!\); exit(1);
cout<<\; return 0; WSACleanup(); return 0;
std::find(client_fd.begin(),itr_end,nSocket);
&dwPingThreadID);
}
DWORD WINAPI qtPingServerThreadFunc(LPVOID lpThreadParameter) { }
while (1) { }
return 0x1001;
for (int n = 0;n < client_fd.size(); n++) { }
int recvbytes;
if ((recvbytes=recv(client_fd[n], buf, MAXDATASIZE, 0)) !=-1) { }
buf[recvbytes] = '\\0';
printf( \,n,buf); send(client_fd[n], buf, sizeof(buf), 0);
}
for (int n = 0;n < client_fd.size(); n++) { }
client_fd.clear();
closesocket(client_fd[n]); }
}
客户端:
#include
#pragma comment(lib, %using namespace std;
#define SERVPORT 7861 /*服务器监听端口号*/ #define DEST_IP \#define MAXDATASIZE 100;
int _tmain(int argc, _TCHAR* argv[]) {
int sockfd, recvbytes; char buf[MAXDATASIZE]; struct hostent *host;
struct sockaddr_in serv_addr;
}
if (LOBYTE(wsaData.wVersion)!= 2 || HIBYTE(wsaData.wVersion) != 0) { }
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ }
serv_addr.sin_family=AF_INET; serv_addr.sin_port=htons(SERVPORT);
serv_addr.sin_addr.s_addr = inet_addr(DEST_IP); memset( &(serv_addr.sin_zero),0,8);
if (connect(sockfd, (struct sockaddr *) &serv_addr, \\ }
char szMsg[] = \; int nlen = sizeof(serv_addr); int uIndex = 0; while (1) {
Sleep(1000);
if (send(sockfd, \, 23, 0) == -1) { } else {
cout< sizeof(struct sockaddr)) == -1) { perror(\出错!\); //exit(1); perror(\创建出错!\); exit(1); WSACleanup(); return 0; WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(2,0); err = WSAStartup(wVersionRequested,&wsaData); if (0 != err) { cout<<\; return 0; struct sockaddr_in dest_addr; /* 目的地址*/ /* serv_addr.sin_addr = *((struct in_addr *)host->h_addr);*/ } return 0; } closesocket(sockfd); } if ((recvbytes=recv(sockfd, buf, MAXDATASIZE, 0)) !=-1) { } buf[recvbytes] = '\\0'; printf( \,buf); cout< 一.WinSock基本知识 这里不打算系统地介绍socket或者WinSock的知识。首先介绍WinSock API函数,讲解阻塞/非阻塞的概念;然后介绍socket的使用。 1.WinSock API Socket接口是网络编程(通常是TCP/IP协议,也可以是其他协议)的API。最早的Socket接口是Berkeley接口,在Unxi操作系统中实现。WinSock也是一个基于Socket模型的API,在Microsoft Windows操作系统类中使用。它在Berkeley接口函数的基础之上,还增加了基于消息驱动机制的Windows扩展函数。Winscok1.1只支持TCP/IP网络,WinSock2.0增加了对更多协议的支持。这里,讨论TCP/IP网络上的API。 Socket接口包括三类函数: 第一类是WinSock API包含的Berkeley socket函数。这类函数分两部分。第一部分是用于网络I/O的函数,如 accept、Closesocket、connect、recv、recvfrom、Select、Send、Sendto 另一部分是不涉及网络I/O、在本地端完成的函数,如 bind、getpeername、getsockname、getsocketopt、htonl、htons、inet_addr、inet_nton ioctlsocket、listen、ntohl、ntohs、setsocketopt、shutdow、socket等 第二类是检索有关域名、通信服务和协议等Internet信息的数据库函数,如 gethostbyaddr、gethostbyname、gethostname、getprotolbyname getprotolbynumber、getserverbyname、getservbyport。 第三类是Berkekley socket例程的Windows专用的扩展函数,如gethostbyname对应的WSAAsynGetHostByName(其他数据库函数除了gethostname都有异步版本),select对应的WSAAsynSelect,判断是否阻塞的函数WSAIsBlocking,得到上一次Windsock API错误信息的WSAGetLastError,等等。 从另外一个角度,这些函数又可以分为两类,一是阻塞函数,一是非阻塞函数。所谓阻塞函数,是指其完成指定的任务之前不允许程序调用另一个函数,在Windows下还会阻塞本线程消息的发送。所谓非阻塞函数,是指操作启动之后,如果可以立即得到结果就返回结果,否则返回表示结果需要等待的错误信息,不等待任务完成函数就返回。 首先,异步函数是非阻塞函数; 其次,获取远地信息的数据库函数是阻塞函数(因此,WinSock提供了其异步版本); 在Berkeley socket函数部分中,不涉及网络I/O、本地端工作的函数是非阻塞函数; 在Berkeley socket函数部分中,网络I/O的函数是可阻塞函数,也就是它们可以阻塞执行,也可以不阻塞执行。这些函数都使用了一个socket,如果它们使用的socket是阻塞的,则这些函数是阻塞函数;如果它们使用的socket是非阻塞的,则这些函数是非阻塞函数。 创建一个socket时,可以指定它是否阻塞。在缺省情况下,Berkerley的Socket函数和WinSock都创建“阻塞”的socket。阻塞socket通过使用select函数或者WSAAsynSelect函数在指定操作下变成非阻塞的。WSAAsyncSelect函数原型如下。 int WSAAsyncSelect( SOCKET s, HWND hWnd, u_int wMsg, long lEvent ); 其中,参数1指定了要操作的socket句柄;参数2指定了一个窗口句柄;参数3指定了一个消息,参数4指定了网络事件,可以是多个事件的组合,如: FD_READ 准备读 FD_WRITE 准备写 FD_OOB 带外数据到达 FD_ACCEPT 收到连接 FD_CONNECT 完成连接 FD_CLOSE 关闭socket。 用OR操作组合这些事件值,如FD_READ|FD_WRITE WSAAsyncSelect函数表示对socket s监测lEvent指定的网络事件,如果有事件发生,则给窗口hWnd发送消息wMsg。 假定应用程序的一个socket s指定了监测FD_READ事件,则在FD_READ事件上变成非阻塞的。当read函数被调用时,不管是否读到数据都马上返回,如果返回一个错误信息表示还在等待,则在等待的数据到达后,消息wMsg发送给窗口hWnd,应用程序处理该消息读取网络数据。 对于异步函数的调用,以类似的过程最终得到结果数据。以gethostbyname的异步版本的使用为例进行说明。该函数原型如下: HANDLE WSAAsyncGetHostByName( HWND hWnd, u_int wMsg, const char FAR *name, char FAR *buf, int buflen ); 在调用WSAAsyncGetHostByName启动操作时,不仅指定主机名字name,还指定了一个窗口句柄hWnd,一个消息ID wMsg,一个缓冲区及其长度。如果不能立即得到主机地址,则返回一个错误信息表示还在等待。当要的数据到达时,WinSock DLL给窗口hWnd发送消息wMsg告知得到了主机地址,窗口过程从指定的缓冲区buf得到主机地址。 使用异步函数或者非阻塞的socket,主要是为了不阻塞本线程的执行。在多进程或者多线程的情况下,可以使用两个线程通过同步手段来完成异步函数或者非阻塞函数的功能。 2.Socket的使用 WinSock以DLL的形式提供,在调用任何WinSock API之前,必须调用函数WSAStartup进行初始化,最后,调用函数WSACleanUp作清理工作。 MFC使用函数AfxSocketInit包装了函数WSAStartup,在WinSock应用程序的初始化函数IninInstance中调用AfxSocketInit进行初始化。程序不必调用WSACleanUp。 Socket是网络通信过程中端点的抽象表示。Socket在实现中以句柄的形式被创建,包含了进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。 要使用socket,首先必须创建一个socket;然后,按要求配置socket;接着,按要求通过socket接收和发送数据;最后,程序关闭此socket。 为了创建socket,使用socket函数得到一个socket句柄: socket_handle = socket(protocol_family. Socket_type, protocol); 其中:protocol_family指定socket使用的协议,取值PF_INET,表示Internet(TCP/IP)协议族;Socket_type指socket面向连接或者使用数据报;第三个参数表示使用TCP或者UDP协议。 当一个socket被创建时,WinSock将为一个内部结构分配内存,在此结构中保存此socket的信息,到此,socket连接使用的协议已经确定。 创建了socket之后,配置socket: 对于面向连接的客户,WinSock自动保存本地IP地址和选择协议端口,但是必须使用connect函数配置远地IP地址和远地协议端口: result = connect(socket_handle, remote_socket_address, address_length) remote_socket_address是一个指向特定socket结构的指针,该地址结构为socket保存了地址族、协议端口、网络主机地址。 面向连接的服务器则使用bind指定本地信息,使用listen和accept获取远地信息。 使用数据报的客户或者服务器使用bind给socket指定本地信息,在发送或者接收数据时指定远地信息。 bind给socket指定一个本地IP地址和协议端口,如下: result = bind( socket_hndle, local_socket_address, address_length) 参数类型同connect。 函数listen监听bind指定的端口,如果有远地客户请求连接,使用accept接收请求,创建一个新的socket,并保存信息。 socket_new = accept(socket_listen, socket_address, address_length) 在socket配置好之后,使用socket发送或者接收数据: 面向连接的socket使用send发送数据,recv接收数据; 使用数据报的socket使用sendto发送数据,recvfrom接收数据。 1.MFC对WinSockt API的封装 MFC提供了两个类CAsyncSocket和CSocket来封装WinSock API,这给程序员提供了一个更简单的 网络编程接口。 CAsyncSocket在较低层次上封装了WinSock API,缺省情况下,使用该类创建的socket是非阻塞 的socket,所有操作都会立即返回,如果没有得到结果,返回WSAEWOULDBLOCK,表示是一个阻 塞操作。 CSocket建立在CAsyncSocket的基础上,是CAsyncSocket的派生类。也就是缺省情况下使用该类 创建的socket是非阻塞的socket,但是CSocket的网络I/O是阻塞的,它在完成任务之后才返回 。CSocket的阻塞不是建立在“阻塞”socket的基础上,而是在“非阻塞”socket上实现的阻塞 操作,在阻塞期间,CSocket实现了本线程的消息循环,因此,虽然是阻塞操作,但是并不影响 消息循环,即用户仍然可以和程序交互。 1.CAsyncSocket CAsyncSocket封装了低层的WinSock API,其成员变量m_hSocket保存其对应的socket句柄。 使用CAsyncSocket的方法如下: 首先,在堆或者栈中构造一个CAsyncSocket对象,例如: CAsyncSocket sock;或者 CAsyncSocket *pSock = new CAsyncSocket; 其次,调用Create创建socket,例如: 使用缺省参数创建一个面向连接的socket sock.Create() 指定参数参数创建一个使用数据报的socket,本地端口为30 pSocket.Create(30, SOCK_DGRM); 其三,如果是客户程序,使用Connect连接到远地;如果是服务程序,使用Listen监听远地 的连接请求。 其四,使用成员函数进行网络I/O。 最后,销毁CAsyncSocket,析构函数调用Close成员函数关闭socket。 下面,分析CAsyncSocket的几个函数,从中可以看到它是如何封装低层的WinSock API,简 化有关操作的;还可以看到它是如何实现非阻塞的socket和非阻塞操作。 2.socket对象的创建和捆绑 (1)Create函数 首先,讨论Create函数,分析socket句柄如何被创建并和CAsyncSocket对象关联。Create的 实现如下: BOOL CAsyncSocket::Create(UINT nSocketPort, int nSocketType, long lEvent, LPCTSTR lpszSocketAddress) { if (Socket(nSocketType, lEvent)) { if (Bind(nSocketPort,lpszSocketAddress)) return TRUE; int nResult = GetLastError(); Close(); WSASetLastError(nResult); } return FALSE; } 其中: 参数1表示本socket的端口,缺省是0,如果要创建数据报的socket,则必须指定一个端口号 。 参数2表示本socket的类型,缺省是SOCK_STREAM,表示面向连接类型。 参数3是屏蔽位,表示希望对本socket监测的事件,缺省是FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE。 参数4表示本socket的IP地址字符串,缺省是NULL。 Create调用Socket函数创建一个socket,并把它捆绑在this所指对象上,监测指定的网络事 件。参数2和3被传递给Socket函数,如果希望创建数据报的socket,不要使用缺省参数,指 定参数2是SOCK_DGRM。 如果上一步骤成功,则调用bind给新的socket分配端口和IP地址。 (2)Socket函数 接着,分析Socket函数,其实现如下: BOOL CAsyncSocket::Socket(int nSocketType, long lEvent, int nProtocolType, int nAddressFormat) { ASSERT(m_hSocket == INVALID_SOCKET); m_hSocket = socket(nAddressFormat,nSocketType,nProtocolType); if (m_hSocket != INVALID_SOCKET) { CAsyncSocket::AttachHandle(m_hSocket, this, FALSE); return AsyncSelect(lEvent); } return FALSE; } 其中: 参数1表示Socket类型,缺省值是SOCK_STREAM。 参数2表示希望监测的网络事件,缺省值同Create,指定了全部事件。 参数3表示使用的协议,缺省是0。实际上,SOCK_STREAM类型的socket使用TCP协议, SOCK_DGRM的socket则使用UDP协议。 参数4表示地址族(地址格式),缺省值是PF_INET(等同于AF_INET)。对于TCP/IP来说, 协议族和地址族是同值的。 在socket没有被创建之前,成员变量m_hSocket是一个无效的socket句柄。Socket函数把协 议族、socket类型、使用的协议等信息传递给WinSock API函数socket,创建一个socket。 如果创建成功,则把它捆绑在this所指对象。 (3)捆绑(Attatch) 捆绑过程类似于其他Windows对象,将在模块线程状态的WinSock映射中添加一对新的映射: this所指对象和新创建的socket对象的映射。 另外,如果本模块线程状态的“socket窗口”没有创建,则创建一个,该窗口 在异步操作时 用来接收WinSock的通知消息,窗口句柄保存到模块线程状态的m_hSocketWindow变量中。函 数AsyncSelect将指定该窗口为网络事件消息的接收窗口。 函数AttachHandle的实现在此不列举了。 (4)指定要监测的网络事件 在捆绑完成之后,调用AsyncSelect指定新创建的socket将监测的网络事件。AsyncSelect实 现如下: BOOL CAsyncSocket::AsyncSelect(long lEvent) { ASSERT(m_hSocket != INVALID_SOCKET); _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState; ASSERT(pState->m_hSocketWindow != NULL); return WSAAsyncSelect(m_hSocket, pState->m_hSocketWindow, WM_SOCKET_NOTIFY, lEvent) != SOCKET_ERROR; } 函数参数lEvent表示希望监视的网络事件。 _ afxSockThreadState得到的是当前的模块线程状态,m_ hSocketWindow是本模块在当前线 程的“socket窗口”,指定监视m_hSocket的网络事件,如指定事件发生,给窗口 m_hSocketWindow发送WM_SOCKET_NOTIFY消息。 被指定的网络事件对应的网络I/O将是异步操作,是非阻塞操作。例如:指定FR_READ导致 Receive是一个异步操作,如果不能立即读到数据,则返回一个错误WSAEWOULDBLOCK。在数 据到达之后,WinSock通知窗口m_hSocketWindow,导致OnReceive被调用。 指定FR_WRITE导致Send是一个异步操作,即使数据没有送出也返回一个错误WSAEWOULDBLOCK 。在数据可以发送之后,WinSock通知窗口m_hSocketWindow,导致OnSend被调用。 指定FR_CONNECT导致Connect是一个异步操作,还没有连接上就返回错误信息 WSAEWOULDBLOCK,在连接完成之后,WinSock通知窗口m_hSocketWindow,导致OnConnect被 调用。 对于其他网络事件,就不一一解释了。 所以,使用CAsyncSocket时,如果使用Create缺省创建socket,则所有网络I/O都是异步操 作,进行有关网络I/O时则必须覆盖以下的相关函数: OnAccept、OnClose、OnConnect、OnOutOfBandData、OnReceive、OnSend。 (5)Bind函数 经过上述过程,socket创建完毕,下面,调用Bind函数给m_hSocket指定本地端口和IP地址 。Bind的实现如下: BOOL CAsyncSocket::Bind(UINT nSocketPort, LPCTSTR lpszSocketAddress) { USES_CONVERSION; //使用WinSock的地址结构构造地址信息 SOCKADDR_IN sockAddr; memset(&sockAddr,0,sizeof(sockAddr)); //得到地址参数的值 LPSTR lpszAscii = T2A((LPTSTR)lpszSocketAddress); //指定是Internet地址类型 sockAddr.sin_family = AF_INET; if (lpszAscii == NULL) //没有指定地址,则自动得到一个本地IP地址 //把32比特的数据从主机字节序转换成网络字节序 sockAddr.sin_addr.s_addr = htonl(INADDR_ANY); else { //得到地址 DWORD lResult = inet_addr(lpszAscii); if (lResult == INADDR_NONE) { WSASetLastError(WSAEINVAL); return FALSE; } sockAddr.sin_addr.s_addr = lResult; } //如果端口为0,则WinSock分配一个端口(1024—5000) //把16比特的数据从主机字节序转换成网络字节序 sockAddr.sin_port = htons((u_short)nSocketPort); //Bind调用WinSock API函数bind return Bind((SOCKADDR*)&sockAddr, sizeof(sockAddr)); } 其中:函数参数1指定了端口;参数2指定了一个包含本地地址的字符串,缺省是NULL。 函数Bind首先使用结构SOCKADDR_IN构造地址信息。该结构的域sin_family表示地址格式 (TCP/IP同协议族),赋值为AF_INET(Internet地址格式);域 sin_port表示端口,如果参 数1为0,则WinSock分配一个端口给它,范围在1024和5000之间;域sin_addr是表示地址信 息,它是一个联合体,其中s_addr表示如下形式的字符串,“28.56.22.8”。如果参数没有 指定地址,则WinSock自动地得到本地IP地址(如果有几个网卡,则使用其中一个的地址) 。 (6)总结Create的过程 首先,调用socket函数创建一个socket;然后把创建的socket对象映射到CAsyncSocket对象 (捆绑在一起),指定本socket要通知的网络事件,并创建一个“socket窗口”来接收网络 事件消息,最后,指定socket的本地信息。 下一步,是使用成员函数Connect连接远地主机,配置socket的远地信息。函数Connect类似 于Bind,把指定的远地地址转换成SOCKADDR_IN对象表示的地址信息(包括网络字节序的转 换),然后调用WinSock函数Connect连接远地主机,配置socket的远地端口和远地IP地址。 3.异步网络事件的处理 当网络事件发生时,“socket窗口”接收WM_SOCKET_NOTIFY消息,消息处理函数 OnSocketNotify被调用。“socket窗口”的定义和消息处理是MFC实现的,这里不作详细的 讨论。 OnSocketNotify回调CAsyncSocket的成员函数DoCallBack,DoCallBack调用事件处理函数, 如OnRead、OnWrite等。摘录DoCallBack的一段代码如下: switch (WSAGETSELECTEVENT(lParam)) { case FD_READ: { DWORD nBytes; //得到可以一次读取的字节数 pSocket->IOCtl(FIONREAD, &nBytes); if (nBytes != 0) pSocket->OnReceive(nErrorCode); } break; case FD_WRITE: pSocket->OnSend(nErrorCode); break; case FD_OOB: pSocket->OnOutOfBandData(nErrorCode); break; case FD_ACCEPT: pSocket->OnAccept(nErrorCode); break; case FD_CONNECT: pSocket->OnConnect(nErrorCode); break; case FD_CLOSE: pSocket->OnClose(nErrorCode); break; lParam是WM_SOCKET_NOFITY的消息参数,OnSocketNotify传递给函数DoCallBack,表示通知 事件。 函数IOCtl是CAsyncSocket的成员函数,用来对socket的I/O进行控制。这里的使用表示本次 调用Receive函数至多可以读nBytes个字节。 从上面的讨论可以看出,从创建socket到网络I/O,CAsyncSocket直接封装了低层的WinSock API,简化了WinSock编程,实现了一个异步操作的界面。如果希望某个操作是阻塞操作,则 在调用Create时不要指定该操作对应的网 络事件。例如,希望Connect和Send是阻塞操作, 在任务完成之后才返回,则可以使用如下的语句: pSocket->Create(0, SOCK_STREAM, FR_WRITE|FR_OOB|FR_ACCEPT|FR_CLOSE); 这样,在Connect和Send时,如果是用户界面线程的话,可能阻塞线程消息循环。所以,最 好在工作者线程中使用阻塞操作。 2.CSocket 如果希望在用户界面线程中使用阻塞socket,则可以使用CSocket。它在非阻塞socket基础之上实 现了阻塞操作,在阻塞期间实现了消息循环。 对于CSocket,处理网络事件通知的函数OnAccept、OnClose、OnReceive仍然可以使用, OnConnect、OnSend在CSocket中永远不会被调用,另外OnOutOfBandData在CSocket中不鼓励使用 。 CSocket对象在调用Connect、Send、Accept、Close、Receive等成员函数后,这些函数在完成任 务之后(连接被建立、数据被发送、连接请求被接收、socket被关闭、数据被读取)之后才会返 回。因此,Connect和Send不会导致OnConnect和OnSend被调用。如果覆盖虚拟函数OnReceive、 OnAccept、OnClose,不主动调用Receive、Accept、Close,则在网络事件到达之后导致对应的虚 拟函数被调用,虚拟函数的实现应该调用Receive、Accept、Close来完成操作。下面,就一个函 数Receive来考察CSocket如何实现阻塞操作和消息循环的。 int CSocket::Receive(void* lpBuf, int nBufLen, int nFlags) { //m_pbBlocking是CSocket的成员变量,用来标识当前是否正在进行 //阻塞操作。但不能同时进行两个阻塞操作。 if (m_pbBlocking != NULL) { WSASetLastError(WSAEINPROGRESS); return FALSE; } //完成数据读取 int nResult; while ((nResult = CAsyncSocket::Receive(lpBuf, nBufLen, nFlags)) == SOCKET_ERROR) { if (GetLastError() == WSAEWOULDBLOCK) { //进入消息循环,等待网络事件FD_READ if (!PumpMessages(FD_READ)) return SOCKET_ERROR; } else return SOCKET_ERROR; } return nResult; } 其中: 参数1指定一个缓冲区保存读取的数据;参数2指定缓冲区的大小;参数3取值MSG_PEEK(数据拷贝 到缓冲区,但不从输入队列移走),或者MSG_OOB(处理带外数据),或者MSG_PEEK|MSG_OOB。 Receive函数首先判断当前CSocket对象是否正在处理一个阻塞操作,如果是,则返回错误 WSAEINPROGRESS;否则,开始数据读取的处理。 读取数据时,如果基类CAsyncSocket的Receive读取到了数据,则返回;否则,如 果返回一个错误 ,而且错误号是WSAEWOULDBLOCK,则表示操作阻塞,于是调用PumpMessage进入消息循环等待数据 到达(网络事件FD_READ发生)。数据到达之后退出消息循环,再次调用CAsyncSocket的Receive 读取数据,直到没有数据可读为止。 PumpMessages是CSocket的成员函数,它完成以下工作: (1)设置m_pbBlocking,表示进入阻塞操作。 (2)进行消息循环,如果有以下事件发生则退出消息循环:收到指定定时器的定时事件消息 WM_TIMER,退出循环,返回TRUE;收到发送给本socket的消息WM_SOCKET_NOTIFY,网络事件 FD_CLOSE或者等待的网络事件发生,退出循环,返回TRUE;发送错误或者收到WM_QUIT消息,退出 循环,返回FALSE; (3)在消息循环中,把WM_SOCKET_DEAD消息和发送给其他socket的通知消息WM_SOCKET_NOFITY放 进模块线程状态的通知消息列表m_listSocketNotifications,在阻塞操作完成之后处理;对其他 消息,则把它们送给目的窗口的窗口过程处理。 3.CSocketFile MFC还提供了一个网络编程模式,可以充分利用CSocket的特性。该模式的基础是CSocketFile类。 使用方法如下: 首先,构造一个CSocket对象;调用Create函数创建一个socket对象(SOCK_STREAM类型)。 接着,如果是客户程序,调用Connect连接到远地主机;如果是服务器程序,先调用Listen监听 socket端口,收到连接请求后调用Accept接收请求。 然后,创建一个和CSocket对象关联的CSocketFile对象,创建一个和CSocketFile对象关联的 CArchive对象,指定CArchive对象是用于读或者写。如果既要读又要写,则创建两个CArchive对 象。 创建工作完成之后,使用CArchive对象在客户和服务器之间传送数据 使用完毕,销毁CArchive对象、CSocketFile对象、CSocket对象。 从前面的章节可以知道,CArchive可以以一个CFile对象为基础,通过<<和>>操作符完成对文件的 二进制 流的操作。所以可以从CFile派生一个类,实现CFile的操作界面(Read和Write)。由于CSocket 提供了阻 塞操作,所以完全可以像读写文件一样读写socket数据。 当 Client 端 socket 与 Server 端 socket 相互通信时,两端均会触发 socket 事件。这里仅简要说明两个 socket 事件: FD_CONNECT: 连接事件 , 通常 Client 端 socket 调用 socket API 函数 Connect 时所触发,这个事件发生在 Client 端。 ? FD_ACCEPT :正在引入的连接事件,通常 Server 端 socket 正在接收来自 Client 端 socket 连接时触发,这个事件发生在 Server 端。 ? 网络传输服务进程 将 socket 事件 保存至 socket 的事件队列中。此外, 网络传输服务进程 还会向 socket window 发送消息 WM_SOCKET_NOTIFY , 通知有 socket 事件 产生,见下文对 socket window 的详细说明。 调用 CSocket::Create 函数后,socket 被创建。 socket 创建过程中调用 CAsyncSocket::AttachHandle(SOCKET hSocket, CAsyncSocket* pSocket, BOOL bDead) 。该函数的作用是: 将 socket 实例句柄和 socket 指针添加至 当前模块状态 ( 注 1 )的一个映射表变量 m_pmapSocketHandle 中。 ? 在 AttachHandle 过程中,会 new 一个 CSocketWnd 实例 ( 基于 CWnd 派生 ) ,这里将这个实例称之为 socket window ,进一步理解为它是存放所有 sockets 的消息池 ( window 消息),请仔细查看,这里 socket 后多加了一个 s ,表示创建的多个 socket 将共享一个 消息池 。 ? 当 Client 端 socket 与 Server 端相互通信时 , 此时 网络传输服务进程 向 socket window 发送消息 WM_SOCKET_NOTIFY ,需要说明的是 CSocketWnd 窗口句柄保存在 当前模块状态 的 m_hSocketWindow 变量中。 ? 2、阻塞模式 阻塞模式下 Server 端与 Client 端之间的通信处于同步状态下。在 Server 端直接实例化 CSocket 类,调用 Create 方法创建 socket ,然后调用方法 Listen 开始侦听,最后用一个 while 循环阻塞调用 Accept 函数用于等待来自 Client 端的连接,如果这个 socket 在主线程(主程序)中运行,这将导致主线程的阻塞。因此,需要创建一个新的线程以运行 socket 服务。 调试跟踪至 CSocket::Accept 函数源码: while(!Accept(...)) { // The socket is marked as nonblocking and no connections are present to be accepted. if (GetLastError() == WSAEWOULDBLOCK) PumpMessage(FD_ACCEPT); else return FALSE; } 它不断调用 CAsyncSocket::Accept ( CSocket 派生自 CAsyncSocket 类)判断 Server 端 socket 的事件队列中是否存在正在引入的连接事件 - FD_ACCEPT (见 1 ),换句话说,就是判断是否有来自 Client 端 socket 的连接请求。 如果当前 Server 端 socket 的事件队列中存在正在引入的连接事件, Accept 返回一个非 0 值。否则, Accept 返回 0,此时调用 GetLastError 将返回错误代码 WSAEWOULDBLOCK ,表示队列中无任何连接请求。注意到在循环体内有一句代码: PumpMessage(FD_ACCEPT); PumpMessage 作为一个消息泵使得 socket window 中的消息能够维持在活动状态。实际跟踪进入 PumpMessage 中,发现这个消息泵与 Accept 函数的调用并不相关,它只是使很少的 socket window 消息(典型的是 WM_PAINT 窗口重绘消息)处于活动状态,而绝大部分的 socket window 消息被阻塞,被阻塞的消息中含有 WM_SOCKET_NOTIFY。 很显然,如果没有来自 Client 端 socket 的连接请求, CSocket 就会不断调用 Accept 产生循环阻塞,直到有来自 Client 端 socket 的连接请求而解除阻塞。 阻塞解除后,表示 Server 端 socket 和 Client 端 socket 已成功连接, Server 端与 Client 端彼此相互调用 Send 和 Receive 方法开始通信。 3、非阻塞模式 在非阻塞模式下 利用 socket 事件 的消息机制, Server 端与 Client 端之间的通信处于异步状态下。 通常需要从 CSocket 类派生一个新类,派生新类的目的是重载 socket 事件 的消息函数,然后在 socket 事件 的消息函数中添入合适的代码以完成 Client 端与 Server 端之间的通信,与阻塞模式相比,非阻塞模式无需创建一 个新线程。 这里将讨论当 Server 端 socket 事件 - FD_ACCEPT 被触发后,该事件的处理函数 OnAccept 是如何进一步被触发的。其它事件的处理函数如 OnConnect, OnReceive 等的触发方式与此类似。 在 1 中已提到 Client/Server 端通信时, Server 端 socket 正在接收来自 Client 端 socket 连接请求,这将会触发 FD_ACCEPT 事件,同时 Server 端的 网络传输服务进程 向 Server 端的 socket window (CSocketWnd )发送事件通知消息 WM_SOCKET_NOTIFY , 通知有 FD_ACCEPT 事件产生 , CsocketWnd 在收到事件通知消息后,调用消息处理函数 OnSocketNotify: LRESULT CSocketWnd::OnSocketNotify(WPARAM wParam, LPARAM lParam) { CSocket::AuxQueueAdd(WM_SOCKET_NOTIFY, wParam, lParam); CSocket::ProcessAuxQueue(); return 0L ; } 消息参数 wParam 是 socket 的句柄, lParam 是 socket 事件 。这里稍作解释一下,CSocketWnd 类是作为 CSocket 类的 友元类 ,这意味着它可以访问 CSocket 类中的保护和私有成员函数和变量, AuxQueueAdd 和 ProcessAuxQueue 是 CSocket 类的静态成员函数,如果你对友元不熟悉,请迅速找本有关 C++ 书看一下友元的使用方法吧! ProcessAuxQueue 是实质处理 socket 事件的函数,在该函数中有这样一句代码: CAsyncSocket* pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, TRUE); 其实也就是由 socket 句柄得到发送事件通知消息的 socket 指针 pSocket:从 m_pmapSocketHandle 中查找(见 1 )! 最后, WSAGETSELECTEVENT(lParam) 会取出事件类型,在一个简单的 switch 语句中判断事件类型并调用事件处理函数。在这里,事件类型是 FD_ACCEPT ,当然就调用 pSocket->OnAccept ! 结束语 Server 端 socket 处于阻塞调用模式下,它必须在一个新创建的线程中工作,防止主线程被阻塞。 当有多个 Client 端 socket 与 Server 端 socket 连接及通信时, Server 端采用阻塞模式就显得不适合了,应该采用非阻塞模式 , 利用 socket 事件 的消息机制来接受多个 Client 端 socket 的连接请求并进行通信。 在非阻塞模式下,利用 CSocketWnd 作为所有 sockets 的消息池,是实现 socket 事件 的消息机制的关键技术。文中存在用词不妥和可能存在的技术问题,请大家原谅,也请批评指正,谢谢! 注: 1. 当前模块状态——用于保存当前线程和模块状态的一个结构,可以通过 AfxGetThreadModule() 获得。AFX_MODULE_THREAD_STATE 在 CSocket 重新定义为 _AFX_SOCK_THREAD_STATE 。 2. socket 类型——在 TCP/IP 协议中, Client/Server 网络程序采用 TCP 协议:即 socket 类型为 SOCK_STREAM ,它是可靠的连接方式。在这里不采用 UDP 协议:即 socket 类型为 SOCK_DGRAM ,它是不可靠的连接方式。 3如何设置socket函数的非阻塞调用? windows的socket在创建后,默认是阻塞调用的,也就是说函数recv,recvfrom,send,sendto等函数都是阻塞的;那么我们如何将他们设置成非阻塞调用呢?我们可以通过windows为我们提供的ioctlsocket 函数实现;先给出一个例子: BOOL LoadSocketSystem(void) { WORD wVersionRequested; SOCKADDR_IN addrSrv; BOOL bRet = FALSE; WSADATA wsaData; SOCKET sockClient; int err; int iMode; wVersionRequested = MAKEWORD( 1, 1 ); err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return bRet; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return bRet; } s_socketStatus.m_hsocket = socket(AF_INET,SOCK_STREAM,0); if( INVALID_SOCKET == s_socketStatus.m_hsocket ) { WSACleanup( ); return bRet; } //------------------------- // Set the socket I/O mode: In this case FIONBIO // enables or disables the blocking mode for the // socket based on the numerical value of iMode. // If iMode = 0, blocking is enabled; // If iMode != 0, non-blocking mode is enabled. iMode = 1; ioctlsocket(s_socketStatus.m_hsocket,FIONBIO,(u_long FAR*) &iMode); s_socketStatus.m_isConnected = TRUE; sockClient = s_socketStatus.m_hsocket; addrSrv.sin_addr.S_un.S_addr=inet_addr(\addrSrv.sin_family=AF_INET; addrSrv.sin_port=htons(6000); connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)); return TRUE; } 上面红色表示的就是将刚才创建的socket设置成非阻塞操作。 当然我们这样做是最简单的,但是如果对于一个复杂的系统,系统的其他部分可能已经对该socket调用了另两个windows函数--WSAAsyncSelect or WSAEventSelect ,这个个函数都是自动的将socket设置成非阻塞形式,所有在这样的情况下你调用ioctlsocket 想将其设置回阻塞形式是不会成功的;要想成功,你必须将由于调用上面两个函数对socket系统产生的影响clear掉,然后才能成功调用ioctlsocket 函数;如果clear呢? 如下调用WSAEventSelect函数: rc = WSAEventSelect(s, hEventObject, 0); 如果想详细了解上述几个函数,请参阅MSDN。
正在阅读:
学校家长会实施方案10-11
GEODE地震仪09-30
不要留恋过去的相关文章推荐02-14
基于ansoft的电感解析计算04-18
源城区对外贸易经济合作局办事指南06-22
工程热力学课后习题--华永明版06-12
小学生二年级狂欢万圣节作文06-13
西交2012春季专业英语离线作业及答案06-18
- 多层物业服务方案
- (审判实务)习惯法与少数民族地区民间纠纷解决问题(孙 潋)
- 人教版新课标六年级下册语文全册教案
- 词语打卡
- photoshop实习报告
- 钢结构设计原理综合测试2
- 2014年期末练习题
- 高中数学中的逆向思维解题方法探讨
- 名师原创 全国通用2014-2015学年高二寒假作业 政治(一)Word版
- 北航《建筑结构检测鉴定与加固》在线作业三
- XX县卫生监督所工程建设项目可行性研究报告
- 小学四年级观察作文经典评语
- 浅谈110KV变电站电气一次设计-程泉焱(1)
- 安全员考试题库
- 国家电网公司变电运维管理规定(试行)
- 义务教育课程标准稿征求意见提纲
- 教学秘书面试技巧
- 钢结构工程施工组织设计
- 水利工程概论论文
- 09届九年级数学第四次模拟试卷
- 阻塞
- C++
- 总结
- Socket
- TCP
- 上市公司财务报表粉饰行为及其治~
- 郑州大学远程教育《物理化学》在线测试
- 200T电池极片轧机液压伺服系统设计
- 建筑设备试题库
- 南苑街道翁梅社区多高层公寓C区块项目
- 剩余电流动作保护附件 - 图文
- 第五章习题
- 长春工务段标准化检控、桥梁、车间
- 工程法律风险防控体系 - 图文
- 我国人口老龄化与老年人力资源开发
- 《java程序设计》题库
- 杨浦区20学年度第十学期期末质量调研高五物理
- 计算机系C语言编程50题(含答案)
- 国际金融题库1
- 高中时态,非谓语动词等易错疑难题及其解析
- 医院网络解决方案(模板)
- 2016-2021年中国安全阀行业市场分析及投资可行性研究报告
- (最新版)300MW光伏发电建设项目可行性研究报告 - 图文
- 可编程序控制器形成性考核作业及答案
- 狼人游戏