简体   繁体   English

使用TcpClient类C#的异步套接字客户端

[英]Async socket client using TcpClient class C#

I have implemented a socket client using TcpClient class. 我已经使用TcpClient类实现了一个套接字客户端。 So I can send and receive data, everything works good. 所以我可以发送和接收数据,一切都很好。 But I ask some of the guru's out there :) Is there something wrong with my implementation? 但我问一些大师在那里:)我的实施有什么问题吗? maybe there is a better way of doing things. 也许有更好的做事方式。 In particular, how do I handle disconnects? 特别是,我该如何处理断开连接? Is there some indicator (or maybe I can write one myself) that tells me that socket has disconnected? 是否有一些指示器(或者我可以自己编写一个)告诉我套接字已断开连接?

I also have looked into async await features of Socket class, but can't wrap my head around "SocketAsyncEventArgs", why is it there in the first place. 我也研究过异步等待Socket类的功能,但不能把我的脑袋包裹起来“SocketAsyncEventArgs”,为什么它首先出现在那里。 Why cant i just: await Client.SendAsync("data"); 为什么我不能: 等待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));
    }
}

And the usage: 用法:

static void Main(string[] args)
{
    var client = new Client();
    client.Initialize("127.0.0.1", 8778);

    client.BeginRead();
    client.BeginSend("<Names><Name>John</Name></Names>");

    Console.ReadLine();
}

Okay it took me 10 seconds to find biggest issue you could've done : 好吧,我花了10秒钟才找到你可以做到的最大问题:

public void BeginRead()
{
    var buffer = new byte[4096];
    var ns = tcpClient.GetStream();
    ns.BeginRead(buffer, 0, buffer.Length, EndRead, buffer);
}

But don't worry that's why we are on SO. 但不要担心这就是为什么我们在SO上。


First let me explain why it's such a big issue. 首先让我解释为什么这是一个很大的问题。

Assume that you're sending message which is 4097 bytes long. 假设您正在发送长度为4097字节的消息。 Your buffer can only accept 4096 bytes meaning that you cannot pack whole message into this buffer. 您的缓冲区只能接受4096个字节,这意味着您无法将整个消息打包到此缓冲区中。

Assume you're sending message which is 12 bytes long. 假设您正在发送长度为12个字节的消息。 You're still allocating 4096 bytes in your memory just to store 12 bytes . 你仍在内存中分配4096个字节只是为了存储12个字节

How to deal with this? 怎么处理这个?

Every time you work with networking you should consider making some kind of protocol ( some people call it message framing , but it's just a protocol ) that will help you to identify incomming package. 每次使用网络时,您都应该考虑制定某种协议 (有些人称之为消息框架 ,但它只是一个协议),这将有助于您识别包装。

Example of a protocol could be : 协议示例可以是:

[1B = type of message][4B = length][XB = message] [1B =消息类型] [4B =长度] [XB =消息]
- where X == BitConvert.ToInt32(length);

  • Receiver: 接收器:

     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 ... } } 
  • Sender: 发件人:

     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); 

Rest of your code looks okay. 其余的代码看起来还不错。 But in my opinion using asynchronous operations in your case is just useless. 但在我看来,在你的情况下使用异步操作是没用的。 You can do the same with synchronous calls. 您可以对同步调用执行相同的操作。

It's hard to answer because theres no exact question here but more some kind of code review. 这很难回答,因为这里没有确切的问题,但更多的是某种代码审查。 But still some hints: 但仍有一些提示:

  • Your connect mechanism seems wrong. 您的连接机制似乎错了。 I don't think TcpClient.Connected will block until the connection was established. 我不认为TcpClient.Connected将阻止,直到建立连接。 So it will often just fail as the connection is in progress and then you start all over again. 所以它通常会在连接正在进行时失败,然后你重新开始。 You should switch to using a blocking or async Connect method. 您应该切换到使用阻止或异步Connect方法。
  • SocketAsyncEventArgs is a mechanism for high performance async data transmission. SocketAsyncEventArgs是一种用于高性能异步数据传输的机制。 There's rarely a need for it. 很少需要它。 You should just ignore it 你应该忽略它
  • If you want to send data asynchronously you should use the Async methods which return a Task , because these can be easily combined with async/await. 如果要异步发送数据,则应使用返回TaskAsync方法,因为这些方法可以很容易地与async / await结合使用。
  • The APM model (BeginXYZ/EndXYZ) is kind of deprecated, you shouldn't use it anymore in new code. APM模型(BeginXYZ / EndXYZ)已被弃用,您不应再在新代码中使用它。 One issue with it is that sometimes the End method is called synchronously inside the Begin method which can lead to surprising behavior. 它的一个问题是有时在Begin方法中同步调用End方法,这可能导致令人惊讶的行为。 If this is not the case the completion callback will be performed from a random thread on the ThreadPool. 如果不是这种情况,则将从ThreadPool上的随机线程执行完成回调。 This is also often not what you want. 这通常也不是你想要的。 The TPL methods avoid this. TPL方法避免了这种情况。
  • For your simple use case the blocking methods are also totally fine and do not come with the complexity of the various async methods. 对于您的简单用例,阻塞方法也很完美,并没有各种异步方法的复杂性。

The reading side of the code with TPL methods (untested): 使用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));
    }
}

In the initialization part you would do: 在初始化部分,您将执行以下操作:

await client.Initialize(ip, port);
// Start reading task
Task.Run(() => client.Read());

For using synchronous methods delete all Async occurences and replace Task with Thread. 对于使用同步方法,删除所有Async并用线程替换任务。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM