七 C# Socket通信 阻塞性线程的快速终止
Socket编程中,客户端(即侦听端)会开启新线程用于侦听服务端套接字,当客户端需要开启多个信道以侦听不同的终端或同一终端的不同端口时,往往会给每一个通信对象开启一个线程,当两个终端超过一定的时限仍没有消息往来时,就需要关闭线程,那么问题来了:侦听线程会被阻塞在一个无限循环里,使用thread.Abort()或者thread.Suspend()方法,并不会使侦听线程立即结束,而是要等待相当长一段时间后,主线程才能终止该线程。网上调查了很多解决办法,有人提高使用互斥来通知该线程终结,但在Socket编程中,这种方法却根本不能被使用,因为listenSocket.Accept()方法内部本身就封装了一个无限循环,直到侦听到新的套接字连接,才会跳出这个内部阻塞;或者listenSocket.Receive(byte[] byte)也会等待服务器端发送消息过来,否则将一直阻塞在这里,此时互斥根本不会被通知到。最后我想了一个办法,那就是通过套接字本身来给阻塞线程发送信号,阻塞线程收到信号后,直接break,跳出while循环,然后外部再进行thread.Abort(); thread.Join(),便能立即停止该线程。下面给出实例代码:
发送端:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace DelTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{}
private void button1_Click(object sender, EventArgs e)
{
Socket _sender = null;//每次发送时,都实例化一个套接字,并与客户端进行连接
try
{
_sender = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_sender.Connect(IPAddress.Parse("127.0.0.1"), 2000);
if (_sender.Connected)
{
byte[] sends = Encoding.Unicode.GetBytes(this.textBox1.Text);
_sender.Send(sends);
}
}
catch (Exception ee)
{ }
finally
{
_sender.Close();
}
}
}
}
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace DelTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{}
private void button1_Click(object sender, EventArgs e)
{
Socket _sender = null;//每次发送时,都实例化一个套接字,并与客户端进行连接
try
{
_sender = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_sender.Connect(IPAddress.Parse("127.0.0.1"), 2000);
if (_sender.Connected)
{
byte[] sends = Encoding.Unicode.GetBytes(this.textBox1.Text);
_sender.Send(sends);
}
}
catch (Exception ee)
{ }
finally
{
_sender.Close();
}
}
}
}
发送端界面如下图2所示:
图1 图2
接收端——注意“终止侦听线程”按钮中的终止线程的方法(界面见上图2):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace SocketRec
{
public partial class Form1 : Form
{
delegate void ThreadMethod(object obj);
ThreadMethod _tm = null;
private Socket _listener = null;
private string _localIP = null;
private Thread thread = null;
private ManualResetEvent me = new ManualResetEvent(false);
private void Form1_Load(object sender, EventArgs e)
{
_tm = Show;
_localIP = Dns.GetHostAddresses(Dns.GetHostName())[0].ToString();
_listener = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_listener.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2000));
_listener.Listen(50);
me.Set();
thread = new Thread(new ThreadStart(Receive));
thread.IsBackground = true;
thread.Start();
}
private void Receive()
{
try
{
while (true)
{
Socket accept = _listener.Accept();
me.WaitOne();
byte[] rec = new byte[400];
accept.Receive(rec);
/**当侦听到的消息头两个字节是1和2时,则退出while循环,
* 从而该阻塞线程可以被立即终止掉
*/
if (rec[0] == (byte)1 && rec[1] == (byte)2)
{
Thread.CurrentThread.Abort();
break;
}
string recStr = Encoding.Unicode.GetString(rec);
_tm(recStr);
}
}
catch (Exception ee)
{
Receive();
}
}
/// <summary>
/// 委托实例
/// </summary>
/// <param name="obj">在多线程间传递数据</param>
private void Show(object obj)
{
if (this.richTextBox1.InvokeRequired)
{
/**由于windows窗体及其他控件不具备跨线程的能力,
* 所以这里必须调用控件的异步委托方法
*/
this.richTextBox1.Invoke(new EventHandler(ControlDelegate),
new object[] { obj, EventArgs.Empty });
}
}
/// <summary>
/// 控件异步委托调用的方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ControlDelegate(object sender, EventArgs e)
{
this.richTextBox1.AppendText(sender.ToString());
}
/// <summary>
/// “终止侦听线程”按钮单击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAbortListenThread_Click(object sender, EventArgs e)
{
IPAddress ipa = IPAddress.Parse("127.0.0.1");
IPEndPoint ipe = new IPEndPoint(ipa, 2000);
Socket client = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
client.Connect(ipe);
if (client.Connected)
{
//当本机接收到1和2这两个字节时,就退出while循环
client.Send(new byte[] { (byte)1, (byte)2 });
}
}
catch (SocketException se)
{}
//在主线程中终止侦听线程
Thread.Sleep(20);
thread.Abort();
thread.Join();
//当侦听线程被终止后,这里弹出的消息应该是:“ThreadStopped”
MessageBox.Show(thread.ThreadState.ToString());
}
public Form1()
{
InitializeComponent();
}
}
}
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace SocketRec
{
public partial class Form1 : Form
{
delegate void ThreadMethod(object obj);
ThreadMethod _tm = null;
private Socket _listener = null;
private string _localIP = null;
private Thread thread = null;
private ManualResetEvent me = new ManualResetEvent(false);
private void Form1_Load(object sender, EventArgs e)
{
_tm = Show;
_localIP = Dns.GetHostAddresses(Dns.GetHostName())[0].ToString();
_listener = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_listener.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2000));
_listener.Listen(50);
me.Set();
thread = new Thread(new ThreadStart(Receive));
thread.IsBackground = true;
thread.Start();
}
private void Receive()
{
try
{
while (true)
{
Socket accept = _listener.Accept();
me.WaitOne();
byte[] rec = new byte[400];
accept.Receive(rec);
/**当侦听到的消息头两个字节是1和2时,则退出while循环,
* 从而该阻塞线程可以被立即终止掉
*/
if (rec[0] == (byte)1 && rec[1] == (byte)2)
{
Thread.CurrentThread.Abort();
break;
}
string recStr = Encoding.Unicode.GetString(rec);
_tm(recStr);
}
}
catch (Exception ee)
{
Receive();
}
}
/// <summary>
/// 委托实例
/// </summary>
/// <param name="obj">在多线程间传递数据</param>
private void Show(object obj)
{
if (this.richTextBox1.InvokeRequired)
{
/**由于windows窗体及其他控件不具备跨线程的能力,
* 所以这里必须调用控件的异步委托方法
*/
this.richTextBox1.Invoke(new EventHandler(ControlDelegate),
new object[] { obj, EventArgs.Empty });
}
}
/// <summary>
/// 控件异步委托调用的方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ControlDelegate(object sender, EventArgs e)
{
this.richTextBox1.AppendText(sender.ToString());
}
/// <summary>
/// “终止侦听线程”按钮单击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAbortListenThread_Click(object sender, EventArgs e)
{
IPAddress ipa = IPAddress.Parse("127.0.0.1");
IPEndPoint ipe = new IPEndPoint(ipa, 2000);
Socket client = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
client.Connect(ipe);
if (client.Connected)
{
//当本机接收到1和2这两个字节时,就退出while循环
client.Send(new byte[] { (byte)1, (byte)2 });
}
}
catch (SocketException se)
{}
//在主线程中终止侦听线程
Thread.Sleep(20);
thread.Abort();
thread.Join();
//当侦听线程被终止后,这里弹出的消息应该是:“ThreadStopped”
MessageBox.Show(thread.ThreadState.ToString());
}
public Form1()
{
InitializeComponent();
}
}
}
利用socket来传递消息,其实也算是线程间通信的一种好办法。