无法从C#中的.Net套接字正确读取数据

本文关键字:读取 数据 套接字 Net 中的 | 更新日期: 2023-09-27 18:29:57

我在C#中有一个使用套接字通信的客户端和服务器类。服务器看起来像这样:

    public class AsyncTcpServer
    {
        private Socket _server_socket;
        private Socket _client_socket;
        private byte[] _receive_buffer;
        private byte[] _send_buffer;
        private NetworkStream _ns;

        public void Start()
        {
            try
            {
                _server_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _server_socket.Bind(new IPEndPoint(IPAddress.Any, 17999));
                _server_socket.Listen(0);
                _server_socket.BeginAccept(new AsyncCallback(BeginAccept), null);
            }
            catch(Exception e)
            {
                Debug.Print(e.Message);
            }
        }
        private void BeginAccept(IAsyncResult ar)
        {
            try
            {         
                _client_socket = _server_socket.EndAccept(ar);
                _receive_buffer = new byte[_client_socket.ReceiveBufferSize];
                _send_buffer = new byte[_client_socket.ReceiveBufferSize]; 
                _ns = new NetworkStream(_client_socket);
                _client_socket.BeginReceive(_receive_buffer, 0, _receive_buffer.Length, SocketFlags.None, new AsyncCallback(RecieveCallback), null);
            }
            catch(Exception e)
            {
                Debug.Print(e.Message);
            }
        }
        private void RecieveCallback(IAsyncResult ar)
        {
            try 
            {
                string text = Encoding.ASCII.GetString(_receive_buffer);
                Debug.Print("Server Received: " + text);                
            }
            catch (Exception e)
            {
                Debug.Print("Unexpected exception: " + e.Message);
            }
        }
        public void Send(byte [] bytes)
        {
            try
            {
                _ns.Write(bytes, 0, bytes.Length);
            }
            catch (Exception e)
            {
                Debug.Print("Unexpected exception: " + e.Message);
            }            
        }
    }

客户端看起来是这样的:

    public class AsyncTcpClient
    {
        private Socket _client_socket;
        private byte[] _buffer;
        private const int HEADER_SIZE = sizeof(int);
        public void Start()
        {
            _client_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);         
            _client_socket.BeginConnect(new IPEndPoint(IPAddress.Loopback, 17999), new AsyncCallback(ConnectCallback), null);
        }
        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                _client_socket.EndConnect(ar);                
                _buffer = new byte[_client_socket.ReceiveBufferSize];
                StartReceive();
                byte[] buffer = Encoding.ASCII.GetBytes("Connected!");
                _client_socket.Send(buffer);
            }
            catch (Exception e)
            {
                Debug.Print(e.Message);
            }
        }
        private void StartReceive(int offset = 0)
        {
            try
            {
                _client_socket.BeginReceive(_buffer, offset, _buffer.Length, SocketFlags.None, new AsyncCallback(RecieveCallback), null);
            }
            catch (Exception e)
            {
                Debug.Print(e.Message);
            }
        }
        private void RecieveCallback(IAsyncResult ar)
        {
            try
            {
                int bytes_processed = 0;
                int bytes_read = _client_socket.EndReceive(ar);
                if (bytes_read > 0)
                {
                    NetworkStream ns = new NetworkStream(_client_socket);
                    while (ns.DataAvailable && (bytes_processed < bytes_read))
                    {
                        byte[] len_bytes = new byte[HEADER_SIZE];
                        ns.Read(len_bytes, 0, HEADER_SIZE);
                        int current_chunk_size = BitConverter.ToInt32(len_bytes, 0);
                        if (current_chunk_size > 0)
                        {
                            byte[] data_buff = new byte[current_chunk_size];
                            ns.Read(data_buff, 0, current_chunk_size);
                            string s = Encoding.ASCII.GetString(data_buff);
                            bytes_processed += (HEADER_SIZE + current_chunk_size);
                            Debug.WriteLine(s);
                        }
                    }                                        
                }
                StartReceive();         
            }
            catch (Exception e)
            {
                Debug.Print(e.Message);
            }
            StartReceive();
        }
    }

他们的合作方式如下:

  • 服务器启动
  • 客户端连接
  • 服务器向客户端发送自定义数据包以供其消费

我使用以下"数据结构"在服务器端打包我的传输数据发送到客户端:

{[DATA_LENGTH_IN_BYTES][PAYLOAD_BYTES]}

在客户端,我解析前4个字节(sizeof(int))以确定负载长度,然后解析负载本身。这在我第一次这样做时是有效的,但在那之后,NetworkStreamDataAvailable成员为false,我无法解析其余的有效载荷。

为什么DataAvailable为false?我对用C#做这些事情还很陌生——我是不是完全错了?

提前感谢!

无法从C#中的.Net套接字正确读取数据

这是我确定的解决方案:

服务器:

public class Listener
    {
        private TcpListener _listener;
        private TcpClient _client;
        public void Start()
        {
            _listener = new TcpListener(IPAddress.Loopback, 17999);
            _listener.Start();
            _listener.BeginAcceptTcpClient(new AsyncCallback(AcceptTcpClientCallback), _listener);
        }
        private void AcceptTcpClientCallback(IAsyncResult ar)
        {
            try
            {
                Debug.WriteLine("Accepted tcp client callback");
                _client = _listener.EndAcceptTcpClient(ar);               
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }            
        }
        public void Write(string data)
        {
            try
            {
                NetworkStream ns = _client.GetStream();
                byte[] buffer = Encoding.ASCII.GetBytes(data);
                ns.Write(buffer, 0, buffer.Length);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }
        }
    }

客户:

public class Client
    {
        private TcpClient _client;
        private byte[] _buffer;
        public void Start()
        {
            try
            {
                _client = new TcpClient();
                _client.BeginConnect(IPAddress.Loopback, 17999, new AsyncCallback(ConnectCallback), _client);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }
        }
        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                NetworkStream ns = _client.GetStream();
                _buffer = new byte[_client.ReceiveBufferSize];
                ns.BeginRead(_buffer, 0, _buffer.Length, new AsyncCallback(ReadCallback), null);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }
        }
        private void ReadCallback(IAsyncResult ar)
        {
            try
            {
                NetworkStream ns = _client.GetStream();
                int read = ns.EndRead(ar);
                string data = Encoding.ASCII.GetString(_buffer, 0, read);
                var res = data.Split(new [] { ''r', ''n' }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var r in res)
                {
                    Debug.WriteLine(r); // process messages
                }
                ns.BeginRead(_buffer, 0, _buffer.Length, ReadCallback, _client);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }            
        }
    }

从服务器到客户端的消息形成如下:

string message = "This is a message" + "'r'n";
_listener.Send(message);

我喜欢这个替代方案,因为它很简单。它更短,(至少对我来说)更容易管理。

HTH

我想您忘记了RecieveCallback中的EndReceive。(服务器代码)

    private void RecieveCallback(IAsyncResult ar)
    {
        try 
        {
            int bytesReceived = _client_socket.EndReceive(ar); // <---
            string text = Encoding.ASCII.GetString(_receive_buffer);
            Debug.Print("Server Received: " + text);                
        }
        catch (Exception e)
        {
            Debug.Print("Unexpected exception: " + e.Message);
        }
    }

我建议创建一个读/写套接字的类(实现协议)。处理对Sockets的读/写的基类,Sockets是从SocketConnection派生的客户端套接字,但首先将连接到ipendpoint。具有SocketConnection列表的服务器。通过这种方式,您可以将客户端/服务器功能和套接字上的消息处理分离开来。但两者都使用相同的代码来接收/发送消息。这里有一个例子:

// base class that handle receive/sent packets
class SocketConnection
{
    // the reason for a Start method is that event can be bound before the Start is executed.
    void Start(Socket socket)
    {
        StartReceive();
    }
    void StartReceive()
    {
        socket.BeginReceive(...);
    }
    void EndReceive()
    {
        socket.EndReceive(...);
        // handle received message.
        // call the ondata event or something
        StartReceive();
    }
}
class ClientSocket : SocketConnection
{
    void Connect()
    {
        Socket socket = new Socket(...);
        socket.Connect(..);
        // start receiving from the client socket.
        base.Start(socket);
    }
}
class Server
{
    List<SocketConnection> _clients;
    void Start()
    {
        // Create server socket + listening etc..
        StartAccept();
    }
    void StartAccept()
    {
        serverSocket.BeginAccept(...);
    }
    void EndAccept()
    {
        Socket serverClientSocket = EndAccept(...);
        // create a base socket handler.....
        SocketConnection clientSocket = new SocketConnection();
        _clients.Add(clientSocket);
        // bind the ondata event of the client and pass it to the clientondata event of the server.
        // Start receiving from the socket.
        clientSocket.Start(serverClientSocket);
        // accept new clients.
        StartAccept();
    }
}

更新:

"关于如何处理缓冲区,你认为避免丢失单独数据包中数据的最佳方法是什么?"

我会先发送一个尺寸:

// sending part.
string message = "This is a message";
byte[] buffer = Encoding.ASCII.GetBytes(message);
_client_socket.Send(BitConverter.GetBytes(buffer.Length)); // sends a int as 4 bytes.
_client_socket.Send(data);

// receiving part.
// try to receive at least 4 bytes. (no more/ no less)
int length = BitConverter.ToInt32(buffer, 0); 
// try to receive data with `length` size,  (no more/ no less)

这将分离不同的数据包。


异步示例:


您仍然需要添加一些异常处理代码。

public static class SocketReader
{
    public static void ReadFromSocket(Socket socket, int count, Action<byte[]> endRead)
    {
        // read from socket, construct a new buffer.
        DoReadFromSocket(socket, 0, count, new byte[count], endRead);
    }
    public static void ReadFromSocket(Socket socket, int count, ref byte[] buffer, Action<byte[]> endRead)
    {
        // if you do have a buffer available, you can pass that one. (this way you do not construct new buffers for receiving.
        // the ref is because if the buffer is too small, it will return the newly created buffer.
        // if the buffer is too small, create a new one.
        if (buffer.Length < count)
            buffer = new byte[count];
        DoReadFromSocket(socket, 0, count, buffer, endRead);
    }
    // This method will continues read until count bytes are read. (or socket is closed)
    private static void DoReadFromSocket(Socket socket, int bytesRead, int count, byte[] buffer, Action<byte[]> endRead)
    {
        // Start a BeginReceive.
        socket.BeginReceive(buffer, bytesRead, count - bytesRead, SocketFlags.None, (result) =>
        {
            // Get the bytes read.
            int read = socket.EndReceive(result);
            // if zero bytes received, the socket isn't available anymore.
            if (read == 0)
            {
                endRead(new byte[0]);
                return;
            }
            // increase the bytesRead, (index point for the buffer)
            bytesRead += read;
            // if all bytes are read, call the endRead with the buffer.
            if (bytesRead == count)
                endRead(buffer);
            else
                // if not all bytes received, start another BeginReceive.
                DoReadFromSocket(socket, bytesRead, count, buffer, endRead);
        }, null);
    }
}

如何使用示例:

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

// read header. (length of data) (4 bytes)
SocketReader.ReadFromSocket(socket, 4, (headerBuffer) =>
    {
        if (headerBuffer.Length == 0)
        {
            // disconnected;
            return;
        }

        int length = BitConverter.ToInt32(headerBuffer, 0);
        // read bytes specified in length.
        SocketReader.ReadFromSocket(socket, length, (dataBuffer) =>
            {
                if (dataBuffer.Length == 0)
                {
                    // disconnected;
                    return;
                }
                // if you want this in a stream, you can do: This way the stream is readonly and only wraps arround the bytearray.
                using (MemoryStream stream = new MemoryStream(dataBuffer, 0, length))
                using (StreamReader reader = new StreamReader(stream))
                    while (!reader.EndOfStream)
                        Debug.WriteLine(reader.ReadLine());
            });
    });