【问题标题】:How do I re-use TCP Client?如何重用 TCP 客户端?
【发布时间】:2012-07-23 17:33:59
【问题描述】:

在与多线程和套接字消耗的兼容性方面,我遇到了 System.Net.WebRequest 和 System.Net.HttpRequest 的问题。我试图降低一个级别并推出我自己的简单 Http 类。

由于之前的问题是每个线程都太快地创建了太多套接字,因此我尝试在多次迭代(for 循环)中使用单个套接字(每个线程 1 个)。

代码:

我的测试类(有硬编码的 ip 和端口,直到我可以让它工作):

public sealed class Foo : IDisposable {

        private string m_ip = "localhost";
        private int m_port = 52395;
        private TcpClient m_tcpClient;


        public Foo() {
            m_tcpClient = new TcpClient( m_ip, m_port );
        }

        public void Execute() {
            using( var stream = m_tcpClient.GetStream() )
            using( var writer = new StreamWriter( stream ) )
            using( var reader = new StreamReader( stream ) ) {
                writer.AutoFlush = true;
                // Send request headers
                writer.WriteLine( "GET /File HTTP/1.1" );
                writer.WriteLine( "Host: " + m_ip + ":" + m_port.ToString() );
                writer.WriteLine( "Connection: Keep-Alive" );
                writer.WriteLine();
                writer.WriteLine();

                // Read the response from server
                string response = reader.ReadToEnd();
                Console.WriteLine( response );
            }
        }    

        void IDisposable.Dispose() {
            m_tcpClient.Client.Dispose();
        }
    }

静态无效主要:

using( Foo foo = new Foo() ) {
    for( int i = 0; i < 10; i++ ) {
        foo.Execute();
    }
}

错误

在 for 循环的第一次迭代成功完成后,我收到的错误是 The operation is not allowed on non-connected sockets.

我了解错误的原因,(读取响应后TcpClient.Client 关闭),但我不知道如何明确告诉套接字保持打开状态。

编辑 进一步检查我从服务器返回的 HTTP 响应,其中包含 Connection: Close。我假设因为这是原始 TCP,所以它不会解析 HTTP。这可能是问题的根源吗? (如果是有没有办法忽略它)

【问题讨论】:

    标签: c# tcp tcpclient system.net.sockets


    【解决方案1】:

    main 方法中更改顺序,这样每次迭代都会创建一个新对象

    for( int i = 0; i < 10; i++ ) 
    {
        using( Foo foo = new Foo() ) 
        {
            foo.Execute();
        }
    }
    

    如果你想保持你的套接字打开,你需要稍微重构你的应用程序,这样它就不会在一次迭代后调用Dispose,例如

    public sealed class Foo : IDisposable {    
        private string m_ip = "localhost";
        private int m_port = 52395;
        private TcpClient m_tcpClient;
    
        private Stream stream;
        private StreamWriter writer;
        private StreamReader reader;
    
        public void Execute() {         
            // Send request headers
                ...    
            // Read the response from server                
        }   
    
        void Open(){
            m_tcpClient = new TcpClient( m_ip, m_port );
            stream = m_tcpClient.GetStream();
            writer = new StreamWriter( stream );
            reader = new StreamReader( stream );
        }   
    
        void Close() {
            m_tcpClient.Client.Dispose();
            reader.Dispose();
            writer.Dispose();
            stream.Dispose();
        }
    
        //Plus Dispose implementation
    }
    

    这是用法

    using( Foo foo = new Foo() ) {
        foo.Open();
        for( int i = 0; i < 10; i++ ) {
            foo.Execute();
        }
        foo.Close();
    }
    

    【讨论】:

    • 我读到他想要创建多个实例的问题。
    • 虽然这将在同步程序中起作用,但当我尝试在多线程环境中使用它时,它并不能帮助我处理套接字消耗。 (操作系统对socket的清理与线程迭代不匹配,单个线程会创建多个socket,消耗资源很快)
    • Dispose 应该调用Close,如果你要这样做的话。不过,我没有看到一个令人信服的理由不将Close 重命名为Dispose。与Open 对称,也许……但Close 实际上只是处置,所以……
    • @cHao 是的,我同意,但这只是对 OP 的提示,我试图表明不应在每个 for 步骤之后处理连接。还有一些阻塞代码缺失,以及异常处理和参数验证等。
    【解决方案2】:

    我认为您的 KeepAlive 实施不太正确。当您关闭流时,您正在关闭底层套接字。将您的流创建移至构造函数。

    public Foo() 
    {
        m_tcpClient = new TcpClient( m_ip, m_port );
        m_tcpStream = m_tcpClient.GetStream();
    }
    

    然后让它们都活着,直到整个对象都关闭:

        void IDisposable.Dispose() 
        {
            m_tcpStream.Close();
            m_tcpClient.Client.Dispose();
        }
    

    【讨论】:

    • 这导致错误Stream was not writable System.IO Exception,其根源是TcpClient仍在关闭
    • 我想知道它是否期望特定的事件序列以使连接可​​读可写?编辑:您是否有理由必须实施 iDisposable?为什么不尝试删除它,看看它的行为是否更可预测?
    【解决方案3】:

    当第一个 Execute 被调用时,using 关闭(释放)m_tcpClient。你不需要那些usings

    using( var stream = m_tcpClient.GetStream() )
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-08
      • 1970-01-01
      • 1970-01-01
      • 2014-04-23
      • 1970-01-01
      • 1970-01-01
      • 2010-11-18
      相关资源
      最近更新 更多