事实上Socket可以象流Stream一样被视为一个应用程序端(客户端)和远程服务器端之间数据通道,通过这个通道来对数据进行读取(接收)和写入(发送)。
异步模式所提供的革新之一就是调用方确定特定调用是否应是异步的。对于被调用的对象,没有必要执行附加的编程来用于支持其客户端的异步行为;在该模式中异步委托提供此功能。
如果应用程序在执行期间只需要一个线程,请使用我在《实例解析SOCKET编程模型》中介绍的方法,这些方法适用于单线程同步操作模式。同步操作模式对执行网络操作的函数(如 Send 和 Receive)的调用一直等到操作完成后才将控制返回给调用程序。
若要在执行过程中使用单独的线程处理通信,请使用下面的方法,这些方法适用于异步操作模式。异步操作模式对执行网络操作的函数的调用立即返回。
当数据发送和数据接收完成之后,可使用 Shutdown 方法来禁用 Socket。在调用 Shutdown 之后,可调用 Close 方法来释放与 Socket 关联的所有资源。
Socket 类允许使用 SetSocketOption 方法来配置 Socket。可使用 GetSocketOption 方法来检索这些设置。
注意 如果编写较简单的应用程序,而且只需同步数据传输,则可以考虑使用 TcpClient、TcpListener 和 UdpClient。这些类为 Socket 通信提供了更简单、对用户更友好的接口。
异步服务器套接字使用 .NET Framework 异步编程模型处理网络服务请求。Socket 类遵循标准 .NET Framework 异步命名模式;例如,同步 Accept 方法对应异步 BeginAccept 和 EndAccept 方法。
异步服务器套接字需要一个开始接受网络连接请求的方法,一个处理连接请求并开始接收网络数据的回调方法以及一个结束接收数据的回调方法。本节将进一步讨论所有这些方法。
在下面的源码中,为开始接受网络连接请求,方法 StartListening
初始化 Socket,然后使用 BeginAccept 方法开始接受新连接。当套接字上接收到新连接请求时,将调用接受回调方法。它负责获取将处理连接的 Socket 实例,并将 Socket 提交给将处理请求的线程。接受回调方法实现 AsyncCallback 委托;它返回 void,并带一个 IAsyncResult 类型的参数。下面的示例是接受回调方法的外壳程序: private void AcceptCallBack(IAsyncResult ar){}
BeginAccept 方法带两个参数:指向接受回调方法的 AsyncCallback 委托和一个用于将状态信息传递给回调方法的对象。在下面的示例中,侦听 Socket 通过状态参数传递给回调方法。本示例创建一个 AsyncCallback 开始一个异步操作来接受一个传入的连接尝试 listeningSocket.BeginAccept(new AsyncCallback(AcceptCallBack),listeningSocket);//
异步套接字使用系统线程池中的线程处理传入的连接。一个线程负责接受连接,另一线程用于处理每个传入的连接,还有一个线程负责接收连接数据。这些线程可以是同一个线程,具体取决于线程池所分配的线程。System.Threading.ManualResetEvent 类挂起主线程的执行并在执行可以继续时发出信号。
接受回调方法(即前例中的 acceptCallback
)负责向主应用程序发出信号,让它继续执行处理、建立与客户端的连接并开始异步读取客户端数据。下面的示例是 acceptCallback
方法实现的第一部分。该方法的此节向主应用程序线程发出信号,让它继续处理并建立与客户端的连接。
开始从客户端套接字接收数据的 acceptCallback
方法的此节首先初始化 StateObject
类的一个实例,然后调用 BeginReceive 方法以开始从客户端套接字异步读取数据。需要为异步套接字服务器实现的 final 方法是返回客户端发送的数据的读取回调方法。与接受回调方法一样,读取回调方法也是一个 AsyncCallback 委托。该方法将来自客户端套接字的一个或多个字节读入数据缓冲区,然后再次调用 BeginReceive 方法,直到客户端发送的数据完成为止。
创建线程时,将使用采用 ThreadStart 委托作为其唯一参数的构造函数创建 Thread 类的新实例。但线程在调用 Start 方法前不会开始执行。调用 Start 后,将从由 ThreadStart 委托引用的方法的第一行开始执行。如下例所示: Thread thread=new Thread(new ThreadStart(ThreadProc));
thread.Start();
在以下的源码中我们使用了ManualResetEvent 来允许线程通过发信号互相通信。通常,此通信涉及一个线程在其他线程进行之前必须完成的任务。
当线程开始一个活动(此活动必须在其他线程进行之前完成)时,它调用 Reset 将 ManualResetEvent 设置为非终止状态。此线程可被视为控制 ManualResetEvent。调用 ManualResetEvent 上的 WaitOne 的线程将阻塞,并等待信号。当控制线程完成活动时,它调用 Set 以发出等待线程可以继续进行的信号。并释放所有等待线程。
一旦它被终止,ManualResetEvent 将保持终止状态,直到它被手动重置。即对 WaitOne 的调用将立即返回。
可以通过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态,如果初始状态处于终止状态,为 true;否则为 false。
//以下是异步聊天服务器端详细实现方法
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
namespace 聊天_socket
{
/// <summary>
/// Form1 的摘要说明。
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.StatusBar statusBar1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.RichTextBox rtbReceive;
private System.Windows.Forms.RichTextBox rtbSend;
private System.Windows.Forms.TextBox txtServer;
private System.Windows.Forms.TextBox txtPort;
private System.Windows.Forms.Button btnListen;
private System.Windows.Forms.Button btnSend;
private System.Windows.Forms.Button btnStop;
private IPAddress hostIPAddress=IPAddress.Parse("127.0.0.1");
private IPEndPoint Server;
private Socket listeningSocket;
private Socket handler;
private Socket mySocket;
private static ManualResetEvent Done=new ManualResetEvent(false);
private const int BufferSize=256;
private byte[] buffer=new byte[BufferSize];
string port;
/// <summary>
/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.Container components = null;
public Form1()
{
//
// Windows 窗体设计器支持所必需的
//
InitializeComponent();
//
// TODO: 在 InitializeComponent 调用后添加任何构造函数代码
//
}
/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows 窗体设计器生成的代码
/// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.rtbReceive = new System.Windows.Forms.RichTextBox();
this.rtbSend = new System.Windows.Forms.RichTextBox();
this.txtServer = new System.Windows.Forms.TextBox();
this.txtPort = new System.Windows.Forms.TextBox();
this.statusBar1 = new System.Windows.Forms.StatusBar();
this.btnListen = new System.Windows.Forms.Button();
this.btnSend = new System.Windows.Forms.Button();
this.btnStop = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// rtbReceive
//
this.rtbReceive.Location = new System.Drawing.Point(80, 56);
this.rtbReceive.Name = "rtbReceive";
this.rtbReceive.Size = new System.Drawing.Size(264, 96);
this.rtbReceive.TabIndex = 0;
this.rtbReceive.Text = "";
//
// rtbSend
//
this.rtbSend.Location = new System.Drawing.Point(80, 152);
this.rtbSend.Name = "rtbSend";
this.rtbSend.Size = new System.Drawing.Size(264, 96);
this.rtbSend.TabIndex = 1;
this.rtbSend.Text = "";
//
// txtServer
//
this.txtServer.Location = new System.Drawing.Point(72, 16);
this.txtServer.Name = "txtServer";
this.txtServer.TabIndex = 2;
this.txtServer.Text = "127.0.0.1";
//
// txtPort
//
this.txtPort.Location = new System.Drawing.Point(288, 16);
this.txtPort.Name = "txtPort";
this.txtPort.Size = new System.Drawing.Size(48, 21);
this.txtPort.TabIndex = 3;
this.txtPort.Text = "19811";
//
// statusBar1
//
}
#endregion
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void btnListen_Click(object sender, System.EventArgs e)
{
try
{
hostIPAddress=IPAddress.Parse(txtServer.Text);
port=txtPort.Text;
}
catch{MessageBox.Show("请输入正确的IP地址格式");}
try
{ //通过组合服务的主机 IP 地址和端口号,IPEndPoint 类形成到服务的连接点。
Server=new IPEndPoint(hostIPAddress,Int32.Parse(port));
// Create a socket object to establish a connection with the server
listeningSocket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
listeningSocket.Bind(Server); //绑定该主机端口
listeningSocket.Listen(50); //监听端口,等待客户端连接请求。50是队列中最多可容纳的等待接受的传入连接数
statusBar1.Text="主机"+txtServer.Text+"端口"+txtPort.Text+"开始监听.....";
//Accept 以同步方式从侦听套接字的连接请求队列中提取第一个挂起的连接请求,然后创建并返回新的 Socket。
//mySocket=listeningSocket.Accept();
//一个进程可以创建一个或多个线程以执行与该进程关联的部分程序代码。使用 ThreadStart 委托指定由线程执行的程序代码。
Thread thread=new Thread(new ThreadStart(ThreadProc));
thread.Start();
}
catch(Exception ee){statusBar1.Text=ee.Message;}
}
private void ThreadProc()
{
//if(mySocket.Connected)
//{
//statusBar1.Text="与客户建立连接.";
while(true)
{
/*Byte[] ByteRecv=new Byte[256];
mySocket.Receive(ByteRecv,ByteRecv.Length,0);
string strRecv=Encoding.BigEndianUnicode.GetString(ByteRecv);
rtbReceive.AppendText(strRecv+"\r\n");*/
Done.Reset(); //将状态设为非终止
listeningSocket.BeginAccept(new AsyncCallback(AcceptCallBack),listeningSocket);//开始一个异步操作来接受一个传入的连接尝试
Done.WaitOne(); //阻塞当前线程,直到当前线程收到信号。
}
//}
}
private void AcceptCallBack(IAsyncResult ar)//ar表示异步操作的状态。
{
Done.Set();//设为终止
mySocket=(Socket)ar.AsyncState; //获取状态
handler=mySocket.EndAccept(ar); //异步接受传入的连接尝试,并创建新的 Socket 来处理远程主机通信,获取结果
try
{
byte[] byteData=Encoding.BigEndianUnicode.GetBytes("准备完毕,可以通话"+"\r\n");
//调用SendCallBack异步发送数据,
handler.BeginSend(byteData,0,byteData.Length,0,new AsyncCallback(SendCallBack),handler);
}
catch(Exception ee){MessageBox.Show(ee.Message);}
Thread thread=new Thread(new ThreadStart(ThreadRev));
thread.Start();
}
private void SendCallBack(IAsyncResult ar)
{
try
{
handler=(Socket)ar.AsyncState; //获取状态
int bytesSent=handler.EndSend(ar);//结束挂起的异步发送,返回向 Socket 发送的字节数
}
catch{}
}
private void ThreadRev()
{
handler.BeginReceive(buffer,0,BufferSize,0,new AsyncCallback(ReadCallBack),handler);
}
private void ReadCallBack(IAsyncResult ar)
{
int bytesRead=handler.EndReceive(ar); //结束挂起的异步读取,返回接收到的字节数。
StringBuilder sb=new StringBuilder(); //接收数据的可变字符字符串,在通过追加、移除、替换或插入字符而创建它后可以对它进行修改。
sb.Append(Encoding.BigEndianUnicode.GetString(buffer,0,bytesRead));//追加字符串
string content=sb.ToString(); //转换为字符串
sb.Remove(0,content.Length); //清除sb内容
rtbReceive.AppendText(content+"\r\n");
handler.BeginReceive(buffer,0,BufferSize,0,new AsyncCallback(ReadCallBack),handler);
}
private void btnStop_Click(object sender, System.EventArgs e)
{
try
{
listeningSocket.Close();
statusBar1.Text="主机"+txtServer.Text+"端口"+txtPort.Text+"监听停止";
}
catch{MessageBox.Show("监听尚未开始,关闭无效");}
}
private void btnSend_Click(object sender, System.EventArgs e)
{
try
{
string strSend ="Server--->"+rtbSend.Text+"\r\n";
Byte[] ByteSend = Encoding.BigEndianUnicode.GetBytes(strSend);
handler.BeginSend(ByteSend,0,ByteSend.Length,0,new AsyncCallback(SendCallBack),handler);
}
catch{MessageBox.Show("连接尚未建立,无法发送.");}
}
private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
try
{
listeningSocket.Close();//在窗口关闭之前关闭Scoket连接并释放所有关联的资源。
}
catch{}
}
}
}