在实际项目中,涉及套接字的都必须使用多线程,当接收端所接收到的消息需要显示到窗体控件上时,必须使用委托。这是因为窗体控件在窗体初始化时便被声明,即控件在主线程中被定义;而窗体控件具有一个属性,那就是:控件不具备跨线程的能力。因此,套接字编程中凡是涉及到窗体控件时,都必须调用控件的异步方法。下面举例说明:
1 发送端(或者客户端)程序代码:
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
{
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
IPAddress ipa = IPAddress.Parse("127.0.0.2");
IPEndPoint ipe = new IPEndPoint(ipa, 2000);
Socket client = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
client.Connect(ipe);
if (client.Connected)
{
byte[] sends = Encoding.Unicode.GetBytes(this.textBox1.Text);
client.Send(sends);
}
}
catch (Exception ee)
{}
}
/// <summary>
/// 与远程进行连接
/// </summary>
/// <param name="ip">远程IP地址</param>
/// <param name="port">远程侦听端口</param>
/// <returns></returns>
private Socket Connect(string ip, int port)
{
IPAddress ipa = IPAddress.Parse(ip);
IPEndPoint ipe = new IPEndPoint(ipa, port);
Socket sender = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
sender.Connect(ipe);
}
catch (SocketException se)
{
return null;
}
return sender;
}
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 DelTest
{
public partial class Form1 : Form
{
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
IPAddress ipa = IPAddress.Parse("127.0.0.2");
IPEndPoint ipe = new IPEndPoint(ipa, 2000);
Socket client = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
client.Connect(ipe);
if (client.Connected)
{
byte[] sends = Encoding.Unicode.GetBytes(this.textBox1.Text);
client.Send(sends);
}
}
catch (Exception ee)
{}
}
/// <summary>
/// 与远程进行连接
/// </summary>
/// <param name="ip">远程IP地址</param>
/// <param name="port">远程侦听端口</param>
/// <returns></returns>
private Socket Connect(string ip, int port)
{
IPAddress ipa = IPAddress.Parse(ip);
IPEndPoint ipe = new IPEndPoint(ipa, port);
Socket sender = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
sender.Connect(ipe);
}
catch (SocketException se)
{
return null;
}
return sender;
}
public Form1()
{
InitializeComponent();
}
}
}
发送端的界面很简单,如下图图1所示:
图1 图2
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 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(_localIP), 2000));
_listener.Listen(50);
Thread thread = new Thread(new ThreadStart(Receive));
thread.IsBackground = true;
thread.Start();
}
private void Receive()
{
try
{
Socket accept = _listener.Accept();
while (true)
{
byte[] rec = new byte[4900];
accept.Receive(rec);
string recStr = Encoding.Unicode.GetString(rec);
_tm(recStr);
}
}
catch (Exception ee)
{
}
}
/// <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 });
}
}
//控件调用的方法
private void ControlDelegate(object sender, EventArgs e)
{
this.richTextBox1.AppendText(sender.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 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(_localIP), 2000));
_listener.Listen(50);
Thread thread = new Thread(new ThreadStart(Receive));
thread.IsBackground = true;
thread.Start();
}
private void Receive()
{
try
{
Socket accept = _listener.Accept();
while (true)
{
byte[] rec = new byte[4900];
accept.Receive(rec);
string recStr = Encoding.Unicode.GetString(rec);
_tm(recStr);
}
}
catch (Exception ee)
{
}
}
/// <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 });
}
}
//控件调用的方法
private void ControlDelegate(object sender, EventArgs e)
{
this.richTextBox1.AppendText(sender.ToString());
}
public Form1()
{
InitializeComponent();
}
}
}
3 总结
凡是涉及窗体控件的套接字编程,当控件需要显示侦听到的套接字时,必须使用控件的Invoke(EventHandler void, object obj)异步调用方法,否则程序肯定报错。通常在套接字编程中使用到的控件有:TextBox、RichTextBox、PictureBox……
在一个大型项目中,套接字的侦听一般被封装在最底层,而控件的显示则封装在最上层,这中间可能隔了若干层,那么侦听到的套接字流只能通过委托来一层一层地传递数据,因为委托的功能很强大,可以跨线程。委托的签名只需满足void delegateMethod(object obj)即可,即:使用obj这个参数来传递数据。
当然,在真正的项目中,套接字编程基本都会使用多线程技术,如果连接的终端很多,还会使用异步技术,下面的几篇文章将会介绍一下C#中的线程使用方法以及多线程如何共享数据和如何让一个方法在多线程中同步。最后再帖上项目中的主要代码,以做留念和参考。