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

时间:2013-09-02 09:56:40

标签: c# sockets asyncsocket networkstream

我在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))以确定有效负载长度,然后解析有效负载本身。这是第一次我这样做但在此之后DataAvailable的{​​{1}}成员是假的,我无法解析其余的有效负载。

为什么NetworkStream是假的?我很擅长用C#做这些事情 - 我是不是完全以错误的方式接近它?

提前致谢!

2 个答案:

答案 0 :(得分:2)

我想你忘记了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);
        }
    }

我建议创建一个读/写套接字的类(实现协议)。一个处理Socket读取/写入的Base类,一个从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());

            });

    });

答案 1 :(得分:2)

以下是我解决的解决方案:

服务器:

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