Torque2D MIT 学习笔记(24) ---- NetWork系统机制(1)
前言
Torque2.0用的是UDP连接方式,UDP和TCP的长短这里不做品论,这里只就事论事,全面分析Torque当前的网络模式.
从代码说明,超详细网络模块注释,方便大家一起学习,今天先放出WinNet的实现代码,后期再说明上层的NetConnect以及NetObject,NetEvent的实现细节和相互关系.
代码
#include "platformWin32/platformWin32.h" #include "platform/platform.h" #include "platform/event.h" #include <winsock.h> #if !defined(USE_IPX) || defined(TORQUE_COMPILER_MINGW) # define NO_IPX_SUPPORT #endif #if !defined(NO_IPX_SUPPORT) # include <wsipx.h> #else typedef void* SOCKADDR_IPX; #endif #include "console/console.h" #include "game/gameInterface.h" #include "io/fileStream.h" // 异步任务保存(WSAAsyncGetHostByName) struct NameLookup { U8 hostEntStruct[MAXGETHOSTSTRUCT]; // 请求主机结果 HANDLE lookupHandle; // 任务句柄 SOCKET socket; // 套接字 U16 port; // 端口 NameLookup *nextLookup; // 链表节点 }; static NameLookup *lookupList = NULL; static Net::Error getLastError(); static S32 defaultPort = 28000; static S32 netPort = 0; static SOCKET ipxSocket = INVALID_SOCKET; static SOCKET udpSocket = INVALID_SOCKET; // 网络连接常量定义 enum WinNetConstants { MaxConnections = 1024, // 最大连接数 }; HWND winsockWindow = NULL; // 网络消息处理 static LRESULT PASCAL WinsockProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { U32 error; U32 bufLen; U32 event; SOCKET socket; Net::Error err; S32 bytesRead; static ConnectedNotifyEvent notifyEvent; static ConnectedReceiveEvent receiveEvent; static ConnectedAcceptEvent acceptEvent; switch(message) { case WM_USER: error = WSAGETSELECTERROR(lParam); event = WSAGETSELECTEVENT(lParam); socket = wParam; switch(event) { case FD_READ: // 准备读 { err = Net::recv(socket, receiveEvent.data, MaxPacketDataSize, &bytesRead); if(err == Net::NoError && bytesRead != 0) { // 发送接收到网络数据消息 receiveEvent.tag = socket; receiveEvent.size = ConnectedReceiveEventHeaderSize + bytesRead; Game->postEvent(receiveEvent); } } break; case FD_CONNECT: // 连接完成 { notifyEvent.tag = socket; if(error) notifyEvent.state = ConnectedNotifyEvent::ConnectFailed; else notifyEvent.state = ConnectedNotifyEvent::Connected; // 发送连接结果 Game->postEvent(notifyEvent); } break; case FD_CLOSE: // 连接已关闭 { // 在断开连接前,再次确认有没有残留的数据要读取 for(;;) { err = Net::recv(socket, receiveEvent.data, MaxPacketDataSize, &bytesRead); if(err != Net::NoError || bytesRead == 0) break; // 发送消息 receiveEvent.tag = socket; receiveEvent.size = ConnectedReceiveEventHeaderSize + bytesRead; Game->postEvent(receiveEvent); } // 发送断开连接消息 notifyEvent.tag = socket; notifyEvent.state = ConnectedNotifyEvent::Disconnected; Game->postEvent(notifyEvent); } break; case FD_ACCEPT: // 准备接收新的将要到来的连接 { acceptEvent.portTag = socket; acceptEvent.connectionTag = Net::accept(socket, &acceptEvent.address); if(acceptEvent.connectionTag != InvalidSocket) { Net::setBlocking(acceptEvent.connectionTag, false); WSAAsyncSelect(acceptEvent.connectionTag, winsockWindow, WM_USER, FD_READ | FD_CONNECT | FD_CLOSE); // 发送新连接被接收的消息 Game->postEvent(acceptEvent); } } break; } break; case WM_USER + 1: error = WSAGETASYNCERROR(lParam); bufLen = WSAGETASYNCBUFLEN(lParam); HANDLE handle; handle = HANDLE(wParam); NameLookup **walk; for(walk = &lookupList; *walk; walk = &((*walk)->nextLookup)) { if((*walk)->lookupHandle == handle) { NameLookup *temp = *walk; struct hostent *hp = (struct hostent *) temp->hostEntStruct; // 如果域名查询失败,则关闭套接字 if(error) { notifyEvent.state = ConnectedNotifyEvent::DNSFailed; notifyEvent.tag = temp->socket; ::closesocket(temp->socket); } // 否则创建连接 else { SOCKADDR_IN ipAddr; memcpy(&ipAddr.sin_addr.s_addr, hp->h_addr, sizeof(IN_ADDR)); ipAddr.sin_port = temp->port; ipAddr.sin_family = AF_INET; notifyEvent.tag = temp->socket; WSAAsyncSelect(temp->socket, winsockWindow, WM_USER, FD_READ | FD_CONNECT | FD_CLOSE); bool wserr = ::connect(temp->socket, (PSOCKADDR) &ipAddr, sizeof(ipAddr)); // always errors out if (wserr && WSAGetLastError() == WSAEWOULDBLOCK) notifyEvent.state = ConnectedNotifyEvent::DNSResolved; else { Con::printf("Connect error: %d", WSAGetLastError()); ::closesocket(temp->socket); notifyEvent.state = ConnectedNotifyEvent::ConnectFailed; } } Game->postEvent(notifyEvent); *walk = temp->nextLookup; delete temp; break; } } break; default: return DefWindowProc( hWnd, message, wParam, lParam ); } return 0; } // 初始化网络载体窗口 static void InitNetWindow() { WNDCLASS wc; dMemset(&wc, 0, sizeof(wc)); wc.style = 0; wc.lpfnWndProc = WinsockProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = winState.appInstance; wc.hIcon = 0; wc.hCursor = 0; wc.hbrBackground = 0; wc.lpszMenuName = 0; wc.lpszClassName = dT("WinSockClass"); RegisterClass( &wc ); winsockWindow = CreateWindowEx( 0, dT("WinSockClass"), dT(""), 0, 0, 0, 0, 0, NULL, NULL, winState.appInstance, NULL); } /* WSADATA说明 WSADATA结构被用来储存调用AfxSocketInit全局函数返回的Windows Sockets初始化信息。 这个结构被用来存储被WSAStartup函数调用后返回的Windows Sockets数据。它包含Winsock.dll执行的数据。 struct WSAData { WORD wVersion; // Windows Sockets DLL期望调用者使用的Windows Sockets规范的版本。 // 高位字节存储副版本号, 低位字节存储主版本号,可以用WORD MAKEWORD(BYTE,BYTE ) 返回这个值,例如:MAKEWORD(1,1) WORD wHighVersion; // 这个DLL能够支持的Windows Sockets规范的最高版本。通常它与wVersion相同。 char szDescription[WSADESCRIPTION_LEN+1]; // 以null结尾的ASCII字符串,Windows Sockets DLL将对Windows Sockets实现的描述拷贝到这个字符串中,包括制造商标识。 // 文本(最多可以有256个字符)可以包含任何字符,但是要注意不能包含控制字符和格式字符,应用程序对其最可能的使用方式是把它(可能被截断)显示在在状态信息中。 char szSystemStatus[WSASYSSTATUS_LEN+1]; // 以null结尾的ASCII字符串,Windows Sockets DLL把有关的状态或配置信息拷贝到该字符串中。 // Windows Sockets DLL应当仅在这些信息对用户或支持人员有用时才使用它们,它不应被作为szDescription域的扩展。 unsigned short iMaxSockets; // 单个进程能够打开的socket的最大数目。Windows Sockets的实现能提供一个全局的socket池,可以为任何进程分配; // 或者它也可以为socket分配属于进程的资源。这个数字能够很好地反映Windows Sockets DLL或网络软件的配置方式。 // 应用程序的编写者可以通过这个数字来粗略地指明Windows Sockets的实现方式对应用程序是否有用。 // 例如,X Windows服务器在第一次启动的时候可能会检查iMaxSockets的值: // 如果这个值小于8,应用程序将显示一条错误信息,指示用户重新配置网络软件(这是一种可能要使用szSystemStatus文本的场合)。 // 显然无法保证某个应用程序能够真正分配iMaxSockets个socket,因为可能有其它WindowsSockets应用程序正在使用。 unsigned short iMaxUdpDg; // WinSock2.0版中已被废弃。 char *lpVendorInfo; // WinSock2.0版中已被废弃。 }; */ /* WSAStartup说明 WSAStartup,即WSA(Windows SocKNDs Asynchronous,Windows异步套接字)的启动命令。是Windows下的网络编程接口软件Winsock1 或 Winsock2 里面的一个命令 (Ps:Winsock 是由Unix下的BSD Socket发展而来,是一个与网络协议无关的编程接口)。 为了在应用程序当中调用任何一个Winsock API函数,首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务的初始化,因此需要调用WSAStartup函数。 使用Socket的程序在使用Socket之前必须调用WSAStartup函数。 该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本; 操作系统利用第二个参数返回请求的Socket的版本信息。当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。 以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。 int WSAStartup ( WORD wVersionRequested, LPWSADATA lpWSAData ); ⑴ wVersionRequested:一个WORD(双字节)型数值,在最高版本的Windows Sockets支持调用者使用,高阶字节指定小版本(修订本)号,低位字节指定主版本号。 ⑵ lpWSAData 指向WSADATA数据结构的指针,用来接收Windows Sockets[1]实现的细节。 WindowsSockets API提供的调用方可使用的最高版本号。高位字节指出副版本(修正)号,低位字节指明主版本号。 本函数必须是应用程序或DLL调用的第一个Windows Sockets函数。它允许应用程序或DLL指明Windows Sockets API的版本号及获得特定Windows Sockets实现的细节。 应用程序或DLL只能在一次成功的WSAStartup()调用之后才能调用进一步的Windows Sockets API函数。 当它完成了使用该Windows Sockets DLL的服务后,应用程序或DLL必须调用WSACleanup()以允许Windows Sockets DLL释放任何该应用程序的资源。 返回值 0 成功。 否则返回下列的错误代码之一。注意通常依靠应用程序调用WSAGetLastError()机制获得的错误代码是不能使用的, 因为Windows Sockets DLL可能没有建立“上一错误”信息储存的客户数据区域。 关于Windows Sockets提供者的说明: 每一个Windows Sockets应用程序必须在进行其它Windows Sockets API调用前进行WSAStartup()调用。这样,本函数就可以用于初始化的目的。 进一步的说明在WSACleanup()的说明中有讨论。 错误代码 WSASYSNOTREADY 指出网络通信依赖的网络子系统还没有准备好。 WSAVERNOTSUPPORTED 所需的Windows Sockets API的版本未由特定的Windows Sockets实现提供。 WSAEINVAL 应用程序指出的Windows Sockets版本不被该DLL支持。 */ bool Net::init() { WSADATA stWSAData; InitNetWindow(); return !WSAStartup(0x0101, &stWSAData); } void Net::shutdown() { // 取消所有未完成的异步操作 while(lookupList) { NameLookup *temp = lookupList; lookupList = temp->nextLookup; WSACancelAsyncRequest ( temp->lookupHandle ); delete temp; } // 销毁窗体 DestroyWindow(winsockWindow); // 端口关闭 closePort(); // SOCKET服务关闭 WSACleanup(); } /* sockaddr说明 在 internet 地址族, Windows 套接字用于 SOCKADDR_IN 结构指定连接套接字的本地或远程终结点地址。 struct sockaddr_in { short sin_family; // 地址族 unsigned short sin_port; // IP端口 struct in_addr sin_addr; // IP地址 char sin_zero[8]; // 若要使框架相同大小的填充与SOCKADDR }; 这是 SOCKADDR 框架特定的窗体对 internet 地址族,并可转换为 SOCKADDR。 */ // NetAddress到SOCKADDR_IN的转换 static void netToIPSocketAddress(const NetAddress *address, SOCKADDR_IN *sockAddr) { dMemset(sockAddr, 0, sizeof(SOCKADDR_IN)); sockAddr->sin_family = AF_INET; sockAddr->sin_port = htons(address->port); sockAddr->sin_addr.s_net = address->netNum[0]; sockAddr->sin_addr.s_host = address->netNum[1]; sockAddr->sin_addr.s_lh = address->netNum[2]; sockAddr->sin_addr.s_impno = address->netNum[3]; } // SOCKADDR_IN到NetAddress的转换 static void IPSocketToNetAddress(const SOCKADDR_IN *sockAddr, NetAddress *address) { address->type = NetAddress::IPAddress; address->port = htons(sockAddr->sin_port); address->netNum[0] = sockAddr->sin_addr.s_net; address->netNum[1] = sockAddr->sin_addr.s_host; address->netNum[2] = sockAddr->sin_addr.s_lh; address->netNum[3] = sockAddr->sin_addr.s_impno; } // NetAddress到SOCKADDR_IPX的转换 static void netToIPXSocketAddress(const NetAddress *address, SOCKADDR_IPX *sockAddr) { #if !defined(NO_IPX_SUPPORT) dMemset(sockAddr, 0, sizeof(SOCKADDR_IPX)); sockAddr->sa_family = AF_INET; sockAddr->sa_socket = htons(address->port); sockAddr->sa_netnum[0] = address->netNum[0]; sockAddr->sa_netnum[1] = address->netNum[1]; sockAddr->sa_netnum[2] = address->netNum[2]; sockAddr->sa_netnum[3] = address->netNum[3]; sockAddr->sa_nodenum[0] = address->nodeNum[0]; sockAddr->sa_nodenum[1] = address->nodeNum[1]; sockAddr->sa_nodenum[2] = address->nodeNum[2]; sockAddr->sa_nodenum[3] = address->nodeNum[3]; sockAddr->sa_nodenum[4] = address->nodeNum[4]; sockAddr->sa_nodenum[5] = address->nodeNum[5]; #endif } // SOCKADDR_IPX到NetAddress的转换 static void IPXSocketToNetAddress(const SOCKADDR_IPX *sockAddr, NetAddress *address) { #if !defined(NO_IPX_SUPPORT) address->type = NetAddress::IPXAddress; address->port = htons(sockAddr->sa_socket); address->netNum[0] = sockAddr->sa_netnum[0] ; address->netNum[1] = sockAddr->sa_netnum[1] ; address->netNum[2] = sockAddr->sa_netnum[2] ; address->netNum[3] = sockAddr->sa_netnum[3] ; address->nodeNum[0] = sockAddr->sa_nodenum[0]; address->nodeNum[1] = sockAddr->sa_nodenum[1]; address->nodeNum[2] = sockAddr->sa_nodenum[2]; address->nodeNum[3] = sockAddr->sa_nodenum[3]; address->nodeNum[4] = sockAddr->sa_nodenum[4]; address->nodeNum[5] = sockAddr->sa_nodenum[5]; #endif } /** WSAAsyncSelect 说明 简述: 通知套接字端口有请求事件发生. #include <winsock.h> int PASCAL FAR WSAAsyncSelect (SOCKET s,HWND hWnd, unsigned int wMsg,long lEvent); s 标识一个需要事件通知的套接口的描述符. hWnd 标识一个在网络事件发生时需要接收消息的窗口句柄. wMsg 在网络事件发生时要接收的消息. lEvent 位屏蔽码,用于指明应用程序感兴趣的网络事件集合. 本函数用来请求Windows Sockets DLL为窗口句柄发一条消息-无论它何时检测到由lEvent参数指明的网络事件.要发送的消息由wMsg参数标明.被通知的套接口由s标识. 本函数自动将套接口设置为非阻塞模式. lEvent参数由下表中列出的值组成. 值 意义 FD_READ 欲接收读准备好的通知. FD_WRITE 欲接收写准备好的通知. FD_OOB 欲接收带边数据到达的通知. FD_ACCEPT 欲接收将要连接的通知. FD_CONNECT 欲接收已连接好的通知. FD_CLOSE 欲接收套接口关闭的通知. 启动一个WSAAsyncSelect()将使为同一个套接口启动的所有先前的WSAAsyncSelect()作废. 例如,要接收读写通知,应用程序必须同时用FD_READ和FD_WRITE调用WSAAsyncSelect(),如下: rc = WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE); 对不同的事件区分不同的消息是不可能的.下面的代码将不会工作;第二个调用将会使第一次调用的作用失效,只有FD_WRITE会通过wMsg2消息通知到. rc = WSAAsyncSelect(s,hWnd,wMsg1,FD_READ); rc = WSAAsyncSelect(s,hWnd,wMsg2,FD_WRITE); 如果要取消所有的通知,也就是指出Windows Sockets的实现不再在套接口上发送任何和网络事件相关的消息,则lEvent应置为0. rc = WSAAsyncSelect(s,hWnd,0,0); 尽管在本例中,WSAAsyncSelect()立即使传给该套接口的事件消息无效,仍有可能有消息等在应用程序的消息队列中. 应用程序因此也必须仍准备好接收网络消息-即使消息作废.用closesocket()关闭一个套接口也同样使WSAAsyncSelect()发送的消息作废,但在closesocke()之前队列中的消息仍然起作用. 由于一个已调用accept()的套接口和用来接收它的侦听套接口有同样的属性,任何为侦听套接口设置的WSAAsyncSelect()事件也同样对已接收的套接口起作用. 例如 如果一个侦听的套接口有WSAAsyncSelect()事件FD_ACCEPT,FD_READ,FD_WRITE, 则任何在那个侦听的套接口上接收的套接口将也有FD_ACCEPT,FD_READ,FD_WRITE事件, 以及同样的wMsg的值.若需要不同的wMsg及事件,应用程序应调用WSAAsyncSelect(),将已接收的套接口和想要发送的新消息作为参数传递. 当某一套接口s上发生了一个已命名的网络事件,应用程序窗口hWnd会接收到消息wMsg.wParam参数标识了网络事件发生的套接口. lParam的低字指明了发生的网络事件. lParam的高字则含有一个错误代码.该错误代码可以是winsock.h中定义的任何错误. 错误代码和事件可以通过WSAGETSELECTERRORH和WSAGETSELECTEVENT宏从lParam中取出.定义如下: #define WSAGETSELECTERROR(lParam) HIWORD(lParam) #define WSAGETSELECTEVENT(lParam) LOWORD(lParam) 注意 在accept()调用和为改变事件或wMsg的WSAAsyncSelect()调用中有一个计时窗口. 应用程序如果需要给侦听的和调用过accept()的套接口以不同的wMsg,它就应该在侦听的套接口上请求FD_ACCEPT事件,然后在accept()调用后设置相应的事件. 由于FD_ACCEPT从不发送给已连接的套接口,而FD_READ,FD_WRITE,FD_OOB及FD_CLOSE也从不发送给侦听套接口,所以不会产生困难. 使用以上的宏将最大限度的提高应用程序的可移植性. */ /** WSAAsyncGetHostByName 说明 本函数是gethostbyname()的异步版本,是用来获取对应于一个主机名的主机名称和地址信息。 Windows Sockets的实现启动该操作后立刻返回调用方,并传回一个异步任务句柄,应用程序可以用它来标识该操作。 当操作完成时,结果(若有的话)将会拷贝到调用方提供的缓冲区,同时向应用程序的窗口发一条消息。 获得对应于一个主机名的主机信息.-异步版本. HANDLE PASCAL FAR WSAAsyncGetHostByName (HWND hWnd, unsigned int wMsg,const char FAR * name,char FAR * buf, int buflen); hWnd 当异步请求完成时,应该接收消息的窗口句柄. wMsg 当异步请求完成时,将要接收的消息. name 指向主机名的指针. buf 接收hostent数据的数据区指针.注意该数据区必须大于hostent结构的大小.这是因为不仅Windows Sockets实现要用该数据区域容纳hostent结构,hostent结构的成员引用的所有数据也要在该区域内.建议用户提供一个MAXGETHOSTSTRUCT字节大小的缓冲区. buflen 上述数据区的大小. */ // 打开一个监听端口 NetSocket Net::openListenPort(U16 port) { #ifdef TORQUE_ALLOW_JOURNALING if(Game->isJournalReading()) { U32 ret; Game->journalRead(&ret); return NetSocket(ret); } #endif //TORQUE_ALLOW_JOURNALING // 创建一个套接字 NetSocket sock = openSocket(); // 端口绑定 bind(sock, port); // 监听 listen(sock, 4); // 设置非阻塞 setBlocking(sock, false); // 绑定套接字消息与接收消息的窗体句柄 if(WSAAsyncSelect ( sock, winsockWindow, WM_USER, FD_ACCEPT )) Con::printf("Connect error: %d", WSAGetLastError()); #ifdef TORQUE_ALLOW_JOURNALING if(Game->isJournalWriting()) Game->journalWrite(U32(sock)); #endif //TORQUE_ALLOW_JOURNALING return sock; } // 创建新的连接到指定的地址 NetSocket Net::openConnectTo(const char *addressString) { // 如果是ipx,返回失败 if(!dStrnicmp(addressString, "ipx:", 4)) return InvalidSocket; // 如果是IP地址,偏移一下 if(!dStrnicmp(addressString, "ip:", 3)) addressString += 3; // eat off the ip: char remoteAddr[256]; dStrcpy(remoteAddr, addressString); char *portString = dStrchr(remoteAddr, ':'); // 读取端口 U16 port; if(portString) { *portString++ = 0; port = htons(dAtoi(portString)); } else port = htons(defaultPort); if(!dStricmp(remoteAddr, "broadcast")) return InvalidSocket; #ifdef TORQUE_ALLOW_JOURNALING if(Game->isJournalReading()) { U32 ret; Game->journalRead(&ret); return NetSocket(ret); } #endif //TORQUE_ALLOW_JOURNALING // 创建套接字 NetSocket sock = openSocket(); // 非阻塞设置 setBlocking(sock, false); SOCKADDR_IN ipAddr; ipAddr.sin_addr.s_addr = inet_addr(remoteAddr); // 如果是有效的IP地址,那么通过IP地址连接 if(ipAddr.sin_addr.s_addr != INADDR_NONE) { ipAddr.sin_port = port; ipAddr.sin_family = AF_INET; WSAAsyncSelect(sock, winsockWindow, WM_USER, FD_READ | FD_CONNECT | FD_CLOSE); if(::connect(sock, (PSOCKADDR) &ipAddr, sizeof(ipAddr) ) ) { if(WSAGetLastError() != WSAEWOULDBLOCK) { Con::printf("Connect error: %d", WSAGetLastError()); ::closesocket(sock); sock = InvalidSocket; } } } // 如果不是IP,那么用主机名连接 else { NameLookup *lookup = new NameLookup; lookup->socket = sock; lookup->port = port; lookup->lookupHandle = WSAAsyncGetHostByName (winsockWindow, WM_USER + 1, remoteAddr, (char *) lookup->hostEntStruct, MAXGETHOSTSTRUCT ); if(!lookup->lookupHandle) { delete lookup; ::closesocket(sock); sock = InvalidSocket; } else { lookup->nextLookup = lookupList; lookupList = lookup; } } #ifdef TORQUE_ALLOW_JOURNALING if(Game->isJournalWriting()) Game->journalWrite(U32(sock)); #endif //TORQUE_ALLOW_JOURNALING return sock; } // 关闭连接 void Net::closeConnectTo(NetSocket sock) { #ifdef TORQUE_ALLOW_JOURNALING if(Game->isJournalReading()) return; #endif //TORQUE_ALLOW_JOURNALING for(NameLookup **walk = &lookupList; *walk; walk = &((*walk)->nextLookup) ) { NameLookup *lookup = *walk; if(lookup->socket == sock) { WSACancelAsyncRequest ( lookup->lookupHandle ); closesocket(lookup->socket); *walk = lookup->nextLookup; delete lookup; return; } } closesocket(sock); } /* send 说明 向一个已连接的套接口发送数据。 int PASCAL FAR send( SOCKET s, const char FAR* buf, int len, int flags); s 一个用于标识已连接套接口的描述字。 buf 包含待发送数据的缓冲区。 len 缓冲区中数据的长度。 flags 调用执行方式。 send()适用于已连接的数据包或流式套接口发送数据。对于数据报类套接口,必需注意发送数据长度不应超过通讯子网的IP包最大长度。 IP包最大长度在WSAStartup()调用返回的WSAData的iMaxUdpDg元素中。如果数据太长无法自动通过下层协议,则返回WSAEMSGSIZE错误,数据不会被发送。 请注意成功地完成send()调用并不意味着数据传送到达。 如果传送系统的缓冲区空间不够保存需传送的数据,除非套接口处于非阻塞I/O方式,否则send()将阻塞。 对于非阻塞SOCK_STREAM类型的套接口,实际写的数据数目可能在1到所需大小之间,其值取决于本地和远端主机的缓冲区大小。可用select()调用来确定何时能够进一步发送数据。 在相关套接口的选项之上,还可通过标志位flag来影响函数的执行方式。也就是说,本函数的语义既取决于套接口的选项也取决于标志位。后者由以下一些值组成: MSG_DONTROUTE 指明数据不选径。一个WINDOWS套接口供应商可以忽略此标志; MSG_OOB 发送带外数据(仅适用于SO_STREAM;)。 返回值 若无错误发生,send()返回所发送数据的总数(请注意这个数字可能小于len中所规定的大小)。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。 错误代码: WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。 WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。 WSAEACESS:要求地址为广播地址,但相关标志未能正确设置。 WSAEINTR:通过一个WSACancelBlockingCall()来取消一个(阻塞的)调用。 WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。 WSAEFAULT:buf参数不在用户地址空间中的有效位置。 WSAENETRESET:由于WINDOWS套接口实现放弃了连接,故该连接必需被复位。 WSAENOBUFS:WINDOWS套接口实现报告一个缓冲区死锁。 WSAENOTCONN:套接口未被连接。 WSAENOTSOCK:描述字不是一个套接口。 WSAEOPNOTSUPP:已设置了MSG_OOB,但套接口非SOCK_STREAM类型。 WSAESHUTDOWN:套接口已被关闭。一个套接口以1或2的how参数调用shutdown()关闭后,无法再用sned()函数。 WSAEWOULDBLOCK: WSAEMSGSIZE:套接口为SOCK_DGRAM类型,且数据报大于WINDOWS套接口实现所支持的最大值。 WSAEINVAL:套接口未用bind()捆绑。 WSAECONNABORTED:由于超时或其他原因引起虚电路的中断。 WSAECONNRESET:虚电路被远端复位。 */ // 发送消息 Net::Error Net::sendtoSocket(NetSocket socket, const U8 *buffer, S32 bufferSize) { #ifdef TORQUE_ALLOW_JOURNALING if(Game->isJournalReading()) { U32 e; Game->journalRead(&e); return (Net::Error) e; } #endif //TORQUE_ALLOW_JOURNALING Net::Error e = send(socket, buffer, bufferSize); #ifdef TORQUE_ALLOW_JOURNALING if(Game->isJournalWriting()) Game->journalWrite(U32(e)); #endif //TORQUE_ALLOW_JOURNALING return e; } // 打开端口 bool Net::openPort(S32 port) { if(udpSocket != INVALID_SOCKET) closesocket(udpSocket); if(ipxSocket != INVALID_SOCKET) closesocket(ipxSocket); udpSocket = socket(AF_INET, SOCK_DGRAM, 0); #if !defined(NO_IPX_SUPPORT) ipxSocket = socket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX); #else ipxSocket = INVALID_SOCKET; #endif if(udpSocket != INVALID_SOCKET) { Net::Error error; error = bind(udpSocket, port); if(error == NoError) error = setBufferSize(udpSocket, 32768); if(error == NoError) error = setBroadcast(udpSocket, true); if(error == NoError) error = setBlocking(udpSocket, false); if(error == NoError) Con::printf("UDP initialized on port %d", port); else { closesocket(udpSocket); udpSocket = INVALID_SOCKET; Con::printf("Unable to initialize UDP - error %d", error); } } #if !defined(NO_IPX_SUPPORT) if(ipxSocket != INVALID_SOCKET) { Net::Error error = NoError; SOCKADDR_IPX ipxAddress; memset((char *)&ipxAddress, 0, sizeof(ipxAddress)); ipxAddress.sa_family = AF_IPX; ipxAddress.sa_socket = htons(port); S32 err = ::bind(ipxSocket, (PSOCKADDR) &ipxAddress, sizeof(ipxAddress)); if(err) error = getLastError(); if(error == NoError) error = setBufferSize(ipxSocket, 32768); if(error == NoError) error = setBroadcast(ipxSocket, true); if(error == NoError) error = setBlocking(ipxSocket, false); if(error == NoError) Con::printf("IPX initialized on port %d", port); else { closesocket(ipxSocket); ipxSocket = INVALID_SOCKET; Con::printf("Unable to initialize IPX - error %d", error); } } #endif netPort = port; return ipxSocket != INVALID_SOCKET || udpSocket != INVALID_SOCKET; } void Net::closePort() { if(ipxSocket != INVALID_SOCKET) closesocket(ipxSocket); if(udpSocket != INVALID_SOCKET) closesocket(udpSocket); } // 直接发送消息到指定地址 Net::Error Net::sendto(const NetAddress *address, const U8 *buffer, S32 bufferSize) { #ifdef TORQUE_ALLOW_JOURNALING if(Game->isJournalReading()) return NoError; #endif //TORQUE_ALLOW_JOURNALING if(address->type == NetAddress::IPXAddress) { SOCKADDR_IPX ipxAddr; netToIPXSocketAddress(address, &ipxAddr); if(::sendto(ipxSocket, (const char*)buffer, bufferSize, 0, (PSOCKADDR) &ipxAddr, sizeof(SOCKADDR_IPX)) == SOCKET_ERROR) return getLastError(); else return NoError; } else { SOCKADDR_IN ipAddr; netToIPSocketAddress(address, &ipAddr); if(::sendto(udpSocket, (const char*)buffer, bufferSize, 0, (PSOCKADDR) &ipAddr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) return getLastError(); else return NoError; } } void Net::process() { SOCKADDR sa; PacketReceiveEvent receiveEvent; for(;;) { S32 addrLen = sizeof(sa); S32 bytesRead = SOCKET_ERROR; if(udpSocket != INVALID_SOCKET) bytesRead = recvfrom(udpSocket, (char *) receiveEvent.data, MaxPacketDataSize, 0, &sa, &addrLen); if(bytesRead == SOCKET_ERROR && ipxSocket != INVALID_SOCKET) { addrLen = sizeof(sa); bytesRead = recvfrom(ipxSocket, (char *) receiveEvent.data, MaxPacketDataSize, 0, &sa, &addrLen); } if(bytesRead == SOCKET_ERROR) break; if(sa.sa_family == AF_INET) IPSocketToNetAddress((SOCKADDR_IN *) &sa, &receiveEvent.sourceAddress); else if(sa.sa_family == AF_IPX) IPXSocketToNetAddress((SOCKADDR_IPX *) &sa, &receiveEvent.sourceAddress); else continue; NetAddress &na = receiveEvent.sourceAddress; if(na.type == NetAddress::IPAddress && na.netNum[0] == 127 && na.netNum[1] == 0 && na.netNum[2] == 0 && na.netNum[3] == 1 && na.port == netPort) continue; if(bytesRead <= 0) continue; receiveEvent.size = PacketReceiveEventHeaderSize + bytesRead; Game->postEvent(receiveEvent); } } /** socket 说明 Socket的英文原义是“孔”或“插座”。作为4BDS UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。 在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。 Socket正如其英文原意那样,象一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务 socket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。 正如打电话之前,双方必须各自拥有一台电话机一样。在网间网内部,每一个socket用一个半相关描述:(协议,本地地址,本地端口) 一个完整的socket有一个本地唯一的socket号,由操作系统分配。 最重要的是,socket 是面向客户/服务器模型而设计的,针对客户和服务器程序提供不同的socket 系统调用。 客户随机申请一个socket (相当于一个想打电话的人可以在任何一台入网电话上拨号呼叫),系统为之分配一个socket号; 服务器拥有全局公认的 socket ,任何客户都可以向它发出连接请求和信息请求(相当于一个被呼叫的电话拥有一个呼叫方知道的电话号码)。 SOCKET连接过程 根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。 服务器监听: 是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。 客户端请求: 是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。 为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。 连接确认: 是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端, 一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。 int socket(int domain, int type, int protocol); 第一个参数: 指定应用程序使用的通信协议的协议族,对于TCP/IP协议族,该参数置AF_INET; 第二个参数: 指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据报套接字类型为SOCK_DGRAM、原始套接字SOCK_RAW (WinSock接口并不适用某种特定的协议去封装它,而是由程序自行处理数据包以及协议首部); 第三个参数: 指定应用程序所使用的通信协议。此参数可以指定单个协议系列中的不同传输协议。 在Internet通讯域中,此参数一般取值为0,系统会根据套接字的类型决定应使用的传输层协议。 */ // 创建套接字 NetSocket Net::openSocket() { // 创建套接字 SOCKET retSocket; retSocket = socket(AF_INET, SOCK_STREAM, 0); // 返回结果 if(retSocket == INVALID_SOCKET) return InvalidSocket; else return retSocket; } // 关闭套接字 Net::Error Net::closeSocket(NetSocket socket) { if(socket != InvalidSocket) { if(!closesocket(socket)) return NoError; else return getLastError(); } else return NotASocket; } /* connect 说明 int connect (int sockfd,struct sockaddr * serv_addr,int addrlen); 用来将参数sockfd 的socket 连至参数serv_addr 指定的网络地址。结构sockaddr请参考bind()。参数addrlen为sockaddr的结构长度。 参数: 参数一:套接字描述符 参数二:指向数据机构sockaddr的指针,其中包括目的端口和IP地址 参数三:参数二sockaddr的长度,可以通过sizeof(struct sockaddr)获得 返回值: 成功则返回0,失败返回-1,错误码GetLastError()。 错误代码: EBADF 参数sockfd 非合法socket处理代码 EFAULT 参数serv_addr指针指向无法存取的内存空间 ENOTSOCK 参数sockfd为一文件描述词,非socket。 EISCONN 参数sockfd的socket已是连线状态 ECONNREFUSED 连线要求被server端拒绝。 ETIMEDOUT 企图连线的操作超过限定时间仍未有响应。 ENETUNREACH 无法传送数据包至指定的主机。 EAFNOSUPPORT sockaddr结构的sa_family不正确。 EALREADY socket为不可阻断且先前的连线操作还未完成。 */ // 连接 Net::Error Net::connect(NetSocket socket, const NetAddress *address) { // 如果地址类型不是IP地址,那么返回协议错误 if(address->type != NetAddress::IPAddress) return WrongProtocolType; // 地址转换 SOCKADDR_IN socketAddress; netToIPSocketAddress(address, &socketAddress); // 连接 if(!::connect(socket, (PSOCKADDR) &socketAddress, sizeof(socketAddress))) return NoError; // 返回结果 return getLastError(); } /** listen 说明 listen在套接字函数中表示让一个套接字处于监听到来的连接请求的状态 函数声明: int listen(int sockfd, int backlog); 功能: listen函数使用主动连接套接口变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。 在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。 listen函数在一般在调用bind之后-调用accept之前调用。 参数: sockfd 一个已绑定未被连接的套接字描述符 backlog 连接请求队列(queue of pending connections)的最大长度(一般由2到4)。用SOMAXCONN则由系统确定。 返回值: 无错误,返回0, 否则,返回SOCKET ERROR,可以调用函数WSAGetLastError取得错误代码。 说明: (1) 执行listen 之后套接字进入被动模式。 (2) 队列满了以后,将拒绝新的连接请求。客户端将出现连接D 错误WSAECONNREFUSED。 (3) 在正在listen的套接字上执行listen不起作用。 */ // 监听 Net::Error Net::listen(NetSocket socket, S32 backlog) { if(!::listen(socket, backlog)) return NoError; return getLastError(); } /* accept 说明 在一个套接口接受一个连接。accept() 是c语言中网络编程的重要的函数,和windows 编程也有所不同,其不是在winsock.h头文件中,而是在#include <sys/socket.h>中。 原型 SOCKET PASCAL FAR accept( SOCKET s, struct sockaddr FAR* addr,int FAR* addrlen); 参数 s: 套接口描述字,该套接口在listen()后监听连接。 addr: (可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。 addrlen: (可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。 返回值 成功返回一个新的套接字描述符,失败返回-1。 */ // 接受连接 NetSocket Net::accept(NetSocket acceptSocket, NetAddress *remoteAddress) { SOCKADDR_IN socketAddress; S32 addrLen = sizeof(socketAddress); SOCKET retVal = ::accept(acceptSocket, (PSOCKADDR) &socketAddress, &addrLen); if(retVal != INVALID_SOCKET) { IPSocketToNetAddress(&socketAddress, remoteAddress); return retVal; } return InvalidSocket; } /* bind 说明 将一本地地址与一套接口捆绑。本函数适用于未连接的数据报或流类套接口,在connect()或listen()调用前使用。 当用socket()创建套接口后,它便存在于一个名字空间(地址族)中,但并未赋名。bind()函数通过给一个未命名套接口分配一个本地名字来为套接口建立本地捆绑(主机地址/端口号)。 int PASCAL FAR bind( SOCKET s, const struct sockaddr FAR* name, int namelen); s:标识一未捆绑套接口的描述字。 name:赋予套接口的地址 namelen:name名字的长度 在Internet地址族中,一个名字包括几个组成部分,对于SOCK_DGRAM和SOCK_STREAM类套接口,名字由三部分组成:主机地址,协议号(显式设置为UDP和TCP)和用以区分应用的端口号。 如果一个应用并不关心分配给它的地址,则可将Internet地址设置为INADDR_ANY,或将端口号置为0。如果Internet地址段为INADDR_ANY,则可使用任意网络接口, 且在有多种主机环境下可简化编程。如果端口号置为0,则Windows套接口实现将给应用程序分配一个值在1024到5000之间的唯一的端口。 应用程序可在bind()后用getsockname()来获知所分配的地址,但必需注意的是,getsockname()只有在套接口连接成功后才会填写Internet地址, 这是由于在多种主机环境下若干种Internet地址都是有效的。 返回值 如无错误发生,则bind()返回0。否则的话,将返回SOCKET_ERROR,应用程序可通过WSAGetLastError()获取相应错误代码。 错误代码 WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。 WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。 WSAEADDRINUSE:所定端口已在使用中(参见setoption()中的SO_REUSEADDR选项)。 WSAEFAULT:namelen参数太小(小于sockaddr结构的大小)。 WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。 WSAEAFNOSUPPORT:本协议不支持所指定的地址族。 WSAEINVAL:该套接口已与一个地址捆绑。 WSAENOBUFS:无足够可用缓冲区,连接过多。 WSAENOTSOCK:描述字不是一个套接口。 */ Net::Error Net::bind(NetSocket socket, U16 port) { S32 error; SOCKADDR_IN socketAddress; dMemset((char *)&socketAddress, 0, sizeof(socketAddress)); socketAddress.sin_family = AF_INET; // 考虑到可能会有两块网卡,所以让用户自己决定绑定地址 const char* serverIP = Con::getVariable( "Pref::Net::BindAddress" ); AssertFatal( serverIP, "serverIP is NULL!" ); if( serverIP[0] != '\0' ) { // we're not empty socketAddress.sin_addr.s_addr = inet_addr( serverIP ); if( socketAddress.sin_addr.s_addr != INADDR_NONE ) { Con::printf( "Binding server port to %s", serverIP ); } else { Con::warnf( ConsoleLogEntry::General, "inet_addr() failed for %s while binding!", serverIP ); socketAddress.sin_addr.s_addr = INADDR_ANY; } } else { Con::printf( "Binding server port to default IP" ); socketAddress.sin_addr.s_addr = INADDR_ANY; } socketAddress.sin_port = htons(port); error = ::bind(socket, (PSOCKADDR) &socketAddress, sizeof(socketAddress)); if(!error) return NoError; return getLastError(); } /* setsockopt 说明 设置套接口的选项。 int PASCAL FAR setsockopt(SOCKET s,int level,int optname, const char FAR *optval,int optlen); s 标识一个套接口的描述字。 level 选项定义的层次;目前仅支持SOL_SOCKET和IPPROTO_TCP层次。 optname 需设置的选项。 optval 指针,指向存放选项值的缓冲区。 optlen optval缓冲区长度。 setsockopt()函数用于任意类型、任意状态套接口的设置选项值。 尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。 选项影响套接口的操作,诸如加急数据是否在普通数据流中接收,广播数据是否可以从套接口发送等等。 有两种套接口的选项: 一种是布尔型选项,允许或禁止一种特性; 一种是整形或结构选项。允许一个布尔型选项,则将optval指向非零整形数;禁止一个选项optval指向一个等于零的整形数。对于布尔型选项,optlen应等于sizeof(int); 对其他选项,optval指向包含所需选项的整形数或结构,而optlen则为整形数或结构的长度。 SO_LINGER选项用于控制下述情况的行动:套接口上有排队的待发送数据,且closesocket()调用已执行。 参见closesocket()函数中关于SO_LINGER选项对closesocket()语义的影响。 setsockopt()支持下列选项。其中“类型”表明optval所指数据的类型。 选项 类型 意义 SO_BROADCAST BOOL 允许套接口传送广播信息。 SO_DEBUG BOOL 记录调试信息。 SO_DONTLINER BOOL 不要因为数据未发送就阻塞关闭操作。设置本选项相当于将SO_LINGER的l_onoff元素置为零。 SO_DONTROUTE BOOL 禁止选径;直接传送。 SO_KEEPALⅣE BOOL 发送“保持活动”包。 SO_LINGER struct linger FAR* 如关闭时有未发送数据,则逗留。 SO_OOBINLINE BOOL 在常规数据流中接收带外数据。 SO_RCVBUF int 为接收确定缓冲区大小。 SO_REUSEADDR BOOL 允许套接口和一个已在使用中的地址捆绑(参见bind())。 SO_SNDBUF int 指定发送缓冲区大小。 TCP_NODELAY BOOL 禁止发送合并的Nagle算法。 返回值 若无错误发生,setsockopt()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。 错误代码: WSANOTINITIALISED 在使用此API之前应首先成功地调用WSAStartup()。 WSAENETDOWN WINDOWS套接口实现检测到网络子系统失效。 WSAEFAULT optval不是进程地址空间中的一个有效部分。 WSAEINPROGRESS 一个阻塞的WINDOWS套接口调用正在运行中。 WSAEINVAL level值非法,或optval中的信息非法。 WSAENETRESET 当SO_KEEPALⅣE设置后连接超时。 WSAENOPROTOOPT 未知或不支持选项。其中,SOCK_STREAM类型的套接口不支持SO_BROADCAST选项,SOCK_DGRAM类型的套接口不支持SO_DONTLINGER 、SO_KEEPALⅣE、SO_LINGER和SO_OOBINLINE选项。 WSAENOTCONN 当设置SO_KEEPALⅣE后连接被复位。 WSAENOTSOCK 描述字不是一个套接口。 */ Net::Error Net::setBufferSize(NetSocket socket, S32 bufferSize) { S32 error; error = setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char *) &bufferSize, sizeof(bufferSize)); if(!error) error = setsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char *) &bufferSize, sizeof(bufferSize)); if(!error) return NoError; return getLastError(); } Net::Error Net::setBroadcast(NetSocket socket, bool broadcast) { S32 bc = broadcast; S32 error = setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char*)&bc, sizeof(bc)); if(!error) return NoError; return getLastError(); } /* ioctlsocket 说明 控制套接口的模式。本函数可用于任一状态的任一套接口。它用于获取与套接口相关的操作参数,而与具体协议或通讯子系统无关。支持下列命令: int PASCAL FAR ioctlsocket( SOCKET s, long cmd, u_long FAR* argp); s:一个标识套接口的描述字。 cmd:对套接口s的操作命令。 argp:指向cmd命令所带参数的指针。 FIONBIO: 允许或禁止套接口s的非阻塞模式。argp指向一个无符号长整型。如允许非阻塞模式则非零,如禁止非阻塞模式则为零。 当创建一个套接口时,它就处于阻塞模式(也就是说非阻塞模式被禁止)。这与BSD套接口是一致的。 WSAAsynSelect()函数将套接口自动设置为非阻塞模式。如果已对一个套接口进行了WSAAsynSelect() 操作,则任何用ioctlsocket()来把套接口重新设置成阻塞模式的试图将以WSAEINVAL失败。 为了把套接口重新设置成阻塞模式,应用程序必须首先用WSAAsynSelect()调用(IEvent参数置为0)来禁止WSAAsynSelect()。 FIONREAD: 确定套接口s自动读入的数据量。argp指向一个无符号长整型,其中存有ioctlsocket()的返回值。 如果s是SOCKET_STREAM类型,则FIONREAD返回在一次recv()中所接收的所有数据量。这通常与套接口中排队的数据总量相同。 如果S是SOCK_DGRAM 型,则FIONREAD返回套接口上排队的第一个数据报大小。 SIOCATMARK: 确实是否所有的带外数据都已被读入。这个命令仅适用于SOCK_STREAM类型的套接口,且该套接口已被设置为可以在线接收带外数据(SO_OOBINLINE)。 如无带外数据等待读入,则该操作返回TRUE真。否则的话返回FALSE假,下一个recv()或recvfrom()操作将检索“标记”前一些或所有数据。 应用程序可用SIOCATMARK操作来确定是否有数据剩下。如果在“紧急”(带外)数据[前有常规数据,则按序接收这些数据 (请注意,recv()和recvfrom()操作不会在一次调用中混淆常规数据与带外数]据)。argp指向一个BOOL型数,ioctlsocket()在其中存入返回值。 返回值: 成功后,ioctlsocket()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。 错误代码: WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。 WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。 WSAEINVAL:cmd为非法命令,或者argp所指参数不适用于该cmd命令,或者该命令 不适用于此种类型的套接口。 WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。 WSAENOTSOCK:描述字不是一个套接口。 */ Net::Error Net::setBlocking(NetSocket socket, bool blockingIO) { DWORD notblock = !blockingIO; S32 error = ioctlsocket(socket, FIONBIO, ¬block); if(!error) return NoError; return getLastError(); } Net::Error Net::send(NetSocket socket, const U8 *buffer, S32 bufferSize) { S32 error = ::send(socket, (const char*)buffer, bufferSize, 0); if(!error) return NoError; return getLastError(); } Net::Error Net::recv(NetSocket socket, U8 *buffer, S32 bufferSize, S32 *bytesRead) { *bytesRead = ::recv(socket, (char*)buffer, bufferSize, 0); if(*bytesRead == SOCKET_ERROR) return getLastError(); return NoError; } bool Net::compareAddresses(const NetAddress *a1, const NetAddress *a2) { if((a1->type != a2->type) || (*((U32 *)a1->netNum) != *((U32 *)a2->netNum)) || (a1->port != a2->port)) return false; if(a1->type == NetAddress::IPAddress) return true; for(S32 i = 0; i < 6; i++) if(a1->nodeNum[i] != a2->nodeNum[i]) return false; return true; } bool Net::stringToAddress(const char *addressString, NetAddress *address) { if(dStrnicmp(addressString, "ipx:", 4)) { // assume IP if it doesn't have ipx: at the front. if(!dStrnicmp(addressString, "ip:", 3)) addressString += 3; // eat off the ip: SOCKADDR_IN ipAddr; char remoteAddr[256]; if(strlen(addressString) > 255) return false; dStrcpy(remoteAddr, addressString); char *portString = dStrchr(remoteAddr, ':'); if(portString) *portString++ = 0; struct hostent *hp; if(!dStricmp(remoteAddr, "broadcast")) ipAddr.sin_addr.s_addr = htonl(INADDR_BROADCAST); else { ipAddr.sin_addr.s_addr = inet_addr(remoteAddr); if(ipAddr.sin_addr.s_addr == INADDR_NONE) { if((hp = gethostbyname(remoteAddr)) == NULL) return false; else memcpy(&ipAddr.sin_addr.s_addr, hp->h_addr, sizeof(IN_ADDR)); } } if(portString) ipAddr.sin_port = htons(dAtoi(portString)); else ipAddr.sin_port = htons(defaultPort); ipAddr.sin_family = AF_INET; IPSocketToNetAddress(&ipAddr, address); return true; } else { S32 i; S32 port; address->type = NetAddress::IPXAddress; for(i = 0; i < 6; i++) address->nodeNum[i] = 0xFF; // it's an IPX string addressString += 4; if(!dStricmp(addressString, "broadcast")) { address->port = defaultPort; return true; } else if(sscanf(addressString, "broadcast:%d", &port) == 1) { address->port = port; return true; } else { S32 nodeNum[6]; S32 netNum[4]; S32 count = dSscanf(addressString, "%2x%2x%2x%2x:%2x%2x%2x%2x%2x%2x:%d", &netNum[0], &netNum[1], &netNum[2], &netNum[3], &nodeNum[0], &nodeNum[1], &nodeNum[2], &nodeNum[3], &nodeNum[4], &nodeNum[5], &port); if(count == 10) { port = defaultPort; count++; } if(count != 11) return false; for(i = 0; i < 6; i++) address->nodeNum[i] = nodeNum[i]; for(i = 0; i < 4; i++) address->netNum[i] = netNum[i]; address->port = port; return true; } } } void Net::addressToString(const NetAddress *address, char addressString[256]) { if(address->type == NetAddress::IPAddress) { SOCKADDR_IN ipAddr; netToIPSocketAddress(address, &ipAddr); if(ipAddr.sin_addr.s_addr == htonl(INADDR_BROADCAST)) dSprintf(addressString, 256, "IP:Broadcast:%d", ntohs(ipAddr.sin_port)); else dSprintf(addressString, 256, "IP:%d.%d.%d.%d:%d", ipAddr.sin_addr.s_net, ipAddr.sin_addr.s_host, ipAddr.sin_addr.s_lh, ipAddr.sin_addr.s_impno, ntohs(ipAddr.sin_port)); } else { dSprintf(addressString, 256, "IPX:%.2X%.2X%.2X%.2X:%.2X%.2X%.2X%.2X%.2X%.2X:%d", address->netNum[0], address->netNum[1], address->netNum[2], address->netNum[3], address->nodeNum[0], address->nodeNum[1], address->nodeNum[2], address->nodeNum[3], address->nodeNum[4], address->nodeNum[5], address->port); } } Net::Error getLastError() { S32 err = WSAGetLastError(); switch(err) { case WSAEWOULDBLOCK: return Net::WouldBlock; default: return Net::UnknownError; } }
总结
从代码可以看出,C/S一体实现,两边公用~ 熟悉网络编程的朋友已经看出来了.
Torque2.0网络接口基于NetConnection,是用的UDP,也就是监听端口,然后再主循环中运行Net::Process来主动rec消息.有兴趣的朋友可以看看代码,晚安~ :)