异步Socket通信.docx

上传人:b****2 文档编号:24326235 上传时间:2023-05-26 格式:DOCX 页数:13 大小:21.92KB
下载 相关 举报
异步Socket通信.docx_第1页
第1页 / 共13页
异步Socket通信.docx_第2页
第2页 / 共13页
异步Socket通信.docx_第3页
第3页 / 共13页
异步Socket通信.docx_第4页
第4页 / 共13页
异步Socket通信.docx_第5页
第5页 / 共13页
点击查看更多>>
下载资源
资源描述

异步Socket通信.docx

《异步Socket通信.docx》由会员分享,可在线阅读,更多相关《异步Socket通信.docx(13页珍藏版)》请在冰豆网上搜索。

异步Socket通信.docx

异步Socket通信

异步Socket通信

ByJohnMcTainsh

From:

Translateby:

Hillfree

本文介绍如何使用非阻塞方式的Socket通信,并且创建了一个聊天程序的例子来帮助说明。

所谓同步,可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是出于阻塞的,只有接收到返回的值或消息后才往下执行其他的命令。

  

   

  异步,执行完函数或方法后,不必阻塞性地等待返回值或消息,只需要向系统委托一个异步过程,那么当系统接收到返回值或消息时,系统会自动触发委托的异步过程,从而完成一个完整的流程。

  

   

  并不是说谁好谁不好,只是同步的机制不适合在正式应用的项目当中(但自己测试还是可以的)

2.同步,就是实时处理,比如服务器一接收客户端请求,马上响应,这样客户端可以在最短的时间内得到结果,但是如果多个客户端,或者一个客户端发出的请求很频繁,服务器无法同步处理,就会造成涌塞。

  

  异步,就是分时处理,服务器接收到客户端请求后并不是立即处理,而是等待服务器比较空闲的时候加以处理,可以避免涌塞。

  

3.有同步和异步之分

同步就是调用一个函数,直接函数执行完了才返回到调用函数

异步就是被调用函数初始化完后马上返回...

介绍

本文介绍如何在多个应用程序之间创建和使用TCP/IPSocket来进行通信。

这些应用程序可以运行在同一台机器,也可以在局域网内,甚至也可以是跨越Internet的*。

这种方法的好处是不需要你自己来使用线程,而是通过调用Socket的非阻塞模式来实现。

在例子中:

服务器创建病侦听客户端的连接,一旦有客户连接,服务器就将其加入到一个活动客户的列表中,某个客户端发送的消息也有服务器发送到各个连接的客户端,就好像聊天室中的那样。

或许Remoting(远程调用)是做这种工作更好的办法,但是我们这里还是来学习学习如何使用Socket来实现。

*注意:

跨越Internet的通讯要求服务器有独立的IP地址并且不在代理或是放火墙之后。

事件时序

服务器必须要先侦听,客户端才能够连接。

下面的图例说明了在一个异步Socket会话中的事件时序。

运行示例

实例代码分为两部分:

ChatServer和ChatClient.我们首先来创建ChatServer,然后使用下面的Telnet命令来测试它。

telnet{servermachineIPaddressormachinename}399

telnet10.328.32.76399

这时,服务器上应该出现一条消息来表明这个客户连接的地址和端口。

在任一个telnet窗口中键入的字符都会回显到所有与服务器连接的telnet的窗口中。

试试从多台机器上并发连接服务器。

不要使用localhost或者127.0.0.1来作为服务器程序唯一的侦听地址。

然后运行ChatClient实例作相同的试验和多个客户端和多个telnet并存的测试。

为什么要使用.NET的Socket?

.NET在很多地方都用到了sockets,比如:

WebServices和Remoting。

但是在那些应用中底层的Socket支持已经做好了,不需要直接使用。

但是,和其他非.NET系统的Socket打交道或简单通信的场合中Socket的使用还是很有必要的。

它可以用来和诸如DOS,Windows和UNIX系统进行通信。

底层的Socket应用也可以让你减少了诸如组测,权限,域(domains),用户ID,密码等这些麻烦的安全方面的顾虑。

ChatServer/Listener

服务器侦听端口,当有连接请求时,接受该连接并返回一条欢迎信息。

在例子中客户连接被加到一个活动客户列表m_aryClients中去。

这个列表会根据客户加入和离开作相应的增删。

在某些情况下可能会丢失连接,所以在实际的系统中还应该有轮询侦测客户端是否在线的部分。

当服务器端的listener收到客户端发来的信息后,它会把消息广播到所有连接的客户端。

下面讨论两种侦听的方法,一个是用轮询(polling),另外一个在使用事件来侦测连接的请求。

方法1–使用轮询的TcpListener

System.Net.Sockets中的TcpListener类为我们提供了一个侦听和处理客户连接的简单手段。

下面的代码侦听连接,接受连接,并且向客户连接发回一个带有时间戳的欢迎信息。

如果有另外一个连接请求到来,原来的连接将会丢失。

注意,欢迎信息是采用ASCII编码,而不是UNICODE。

privateSocketclient=null;

constintnPortListen=399;

try

{

TcpListenerlistener=newTcpListener(nPortListen);

Console.WriteLine("Listeningas{0}",listener.LocalEndpoint);

listener.Start();

do

{

byte[]m_byBuff=newbyte[127];

if(listener.Pending())

{

client=listener.AcceptSocket();

//Getcurrentdateandtime.

DateTimenow=DateTime.Now;

stringstrDateLine="Welcome"+now.ToString("G")+"\n\r";

//Converttobytearrayandsend.

Byte[]byteDateLine=System.Text.Encoding.ASCII.GetBytes(strDateLine.ToCharArray());

client.Send(byteDateLine,byteDateLine.Length,0);

}

else

{

Thread.Sleep(100);

}

}while(true);//Don'tusethis.

}

catch(Exceptionex)

{

Console.WriteLine(ex.Message);

}

方法2–使用带事件的Socket

一个更为优雅的方法是创建一个事件来捕捉连接请求。

ChatServer实例就采用了这种方法。

首先服务器的名字和地址用下面的代码取得。

IPAddress[]aryLocalAddr=null;

stringstrHostName="";

try

{

//NOTE:

DNSlookupsareniceandallbutquitetimeconsuming.

strHostName=Dns.GetHostName();

IPHostEntryipEntry=Dns.GetHostByName(strHostName);

aryLocalAddr=ipEntry.AddressList;

}

catch(Exceptionex)

{

Console.WriteLine("Errortryingtogetlocaladdress{0}",ex.Message);

}

//VerifywegotanIPaddress.Telltheuserifwedid

if(aryLocalAddr==null||aryLocalAddr.Length<1)

{

Console.WriteLine("Unabletogetlocaladdress");

return;

}

Console.WriteLine("Listeningon:

[{0}]{1}",strHostName,aryLocalAddr[0]);

得到地址之后,我们要把listener这个Socket绑定到这个地址。

我们这里使用的侦听端口是399。

此外,从位于"C:

\WinNT\System32\drivers\etc\Services"的服务文件中读取端口号应该是一个很好的练习。

下面的代码绑定Listener并且开始侦听。

一个事件handler把所有的连接请求都指向了OnConnectRequest。

这样程序就可以不需要等待或者轮询来处理客户连接了。

constintnPortListen=399;

//CreatethelistenersocketinthismachinesIPaddress

Socketlistener=newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);

listener.Bind(newIPEndPoint(aryLocalAddr[0],399));

//listener.Bind(newIPEndPoint(IPAddress.Loopback,399));//Forusewithlocalhost127.0.0.1

listener.Listen(10);

//Setupacallbacktobenotifiedofconnectionrequests

listener.BeginAccept(newAsyncCallback(app.OnConnectRequest),listener);

当客户连接请求到达时,就会激发下面的处理事件。

下面的代码首先创建了client(Socket),然后发回欢迎信息,接着重新建立了接受事件处理(accepteventhandler)。

Socketclient;

publicvoidOnConnectRequest(IAsyncResultar)

{

Socketlistener=(Socket)ar.AsyncState;

client=listener.EndAccept(ar);

Console.WriteLine("Client{0},joined",client.RemoteEndPoint);

//Getcurrentdateandtime.

DateTimenow=DateTime.Now;

stringstrDateLine="Welcome"+now.ToString("G")+"\n\r";

//Converttobytearrayandsend.

Byte[]byteDateLine=System.Text.Encoding.ASCII.GetBytes(strDateLine.ToCharArray());

client.Send(byteDateLine,byteDateLine.Length,0);

listener.BeginAccept(newAsyncCallback(OnConnectRequest),listener);

}

这段代码可以扩展,维护客户Socket的列表,监控数据接收和连接断开。

对于连接断开的侦测放在AsyncCallback事件处理中。

ChatClient部分将在下面细述该机制。

ChatClient

ChatClient是一个WindowsForm应用程序,用来连接服务器,收发消息。

连接

当点击界面上的连接按钮使执行下面的程序使客户连接到服务器。

privateSocketm_sock=null;

privatevoidm_btnConnect_Click(objectsender,System.EventArgse)

{

Cursorcursor=Cursor.Current;

Cursor.Current=Cursors.WaitCursor;

try

{

//Closethesocketifitisstillopen

if(m_sock!

=null&&m_sock.Connected)

{

m_sock.Shutdown(SocketShutdown.Both);

System.Threading.Thread.Sleep(10);

m_sock.Close();

}

//Createthesocketobject

m_sock=newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);

//DefinetheServeraddressandport

IPEndPointepServer=newIPEndPoint(IPAddress.Parse(m_tbServerAddress.Text),399);

//Connecttotheserverblockingmethodandsetupcallbackforrecieveddata

//m_sock.Connect(epServer);

//SetupRecieveCallback(m_sock);

//Connecttoservernon-Blockingmethod

m_sock.Blocking=false;

AsyncCallbackonconnect=newAsyncCallback(OnConnect);

m_sock.BeginConnect(epServer,onconnect,m_sock);

}

catch(Exceptionex)

{

MessageBox.Show(this,ex.Message,"ServerConnectfailed!

");

}

Cursor.Current=cursor;

}

如果连接已经存在就销毁它。

创建一个Socket和指定的端点相连。

被注释掉部分的代码采用简单的阻塞式连接方法。

BeginConnect则用来做一个非阻塞的连接请求。

注意,即使是一个非阻塞的用户连接请求,连接也回被阻塞知道机器名称被解析为IP地址。

所以,要尽量使用IP地址而不是机器名来避免这种情况。

一旦连接请求处理完毕就会调用下面的方法,它显示连接错误或者在成功连接的情况下建立起接收数据的回调。

publicvoidOnConnect(IAsyncResultar)

{

//Socketwasthepassedinobject

Socketsock=(Socket)ar.AsyncState;

//Checkifweweresucessfull

try

{

//sock.EndConnect(ar);

if(sock.Connected)

SetupRecieveCallback(sock);

else

MessageBox.Show(this,"Unabletoconnecttoremotemachine",

"ConnectFailed!

");

}

catch(Exceptionex)

{

MessageBox.Show(this,ex.Message,"UnusualerrorduringConnect!

");

}

}

接收数据 

为了异步接收数据,有必要建立一个AsyncCallback来处理被诸如接到数据和连接断开所激发的事件。

用下面的方法。

privatebyte[]m_byBuff=newbyte[256];//Recieveddatabuffer

publicvoidSetupRecieveCallback(Socketsock)

{

try

{

AsyncCallbackrecieveData=newAsyncCallback(OnRecievedData);

sock.BeginReceive(m_byBuff,0,m_byBuff.Length,SocketFlags.None,

recieveData,sock);

}

catch(Exceptionex)

{

MessageBox.Show(this,ex.Message,"SetupRecieveCallbackfailed!

");

}

}

SetupRecieveCallback方法启动了BeginReceive,并利用代理指针把回调指向OnReceveData方法。

同时它也把一个用来接收数据的缓冲传递过去。

publicvoidOnRecievedData(IAsyncResultar)

{

//Socketwasthepassedinobject

Socketsock=(Socket)ar.AsyncState;

//Checkifwegotanydata

try

{

intnBytesRec=sock.EndReceive(ar);

if(nBytesRec>0)

{

//WrotethedatatotheList

stringsRecieved=Encoding.ASCII.GetString(m_byBuff,0,nBytesRec);

//WARNING:

ThefollowinglineisNOTthreadsafe.Invokeis

//m_lbRecievedData.Items.Add(sRecieved);

Invoke(m_AddMessage,newstring[]{sRecieved});

//Iftheconnectionisstillusablerestablishthecallback

SetupRecieveCallback(sock);

}

else

{

//Ifnodatawasrecievedthentheconnectionisprobablydead

Console.WriteLine("Client{0},disconnected",sock.RemoteEndPoint);

sock.Shutdown(SocketShutdown.Both);

sock.Close();

}

}

catch(Exceptionex)

{

MessageBox.Show(this,ex.Message,"UnusualerrordruingRecieve!

");

}

}

当上面的事件被激发时,接收到的数据被默认为是ASCII编码的。

新数据也会被激发的事件显示出来。

尽管可以调用Add()在列表中显示新数据,但这并不是一个好主意,因为收到的数据很有可能要被送到其他线程中去处理。

注意,需要在接收之后重建接收回调,来确保可以继续接收数据。

因为有可能数据很多,超过最初的buffer容量。

创建AddMessage委托可以降低Socket线程和用户界面线程的耦合程度,如下所示:

//Declarethedelegateprototypetosenddatabacktotheform

delegatevoidAddMessage(stringsNewMessage);

namespaceChatClient

{

...

publicclassFormMain:

System.Windows.Forms.Form

{

privateeventAddMessagem_AddMessage;

//AddMessageEventhandlerforForm

...

publicFormMain()

{

...

//AddMessageEventhandlerforFormdecouplingfrominputthread

m_AddMessage=newAddMessage(OnAddMessage);

...

}

publicvoidOnAddMessage(stringsMessage)

{

//Threadsafeoperationhere

m_lbRecievedData.Items.Add(sMessage);

}

publicvoidOnSomeOtherThread()

{

...

stringsSomeText="BilboBaggins";

Invoke(m_AddMessage,newstring[]{sSomeText});

}

...

}

}

使用UNICODE

当时用比特流来发送接收数据时,数据就需要被适当的编码。

C#采用多字节字符编码,尽管这里使用Encoding.ASCII,但如果需要也可以使用Encoding.UNICODE

不要相信发出什么就能收到什么

当接收数据事件被激发,接收的数据被放置到接收缓冲中去。

在我们的开发中,分组发送往往对应一个分组接收事件。

但是在真正的系统中并非如此。

数据并不是都是规规矩矩的在报文中,而有可能被拆分到若干个分组中。

不要指望总能收到完整的报文,也不要指望建立自己的符号标记报文的开始和结束就万事大吉了。

结论

尽管使用Socket并不难,但是要用的很好还是需要大量的实践练习。

当然在合适的场合你也应该试试使用WebServices或Remoting。

此外,Wrox出版社的ProfessionalADO.NETProgramming这本书很不错,值得一看。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 医药卫生 > 中医中药

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1