C# - 套接字未接收所有字节

C# - Sockets not receiving all bytes

我一直在努力改正我的错误。我的 client/server 在同一台机器上似乎工作正常。当代码在另一台 PC 上执行时,出于某种原因只有部分字节到达服务器。无论我尝试多少,我似乎都无法让字节通过。似乎截止点是367字节,它不想再传输了。如果有人知道我做错了什么,将不胜感激。

提前致谢!

服务器:

using System;
using System.Data.SQLite;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;

namespace DMAssist
{

    // State object for reading client data asynchronously
    public class StateObject
    {
        // Client  socket.
        public Socket workSocket = null;
        // Size of receive buffer.
        public const int BufferSize = 1024;
        // Receive buffer.
        public byte[] buffer = new byte[BufferSize];
        // Received data string.
        public StringBuilder sb = new StringBuilder();
        // Current URL string
    }

    public class asyncserver
    {

        public asyncserver()
        {
        }

        // Thread signal.
        public static ManualResetEvent allDone = new ManualResetEvent(false);

        public static string GetLocalIPAddress()
        {
            var host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (var ip in host.AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    return ip.ToString();
                }
            }
            throw new Exception("Local IP Address Not Found!");
        }

        public void StartListening()
        {
            // Data buffer for incoming data.
            byte[] bytes = new Byte[1024];

            // Establish the local endpoint for the socket.
            // The DNS name of the computer
            // running the listener is "host.contoso.com".
            IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
            IPAddress ipAddress = ipHostInfo.AddressList[0];
            IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 25599);

            // Create a TCP/IP socket.
            Socket listener = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream, ProtocolType.Tcp);

            // Bind the socket to the local endpoint and listen for incoming connections.
            try
            {
                listener.Bind(localEndPoint);
                listener.Listen(100);

                while (true)
                {
                    // Set the event to nonsignaled state.
                    allDone.Reset();

                    // Start an asynchronous socket to listen for connections.
                    Console.WriteLine("Waiting for a connection...");
                    listener.BeginAccept(
                        new AsyncCallback(AcceptCallback),
                        listener);

                    // Wait until a connection is made before continuing.
                    allDone.WaitOne();
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

            Console.WriteLine("\nPress ENTER to continue...");
            Console.Read();

        }

        public void AcceptCallback(IAsyncResult ar)
        {
            // Signal the main thread to continue.
            allDone.Set();

            // Get the socket that handles the client request.
            Socket listener = (Socket)ar.AsyncState;
            Socket handler = listener.EndAccept(ar);

            // Create the state object.
            StateObject state = new StateObject();
            state.workSocket = handler;
            handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                new AsyncCallback(ReadCallback), state);
        }


        public byte[] ObjectToByteArray(object obj)
        {
            if (obj == null)
                return null;

            BinaryFormatter bf = new BinaryFormatter();
            MemoryStream ms = new MemoryStream();
            bf.Serialize(ms, obj);
            return ms.ToArray();
        }

        private Object ByteArrayToObject(byte[] arrBytes)
        {
            MemoryStream memStream = new MemoryStream();
            BinaryFormatter binForm = new BinaryFormatter();
            memStream.Write(arrBytes, 0, arrBytes.Length);
            memStream.Seek(0, SeekOrigin.Begin);
            Object obj = (Object)binForm.Deserialize(memStream);
            return obj;
        }


        public void ReadCallback(IAsyncResult ar)
        {
            String content = String.Empty;
            String connectedAddress = String.Empty;
            // Retrieve the state object and the handler socket
            // from the asynchronous state object.
            StateObject state = (StateObject)ar.AsyncState;
            Socket handler = state.workSocket;
            IPEndPoint remoteIpEndPoint = handler.RemoteEndPoint as IPEndPoint;
            connectedAddress = remoteIpEndPoint.Address.ToString();

            // Read data from the client socket. 
            int bytesRead = handler.EndReceive(ar);

            if (bytesRead > 0)
            {

                Console.WriteLine("Bytes read: " + bytesRead);

                // There  might be more data, so store the data received so far.
                state.sb.Append(Encoding.ASCII.GetString(
                    state.buffer, 0, bytesRead));

                object rawbytes = ByteArrayToObject(state.buffer);
                netObject recvobject = (netObject)rawbytes;

                Send(handler, (object)recvobject);
            }
   }

        private void Send(Socket handler, object data)
        {
            // Convert the string data to byte data using ASCII encoding.
            byte[] byteData = ObjectToByteArray(data);

            // Begin sending the data to the remote device.
            handler.BeginSend(byteData, 0, byteData.Length, 0,
                new AsyncCallback(SendCallback), handler);
        }

        private static void SendCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.
                Socket handler = (Socket)ar.AsyncState;

                // Complete sending the data to the remote device.
                int bytesSent = handler.EndSend(ar);
                Console.WriteLine("Sent {0} bytes to client.", bytesSent);

                handler.Shutdown(SocketShutdown.Both);
                handler.Close();

            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
    }
}

客户:

 using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    using System.Text;
    using System.Windows.Forms;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.IO;
    using System.Collections;

    namespace DMAssist
    {
        public class asyncsclient {

            public asyncsclient()
            {
            }

            public byte[] ObjectToByteArray(object obj)
            {
                if (obj == null)
                    return null;

                BinaryFormatter bf = new BinaryFormatter();
                MemoryStream ms = new MemoryStream();
                bf.Serialize(ms, obj);
                return ms.ToArray();
            }

            private Object ByteArrayToObject(byte[] arrBytes)
            {
                MemoryStream memStream = new MemoryStream();
                BinaryFormatter binForm = new BinaryFormatter();
                memStream.Write(arrBytes, 0, arrBytes.Length);
                memStream.Seek(0, SeekOrigin.Begin);
                Object obj = (Object)binForm.Deserialize(memStream);
                return obj;
            }

            static byte[] GetBytes(string str)
            {
                byte[] bytes = new byte[str.Length * sizeof(char)];
                System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
                return bytes;
            }

            static string GetString(byte[] bytes)
            {
                char[] chars = new char[bytes.Length / sizeof(char)];
                System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
                return new string(chars);
            }

            public object sendObject(object outgoingObject, bool expectRecieve)
            {
                try
                {

                    Socket soc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    IPAddress ip = IPAddress.Parse("x.x.x.x");
                    IPEndPoint remoteEP = new IPEndPoint(ip, 25599);
                    soc.Connect(remoteEP);

                    byte[] byData = ObjectToByteArray(outgoingObject);

                    //Console.WriteLine("Array length:" + byData.Length);

                    soc.Send(byData);


                    if (expectRecieve)
                    {
                        byte[] buffer = new byte[1024];
                        int iRx = soc.Receive(buffer);

                        object rawbytes = ByteArrayToObject(buffer);
                        netObject recvobject = (netObject)rawbytes;

                        Console.WriteLine("Writing stopped");
                        soc.Close();
                        soc.Dispose();

                        return recvobject;
                    }
                    else
                    {
                        soc.Close();
                        soc.Dispose();

                        return "";
                    }
                }
                catch (Exception)
                {
                    MessageBox.Show("Server unreachable. Make sure server is broadcasting on port 25599 successfully.", "DMAssist Error 0xC1");
                }
                return null;
            }
        }
    }

这是设计使然。这是开发人员在套接字方面常犯的错误。

要正确接收数据,您需要一直循环接收数据,直到接收到您需要的字节数。继续追加到接收缓冲区,直到获得所需的字节数。

一些套接字实现有一个 "wait for all" 标志的概念(例如 MSG_WAITALL),但如果远程在发送后关闭连接,即使那样也会 return 部分数据。

防御性编码。代码就好像发送方一次只发送 1 个字节一样。你会很好的。

正如您在代码中提到的,可能有更多数据,因此您存储目前收到的数据。但是您没有代码获取其余数据。

MSDN 有一个关于如何正确执行此操作的示例:

https://msdn.microsoft.com/en-us/library/bew39x2a.aspx?f=255&MSPPError=-2147217396

UDP套接字和TCP套接字之间有很大的区别。 您尝试使用 UDP 会更好,因为该协议更面向数据包。这意味着如果服务器发送一定数量的字节,客户端 'might' 接收相同的一定数量的字节或根本接收不到任何东西。

对于 TCP,它是不同的,因为它是一种流式传输协议,这意味着只要服务器认为合适,字节就会按顺序发送。通常它会尝试填充到 mtu,当你收到时,你很可能会收到你必须自己附加的多个块中的数据。 TCP 也是面向连接且可靠的。字节从客户端确认到服务器,反之亦然,如果未及时收到确认,则重新传输。这意味着即使是在 TCP 级别的数据包也可能被乱序接收。 TCP 会将它们组合在一起。您在应用程序级别将按顺序收到所有内容。

目前你得到的建议是正确的,你必须继续呼叫接收。直到没有任何东西可以接收为止。你怎么知道再也没有什么可接收的了?通常这是通过在接收中接收长度为 0 的 TCP 消息来检测的。这意味着对等方已关闭套接字的末端并且将不再发送。这反过来意味着您可以关闭套接字的末端。

来自winsock2.h: #define MSG_WAITALL 0x8 /* 在数据包完全填满之前不完成 */

您可以将此选项添加到您的 .net 流套接字接收中,如下所示: socket.Receive(缓冲区,(SocketFlags)0x8)

这应该保留消息边界,就像您习惯使用 dgram 套接字一样。