简体   繁体   中英

A very simple TCP server/client in C# occasionally drops packets. What can I do to prevent this?

I've got an intra-PC communication server / client set up to send and receive data from one program to another - in this case, a custom server that is listening to text commands and Unity3D.

For the most part, it works, however every once in awhile, it will drop packets, and Unity will not get them without multiple attempts. The packets seem to be sent but lost, as I do see the "Sent message" console log. The following is the code for the server and client:

SERVER:

class TCPGameServer
{
    public event EventHandler Error;

    public Action<Data> ADelegate;

    TcpListener TCPListener;
    TcpClient TCPClient;
    Client ActiveClient;

    NetworkStream networkStream;

    StreamWriter returnWriter;
    StreamReader streamReader;

    Timer SystemTimer = new Timer();
    Timer PingTimer = new Timer();

    int Port = 8637;

    public TCPGameServer()
    {
        TCPListener = new TcpListener(IPAddress.Loopback, Port);
        SystemTimer.Elapsed += StreamTimer_Tick;
        SystemTimer.AutoReset = true;
        SystemTimer.Interval = 2000;

        PingTimer.Elapsed += PingTimer_Tick;
        PingTimer.AutoReset = true;
        PingTimer.Interval = 30000;
    }

    public void OpenListener()
    {
        TCPListener.Start();
        TCPListener.BeginAcceptTcpClient(AcceptTCPCallBack, null);
        Console.WriteLine("Network Open.");
    }

    public void GameLogout()
    {
        SystemTimer.AutoReset = false;
        SystemTimer.Stop();

        PingTimer.AutoReset = false;
        PingTimer.Stop();

        ActiveClient = null;

        returnWriter.Dispose();
        streamReader.Dispose();

        Console.WriteLine("The client has logged out successfully.");
    }

    private void AcceptTCPCallBack(IAsyncResult asyncResult)
    {
        TCPClient = null;
        ActiveClient = null;
        returnWriter = null;
        streamReader = null;

        try
        {
            TCPClient = TCPListener.EndAcceptTcpClient(asyncResult);

            TCPListener.BeginAcceptTcpClient(AcceptTCPCallBack, null);


            ActiveClient = new Client(TCPClient);
            networkStream = ActiveClient.NetworkStream;

            returnWriter = new StreamWriter(TCPClient.GetStream());
            streamReader = new StreamReader(TCPClient.GetStream());

            Console.WriteLine("Client Connected Successfully.");

            Data Packet = new Data();
            Packet.cmdCommand = Command.Login;
            Packet.strName = "Server";
            Packet.strMessage = "LOGGEDIN";
            SendMessage(Packet);


            SystemTimer.AutoReset = true;
            SystemTimer.Enabled = true;
            SystemTimer.Start();

            Ping();

            PingTimer.AutoReset = true;
            PingTimer.Enabled = true;
            PingTimer.Start();

        } catch (Exception ex)
        {
            OnError(TCPListener, ex);
            return;
        }


    }

    private void StreamTimer_Tick(object source, System.Timers.ElapsedEventArgs e)
    {
        CheckStream();
    }

    private void PingTimer_Tick(object source, System.Timers.ElapsedEventArgs e)
    {
        Ping();
    }

    private void Ping()
    {
        if (TCPClient.Connected)
        {
            Data Packet = new Data();
            Packet.cmdCommand = Command.Ping;
            Packet.strName = "Server";
            Packet.strMessage = "PING";

            SendMessage(Packet);
        }
    }


    public void CheckStream()
    {
        try
        {
            if (TCPClient.Available > 0 || streamReader.Peek() >= 0)
            {
                string PacketString = streamReader.ReadLine();
                Data packet = JsonConvert.DeserializeObject<Data>(PacketString);

                switch (packet.cmdCommand)
                {
                    case Command.Logout:
                        GameLogout();
                        break;
                    case Command.Message:
                        if (ADelegate != null)
                        {
                            ADelegate(packet);
                        }
                        break;
                    case Command.Ping:
                        Console.WriteLine("PONG!");
                        break;
               }
            }
        } catch (IOException e)
        {
            Console.WriteLine(e.Message);
        } catch (NullReferenceException e)
        {
            Console.WriteLine(e.Message);
        }
    }

    public void SendMessage(Data packet)
    {
        if (ActiveClient != null)
        {
            string packetMessage = JsonConvert.SerializeObject(packet);
            returnWriter.WriteLine(packetMessage);
            returnWriter.Flush();
        }
    }


    public void OnError(object sender, Exception ex)
    {
        EventHandler handler = Error;
        if (handler != null)
        {
            ErrorEventArgs e = new ErrorEventArgs(ex);
            handler(sender, e);
        }
    }

    public void RegisterActionDelegate(Action<Data> RegisterDelegate)
    {
        ADelegate += RegisterDelegate;
    }

    public void UnRegisterActionDelegate(Action<Data> UnregisterDelegate)
    {
        ADelegate -= UnregisterDelegate;
    }
} 

CLIENT:

public class TCPNetworkClient
{

    public Action<Data> PacketDelegate;

    public TcpClient TCPClient;
    int Port = 8637;

    StreamReader streamReader;
    StreamWriter streamWriter;

    Timer StreamTimer = new Timer();

    public bool LoggedIn = false;



    public void Start()
    {
        if (LoggedIn == false)
        {
            TCPClient = null;
            StreamTimer.AutoReset = true;
            StreamTimer.Interval = 2000;
            StreamTimer.Elapsed += StreamTimer_Tick;


            try
            {
                TCPClient = new TcpClient("127.0.0.1", Port);

                streamReader = new StreamReader(TCPClient.GetStream());
                streamWriter = new StreamWriter(TCPClient.GetStream());

                StreamTimer.Enabled = true;
                StreamTimer.Start();
            }
            catch (Exception ex)
            {
                Debug.Log(ex.Message);
            }
        }

    }

    private void StreamTimer_Tick(System.Object source,    System.Timers.ElapsedEventArgs e)
    {
        if (TCPClient.Available > 0 || streamReader.Peek() >= 0)
        {
            string PacketString = streamReader.ReadLine();
            Data packet = JsonConvert.DeserializeObject<Data>(PacketString);

            PacketDelegate(packet);
        } 
    }

    public void Logout()
    {
        Data Packet = new Data();
        Packet.cmdCommand = Command.Logout;
        Packet.strMessage = "LOGOUT";
        Packet.strName = "Game";
        SendMessage(Packet);

        if (streamReader != null && streamWriter != null)
        {
            streamReader.Dispose();
            streamWriter.Dispose();
            TCPClient.Close();
            TCPClient = null;
            streamReader = null;
            streamWriter = null;
        }

        StreamTimer.Stop();
    }

    public void SendMessage(Data packet)
    {
        string packetMessage = JsonConvert.SerializeObject(packet);

        try
        {
            streamWriter.WriteLine(packetMessage);
            streamWriter.Flush();
        } catch (Exception e)
        {

        }
    }

    public void RegisterActionDelegate(Action<Data> RegisterDelegate)
    {
        PacketDelegate += RegisterDelegate;
    }

    public void UnRegisterActionDelegate(Action<Data> UnregisterDelegate)
    {
        PacketDelegate -= UnregisterDelegate;
    }
}

I'm not really sure what's going on, or if there are any more additional checks that I need to add into the system. Note: It's TCP so that "when" this fully works, I can drop the client into other programs that I might write that may not fully rely or use Unity.

  1. new TcpClient("127.0.0.1", Port) is not appropriate for the client. Just use TcpClient() . There is no need to specify IP and port, both of which will end up being wrong.
  2. TCPClient.Available is almost always a bug. You seem to assume that TCP is packet based. You can't test whether a full message is incoming or not. TCP only offers a boundaryless stream of bytes. Therefore, this Available check does not tell you if a whole line is available. Also, there could be multiple lines. The correct way to read is to have a reading loop always running and simply reading lines without checking. Any line that arrives will be processed that way. No need for timers etc.
  3. The server has the same problems.

Issue (2) might have caused the appearance of lost packets somehow. You need to fix this in any case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM