C# 套接字:客户端在接收大缓冲区时未收到所有字节
C# Sockets: Client not recieving all bytes when receiving large buffer
因此,当服务器发送少于 16384 字节 时,客户端会毫无问题地接收所有数据。
但是,一旦服务器发送的数量超过该数量,客户端就会收到奇怪的数量,例如 6140。它并不总是这样做,但大部分时间都会这样做,但它经常这样做。它很少收到完整的 16384 字节。
起初我以为连接需要很长时间才能发送全部金额,所以我将接收超时设置为 30 秒(30000 毫秒)。
那没有解决任何问题。每次我接收数据时,我都使用一个新的字节数组,这样它就不会被另一个 BeginRecieve()
覆盖。
我唯一能想到的是硬件(计算机)无法在内存中保存这些大量字节,因此截断了缓冲区。
我有很多代码,但这是基础:
服务器代码(部分):
private void RecieveCallBack(IAsyncResult ar)
{
Socket client = (Socket)ar.AsyncState;
try
{
int len = client.EndReceive(ar);
byte[] tempBuffer = new byte[len];
Array.Copy(buffer, tempBuffer, len);
object RequestObject = GetRequestObject(tempBuffer); // just deserializes the buffer
byte[] response = GetResponseFromObject(RequestObject, client); // creates a buffer to return to the client
client.BeginSend(response, 0, response.Length, SocketFlags.None, new AsyncCallback(SendCallBack), client);
}
catch (Exception)
{
Program.Log("Client disconnected");
client.Close();
clientSockets.Remove(client);
Program.UpdateClientCount(clientSockets.Count);
}
}
private void SendCallBack(IAsyncResult ar)
{
Socket client = (Socket)ar.AsyncState;
int sent = client.EndSend(ar);
Program.Log(sent); // log shows that server sent 16384 bytes
try
{
client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(RecieveCallBack), client);
}
catch (Exception e)
{
Program.Log(e.Message);
}
}
客户代码(部分):
static void SendRequest(object obj)
{
byte[] buffer = SerializeObject(obj);
try
{
serverSockect.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(SendCallBack), null);
}
catch // the send failed
{
serverSockect.Close();
ConnectedToServer = false;
}
}
static Queue<byte[]> buffers = new Queue<byte[]>();
static private void SendCallBack(IAsyncResult ar)
{
serverSockect.EndSend(ar);
try
{
// Create a new buffer for each new BeginRecieve.
// If one global buffer was used, then it might get overwritten by
// a second request when the first request hasn't yet been completed.
byte[] buffer = new byte[16384];
buffers.Enqueue(buffer);
serverSockect.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(RecieveCallBack), null);
}
catch // the recieve failed
{
serverSockect.Close();
ConnectedToServer = false;
}
}
static private void RecieveCallBack(IAsyncResult ar)
{
byte[] buffer = buffers.Dequeue();
int len = serverSockect.EndReceive(ar); // len is usually some weird number
byte[] tempBuffer = new byte[len];
Array.Copy(buffer, tempBuffer, len);
object returnObj = GetResponseObject(tempBuffer);
HandleResponse(returnObj);
}
正如 BugFinder 在评论中已经提到的,TCP 流可以拆分为多个数据包。数据包将以正确的顺序接收,但您不能确定缓冲区是否包含完整的消息。因此,在您的接收方法中,您必须合并接收到的数据,直到获得完整的消息。
另一方面,短消息可以合并为一个数据包。这两者的混合也是可能的(数据包 1 包含消息 1 的第 1 部分,数据包 2 包含消息 1 的第 2 部分和消息 2 的第 1 部分,数据包 3 包含消息 2 的第 2 部分)。因此,您需要一个可以解析以识别消息结尾的协议。
如果你有序列化的对象,最好的方法是在开头有序列化对象的长度。但是,如果没有收到长度的完整数据,请确保能够正确处理数据。在最坏的情况下,每个字节都可能来自一个自己的数据包!
因此,当服务器发送少于 16384 字节 时,客户端会毫无问题地接收所有数据。 但是,一旦服务器发送的数量超过该数量,客户端就会收到奇怪的数量,例如 6140。它并不总是这样做,但大部分时间都会这样做,但它经常这样做。它很少收到完整的 16384 字节。
起初我以为连接需要很长时间才能发送全部金额,所以我将接收超时设置为 30 秒(30000 毫秒)。
那没有解决任何问题。每次我接收数据时,我都使用一个新的字节数组,这样它就不会被另一个 BeginRecieve()
覆盖。
我唯一能想到的是硬件(计算机)无法在内存中保存这些大量字节,因此截断了缓冲区。 我有很多代码,但这是基础:
服务器代码(部分):
private void RecieveCallBack(IAsyncResult ar)
{
Socket client = (Socket)ar.AsyncState;
try
{
int len = client.EndReceive(ar);
byte[] tempBuffer = new byte[len];
Array.Copy(buffer, tempBuffer, len);
object RequestObject = GetRequestObject(tempBuffer); // just deserializes the buffer
byte[] response = GetResponseFromObject(RequestObject, client); // creates a buffer to return to the client
client.BeginSend(response, 0, response.Length, SocketFlags.None, new AsyncCallback(SendCallBack), client);
}
catch (Exception)
{
Program.Log("Client disconnected");
client.Close();
clientSockets.Remove(client);
Program.UpdateClientCount(clientSockets.Count);
}
}
private void SendCallBack(IAsyncResult ar)
{
Socket client = (Socket)ar.AsyncState;
int sent = client.EndSend(ar);
Program.Log(sent); // log shows that server sent 16384 bytes
try
{
client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(RecieveCallBack), client);
}
catch (Exception e)
{
Program.Log(e.Message);
}
}
客户代码(部分):
static void SendRequest(object obj)
{
byte[] buffer = SerializeObject(obj);
try
{
serverSockect.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(SendCallBack), null);
}
catch // the send failed
{
serverSockect.Close();
ConnectedToServer = false;
}
}
static Queue<byte[]> buffers = new Queue<byte[]>();
static private void SendCallBack(IAsyncResult ar)
{
serverSockect.EndSend(ar);
try
{
// Create a new buffer for each new BeginRecieve.
// If one global buffer was used, then it might get overwritten by
// a second request when the first request hasn't yet been completed.
byte[] buffer = new byte[16384];
buffers.Enqueue(buffer);
serverSockect.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(RecieveCallBack), null);
}
catch // the recieve failed
{
serverSockect.Close();
ConnectedToServer = false;
}
}
static private void RecieveCallBack(IAsyncResult ar)
{
byte[] buffer = buffers.Dequeue();
int len = serverSockect.EndReceive(ar); // len is usually some weird number
byte[] tempBuffer = new byte[len];
Array.Copy(buffer, tempBuffer, len);
object returnObj = GetResponseObject(tempBuffer);
HandleResponse(returnObj);
}
正如 BugFinder 在评论中已经提到的,TCP 流可以拆分为多个数据包。数据包将以正确的顺序接收,但您不能确定缓冲区是否包含完整的消息。因此,在您的接收方法中,您必须合并接收到的数据,直到获得完整的消息。
另一方面,短消息可以合并为一个数据包。这两者的混合也是可能的(数据包 1 包含消息 1 的第 1 部分,数据包 2 包含消息 1 的第 2 部分和消息 2 的第 1 部分,数据包 3 包含消息 2 的第 2 部分)。因此,您需要一个可以解析以识别消息结尾的协议。
如果你有序列化的对象,最好的方法是在开头有序列化对象的长度。但是,如果没有收到长度的完整数据,请确保能够正确处理数据。在最坏的情况下,每个字节都可能来自一个自己的数据包!