我已经使用TcpClient类实现了一个套接字客户端.所以我可以发送和接收数据,一切都很好.但我问一些大师在那里:)我的实施有什么问题吗?也许有更好的做事方式.特别是,我该如何处理断开连接?是否有一些指示器(或者我可以自己写一个)告诉我套接字已断开连接?
我也研究过异步等待Socket类的功能,但不能把我的脑袋包裹起来"SocketAsyncEventArgs",为什么它首先出现在那里.为什么我不能:等待Client.SendAsync("data"); ?
public class Client { private TcpClient tcpClient; public void Initialize(string ip, int port) { try { tcpClient = new TcpClient(ip, port); if (tcpClient.Connected) Console.WriteLine("Connected to: {0}:{1}", ip, port); } catch (Exception ex) { Console.WriteLine(ex.Message); Initialize(ip, port); } } public void BeginRead() { var buffer = new byte[4096]; var ns = tcpClient.GetStream(); ns.BeginRead(buffer, 0, buffer.Length, EndRead, buffer); } public void EndRead(IAsyncResult result) { var buffer = (byte[])result.AsyncState; var ns = tcpClient.GetStream(); var bytesAvailable = ns.EndRead(result); Console.WriteLine(Encoding.ASCII.GetString(buffer, 0, bytesAvailable)); BeginRead(); } public void BeginSend(string xml) { var bytes = Encoding.ASCII.GetBytes(xml); var ns = tcpClient.GetStream(); ns.BeginWrite(bytes, 0, bytes.Length, EndSend, bytes); } public void EndSend(IAsyncResult result) { var bytes = (byte[])result.AsyncState; Console.WriteLine("Sent {0} bytes to server.", bytes.Length); Console.WriteLine("Sent: {0}", Encoding.ASCII.GetString(bytes)); } }
用法:
static void Main(string[] args) { var client = new Client(); client.Initialize("127.0.0.1", 8778); client.BeginRead(); client.BeginSend(""); Console.ReadLine(); } John
Mateusz.. 9
好吧,我花了10秒钟才找到你可以做到的最大问题:
public void BeginRead() { var buffer = new byte[4096]; var ns = tcpClient.GetStream(); ns.BeginRead(buffer, 0, buffer.Length, EndRead, buffer); }
但不要担心这就是为什么我们在SO上.
首先让我解释为什么这是一个很大的问题.
假设您正在发送长度为4097字节的消息.您的缓冲区只能接受4096个字节,这意味着您无法将整个消息打包到此缓冲区中.
假设您正在发送长度为12个字节的消息.你仍在内存中分配4096个字节只是为了存储12个字节.
怎么处理这个?
每次使用网络时,您都应该考虑制定某种协议(有些人称之为消息框架,但它只是一个协议),它将帮助您识别包装内容.
协议示例可以是:
[1B =消息类型] [4B =长度] [XB =消息]
- where X == BitConvert.ToInt32(length);
接收器:
byte messageType = (byte)netStream.ReadByte(); byte[] lengthBuffer = new byte[sizeof(int)]; int recv = netStream.Read(lengthBuffer, 0, lengthBuffer.Length); if(recv == sizeof(int)) { int messageLen = BitConverter.ToInt32(lengthBuffer, 0); byte[] messageBuffer = new byte[messageLen]; recv = netStream.Read(messageBuffer, 0, messageBuffer.Length); if(recv == messageLen) { // messageBuffer contains your whole message ... } }
发件人:
byte messageType = (1 << 3); // assume that 0000 1000 would be XML byte[] message = Encoding.ASCII.GetBytes(xml); byte[] length = BitConverter.GetBytes(message.Length); byte[] buffer = new byte[sizeof(int) + message.Length + 1]; buffer[0] = messageType; for(int i = 0; i < sizeof(int); i++) { buffer[i + 1] = length[i]; } for(int i = 0; i < message.Length; i++) { buffer[i + 1 + sizeof(int)] = message[i]; } netStream.Write(buffer);
其余的代码看起来还不错.但在我看来,在你的情况下使用异步操作是没用的.您可以对同步调用执行相同的操作.
好吧,我花了10秒钟才找到你可以做到的最大问题:
public void BeginRead() { var buffer = new byte[4096]; var ns = tcpClient.GetStream(); ns.BeginRead(buffer, 0, buffer.Length, EndRead, buffer); }
但不要担心这就是为什么我们在SO上.
首先让我解释为什么这是一个很大的问题.
假设您正在发送长度为4097字节的消息.您的缓冲区只能接受4096个字节,这意味着您无法将整个消息打包到此缓冲区中.
假设您正在发送长度为12个字节的消息.你仍在内存中分配4096个字节只是为了存储12个字节.
怎么处理这个?
每次使用网络时,您都应该考虑制定某种协议(有些人称之为消息框架,但它只是一个协议),它将帮助您识别包装内容.
协议示例可以是:
[1B =消息类型] [4B =长度] [XB =消息]
- where X == BitConvert.ToInt32(length);
接收器:
byte messageType = (byte)netStream.ReadByte(); byte[] lengthBuffer = new byte[sizeof(int)]; int recv = netStream.Read(lengthBuffer, 0, lengthBuffer.Length); if(recv == sizeof(int)) { int messageLen = BitConverter.ToInt32(lengthBuffer, 0); byte[] messageBuffer = new byte[messageLen]; recv = netStream.Read(messageBuffer, 0, messageBuffer.Length); if(recv == messageLen) { // messageBuffer contains your whole message ... } }
发件人:
byte messageType = (1 << 3); // assume that 0000 1000 would be XML byte[] message = Encoding.ASCII.GetBytes(xml); byte[] length = BitConverter.GetBytes(message.Length); byte[] buffer = new byte[sizeof(int) + message.Length + 1]; buffer[0] = messageType; for(int i = 0; i < sizeof(int); i++) { buffer[i + 1] = length[i]; } for(int i = 0; i < message.Length; i++) { buffer[i + 1 + sizeof(int)] = message[i]; } netStream.Write(buffer);
其余的代码看起来还不错.但在我看来,在你的情况下使用异步操作是没用的.您可以对同步调用执行相同的操作.
这很难回答,因为这里没有确切的问题,但更多的是某种代码审查.但仍有一些提示:
您的连接机制似乎错了.TcpClient.Connected
在连接建立之前,我认为不会阻止.所以它通常会在连接正在进行时失败,然后你重新开始.您应该切换到使用阻止或异步Connect
方法.
SocketAsyncEventArgs是一种用于高性能异步数据传输的机制.很少需要它.你应该忽略它
如果要异步发送数据,则应使用Async
返回a 的方法Task
,因为这些方法可以很容易地与async/await结合使用.
APM模型(BeginXYZ/EndXYZ)已被弃用,您不应再在新代码中使用它.它的一个问题是有时在Begin方法中同步调用End方法,这可能导致令人惊讶的行为.如果不是这种情况,则将从ThreadPool上的随机线程执行完成回调.这通常也不是你想要的.TPL方法避免了这种情况.
对于您的简单用例,阻塞方法也很完美,并没有各种异步方法的复杂性.
使用TPL方法(未经测试)的代码读取方:
public async Task Initialize(string ip, int port) { tcpClient = new TcpClient; await tcpClient.ConnectAsync(ip, port); Console.WriteLine("Connected to: {0}:{1}", ip, port); } public async Task Read() { var buffer = new byte[4096]; var ns = tcpClient.GetStream(); while (true) { var bytesRead = await ns.ReadAsync(buffer, 0, buffer.Length); if (bytesRead == 0) return; // Stream was closed Console.WriteLine(Encoding.ASCII.GetString(buffer, 0, bytesRead)); } }
在初始化部分,您将执行以下操作:
await client.Initialize(ip, port); // Start reading task Task.Run(() => client.Read());
对于使用同步方法,删除所有出现Async
并用Thread替换Task.