RakNet V2.454中对完成端口的修复

RakNet 2.454版本的完成端口功能存在缺陷,主要原因是套接字绑定时机不当和重复捆绑。通过调整绑定时机并调用特定函数SetupIOCompletionPortSocket,可以在服务器端解决此问题,允许更多客户端连接。已提供修复后的源代码下载链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    RakNet是RakkarSoft的一个网络引擎(http://www.rakkarsoft.com/) 。该引擎有商业版和非商业版之分,非商业版意味着免费,同时也意味着代码质量不会很高,因为它不可能傻到把免费版做来比商业版要好。虽然2.454版本的文档明确了可以使用完成端口(注意:RakNet后续版本都不再支持完成端口),但是在实际代码中,完成端口部分的代码却不能工作。由于项目需要,我对其进行了修复。

    下面是在修改过程中对RakNet连接过程及错误的简要分析:

    下图展示了利用RakNet提供的C/S聊天程序进行连接的过程。无论是否采用完成端口,该过程都是相同的。

    下图为状态转换图,清晰地显示了创建连接的全过程。

RakNet 2.454版本中,完成端口不能正常工作主要原因如下:

1、  将加密套接字与完成端口绑定的时机不对。从连接图我们可以看出客户端最好的时机是在客户端收到ID_CONNECTION_REQUEST_ACCEPTED,服务器最好的时机为在收到ID_NEW_INCOMING_CONNECTION的时候。

2、  在程序中,作者直接在创建利用完成端口机制通讯的套接字时,创建了一个与原来监听套接字完全重复捆绑的套接字,导致只能有很少的客户能连入[1]。其实,从代码中可以看出,作者已经意识到这个问题,因而他提供了如下解决函数(但是,不知怎么的,始终没有调用该函数)

#ifdef __USE_IO_COMPLETION_PORTS

bool RakPeer::SetupIOCompletionPortSocket( int index )

{

       SOCKET newSocket;

 

       if ( remoteSystemList[ index ].reliabilityLayer.GetSocket() != INVALID_SOCKET )

              closesocket( remoteSystemList[ index ].reliabilityLayer.GetSocket() );

       newSocket = SocketLayer::Instance()->CreateBoundSocket( myPlayerId.port + index + 1, false , NULL ); // 用端口号不同来得到不同的socket

       SocketLayer::Instance()->Connect( newSocket, remoteSystemList[ index ].playerId.binaryAddress, remoteSystemList[ index ].playerId.port ); // port is the port of the client

 

       remoteSystemList[ index ].reliabilityLayer.SetSocket( newSocket );

 

       // Associate our new socket with a completion port and do the first read

       return SocketLayer::Instance()->AssociateSocketWithCompletionPortAndRead( newSocket, remoteSystemList[ index ].playerId.binaryAddress, remoteSystemList[ index ].playerId.port, this );

}

#endif

因此,在服务器端需要创建完成端口的时候调用该函数就能够解决上述问题。

点击此处[下载]修改后的源代码。



[1] 在代码中作者在创建套接字的时候采用了SO_REUSEADDR选项。《UNIX网络编程 1卷:套接口API》(第三版)180页中对该选项的说明:SO_REUSEADDR允许完全重复的捆绑,当一个IP地址和端口已经绑定到某个套接口上时,如果传输协议支持,同样的IP地址和端口还可以捆绑到另一个套接口上,一般来说本特性仅支持UDP套接口。本特性用于多播时,允许在同一个主机上同时运行同一个应用程序的多个副本。当一个UDP数据报需由这些重复捆绑套接口中的一个接收时,所用规则为:如果该数据报的宿地址是一个广播地址或多播地址,那就给每个匹配的套接口递送一个该数据报的拷贝;但是如果该数据报的宿地址是一个单播地址,那么它只递送给单个套接口。在单播数据报情况下,如果有多个套接口匹配该数据报,那么它该由哪个套接口接受的选择取决于实现。

国人开发强悍IOCP代码,全部代码 其中一个单元的代码: unit uIOCompletionPort; interface uses Windows, WinSock2, uWin32Const, uException, uDIProtocol, uDIClientChannel; type TIOCompletionPort = class private m_hCompletionPort: Thandle; private procedure CreateCompletionPort; public function AssociateSocketWithCompletionPort( hDevice: THandle; dwCompletionKey: DWORD): Boolean; function GetIOCompletionStatus( var FClientChannel: TDIClientChannel; var pHandleData: PPerHandleData; var dwIoSize: DWORD): Boolean; overload; function GetIOCompletionStatus( var FClientChannel: TDIClientChannel; var pHandleData: PPerHandleData; var dwIoSize: DWORD; dwMilliseconds: DWORD): Boolean; overload; function PostIOCompletionStatus( lpCompletionKey: DWORD; lpOverlapped: POverlapped; lpNumberOfBytesTransferred: DWORD): Boolean; public constructor Create; destructor Destroy; override; end; implementation constructor TIOCompletionPort.Create; begin inherited Create; CreateCompletionPort; end; destructor TIOCompletionPort.Destroy; begin CloseHandle(m_hCompletionPort); inherited Destroy; end; procedure TIOCompletionPort.CreateCompletionPort; var s: TSocket; begin s := Winsock2.socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (s = Winsock2.INVALID_SOCKET) then raise TException.Create(ErrWin32Error, GetLastError(), 'Winsock2.socket'); m_hCompletionPort := CreateIOCompletionPort(s, 0, 0, 0); if (m_hCompletionPort = 0) then raise TException.Create(ErrWin32Error, GetLastError(), 'CreateIOCompletionPort'); Winsock2.closesocket(s); end; function TIOCompletionPort.AssociateSocketWithCompletionPort( hDevice: THandle; dwCompletionKey: DWORD ): Boolean; var h: THandle; begin Result := TRUE; h := CreateIOCompletionPort(hDevice, m_hCompletionPort, dwCompletionKey, 0); if (h m_hCompletionPort) then begin Result := FALSE; raise TException.Create(ErrWin32Error, GetLastError(), 'AssociateSocketWithCompletionPort'); end; end; function TIOCompletionPort.GetIOCompletionStatus( var FClientChannel: TDIClientChannel; var pHandleData: PPerHandleData; var dwIoSize: DWORD): Boolean; begin Result := GetQueuedCompletionStatus( m_hCompletionPort, dwIOSize, DWORD(FClientChannel), POVERLAPPED(pHandleData), INFINITE ); end; function TIOCompletionPort.GetIOCompletionStatus( var FClientChannel: TDIClientChannel; var pHandleData: PPerHandleData; var dwIoSize: DWORD; dwMilliseconds: DWORD): Boolean; var bRet: Boolean; nLastError: DWORD; begin bRet := TRUE; if FALSE = GetQueuedCompletionStatus( m_hCompletionPort, dwIOSize, DWORD(FClientChannel), POVERLAPPED(pHandleData), INFINITE ) then begin nLastError := GetLastError(); if (nLastError WAIT_TIMEOUT) then raise TException.Create(ErrWin32Error, GetLastError(), 'GetQueuedCompletionStatus'); bRet := FALSE; end; Result := bRet; end; function TIOCompletionPort.PostIOCompletionStatus( lpCompletionKey: DWORD; lpOverlapped: POverlapped; lpNumberOfBytesTransferred: DWORD): Boolean; begin Result := PostQueuedCompletionStatus( m_hCompletionPort, lpNumberOfBytesTransferred, lpCompletionKey, lpOverlapped ); end; end.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值