解除阻塞AcceptTcpClient调用众所周知,在C#中接受传入TCP连接最简单的方法是循环TcpListener.AcceptTcpClient()。此外,这种方式将阻止代码执行,直到获得连接。这对GUI非常有限,所以我想在单独的线程或任务中监听连接。有人告诉我线程有几个缺点,但没有人向我解释这是什么。所以我没有使用线程,而是使用了任务。这很好用,但由于AcceptTcpClient方法阻止执行,我找不到任何方法来处理任务取消。目前的代码看起来像这样,但我不知道如何在我希望程序停止监听连接时取消任务。首先关闭任务中执行的函数:staticvoidListen(){//创建监听对象TcpListenerserverSocket=newTcpListener(serverAddr,serverPort);//开始监听连接while(true){try{serverSocket.Start();}catch(SocketException){MessageBox.Show("另一台服务器当前正在监听端口"+serverPort);}//阻塞并等待传入??连接if(serverSocket.Pending()){TcpClientserverClient=serverSocket.AcceptTcpClient();//从网络流中获取数据NetworkStreamserverStream=serverClient.GetStream();serverStream.Read(数据,0,数据长度);字符串serverMsg=ascii.GetString(数据);MessageBox.Show("收到消息:"+serverMsg);//关闭流和TcpClient连接serverClient.Close();serverStream.Close();//清空缓冲区data=newByte[256];服务器消息=空;}}二,启动和停止监听服务的函数:privatevoidbtnListen_Click(objectsender,EventArgse){btnListen.Enabled=fa是;btnStop.Enabled=true;任务listenTask=newTask(Listen);listenTask.Start();}privatevoidbtnStop_Click(objectsender,EventArgse){btnListen.Enabled=true;btnStop.Enabled=false;//监听任务.Abort();我只需要一些东西来替换listenTask.Abort()调用(我将其注释掉,因为该方法不存在)以下代码在isRunning变量变为false时关闭/中止AcceptTcpClientpublicstaticboolisRunning;委托voidmThread(参考书正在运行);委托无效AccptTcpClnt(参考TcpClient客户端,TcpListener侦听器);publicstaticmain(){isRunning=true;mThreadt=newmThread(StartListening);线程masterThread=newThread(()=>t(this,refisRunning));masterThread.IsBackground=true;//最好将其作为后台线程运行masterThread.Start();}publicstaticvoidAccptClnt(refTcpClientclient,TcpListenerlistener){if(client==null)client=listener.AcceptTcpClient();}publicstaticvoidStartListening(refboolisRunning){TcpListenerlistener=newTcpListener(newIPEndPoint(IPAddress.Any,portNum));尝试{listener.Start();TcpClient处理程序=null;while(isRunning){AccptTcpClntt=newAccptTcpClnt(AccptClnt);Threadtt=newThread(()=>t(refhandler,listener));tt.IsBackground=true;//AcceptTcpClient()是一个阻塞方法,所以我们在单独的专用线程中调用它//tt.Start();while(isRunning&&tt.IsAlive&&handler==null)Thread.Sleep(500);//根据您的喜好更改时间if(handler!=null){//在此处处理已接受的连接}//正如评论中所建议的那样,以这种方式中止线程//不是一个好习惯。所以我们可以省略elseif块//elseif(!isRunning&&tt.IsAlive)//{//tt.Abort();//}}//当isRunning设置为false时,代码退出while(isRunning)//并调用listner.Stop()抛出SocketExceptionlistener.Stop();}//按照投票最多的答案捕获SocketExceptioncatch(SocketExceptione){}}取消AcceptTcpClient取消阻止AcceptTcpClient操作的最佳选择是调用TcpListener.Stop,如果您想明确检查操作是否被取消,它将抛出SocketExceptionTcpListenerserverSocket=newTcpListener(serverAddr,serverPort);...尝试{TcpClientserverClient=serverSocket.AcceptTcpClient();//做点什么}catch(SocketExceptione){if((e.SocketErrorCode==SocketError.Interrupted))//阻塞监听已经被取消}...//在其他地方你的代码将停止阻塞监听:serverSocket。停止();无论您想在TcpListener上调用Stop都需要某种级别的访问权限,因此您可以将其范围限定在Listen方法之外,或者将监听器逻辑包装在管理TcpListener并公开Start和Stop方法的对象内部(使用Stop调用TcpListener.Stop())。异步终止由于接受的答案使用Thread.Abort()来终止线程,请注意,终止异步操作的最佳方法是通过合作取消而不是硬中止。在协作模型中,目标操作可以监视由终结器发出的取消指示符。这允许目标检测取消请求,根据需要进行清理,然后在适当的时间将终止状态传递回终结器。如果没有这种方法,突然终止操作可能会使线程的资源,甚至托管进程或应用程序域处于损坏状态。从.NET4.0开始,实现此模式的最佳方法是使用CancellationToken。使用线程时,令牌可以作为参数传递给在线程上执行的方法。使用Tasks,可以将对CancellationTokens的支持内置到多个Task构造函数中。取消标记在这篇MSDN文章中有更详细的讨论。为了完整起见,上述答案的异步副本:asyncTaskAcceptAsync(TcpListenerlistener,CancellationTokenct){using(ct.Register(listener.Stop)){}catch(SocketExceptione){if(e.SocketErrorCode==SocketError.Interrupted)thrownewOperationCanceledException();扔;更新:正如@Mitch在评论中建议的那样(并且正如本讨论所证实的那样),等待AcceptTcpClientAsync可能会在停止时失败(我们调用后会抛出ObjectDisposedException),因此捕获ObjectDisposedException也很有意义:asyncTaskAcceptAsync(TcpListenerlistener,CancellationTokenct){使用(ct.Register(listener.Stop)){try{returnawaitlistener.AcceptTcpClientAsync();}catch(SocketExceptione)when(e.SocketErrorCode==SocketError.Interrupted){thrownewOperationCanceledException();}catch(ObjectDisposedException)when(ct.IsCancellationRequested){thrownewOperationCanceledException();好吧,在正常情况下回到过去工作异步套接字之前(今天最好的方式IMO,BitMask谈论这个),我们使用了一个简单的技巧:将isRunning设置为false(理想情况下,你想使用CancellationToken,publicstaticbool正在运行;不是终止后台工作人员的线程安全方式:))并为自己启动一个新的TcpClient.Connect-这将使您从Accept调用返回并且您可以优雅地终止正如BitMask已经说过的那样,Thread.Abort绝对不是一种安全的终止方式。事实上,它根本不起作用,因为Accept由本机代码处理,而Thread.Abort无权处理。它起作用的唯一原因是因为您实际上并没有阻塞I/O,而是在检查Pending(非阻塞调用)时运行无限循环。这看起来是在一个内核上使用100%CPU的好方法:)您的代码也有很多其他问题,只是因为您做的很简单,而且因为.NET非常好,您不会在你的脸上爆炸。例如,您总是在正在读取的整个缓冲区上执行GetString-但这是错误的。实际上,这是一个教科书示例,例如C++中的缓冲区溢出-它似乎在C#中工作的唯一原因是因为它将缓冲区预置零,因此GetString不适用于您读取的“真实”字符串。之后数据将被忽略。相反,您需要获取Read调用的返回值-它会告诉您已读取了多少字节,因此需要对其进行解码。另一个非常重要的好处是,这意味着您不再需要在每次读取后重新创建byte[]-您可以简单地重用缓冲区。不要从GUI线程以外的线程使用GUI(是的,您的任务在单独的线程池线程中运行)。MessageBox.Show是一个肮脏的hack,它实际上可以从其他线程运行,但这并不是您想要的。您需要在GUI线程上调用GUI操作(例如,使用Form.Invoke,或使用在GUI线程上具有同步上下文的任务)。这意味着消息框将是您期望的正确对话框。您发布的代码片段仍然存在很多问题,但考虑到这不是代码审查,而且它是一个旧线程,我不会再这样做了:)这就是我如何克服这个问题的。希望这可以帮助。可能不是最干净的,但是对我有用以上是C#学习教程:unblockAcceptTcpClientcall分享所有内容,如果对大家有用需要详细了解C#学习教程,希望大家多多付出注意——公共类consoleService{privateCancellationTokenSourcects;私有TcpListener监听器;私人frmMain主要;公共布尔开始=假;公共布尔停止=假;publicvoidstart(){try{if(started){stop();=newTcpListener(IPAddress.Any,CFDPInstanceData.Settings.RemoteConsolePort);监听器.Start();Task.Run(()=>{AcceptClientsTask(listener,cts.Token);});开始=真;停止=假;functions.Logger.log("StartedRemoteConsoleonport"+CFDPInstanceData.Settings.RemoteConsolePort,"RemoteConsole","General",LOGLEVEL.INFO);}catch(ExceptionE){functions.Logger.log("Errorstartingremoteconsolesocket:"+E.Message,"RemoteConsole","General",LOGLEVEL.ERROR);}}publicvoidstop(){try{if(!started){返回;}停止=假;cts.取消();听众。停止();int尝试=0;while(!stopped&&attempt本文采集自网络,不代表立场,如涉及侵权,请点击右边联系管理员删除,如需转载请注明出处:
