这是我的代码:
public void BroadcastTheConnection()
{
try
{
//............avoid unnecessary codes
while (ServerRunning)
{
s = myList.AcceptSocket();// blocking the content until client request accomplished
displayText.AppendText("nConnected");
Thread tcpHandlerThread = new Thread(tcpHandler);
tcpHandlerThread.Name = "tcpHandler";
tcpHandlerThread.Start();
}
}catch (Exception ex)
{
displayText.AppendText("Error----" + Environment.NewLine + ex.StackTrace);
}
}
当我尝试连接多个客户端时,此代码可以完美运行。 当我在广播连接后尝试移动我的表单时,它不起作用。我知道这是线程问题,但是如何避免这种麻烦?
这是我的按钮:
private void bBroadcast_Click(object sender, EventArgs e)
{
BroadcastTheConnection();
}
我需要使用锁定语句吗?还是委托?有什么想法吗?然后怎么做?
问题是BroadcastTheConnection()
是从 UI 线程本身调用的。 由于它具有while (ServerRunning) {}
构造,因此 UI 线程将在代码上旋转,直到ServerRunning
为 false。
有几种方法可以实现相同的修复:从 UI 线程中获取服务器代码。这些中的每一个都有其权衡。
- 将
BroadcastTheConnection()
用作长时间运行的任务(不推荐) - 站起来一个线程,其中
BroadcastTheConnection()
是主要方法。 - 使用异步套接字调用。 (太复杂了,无法快速回答)
长时间运行的任务
Task.Factory.StartNew(BroadcastTheConnection,
TaskCreationOptions.LongRunning);
这既快速又简单,但您不希望太多长时间运行的任务,因为它们可能会长时间占用任务线程池中的线程。
专用线程
Thread connectionThread = new Thread(BroadcastTheConnection)
{
Name = "BroadcaseTheConnection Thread",
IsBackground = true
};
connectionThread.Start();
这不使用任务线程池中的任何线程,为您提供一个有助于调试的命名线程,并在您忘记结束应用程序时防止线程保持应用程序运行。
从套接字代码使用 UI
每当需要以任何方式与 UI 交互时,都需要再次将调用放入 UI 线程中。 WinForms 和 WPF 执行相同操作的方式略有不同。
赢形
myControl.BeginInvoke(myControl.Method); // nonblocking
myControl.Invoke(myControl.Method); // blocking
可湿性工作基金会
myControl.Dispatcher.BeginInvoke(myControl.Method); // nonblocking
myControl.Dispatcher.Invoke(myControl.Method); // blocking
请注意,对连续BeginInvoke
的调用过多可能会使 UI 线程过载。 对它们进行批处理比连续发出大量请求要好。
使用 Async 和 Await 进行以下更改
private async void bBroadcast_Click(object sender, EventArgs e) //-- Async
{
ipAdrsNew = ipBox.Text;
portNo = Convert.ToInt32(portBox.Text);
await BroadcastTheConnection(); //-- await
}
public Task BroadcastTheConnection()
{
return Task.Run(() =>
{
//---- code for BroadcastTheConnection
});
}
可以使用 async 和 await 在 C# 中实现异步网络。
试试这个(我也重构了你的代码):
public async Task BroadcastConnectionAsync(IPAddress address, int port)
{
try
{
var listener = new TcpListener(address, port);
ServerRunning = true;
// Start Listeneting at the specified port
listener.Start();
displayText.AppendText("The server is running at port 8001...n");
while (ServerRunning)
{
using (var socket = await listener.AcceptSocketAsync())
{
listOFClientsSocks.Add(socket);
listBox1.DataSource = listOFClientsSocks;
displayText.AppendText("nConnected");
new Thread(tcpHandler)
{
Name = "tcpHandler"
}.Start();
}
}
}
catch (Exception ex)
{
displayText.AppendText("Error----" + Environment.NewLine + ex.StackTrace);
}
}
和您的点击事件处理程序:
private async void bBroadcast_Click(object sender, EventArgs e)
{
var address = IPAddress.Parse(ipBox.Text);
int port = Convert.ToInt32(portBox.Text);
await BroadcastConnectionAsync(address, port);
}
使用Thread
与远程端进行侦听和通信的最简单示例:
public class ListenerThread
{
// clients list/queue
Queue<ClientConnection> m_Clients;
// thread used to listen for new connections
Thread m_Thread;
Socket m_Socket;
IPEndPoint m_LocalEndPoint;
volatile bool m_IsListening;
public ListenerThread(int port)
{
// get this machine hostname
IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
// resolve ip address from hostname
IPAddress ipAddress = ipHostInfo.AddressList[0];
// create local end point object
m_LocalEndPoint = new IPEndPoint(ipAddress, port);
}
void Listen()
{
// reset clients list
m_Clients = new Queue<ClientConnection>();
// initialize socket
m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
// bind this socket to listen for incomming connections to specified end point
m_Socekt.Bind(localEndPoint);
// start listening with backlog of 1337 connections
m_Socket.Listen(1337);
// dont forget to dispose after socket was used to "unbind" it
using ( m_Socket )
{
while ( m_IsListening )
{
// while listening just accept connections and start them at another thread
Socket client = m_Socket.Accept();
if ( client != null )
{
m_Clients.Enqueue(new ClientConnection(client));
}
}
}
}
// method used to start the listening server
public void Start()
{
if ( m_Thread == null )
{
m_Thread = new Thread(Listen);
}
m_IsListening = true;
m_Thread.Start();
}
// method used to stop listening server
public void Stop()
{
m_Listening = false;
m_Thread.Join();
while ( m_Clients.Count != 0 )
{
m_Clients.Dequeue().Kill();
}
}
}
// class used to communicate with the client
public class ClientConnection
{
Socket m_Socket; // client socket
Thread m_Thread; // communication thread
volatile bool m_IsCommunicating;
// this should start immediately because of the incomming connection priority
internal ClientConnection(Socket socket)
{
m_Socket = socket;
m_Thread = new Thread(Communicate);
m_Thread.Start();
}
// loop in which you should send/receive data
void Communicate()
{
while ( m_IsCommunicating )
{
// .. do your communication stuff
}
}
// should be only used by ListenerThread to end communication.
internal void Kill()
{
m_IsCommunicating = false;
try
{
m_Thread.Join(5 * 1000);
m_Thread.Abort();
}
catch(Exception ex) { /*...*/ }
}
}
这确实是最简单的示例,因此您应该根据需要对其进行修改。
要将其与您的示例一起使用,只需开始ListenerThread
:
ListenerThread listener = new ListenerThread(8001);
listener.Start();
displayText.AppendText("The server is running at port 8001...n");
最后一件事是,如果您想调用 UI,我建议使用SynchronizationContext
.为了更清楚地说明ListenerThread
构造函数调用:
m_Sync = SynchronizationContext.Current;
并制作另一个字段:
SynchronizationContext m_Sync;
然后只需将此上下文作为new ClientConnection(m_Sync, client);
传递到ClientConnection
构造函数中。
现在您可以使用SynchronizationContext.Post
方法,例如:
m_Sync.Post( state => { someUITextElement.AppendText((string)state); }, "hello world");
有一个异步变体AcceptSocket
称为BeginAcceptSocket
,它异步等待连接并为连接的新套接字启动新线程。您仍然需要等待操作完成,因为您处于while
循环中,但是您可以使用这段时间调用Application.DoEvents
,这将允许UI更新。
while (ServerRunning)
{
AsyncHandler handler = delegate(asyncResult)
{
//Get the new socket
Socket socket = myList.EndAcceptSocket(asyncResult);
//Marshal UI specific code back to the UI thread
MethodInvoker invoker = delegate()
{
listOFClientsSocks.Add(socket);
listBox1.DataSource = listOFClientsSocks;
displayText.AppendText("nConnected");
};
listBox1.Invoke(invoker);
//Call the handler
tcpHandler();
}
IAsyncResult waitResult = myList.BeginAcceptSocket(handler, null);
//Wait until the async result's wait handle receives a signal
//Use a timeout to referesh the application every 100 milliseconds
while (!waitResult.AsyncWaitHandle.WaitOne(100))
{
Application.DoEvents();
if (!ServerRunning)
{
break;
}
}
}
该解决方案使 UI 响应代码结构的相对更改。但是,我建议您重新考虑使用TcpListener
的整个策略。在 UI 线程中侦听 TCP 连接通常不是一个好主意。创建一个专用类,该类在单独的线程中为您执行侦听,并从 UI 代码访问它。
您还应该知道,在上面的代码中,catch
块不会处理BeginAcceptSocket
使用的匿名委托中的任何内容。我还添加了代码,以便在服务器不再运行时停止侦听。这可能不是必需的,因为在这种情况下BeginAcceptSocket
会引发异常。不过,它是一种额外的保障措施。
您也可以使用后台工作者。下面是一个相同的小例子。
private void MainMethod()
{
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += Bg_DoWork;
bg.RunWorkerCompleted += Bg_RunWorkerCompleted;
}
private void Bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//here do your UI related work like changing the color of label or filling up data in grid etc
}
private void Bg_DoWork(object sender, DoWorkEventArgs e)
{
//Here do your time consuming work without blocking ui thread
}