实现基于线路的网络工作者类的正确方法是什么

发布于 2024-11-03 10:24:26 字数 899 浏览 0 评论 0原文

我正在构建一个 IRC 客户端,并且想要一个独立于 IRC 的网络类,该类仅在客户端和服务器之间发送和接收线路。发送线路是可以的,但我不知道实现侦听器线程的好方法。我尝试过的所有方法都有效,但都有我无法克服的缺陷。

我的第一个方法是使用 TcpClient.GetStream() 作为基础创建一个名为 _readerStreamReader,并且只做一个 _reader。 ReadLine()。这样做的优点是后台侦听器线程会自动停止,直到收到一行。问题是,当我断开连接并停止侦听器线程(或者应用程序刚刚退出)时,我不确定等待 ReadLine() 的线程对于垃圾收集等有多大好处被杀。线程是否会被孤立并以某种方式留在后台窃取资源?

我的第二种方法是基于相同的 TcpClient.GetStream() 创建一个名为 _readerNetworkStream,并创建一个检查 的循环_reader.DataAvailable继续 while 循环(如果为 false),否则将字节推入 StringBuilder,该 StringBuilder 会立即检查 \r\n 并提取任何内容整行。这在数据检索方面与 ReadLine 具有相同的效果,但我不喜欢恒定的 _reader.DataAvailable 循环。在我的 Core i5 CPU 上,它不占用任何 CPU,但在我更强大的 i9 笔记本电脑上,它不断地窃取虚拟 CPU。 Thread.Sleep(1)“解决”了这个问题,但看起来像是一个肮脏的修复,互联网上的许多文章将其归类为代码异味。

那么正确的解决方案是什么?

I'm building an IRC client, and want an IRC independant networking class that just sends and receives lines between the client and server. Sending lines is OK, but I'm at a loss for a good way of implementing a listener thread. All the methods I've tried work, but have flaws that I can't get around.

My first method was to create a StreamReader called _reader using TcpClient.GetStream() as a base, and just doing a _reader.ReadLine(). This has the advantage that the background listener thread automatically stops until a line is received. The problem is that when I disconnect and stop the listener thread (or the application just exits), I'm not sure how good it is for garbage collection etc that the thread with the waiting ReadLine() just gets killed. Could the thread get orphaned and somehow stay in the background stealing resources?

My second method was to create a NetworkStream called _reader based on the same TcpClient.GetStream(), and create a loop that checks _reader.DataAvailable and continue the while loop if it's false, and otherwise push the bytes into a StringBuilder that is instantly checked for \r\n and extracts any whole lines. This gives the same effect as the ReadLine on data retrieval, but I don't like the constant _reader.DataAvailable looping. On my Core i5 CPU, it doesn't take any CPU but on my much more powerful i9 laptop it steals a virtual CPU constantly. Thread.Sleep(1) "solves" this, but seems like a dirty fix, and numerous articles around the internet classify this as a code smell.

So what would be the proper solution?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

泼猴你往哪里跑 2024-11-10 10:24:26

如果您要中止侦听器线程,那就不好了。但如果应用程序退出(或者关闭底层套接字),那就没问题了。

列出大多数其他人,我不建议投票。我建议使用异步读取和写入的解决方案,尽管编程要困难得多。

If you're aborting the listener thread, then that's bad. But if the application exits (or if you close the underlying socket), then it'll be just fine.

List most others, I don't recommend polling. I would suggest a solution that uses asynchronous reads and writes, though it is much more difficult to program.

征﹌骨岁月お 2024-11-10 10:24:26

这里有一些可以让你继续前进的东西。

请注意,构建消息时使用 string 效率不高。但它使一切都更具可读性。

public abstract class LineBasedChannel
{   
    Socket _socket;
    string _inbuffer = string.Empty;
    Encoding _encoding;
    byte[] _buffer = new byte[8192];

    public LineBasedChannel(Socket socket)
    {
        _socket = socket;
        _encoding = Encoding.ASCII;
        _sb = new StringBuilder();
    }

    public void Start()
    {
        _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None,OnRead, null);
    }

    public void OnRead(IAsyncResult res)
    {
        var bytesRead = _socket.EndReceive(res);
        if (bytesRead == 0)
        {
            HandleDisconnect();
            return;
        }

        _inbuffer += _encoding.GetString(_buffer, 0, bytesRead);
        _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None,OnRead, null);

        while (true)
        {
            int pos = _inbuffer.IndexOf("\r\n");
            if (pos == -1)
                break;

            OnReceivedLine(_inbuffer.SubString(0, pos+2);
            _inbuffer = _inbuffer.Remove(0,pos+1);
        }
    }

    protected abstract void OnReceivedLine(string line);
}

public class IrcTcpChannel : LineBasedChannel
{
    protected override void OnReceivedLine(string line)
    {
        var cmd = new IrcCommand();
        cmd.Channel = line.SubString(x,y);
        cmd.Message = line.SubString(z,a);
        CommandReceived(this, new IrcCommandEventArgs(cmd));
    }

    public event EventHandler<IrcCommandEventArgs> CommandReceived = delegate {};
}

另请注意,我没有处理该代码中的任何异常,这是强制性的。异步方法中任何未处理的异常都将终止您的应用程序。

Here is something to get you going.

Note that it's not very efficient to use string when building the messages. But it makes everything much more readable.

public abstract class LineBasedChannel
{   
    Socket _socket;
    string _inbuffer = string.Empty;
    Encoding _encoding;
    byte[] _buffer = new byte[8192];

    public LineBasedChannel(Socket socket)
    {
        _socket = socket;
        _encoding = Encoding.ASCII;
        _sb = new StringBuilder();
    }

    public void Start()
    {
        _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None,OnRead, null);
    }

    public void OnRead(IAsyncResult res)
    {
        var bytesRead = _socket.EndReceive(res);
        if (bytesRead == 0)
        {
            HandleDisconnect();
            return;
        }

        _inbuffer += _encoding.GetString(_buffer, 0, bytesRead);
        _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None,OnRead, null);

        while (true)
        {
            int pos = _inbuffer.IndexOf("\r\n");
            if (pos == -1)
                break;

            OnReceivedLine(_inbuffer.SubString(0, pos+2);
            _inbuffer = _inbuffer.Remove(0,pos+1);
        }
    }

    protected abstract void OnReceivedLine(string line);
}

public class IrcTcpChannel : LineBasedChannel
{
    protected override void OnReceivedLine(string line)
    {
        var cmd = new IrcCommand();
        cmd.Channel = line.SubString(x,y);
        cmd.Message = line.SubString(z,a);
        CommandReceived(this, new IrcCommandEventArgs(cmd));
    }

    public event EventHandler<IrcCommandEventArgs> CommandReceived = delegate {};
}

Also note that I didn't handle any exceptions in that code, which is mandatory. Any unhandled exceptions in async methods will terminate your application.

止于盛夏 2024-11-10 10:24:26

老问题了,问题应该早就解决了。不管怎样,它看起来非常像更老的问题 c# - 处理基于线路的网络 I/O 流的好方法是什么? - Stack Overflow

如果是这样,那里的答案应该提供部分解决方案。基本上,它是一个提供 Process(byte[]) 方法的类,该方法返回 IEnumerable,该方法保持尚未形成完整行的部分内容的状态。

使用它可以分离关注点。只需读取可用的数据即可。每次读取一个块时,将其输入该方法,您就会得到完全形成的行。

Old question, the issue must have settled long ago. Anyway, it looks very much like the even older question c# - What is a good method to handle line based network I/O streams? - Stack Overflow

If so, the answer there should provide part of the solution. Basically, it's a class that provides a Process(byte[]) method returning an IEnumerable<string>, which keeps state of the partial content not yet forming a full line.

Using that will allow to separate concerns. Just read data as it is available. Every time a chunk is read, feed it into that method, you get lines as they are fully formed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文