您需要 登录 才可以下载或查看,没有账号?注册
x
一、摘要 本篇博文阐述基于TCP通信协议的异步实现。 二、实验平台 Visual Studio 2010 三、异步通信实现原理及常用方法3.1 建立连接 在同步模式中,在服务器上使用Accept方法接入连接请求,而在客户端则使用Connect方法来连接服务器。相对地,在异步模式下,服务器可以使用BeginAccept方法和EndAccept方法来完成连接到客户端的任务,在客户端则通过BeginConnect方法和EndConnect方法来实现与服务器的连接。 BeginAccept在异步方式下传入的连接尝试,它允许其他动作而不必等待连接建立才继续执行后面程序。在调用BeginAccept之前,必须使用Listen方法来侦听是否有连接请求,BeginAccept的函数原型为:
BeginAccept(AsyncCallback AsyncCallback, Ojbect state)参数:AsyncCallBack:代表回调函数state:表示状态信息,必须保证state中包含socket的句柄 使用BeginAccept的基本流程是:
(1)创建本地终节点,并新建套接字与本地终节点进行绑定;
(2)在端口上侦听是否有新的连接请求;
(3)请求开始接入新的连接,传入Socket的实例或者StateOjbect的实例。 参考代码:
[img]file:///C:/Users/Administrator/AppData/Roaming/Tencent/QQ/Temp/TempPic/F$YW(NS2DNDA%60W%7B9Y[P1~(9.tmp[/img]//定义IP地址IPAddress local = IPAddress.Parse("127.0,0,1");
IPEndPoint iep=newIPEndPoint(local,13000);
//创建服务器的socket对象Socket server =newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
server.Bind(iep);server.Listen(20);
server.BeginAccecpt(newAsyncCallback(Accept),server);
file:///C:/Users/Administrator/AppData/Roaming/Tencent/QQ/Temp/TempPic/V40UKAKUO%7DBG%7DMEYIYG37EH.tmp 当BeginAccept()方法调用结束后,一旦新的连接发生,将调用回调函数,而该回调函数必须包括用来结束接入连接操作的EndAccept()方法。该方法参数列表为 Socket EndAccept(IAsyncResult iar)下面为回调函数的实例:
voidAccept(IAsyncResult iar){
//还原传入的原始套接字
Socket MyServer =(Socket)iar.AsyncState;
//在原始套接字上调用EndAccept方法,返回新的套接字
Socket service =MyServer.EndAccept(iar);
}
至此,服务器端已经准备好了。客户端应通过BeginConnect方法和EndConnect来远程连接主机。在调用BeginConnect方法时必须注册相应的回调函数并且至少传递一个Socket的实例给state参数,以保证EndConnect方法中能使用原始的套接字。下面是一段是BeginConnect的调用:
Socket socket=newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp)IPAddress ip=IPAddress.Parse("127.0.0.1");
IPEndPoint iep=newIPEndPoint(ip,13000);
socket.BeginConnect(iep,newAsyncCallback(Connect),socket);
EndConnect是一种阻塞方法,用于完成BeginConnect方法的异步连接诶远程主机的请求。在注册了回调函数后必须接收BeginConnect方法返回的IASynccReuslt作为参数。下面为代码演示:
voidConnect(IAsyncResult iar){
Socket client=(Socket)iar.AsyncState;try{
client.EndConnect(iar);
}
catch(Exception e) {
Console.WriteLine(e.ToString());
}
finally{ }
}
除了采用上述方法建立连接之后,也可以采用TcpListener类里面的方法进行连接建立。下面是服务器端对关于TcpListener类使用BeginAccetpTcpClient方法处理一个传入的连接尝试。以下是使用BeginAccetpTcpClient方法和EndAccetpTcpClient方法的代码:
publicstaticvoidDoBeginAccept(TcpListener listner){
//开始从客户端监听连接
Console.WriteLine("Waitting for a connection");
//接收连接
//开始准备接入新的连接,一旦有新连接尝试则调用回调函数
DoAcceptTcpClietlistner.BeginAcceptTcpClient(newAsyncCallback(DoAcceptTcpCliet), listner);
}
//处理客户端的连接
publicstaticvoidDoAcceptTcpCliet(IAsyncResult iar)
{
//还原原始的TcpListner对象
TcpListener listener =(TcpListener)iar.AsyncState;
//完成连接的动作,并返回新的
TcpClientTcpClient client =listener.EndAcceptTcpClient(iar);
Console.WriteLine("连接成功");
}
代码的处理逻辑为:
(1)调用BeginAccetpTcpClient方法开开始连接新的连接,当连接视图发生时,回调函数被调用以完成连接操作;
(2)上面DoAcceptTcpCliet方法通过AsyncState属性获得由BeginAcceptTcpClient传入的listner实例;
(3)在得到listener对象后,用它调用EndAcceptTcpClient方法,该方法返回新的包含客户端信息的TcpClient。
BeginConnect方法和EndConnect方法可用于客户端尝试建立与服务端的连接,这里和第一种方法并无区别。下面看实例:
publicvoiddoBeginConnect(IAsyncResult iar){
Socket client=(Socket)iar.AsyncState;
//开始与远程主机进行连接
client.BeginConnect(serverIP[0],13000,requestCallBack,client);
Console.WriteLine("开始与服务器进行连接");
}
privatevoidrequestCallBack(IAsyncResult iar){
try{
//还原原始的TcpClient对象
TcpClient client=(TcpClient)iar.AsyncState;//client.EndConnect(iar
);
Console.WriteLine("与服务器{0}连接成功",client.Client.RemoteEndPoint);
}
catch(Exception e) {
Console.WriteLine(e.ToString());
}finally{ }
}
以上是建立连接的两种方法。可根据需要选择使用。 3.2 发送与接受数据
在建立了套接字的连接后,就可以服务器端和客户端之间进行数据通信了。异步套接字用BeginSend和EndSend方法来负责数据的发送。注意在调用BeginSend方法前要确保双方都已经建立连接,否则会出异常。下面演示代码:
privatestaticvoidSend(Socket handler, String data){
//Convert the string data to byte data using ASCII encoding.byte[] byteData =Encoding.ASCII.GetBytes(data);
//Begin sending the data to the remote device.handler.BeginSend(byteData,0, byteData.Length,0,newAsyncCallback(SendCallback), handler);
}
privatestaticvoidSendCallback(IAsyncResult ar){
try{
//Retrieve the socket from the state object.Socket handler =(Socket)ar.AsyncState;
//Complete sending the data to the remote device.intbytesSent =handler.EndSend(ar);
Console.WriteLine("Sent {0} bytes to client.", bytesSent);
handler.Shutdown(SocketShutdown.Both);
handler.Close();
}
catch(Exception e) {
Console.WriteLine(e.ToString()); \
}
}
接收数据是通过BeginReceive和EndReceive方法:
privatestaticvoidReceive(Socket client){try{//Create the state object.StateObject state =newStateObject(); state.workSocket=client;//Begin receiving the data from the remote device.client.BeginReceive(state.buffer,0, StateObject.BufferSize,0,newAsyncCallback(ReceiveCallback), state); }catch(Exception e) { Console.WriteLine(e.ToString()); }}privatestaticvoidReceiveCallback(IAsyncResult ar){try{//Retrieve the state object and the client socket//from the asynchronous state object.StateObject state =(StateObject)ar.AsyncState; Socket client=state.workSocket;//Read data from the remote device.intbytesRead =client.EndReceive(ar);if(bytesRead >0) {//There might be more data, so store the data received so far.state.sb.Append(Encoding.ASCII.GetString(state.buffer,0, bytesRead));//Get the rest of the data.client.BeginReceive(state.buffer,0, StateObject.BufferSize,0,newAsyncCallback(ReceiveCallback), state); }else{//All the data has arrived; put it in response.if(state.sb.Length >1) { response=state.sb.ToString(); }//Signal that all bytes have been received.receiveDone.Set(); } }catch(Exception e) { Console.WriteLine(e.ToString()); }}
上述代码的处理逻辑为:(1)首先处理连接的回调函数里得到的通讯套接字client,接着开始接收数据;
(2)当数据发送到缓冲区中,BeginReceive方法试图从buffer数组中读取长度为buffer.length的数据块,并返回接收到的数据量bytesRead。最后接收并打印数据。 除了上述方法外,还可以使用基于NetworkStream相关的异步发送和接收方法,下面是基于NetworkStream相关的异步发送和接收方法的使用介绍。
NetworkStream使用BeginRead和EndRead方法进行读操作,使用BeginWreite和EndWrete方法进行写操作,下面看实例:
staticvoidDataHandle(TcpClient client){ TcpClient tcpClient=client;//使用TcpClient的GetStream方法获取网络流NetworkStream ns =tcpClient.GetStream();//检查网络流是否可读if(ns.CanRead) {//定义缓冲区byte[] read =newbyte[1024]; ns.BeginRead(read,0,read.Length,newAsyncCallback(myReadCallBack),ns); }else{ Console.WriteLine("无法从网络中读取流数据"); }}publicstaticvoidmyReadCallBack(IAsyncResult iar){ NetworkStream ns=(NetworkStream)iar.AsyncState;byte[] read =newbyte[1024]; String data="";intrecv; recv=ns.EndRead(iar); data= String.Concat(data, Encoding.ASCII.GetString(read,0, recv));//接收到的消息长度可能大于缓冲区总大小,反复循环直到读完为止while(ns.DataAvailable) { ns.BeginRead(read,0, read.Length,newAsyncCallback(myReadCallBack), ns); }//打印Console.WriteLine("您收到的信息是"+data);}
3.3 程序阻塞与异步中的同步问题
.Net里提供了EventWaitHandle类来表示一个线程的同步事件。EventWaitHandle即事件等待句柄,他允许线程通过操作系统互发信号和等待彼此的信号来达到线程同步的目的。这个类有2个子类,分别为AutoRestEevnt(自动重置)和ManualRestEvent(手动重置)。下面是线程同步的几个方法:
(1)Rset方法:将事件状态设为非终止状态,导致线程阻塞。这里的线程阻塞是指允许其他需要等待的线程进行阻塞即让含WaitOne()方法的线程阻塞;
(2)Set方法:将事件状态设为终止状态,允许一个或多个等待线程继续。该方法发送一个信号给操作系统,让处于等待的某个线程从阻塞状态转换为继续运行,即WaitOne方法的线程不在阻塞;
(3)WaitOne方法:阻塞当前线程,直到当前的等待句柄收到信号。此方法将一直使本线程处于阻塞状态直到收到信号为止,即当其他非阻塞进程调用set方法时可以继续执行。
publicstaticvoidStartListening(){//Data buffer for incoming data.byte[] bytes =newByte[1024];//Establish the local endpoint for the socket.//The DNS name of the computer//running the listener is "host.contoso.com".//IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());//IPAddress ipAddress = ipHostInfo.AddressList[0];IPAddress ipAddress = IPAddress.Parse("127.0.0.1"); IPEndPoint localEndPoint=newIPEndPoint(ipAddress,11000);//Create a TCP/IP socket.Socket listener =newSocket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);//Bind the socket to the local//endpoint and listen for incoming connections.try{ listener.Bind(localEndPoint); listener.Listen(100);while(true) {//Set the event to nonsignaled state.allDone.Reset();//Start an asynchronous socket to listen for connections.Console.WriteLine("Waiting for a connection..."); listener.BeginAccept(newAsyncCallback(AcceptCallback),listener);//Wait until a connection is made before continuing.allDone.WaitOne(); } }catch(Exception e) { Console.WriteLine(e.ToString()); } Console.WriteLine("\nPress ENTER to continue..."); Console.Read();}
上述代码的逻辑为:(1)试用了ManualRestEvent对象创建一个等待句柄,在调用BeginAccept方法前使用Rest方法允许其他线程阻塞;
(2)为了防止在连接完成之前对套接字进行读写操作,务必要在BeginAccept方法后调用WaitOne来让线程进入阻塞状态。
当有连接接入后系统会自动调用会调用回调函数,所以当代码执行到回调函数时说明连接已经成功,并在函数的第一句就调用Set方法让处于等待的线程可以继续执行。 四、实例 下面是一个实例,客户端请求连接,服务器端侦听端口,当连接建立之后,服务器发送字符串给客户端,客户端收到后并回发给服务器端。服务器端和客户端的代码:
using UnityEngine;
using System.Collections;
using System.Net;//网络
using System.Net.Sockets;//套接字
using System.Text;//文本
//我的Socket回调函数
public delegate void AlbertReceiveCallBack(string content);
/// <summary>
/// OuYangJun socket
/// </summary>
public class OuYangJunSocket : MonoBehaviour
{
#region 服务器端
//声明一个服务器端的套接字
Socket serverSocket;
//声明一个比特缓存
byte[] serverBuffer = new byte[1024];
//声明一个服务器端的委托
AlbertReceiveCallBack serverCallBack;
public void InitServer(AlbertReceiveCallBack rcb)
{
//传入委托对象
serverCallBack = rcb;
//初始化服务器端的套接字
serverSocket = new Socket(AddressFamily.InterNetwork/*IPV4*/,
SocketType.Stream/*双向读写流*/, ProtocolType.Tcp/*TCP协议*/);
//实例一个网络端点,传入地址和端口
IPEndPoint serverEp = new IPEndPoint(IPAddress.Any, 23456);
//绑定网络端点
serverSocket.Bind(serverEp);
//设置监听的最大数量
serverSocket.Listen(10);
//异步接受客户端的委托
serverSocket.BeginAccept(new System.AsyncCallback(ServerAccept),
serverSocket);
//发送一个消息,表示服务器已经创建
serverCallBack("Server Has Init");
}
private void ServerAccept(System.IAsyncResult ar)
{
//接受结果状态
serverSocket = ar.AsyncState as Socket;
//接收结果
Socket workingSocket = serverSocket.EndAccept(ar);
//开始异步接收消息
workingSocket.BeginReceive(serverBuffer/*消息*/, 0/*接收消息的偏移量*/, this.serverBuffer.Length/*接收消息的字节数*/,
SocketFlags.None/*Socket标志位*/, new System.AsyncCallback(ServerReceive)/*接收回调*/, workingSocket/*最后状态*/);
//接受客户端的请求
workingSocket.BeginAccept(new System.AsyncCallback(ServerAccept), workingSocket);
}
void ServerReceive(System.IAsyncResult ar)
{
//获取正常工作地Socket对象(用来接收数据)
Socket workingSocket = ar.AsyncState as Socket;
//接收到的数据字节数
int byteCount = 0;
//接收到的数据字节串
string content = "";
try
{
//尝试结束异步接收消息
byteCount = workingSocket.EndReceive(ar);
}
catch (SocketException ex)
{
//如果接收失败,返回详细异常
serverCallBack(ex.ToString());
}
if (byteCount > 0)
{
//转换比特数组为字符串(支持中文)
content = UTF8Encoding.UTF8.GetString(serverBuffer);
}
//发送接收到的消息
serverCallBack(content);
//继续接收消息
workingSocket.BeginReceive(serverBuffer/*消息*/, 0/*接收消息的偏移量*/, this.serverBuffer.Length/*接收消息的字节数*/,
SocketFlags.None/*Socket标志位*/, new System.AsyncCallback(ServerReceive)/*接收回调*/, workingSocket/*最后状态*/);
}
#endregion
#region
//声明客户端的套接字
Socket clientSocket;
//声明客户端的委托对象
AlbertReceiveCallBack clientReceiveCallBack;
//声明客户端的缓存1KB
byte[] clientBuffer = new byte[1024];
/// <summary>
/// Intiys the client
/// </summary
/// <param name="ip">地址</param>
/// <param name="port">端口</param>
/// <param name="rcb">委托对象</param>
public void InitClient(string ip, int port, AlbertReceiveCallBack rcb)
{
//接收客户端的委托对象
clientReceiveCallBack = rcb;
//实例化客户端的Socket
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//实例化一个客户端的网络端点(把IP和端口传进来)
IPEndPoint clientEP = new IPEndPoint(IPAddress.Parse(ip), port);
//连接服务器
clientSocket.Connect(clientEP);
clientSocket.BeginReceive(clientBuffer, 0, this.clientBuffer.Length, SocketFlags.None, new System.AsyncCallback(ClientReceive), this.clientSocket);
}
void ClientReceive(System.IAsyncResult ar)
{
//获取一个客户端正在接收数据的Socket对象
Socket workingSocket = ar.AsyncState as Socket;
int byteCount = 0;
string content = "";
try
{
//结束接收数据,完成储存
byteCount = workingSocket.EndReceive(ar);
}
catch (SocketException ex)
{
//如果接收消息失败,发送异常消息
clientReceiveCallBack(ex.ToString());
}
if (byteCount > 0)
{
content = UTF8Encoding.UTF8.GetString(clientBuffer);
}
//发送数据
clientReceiveCallBack(content);
//继续接收下一波数据
workingSocket.BeginReceive(serverBuffer/*消息*/, 0/*接收消息的偏移量*/, this.serverBuffer.Length/*接收消息的字节数*/,
SocketFlags.None/*Socket标志位*/, new System.AsyncCallback(ServerReceive)/*接收回调*/, workingSocket/*最后状态*/);
}
/// <summary>
/// 客户端发送数据的方法
/// </summary>
/// <param name="msg">消息内容.</param>
public void ClientSendMessage(string msg)
{
if (msg != "")
{
//将要发送的字符串消息转成byte数组
clientBuffer = UTF8Encoding.UTF8.GetBytes(msg);
}
clientSocket.BeginSend(clientBuffer, 0, this.clientBuffer.Length, SocketFlags.None, new System.AsyncCallback(SendMsg), this.clientSocket);
}
/// <summary>
/// 客户端发送消息的回调函数。
/// </summary>
/// <param name="ar">Ar.</param>
void SendMsg(System.IAsyncResult ar)
{
Socket workingSocket = ar.AsyncState as Socket;
workingSocket.EndSend(ar);
}
#endregion
}
分别建两个场景取名为Scene1和Scene2将以下两个小脚本分别挂载在两个场景中的主摄像机上。
脚本一:
using UnityEngine;
using System.Collections;
public class ServerSocketDemo : MonoBehaviour {
private OuYangJunSocket ouyangjunSocket;
string serverContent="";
void Awake () {
ouyangjunSocket = new OuYangJunSocket();
ouyangjunSocket.InitServer(ShowMsg);
}
void ShowMsg (string msg) {
serverContent = msg;
}
void OnGUI()
{
GUILayout.Label(serverContent);
}
}
脚本二:
using UnityEngine;
using System.Collections;
public class ClientSocketDemo : MonoBehaviour {
public OuYangJunSocket ouyangjunSocket;
private string clitentContent;
private string needSendText ="";
void Awake () {
//初始化对象
ouyangjunSocket = new OuYangJunSocket();
ouyangjunSocket.InitClient("127.0.0.1", 23456, (string msg) =>
{
clitentContent = msg;
});
}
void OnGUI()
{
needSendText = GUILayout.TextField(needSendText);
if (GUILayout.Button("点击发送消息"))
{
if (needSendText != "")
{
ouyangjunSocket.ClientSendMessage(needSendText);
}
}
}
}
结果如下图:
|