多线程套接字连接挂起 c#。套接字超时/挂起

Posted

技术标签:

【中文标题】多线程套接字连接挂起 c#。套接字超时/挂起【英文标题】:Multithreaded socket connection hangs c#. Socket timeout/hangs 【发布时间】:2019-10-14 03:33:20 【问题描述】:

我正在尝试通过套接字连接向两台打印机发送打印指令。该脚本工作正常,然后挂起并返回报告:

System.IO.IOException:无法将数据写入传输 连接:发送或接收数据的请求被拒绝,因为 插座已经在那个方向被关闭了 之前的关机通话

System.Net.Sockets.SocketException (0x80004005):连接尝试失败,因为连接方没有正确响应 一段时间后,或建立连接失败,因为 连接的主机没有响应

问题是间歇性的,我无法通过附加的调试器进行重现。

谁能发现导致这种行为的原因?我遇到了线程安全问题,我认为这可能是罪魁祸首。

为代码量道歉。由于这可能归结为线程,我已经尽可能多地包含了范围。

// main entry point
class HomeController

    List<string> lsLabelResults = new List<string>("label result");
    PrinterBench pbBench        = new PrinterBench("192.168.2.20","192.168.2.21");

    void Process()

        oPrintController = new PrintController(this);

        if(GetLabel())
            // should always come out of the big printer (runs in background)
            oPrintController.PrintBySocketThreaded(lsLabelResults, pbBench.PostageLabelIP);
            // should always come out of the small printer
            oPrintController.PrintWarningLabel();
        
    


class PrintController

    HomeController oHC;

    private static Dictionary<string, Socket> lSocks = new Dictionary<string, Socket>();

    private BackgroundWorker _backgroundWorker;
    static readonly object locker = new object();
    double dProgress;
    bool bPrintSuccess = true;


    public PrintController(HomeController oArg_HC)
    
        oHC = oArg_HC;
    


    public bool InitSocks()
    
        // Ensure the IP's / endpoints of users printers are assigned
        if (!lSocks.ContainsKey(oHC.pbBench.PostageLabelIP))
        
            lSocks.Add(oHC.pbBench.PostageLabelIP, null);
        
        if (!lSocks.ContainsKey(oHC.pbBench.SmallLabelIP))
        
            lSocks.Add(oHC.pbBench.SmallLabelIP, null);
        

        // attempt to create a connection to each socket
        try
        
            foreach (string sKey in lSocks.Keys.ToList())
            
                if (lSocks[sKey] == null || !lSocks[sKey].Connected)
                
                    IPEndPoint ep = new IPEndPoint(IPAddress.Parse(sKey), 9100);
                    lSocks[sKey] = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

                    try
                    

                        //lSocks[sKey].Connect(ep);
                        var result = lSocks[sKey].BeginConnect(ep, null, null);

                        bool success = result.AsyncWaitHandle.WaitOne(2000, true);
                        // dont need success
                        if (lSocks[sKey].Connected)
                        
                            lSocks[sKey].EndConnect(result);
                        
                        else
                        
                            lSocks[sKey].Close();
                            throw new SocketException(10060); // Connection timed out.
                        

                    
                    catch(SocketException se)
                    
                        if(se.ErrorCode == 10060)
                        
                            oHC.WriteLog("Unable to init connection to printer. Is it plugged in?", Color.Red);
                        
                        else
                        
                            oHC.WriteLog("Unable to init connection to printer. Error: " + se.ErrorCode.ToString(), Color.Red);
                        
                    
                    catch (Exception e)
                    
                        oHC.WriteLog("Unable to init connection to printer. Error: " + e.ToString(), Color.Red);
                    
                
            
        catch (Exception e)
        
            oHC.WriteLog(e.ToString(), true);
            return false;
        

        return true;
    

    public bool PrintBySocketThreaded(List<string> lsToPrint, string sIP)
    
        // open both the sockets
        InitSocks();

        bBatchPrintSuccess = false;
        _backgroundWorker = new BackgroundWorker();

        _backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
        _backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
        _backgroundWorker.WorkerReportsProgress = true;
        _backgroundWorker.WorkerSupportsCancellation = true;

        object[] parameters = new object[]  lsToPrint, sIP, lSocks ;

        _backgroundWorker.RunWorkerAsync(parameters);
        return true;
    


    // On worker thread, send to print!
    public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    
        object[] parameters = e.Argument as object[];

        double dProgressChunks = (100 / ((List<string>)parameters[0]).Count);
        int iPos = 1;

        Dictionary<string, Socket> dctSocks = (Dictionary<string, Socket>)parameters[2];

        foreach (string sLabel in (List<string>)parameters[0] )
        
            bool bPrinted = false;

            // thread lock print by socket to ensure its not accessed twice
            lock (locker)
            
                // get relevant socket from main thread
                bPrinted = PrintBySocket(sLabel, (string)parameters[1], dctSocks[(string)parameters[1]]);
            

            iPos++;
        

        while (!((BackgroundWorker)sender).CancellationPending)
        
            ((BackgroundWorker)sender).CancelAsync();
            ((BackgroundWorker)sender).Dispose();
            //Thread.Sleep(500);
        
        return;
    


    // Back on the 'UI' thread so we can update the progress bar (have access to main thread data)!
    private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    
        if (e.Error != null) MessageBox.Show(e.Error.Message);
        if (bPrintSuccess) oHC.WriteLog("Printing Complete");

        bBatchPrintSuccess = true;

        ((BackgroundWorker)sender).CancelAsync();
        ((BackgroundWorker)sender).Dispose();
    

    /// sends to printer via socket
    public bool PrintBySocket(string sArg_ToPrint, string sIP, Socket sock = null)
    
        Socket sTmpSock = sock;

        if (sTmpSock == null)
         
            InitSocks();

            if (!lSocks.ContainsKey(sIP))
            
                throw new Exception("Sock not init");
            
            else
            
                sTmpSock = lSocks[sIP];
            
        

        try
        

            if(!sTmpSock.Connected || !sTmpSock.IsBound)
            
                InitSocks();

                if (!sTmpSock.Connected)
                
                    oHC.WriteLog("Unable to init connection to printer. Is it plugged in?", Color.Red);
                
            

            using (NetworkStream ns = new NetworkStream(sTmpSock))
            
                byte[] toSend = null;

                // convert string to byte stream, or use byte stream
                if (byToPrint == null)
                
                    toSend = Encoding.ASCII.GetBytes(sEOL + sArg_ToPrint);
                
                else
                
                    toSend = byToPrint;
                

                ns.BeginWrite(toSend, 0, toSend.Length, OnWriteComplete, null);
                ns.Flush();
            

            return true;

        
        catch (Exception e)
        
            oHC.WriteLog("Print by socket: " + e.ToString(), true);
            DisposeSocks();
        
    


    public bool PrintWarningLabel()
    
        string sOut = sEOL + "N" + sEOL;
        sOut += "A0,150,0,4,3,3,N,\"WARNING MESSAGE TO PRINT\"" + sEOL;
        sOut += sEOL;

        if (PrintBySocket(sOut, oHC.pbBench.SmallLabelIP))
        
            oHC.WriteLog("WARNING LABEL PRINTED");
            return true;
        
        return false;
    

【问题讨论】:

我不知道为什么它不起作用,但这不是通常的做法。你不需要太低级别的套接字,你不需要太老派的后台工作人员,你也不需要手动线程同步。使用 TcpClient 类,await tc.ConnectAsync(...) 然后 tc.GetStream() 然后 await stream.WrteAsync(...)。要将 2 个作业发送到 2 台打印机,请在上层写 await Task.WhenAll(...) 啊,听起来不错!我明天将尝试实施并报告。同时,如果有人能发现目前的情况,将不胜感激。谢谢@Sonts。我想知道我在哪里调用 DisposeSocks()。如果同时调用这两个共享套接字将被释放。 TcpClient 也实现了 IDisposable,using 兼容 async-await。所以你可以编写像using(var tc = new TcpClient() ) await tc.ConnectAsync();… await stream.WriteAsync(); 这样的代码,该语言将保证客户端(以及它的套接字)在成功或异常时都会关闭。 感谢您的出色Soonts!明天将更新我的进展情况。遗憾的是上述解决方案不起作用。除了超时,它工作正常! 您的代码可以修复,这是 .NET 4.5 之前异步 I/O 的一种方式。但在现代 .NET 中,这样的 I/O 变得比您在此处所做的要简单得多,同时更可靠.. 【参考方案1】:

您似乎正在连接到 TCP 服务器并发送一些数据。在现代 .NET 中,您不需要显式的多线程,async-await 简化了很多事情。这就是我可能会这样做的方式。

static class NetworkPrinter

    const ushort tcpPort = 31337;
    const string sEOL = "\r\n";

    /// <summary>Send a single job to the printer.</summary>
    static async Task sendLabel( Stream stream, string what, CancellationToken ct )
    
        byte[] toSend = Encoding.ASCII.GetBytes( sEOL + what );
        await stream.WriteAsync( toSend, 0, toSend.Length, ct );
        await stream.FlushAsync();
    

    /// <summary>Connect to a network printer, send a batch of jobs reporting progress, disconnect.</summary>
    public static async Task printLabels( string ip, string[] labels, Action<double> progress, CancellationToken ct )
    
        IPAddress address = IPAddress.Parse( ip );
        double progressMul = 1.0 / labels.Length;
        using( var tc = new TcpClient() )
        
            await tc.ConnectAsync( address, tcpPort );
            Stream stream = tc.GetStream();
            for( int i = 0; i < labels.Length; )
            
                ct.ThrowIfCancellationRequested();
                await sendLabel( stream, labels[ i ], ct );
                i++;
                progress( i * progressMul );
            
        
    

    /// <summary>Send multiple batches to multiple printers, return true of all of them were good.</summary>
    public static async Task<bool> printBatches( LabelBatch[] batches )
    
        await Task.WhenAll( batches.Select( a => a.print( CancellationToken.None ) ) );
        return batches.All( a => a.completed );
    


/// <summary>A batch of labels to be printed by a single printer.</summary>
/// <remarks>Once printed, includes some status info.</remarks>
class LabelBatch

    readonly string ip;
    readonly string[] labels;
    public bool completed  get; private set;  = false;
    public Exception exception  get; private set;  = null;

    public LabelBatch( string ip, IEnumerable<string> labels )
    
        this.ip = ip;
        this.labels = labels.ToArray();
    

    /// <summary>Print all labels, ignoring the progress. This method doesn't throw, returns false if failed.</summary>
    public async Task<bool> print( CancellationToken ct )
    
        completed = false;
        exception = null;
        try
        
            await NetworkPrinter.printLabels( ip, labels, d =>  , ct );
            completed = true;
        
        catch( Exception ex )
        
            exception = ex;
        
        return completed;
    

如果您没有同步上下文(例如,您正在编写控制台应用程序)来报告进度,您可能仍需要手动线程同步。您可以从多台打印机并行调用进度回调。进度报告应该很快,静态对象上的单个 lock() 就可以了。如果您正在构建 GUI 应用程序并从 GUI 线程调用 printBatches,则不需要,sync.context 会将所有内容序列化到 GUI 线程,尽管网络请求仍将并行运行。

【讨论】:

感谢您的深入回答。 LabelBatch 是一个很好的补充。谢谢!

以上是关于多线程套接字连接挂起 c#。套接字超时/挂起的主要内容,如果未能解决你的问题,请参考以下文章

c ++ system()挂起使用netcat连接到不同线程中的套接字

Python套接字recv()由于硬件而挂起?

Chrome 挂起“发送请求”似乎与套接字相关

远程主机终止后,Recv() 调用挂起

使用 Console.Writeline() 或 Console.Write() 时,多线程 C# 控制台应用程序中不经常挂起

tcp异步机制