X-Git-Url: http://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fgame%2Fg_net.pas;h=83c8398991ff1ed0429f97212a1f80df69f55440;hb=refs%2Fheads%2Fbuffers2;hp=b9f28b981c9bccac022b724867106534932cc390;hpb=54b1109da7cd0eb8498980a6ad57f2dcda49b022;p=d2df-sdl.git diff --git a/src/game/g_net.pas b/src/game/g_net.pas index b9f28b9..83c8398 100644 --- a/src/game/g_net.pas +++ b/src/game/g_net.pas @@ -22,7 +22,7 @@ uses e_log, e_msg, ENet, Classes, MAPDEF{$IFDEF USE_MINIUPNPC}, miniupnpc;{$ELSE};{$ENDIF} const - NET_PROTOCOL_VER = 173; + NET_PROTOCOL_VER = 181; NET_MAXCLIENTS = 24; NET_CHANS = 11; @@ -44,9 +44,13 @@ const NET_CLIENT = 2; NET_BUFSIZE = $FFFF; + NET_PING_PORT = $DF2D; NET_EVERYONE = -1; + NET_UNRELIABLE = 0; + NET_RELIABLE = 1; + NET_DISC_NONE: enet_uint32 = 0; NET_DISC_PROTOCOL: enet_uint32 = 1; NET_DISC_VERSION: enet_uint32 = 2; @@ -62,19 +66,28 @@ const NET_STATE_AUTH = 1; NET_STATE_GAME = 2; + NET_CONNECT_TIMEOUT = 1000 * 10; + BANLIST_FILENAME = 'banlist.txt'; NETDUMP_FILENAME = 'netdump'; + {$IFDEF FREEBSD} + NilThreadId = nil; + {$ELSE} + NilThreadId = 0; + {$ENDIF} + type TNetClient = record - ID: Byte; - Used: Boolean; - State: Byte; - Peer: pENetPeer; - Player: Word; + ID: Byte; + Used: Boolean; + State: Byte; + Peer: pENetPeer; + Player: Word; RequestedFullUpdate: Boolean; RCONAuth: Boolean; Voted: Boolean; + NetOut: array [0..1] of TMsg; end; TBanRecord = record IP: LongWord; @@ -117,6 +130,7 @@ var NetClientPort: Word = 25666; NetIn, NetOut: TMsg; + NetBuf: array [0..1] of TMsg; NetClients: array of TNetClient; NetClientCount: Byte = 0; @@ -148,7 +162,7 @@ var NetIGDService: TURLStr; {$ENDIF} - NetPortThread: TThreadID = 0; + NetPortThread: TThreadID = NilThreadId; NetDumpFile: TStream; @@ -172,7 +186,7 @@ function g_Net_Client_ByName(Name: string): pTNetClient; function g_Net_Client_ByPlayer(PID: Word): pTNetClient; function g_Net_ClientName_ByID(ID: Integer): string; -procedure g_Net_SendData(Data:AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD); +procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD); function g_Net_Wait_Event(msgId: Word): TMemoryStream; function IpToStr(IP: LongWord): string; @@ -199,7 +213,10 @@ implementation uses SysUtils, e_input, g_nethandler, g_netmsg, g_netmaster, g_player, g_window, g_console, - g_main, g_game, g_language, g_weapons, utils; + g_main, g_game, g_language, g_weapons, utils, ctypes; + +var + g_Net_DownloadTimeout: Single; { /// SERVICE FUNCTIONS /// } @@ -263,6 +280,8 @@ var begin NetIn.Clear(); NetOut.Clear(); + NetBuf[NET_UNRELIABLE].Clear(); + NetBuf[NET_RELIABLE].Clear(); SetLength(NetClients, 0); NetPeer := nil; NetHost := nil; @@ -289,14 +308,62 @@ begin end; procedure g_Net_Flush(); +var + T: Integer; + P: pENetPacket; + F, Chan: enet_uint32; + I: Integer; begin - enet_host_flush(NetHost); + F := 0; + Chan := NET_CHAN_GAME; + + if NetMode = NET_SERVER then + for T := NET_UNRELIABLE to NET_RELIABLE do + begin + if NetBuf[T].CurSize > 0 then + begin + P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F); + if not Assigned(P) then continue; + enet_host_broadcast(NetHost, Chan, P); + NetBuf[T].Clear(); + end; + + for I := Low(NetClients) to High(NetClients) do + begin + if not NetClients[I].Used then continue; + if NetClients[I].NetOut[T].CurSize <= 0 then continue; + P := enet_packet_create(NetClients[I].NetOut[T].Data, NetClients[I].NetOut[T].CurSize, F); + if not Assigned(P) then continue; + enet_peer_send(NetClients[I].Peer, Chan, P); + NetClients[I].NetOut[T].Clear(); + end; + + // next and last iteration is always RELIABLE + F := LongWord(ENET_PACKET_FLAG_RELIABLE); + Chan := NET_CHAN_IMPORTANT; + end + else if NetMode = NET_CLIENT then + for T := NET_UNRELIABLE to NET_RELIABLE do + begin + if NetBuf[T].CurSize > 0 then + begin + P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F); + if not Assigned(P) then continue; + enet_peer_send(NetPeer, Chan, P); + NetBuf[T].Clear(); + end; + // next and last iteration is always RELIABLE + F := LongWord(ENET_PACKET_FLAG_RELIABLE); + Chan := NET_CHAN_IMPORTANT; + end; end; procedure g_Net_Cleanup(); begin NetIn.Clear(); NetOut.Clear(); + NetBuf[NET_UNRELIABLE].Clear(); + NetBuf[NET_RELIABLE].Clear(); SetLength(NetClients, 0); NetClientCount := 0; @@ -318,10 +385,10 @@ begin NetMode := NET_NONE; - if NetPortThread <> 0 then + if NetPortThread <> NilThreadId then WaitForThreadTerminate(NetPortThread, 66666); - NetPortThread := 0; + NetPortThread := NilThreadId; g_Net_UnforwardPorts(); if NetDump then @@ -389,7 +456,7 @@ begin if NetPongSock <> ENET_SOCKET_NULL then begin NetPongAddr.host := IPAddr; - NetPongAddr.port := Port + 1; + NetPongAddr.port := NET_PING_PORT; if enet_socket_bind(NetPongSock, @NetPongAddr) < 0 then begin enet_socket_destroy(NetPongSock); @@ -401,6 +468,8 @@ begin NetMode := NET_SERVER; NetOut.Clear(); + NetBuf[NET_UNRELIABLE].Clear(); + NetBuf[NET_RELIABLE].Clear(); if NetDump then g_Net_DumpStart(); @@ -429,6 +498,8 @@ begin enet_peer_reset(NetClients[I].Peer); NetClients[I].Peer := nil; NetClients[I].Used := False; + NetClients[I].NetOut[NET_UNRELIABLE].Free(); + NetClients[I].NetOut[NET_RELIABLE].Free(); end; if (NetMPeer <> nil) and (NetMHost <> nil) then g_Net_Slist_Disconnect; @@ -447,34 +518,29 @@ end; procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean; Chan: Byte = NET_CHAN_GAME); var - P: pENetPacket; - F: enet_uint32; + T: Integer; begin if (Reliable) then - F := LongWord(ENET_PACKET_FLAG_RELIABLE) + T := NET_RELIABLE else - F := 0; + T := NET_UNRELIABLE; if (ID >= 0) then begin if ID > High(NetClients) then Exit; if NetClients[ID].Peer = nil then Exit; - - P := enet_packet_create(NetOut.Data, NetOut.CurSize, F); - if not Assigned(P) then Exit; - - enet_peer_send(NetClients[ID].Peer, Chan, P); + // write size first + NetClients[ID].NetOut[T].Write(Integer(NetOut.CurSize)); + NetClients[ID].NetOut[T].Write(NetOut); end else begin - P := enet_packet_create(NetOut.Data, NetOut.CurSize, F); - if not Assigned(P) then Exit; - - enet_host_broadcast(NetHost, Chan, P); + // write size first + NetBuf[T].Write(Integer(NetOut.CurSize)); + NetBuf[T].Write(NetOut); end; if NetDump then g_Net_DumpSendBuffer(); - g_Net_Flush(); NetOut.Clear(); end; @@ -504,6 +570,7 @@ begin NetOut.Clear(); NetOut.Write(Byte(Ord('D'))); NetOut.Write(Byte(Ord('F'))); + NetOut.Write(NetPort); NetOut.Write(ClTime); g_Net_Slist_WriteInfo(); NPl := 0; @@ -532,10 +599,8 @@ begin Result := 0; if NetUseMaster then - begin g_Net_Slist_Check; - g_Net_Host_CheckPings; - end; + g_Net_Host_CheckPings; while (enet_host_service(NetHost, @NetEvent, 0) > 0) do begin @@ -576,6 +641,8 @@ begin Byte(NetClients[ID].Peer^.data^) := ID; NetClients[ID].State := NET_STATE_AUTH; NetClients[ID].RCONAuth := False; + NetClients[ID].NetOut[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2); + NetClients[ID].NetOut[NET_RELIABLE].Alloc(NET_BUFSIZE*2); enet_peer_timeout(NetEvent.peer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2); @@ -590,7 +657,7 @@ begin TC := @NetClients[ID]; if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength); - g_Net_HostMsgHandler(TC, NetEvent.packet); + g_Net_Host_HandlePacket(TC, NetEvent.packet, g_Net_HostMsgHandler); end; ENET_EVENT_TYPE_DISCONNECT: @@ -618,6 +685,8 @@ begin TC^.Peer := nil; TC^.Player := 0; TC^.RequestedFullUpdate := False; + TC^.NetOut[NET_UNRELIABLE].Free(); + TC^.NetOut[NET_RELIABLE].Free(); FreeMemory(NetEvent.peer^.data); NetEvent.peer^.data := nil; @@ -682,21 +751,20 @@ end; procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME); var - P: pENetPacket; - F: enet_uint32; + T: Integer; begin if (Reliable) then - F := LongWord(ENET_PACKET_FLAG_RELIABLE) + T := NET_RELIABLE else - F := 0; + T := NET_UNRELIABLE; - P := enet_packet_create(NetOut.Data, NetOut.CurSize, F); - if not Assigned(P) then Exit; + // write size first + NetBuf[T].Write(Integer(NetOut.CurSize)); + NetBuf[T].Write(NetOut); - enet_peer_send(NetPeer, Chan, P); if NetDump then g_Net_DumpSendBuffer(); - g_Net_Flush(); NetOut.Clear(); + g_Net_Flush(); // FIXME: for now, send immediately end; function g_Net_Client_Update(): enet_size_t; @@ -708,7 +776,7 @@ begin ENET_EVENT_TYPE_RECEIVE: begin if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength); - g_Net_ClientMsgHandler(NetEvent.packet); + g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientMsgHandler); end; ENET_EVENT_TYPE_DISCONNECT: @@ -730,7 +798,7 @@ begin ENET_EVENT_TYPE_RECEIVE: begin if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength); - g_Net_ClientLightMsgHandler(NetEvent.packet); + g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientLightMsgHandler); end; ENET_EVENT_TYPE_DISCONNECT: @@ -747,6 +815,7 @@ end; function g_Net_Connect(IP: string; Port: enet_uint16): Boolean; var OuterLoop: Boolean; + TimeoutTime, T: Int64; begin if NetMode <> NET_NONE then begin @@ -795,6 +864,9 @@ begin Exit; end; + // предупредить что ждем слишком долго через N секунд + TimeoutTime := GetTimer() + NET_CONNECT_TIMEOUT; + OuterLoop := True; while OuterLoop do begin @@ -814,13 +886,23 @@ begin end; end; + T := GetTimer(); + if T > TimeoutTime then + begin + TimeoutTime := T + NET_CONNECT_TIMEOUT * 100; // одного предупреждения хватит + g_Console_Add(_lc[I_NET_MSG_TIMEOUT_WARN], True); + g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True); + end; + ProcessLoading(true); - if e_KeyPressed(IK_ESCAPE) or e_KeyPressed(IK_SPACE) or e_KeyPressed(VK_ESCAPE) then + if e_KeyPressed(IK_SPACE) or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or + e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then OuterLoop := False; end; g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_TIMEOUT], True); + g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True); if NetPeer <> nil then enet_peer_reset(NetPeer); if NetHost <> nil then begin @@ -901,7 +983,7 @@ begin end; end; -procedure g_Net_SendData(Data:AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD); +procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD); var P: pENetPacket; F: enet_uint32; @@ -930,59 +1012,71 @@ begin enet_host_flush(NetHost); end; +function UserRequestExit: Boolean; +begin + Result := e_KeyPressed(IK_SPACE) or + e_KeyPressed(IK_ESCAPE) or + e_KeyPressed(VK_ESCAPE) or + e_KeyPressed(JOY0_JUMP) or + e_KeyPressed(JOY1_JUMP) or + e_KeyPressed(JOY2_JUMP) or + e_KeyPressed(JOY3_JUMP) +end; + function g_Net_Wait_Event(msgId: Word): TMemoryStream; -var - downloadEvent: ENetEvent; - OuterLoop: Boolean; - MID: Byte; - Ptr: Pointer; - msgStream: TMemoryStream; + var + ev: ENetEvent; + rMsgId: Byte; + Ptr: Pointer; + stream: TMemoryStream; + status: cint; begin - FillChar(downloadEvent, SizeOf(downloadEvent), 0); - msgStream := nil; - OuterLoop := True; - while OuterLoop do - begin - while (enet_host_service(NetHost, @downloadEvent, 0) > 0) do + FillChar(ev, SizeOf(ev), 0); + stream := nil; + repeat + status := enet_host_service(NetHost, @ev, Trunc(g_Net_DownloadTimeout * 1000)); + if status > 0 then begin - if (downloadEvent.kind = ENET_EVENT_TYPE_RECEIVE) then - begin - Ptr := downloadEvent.packet^.data; - - MID := Byte(Ptr^); - - if (MID = msgId) then + case ev.kind of + ENET_EVENT_TYPE_RECEIVE: + begin + Ptr := ev.packet^.data; + rMsgId := Byte(Ptr^); + if rMsgId = msgId then + begin + stream := TMemoryStream.Create; + stream.SetSize(ev.packet^.dataLength); + stream.WriteBuffer(Ptr^, ev.packet^.dataLength); + stream.Seek(0, soFromBeginning); + status := 1 (* received *) + end + else + begin + (* looks that game state always received, so ignore it *) + e_LogWritefln('g_Net_Wait_Event(%s): skip message %s', [msgId, rMsgId]); + status := 2 (* continue *) + end + end; + ENET_EVENT_TYPE_DISCONNECT: begin - msgStream := TMemoryStream.Create; - msgStream.SetSize(downloadEvent.packet^.dataLength); - msgStream.WriteBuffer(Ptr^, downloadEvent.packet^.dataLength); - msgStream.Seek(0, soFromBeginning); - - OuterLoop := False; - enet_packet_destroy(downloadEvent.packet); - break; - end - else begin - enet_packet_destroy(downloadEvent.packet); + if (ev.data <= NET_DISC_MAX) then + g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True); + status := -2 (* error: disconnected *) end; - end else - if (downloadEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then - begin - if (downloadEvent.data <= NET_DISC_MAX) then - g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + - _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + downloadEvent.data)], True); - OuterLoop := False; - Break; - end; + g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True); + status := -3 (* error: unknown event *) + end; + enet_packet_destroy(ev.packet) + end + else + begin + g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True); + status := 0 (* error: timeout *) end; - ProcessLoading(true); - - if e_KeyPressed(IK_ESCAPE) or e_KeyPressed(IK_SPACE) or e_KeyPressed(VK_ESCAPE) then - break; - end; - Result := msgStream; + until (status <> 2) or UserRequestExit(); + Result := stream end; function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean; @@ -1179,7 +1273,7 @@ begin if ForwardPongPort then begin - StrPort := IntToStr(NetPort + 1); + StrPort := IntToStr(NET_PING_PORT); I := UPNP_AddPortMapping( Urls.controlURL, Addr(data.first.servicetype[1]), PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'), @@ -1249,13 +1343,15 @@ end; {$ENDIF} initialization - + conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it'); + g_Net_DownloadTimeout := 60; NetIn.Alloc(NET_BUFSIZE); NetOut.Alloc(NET_BUFSIZE); - + NetBuf[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2); + NetBuf[NET_RELIABLE].Alloc(NET_BUFSIZE*2); finalization - NetIn.Free(); NetOut.Free(); - + NetBuf[NET_UNRELIABLE].Free(); + NetBuf[NET_RELIABLE].Free(); end.