【问题标题】:Send string via TCP on receiving commands在接收命令时通过 TCP 发送字符串
【发布时间】:2019-10-02 14:27:04
【问题描述】:

我正在开发一个阅读器程序。它基于 Winorms。

我需要一个代码,winform 将通过 TCP(端口 3573)按需发送一些数据。 (通过需求,我的意思是当程序通过 TCP 接收到命令 GET 时。

我是一个新手,所以这个主题看起来很难将所有这些结合起来:线程、TCPRead TCP 发送、事件处理程序......

所以我在这里需要有关如何实现它的完整代码或示例的帮助。

我尝试了一些来自互联网的示例代码,但没有一个有效(线程、TCPreader 和 TCPsender、TCPreaded 的事件处理)

在 TCP Reader 上,我们收到 GET,然后我们发送一些字符串,让 TCP Sender 说“hello world”

【问题讨论】:

  • 这是一个很大的领域......坦率地说,我认为尝试在 SO 示例中获得所有这些可能是雄心勃勃的 - 这是一个大量的工作要做网络代码正确,任何适合适量代码的东西几乎肯定是危险天真的......
  • 是的,继续阅读、研究、练习,它就会出现。网上肯定有例子。祝你好运
  • 我明白你的意思,但这是工业编程中的基本功能,用于与 PC 和 PC 在 TCP 级别的通信设备,然后继续与其他系统进行硬件数据处理。一个清晰的简单示例对许多工业程序员很有用。
  • @Ihor.Z。 “清晰+简单,简短,准确” - 选择任何两个。上次我写关于网络代码的博客时,花了 4 篇 非常 长的文章 整个 github 存储库...
  • @marc-gravell 好的。 :) 是否要清晰而简短地开始,然后在问题发生时对其进行故障排除?

标签: c# winforms tcp reader


【解决方案1】:

Sockets 真的很难搞定,而且 API 实在是……讨厌。由于这只是要求错误,我将推荐使用“管道”API,它更符合现代async 代码并且更容易正确(并且更好帧处理方面的选项)。所以;这是一个管道示例;

请注意,这需要 Pipelines.Sockets.Unofficial,它位于 nuget via:

<PackageReference Include="Pipelines.Sockets.Unofficial" Version="2.0.22" />

(添加它会自动添加您需要的所有其他部分)

using Pipelines.Sockets.Unofficial;
using System;
using System.IO.Pipelines;
using System.Net;
using System.Text;
using System.Threading.Tasks;

static class Program
{
    static async Task Main()
    {
        var endpoint = new IPEndPoint(IPAddress.Loopback, 9042);
        Console.WriteLine("[server] Starting server...");
        using (var server = new MyServer())
        {
            server.Listen(endpoint);

            Console.WriteLine("[server] Starting client...");
            Task reader;
            using (var client = await SocketConnection.ConnectAsync(endpoint))
            {
                reader = Task.Run(() => ShowIncomingDataAsync(client.Input));

                await WriteAsync(client.Output, "hello");
                await WriteAsync(client.Output, "world");

                Console.WriteLine("press [return]");
                Console.ReadLine();
            }
            await reader;
            server.Stop();
        }
    }

    private static async Task ShowIncomingDataAsync(PipeReader input)
    {
        try
        {
            while (true)
            {
                var read = await input.ReadAsync();
                var buffer = read.Buffer;
                if (buffer.IsEmpty && read.IsCompleted) break; // EOF

                Console.WriteLine($"[client] Received {buffer.Length} bytes; marking consumed");
                foreach (var segment in buffer)
                {   // usually only one segment, but can be more complex
                    Console.WriteLine("[client] " + Program.GetAsciiString(segment.Span));
                }
                input.AdvanceTo(buffer.End); // "we ate it all"
            }
        }
        catch { }
    }

    private static async Task WriteAsync(PipeWriter output, string payload)
    {
        var bytes = Encoding.ASCII.GetBytes(payload);
        await output.WriteAsync(bytes);
    }

    internal static unsafe string GetAsciiString(ReadOnlySpan<byte> span)
    {
        fixed (byte* b = span)
        {
            return Encoding.ASCII.GetString(b, span.Length);
        }
    }
}

class MyServer : SocketServer
{
    protected override Task OnClientConnectedAsync(in ClientConnection client)
        => RunClient(client);
    private async Task RunClient(ClientConnection client)
    {
        Console.WriteLine($"[server] new client: {client.RemoteEndPoint}");
        await ProcessRequests(client.Transport);
        Console.WriteLine($"[server] ended client: {client.RemoteEndPoint}");
    }

    private async Task ProcessRequests(IDuplexPipe transport)
    {
        try
        {
            var input = transport.Input;
            var output = transport.Output;
            while (true)
            {
                var read = await input.ReadAsync();
                var buffer = read.Buffer;
                if (buffer.IsEmpty && read.IsCompleted) break; // EOF

                Console.WriteLine($"[server] Received {buffer.Length} bytes; returning it, and marking consumed");
                foreach (var segment in buffer)
                {   // usually only one segment, but can be more complex
                    Console.WriteLine("[server] " + Program.GetAsciiString(segment.Span));
                    await output.WriteAsync(segment);
                }
                input.AdvanceTo(buffer.End); // "we ate it all"
            }
        }
        catch { }
    }
}

可以使用原始套接字编写此代码,但需要更多代码来展示最佳实践并避免问题 - 所有这些丑陋都已隐藏在“管道”中。

输出:

[server] Starting server...
[server] Starting client...
[server] new client: 127.0.0.1:63076
press [return]
[server] Received 5 bytes; returning it, and marking consumed
[server] hello
[server] Received 5 bytes; returning it, and marking consumed
[client] Received 5 bytes; marking consumed
[client] hello
[server] world
[client] Received 5 bytes; marking consumed
[client] world

【讨论】:

  • 您可以将它用于任意 TCP 连接,还是它实际上是在 TCP 之上运行并定义消息边界的另一个协议(很好,但与现有协议完全不兼容)?
  • @BenVoigt 完全原始 TCP;通常你会编写一个协议处理器来坐在管道之上,处理帧等。关于管道的最好的事情是你可以说“我检查了所有字节,但只消耗了其中的 12 个” - 非常适合积压处理不完整的框架等
  • @BenVoigt 示例:扫描 CR/LF:如果找到,则将其之前的所有内容作为帧处理;如果没有 CR/LF,就说“我都读完了,没有消耗任何东西”——下次有数据时它会全部还给你
  • 谢谢!这看起来很像我的问题的解决方案。您能否提供有关每个 Pipelines.Sockets.Unofficial 版本的 Net.Framevork 版本的信息?工业计算机相当老旧,有时会使用 Windows 嵌入式或过时的版本,以防 Internet 丢失。所以经常无法使用最新的 Net.Framework 版本。
  • @Ihor.Z。该死,在这种情况下它可能会遇到困难,但这是树:nuget.org/packages/Pipelines.Sockets.Unofficial - 短版本是“.NET Framework 4.6.1、.NET Standard 2.0 或 .NET Core App 2.1” - .NET Standard 2.0 是最“宽” - 但是:听起来您可能需要原始套接字,这......将是大约 8 倍的代码!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-10-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-17
  • 2016-10-11
  • 2014-06-05
相关资源
最近更新 更多