无法从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))以确定负载长度,然后解析负载本身。这在我第一次这样做时是有效的,但在那之后,NetworkStream
的DataAvailable
成员为false,我无法解析其余的有效载荷。
为什么DataAvailable
为false?我对用C#做这些事情还很陌生——我是不是完全错了?
提前感谢!
这是我确定的解决方案:
服务器:
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());
});
});