如何重复使用TCP客户端?

3
我在使用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();
        }
    }

静态 void 入口点:

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

错误

我收到的错误是在第一个循环成功完成后,出现了The operation is not allowed on non-connected sockets.

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

编辑 进一步检查来自服务器的HTTP响应,它里面有 Connection: Close。我假设由于这是原始TCP,它不会解析HTTP。这可能是问题的根源吗?(如果是,是否有忽略它的方法)

3个回答

2

在您的方法中更改顺序,以便每次迭代都创建一个新对象。

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

我看到了问题,他不想创建多个实例。 - Uwe Keim
虽然这个函数在同步程序中可以正常工作,但是当我尝试在多线程环境中使用它时,它并不能帮助我进行套接字消耗的处理。由于操作系统的套接字清理与线程迭代不匹配,因此单个线程将创建多个套接字,快速消耗资源。 - James
如果你要这样做,Dispose 应该可能调用 Close。但我不认为没有令人信服的理由不将 Close 重命名为 Dispose。也许是与 Open 对称,但实际上 Close 只是处置而已。 - cHao
@cHao 是的,我同意,但这只是给 OP 的一个提示,我试图表明在每个 for 步骤之后不应该处理连接。还有一些阻塞代码缺失,以及异常处理和参数验证等。 - oleksii

0

我认为你的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 异常,其根本原因是 TcpClient 仍在关闭。 - James
我想知道它是否期望按特定顺序发生事件以使连接可读可写?编辑:您必须实现iDisposable的原因是什么?为什么不尝试将其删除并查看它是否更可预测? - Ted Spence

0
当第一次调用Execute时,using关闭(处理)m_tcpClient。 您不需要那些using
using( var stream = m_tcpClient.GetStream() )

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接