基于C#的socket編程的TCP異步的實現(xiàn)代碼
一、摘要
本篇闡述基于TCP通信協(xié)議的異步實現(xiàn)。
二、實驗平臺
Visual Studio 2010
三、異步通信實現(xiàn)原理及常用方法
3.1 建立連接
在同步模式中,在服務(wù)器上使用Accept方法接入連接請求,而在客戶端則使用Connect方法來連接服務(wù)器。相對地,在異步模式下,服務(wù)器可以使用BeginAccept方法和EndAccept方法來完成連接到客戶端的任務(wù),在客戶端則通過BeginConnect方法和EndConnect方法來實現(xiàn)與服務(wù)器的連接。
BeginAccept在異步方式下傳入的連接嘗試,它允許其他動作而不必等待連接建立才繼續(xù)執(zhí)行后面程序。在調(diào)用BeginAccept之前,必須使用Listen方法來偵聽是否有連接請求,BeginAccept的函數(shù)原型為:
BeginAccept(AsyncCallback AsyncCallback, Ojbect state)
參數(shù):
AsyncCallBack:代表回調(diào)函數(shù)
state:表示狀態(tài)信息,必須保證state中包含socket的句柄
使用BeginAccept的基本流程是:
(1)創(chuàng)建本地終節(jié)點,并新建套接字與本地終節(jié)點進(jìn)行綁定;
(2)在端口上偵聽是否有新的連接請求;
(3)請求開始接入新的連接,傳入Socket的實例或者StateOjbect的實例。
參考代碼:
//定義IP地址 IPAddress local = IPAddress.Parse("127.0,0,1"); IPEndPoint iep = new IPEndPoint(local,13000); //創(chuàng)建服務(wù)器的socket對象 Socket server = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); server.Bind(iep); server.Listen(20); server.BeginAccecpt(new AsyncCallback(Accept),server);
當(dāng)BeginAccept()方法調(diào)用結(jié)束后,一旦新的連接發(fā)生,將調(diào)用回調(diào)函數(shù),而該回調(diào)函數(shù)必須包括用來結(jié)束接入連接操作的EndAccept()方法。
該方法參數(shù)列表為 Socket EndAccept(IAsyncResult iar)
下面為回調(diào)函數(shù)的實例:
void Accept(IAsyncResult iar) { //還原傳入的原始套接字 Socket MyServer = (Socket)iar.AsyncState; //在原始套接字上調(diào)用EndAccept方法,返回新的套接字 Socket service = MyServer.EndAccept(iar); }
至此,服務(wù)器端已經(jīng)準(zhǔn)備好了??蛻舳藨?yīng)通過BeginConnect方法和EndConnect來遠(yuǎn)程連接主機(jī)。在調(diào)用BeginConnect方法時必須注冊相應(yīng)的回調(diào)函數(shù)并且至少傳遞一個Socket的實例給state參數(shù),以保證EndConnect方法中能使用原始的套接字。下面是一段是BeginConnect的調(diào)用:
Socket socket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp) IPAddress ip=IPAddress.Parse("127.0.0.1"); IPEndPoint iep=new IPEndPoint(ip,13000); socket.BeginConnect(iep, new AsyncCallback(Connect),socket);
EndConnect是一種阻塞方法,用于完成BeginConnect方法的異步連接誒遠(yuǎn)程主機(jī)的請求。在注冊了回調(diào)函數(shù)后必須接收BeginConnect方法返回的IASynccReuslt作為參數(shù)。下面為代碼演示:
void Connect(IAsyncResult iar) { Socket client=(Socket)iar.AsyncState; try { client.EndConnect(iar); } catch (Exception e) { Console.WriteLine(e.ToString()); } finally { } }
除了采用上述方法建立連接之后,也可以采用TcpListener類里面的方法進(jìn)行連接建立。下面是服務(wù)器端對關(guān)于TcpListener類使用BeginAccetpTcpClient方法處理一個傳入的連接嘗試。以下是使用BeginAccetpTcpClient方法和EndAccetpTcpClient方法的代碼:
public static void DoBeginAccept(TcpListener listner) { //開始從客戶端監(jiān)聽連接 Console.WriteLine("Waitting for a connection"); //接收連接 //開始準(zhǔn)備接入新的連接,一旦有新連接嘗試則調(diào)用回調(diào)函數(shù)DoAcceptTcpCliet listner.BeginAcceptTcpClient(new AsyncCallback(DoAcceptTcpCliet), listner); } //處理客戶端的連接 public static void DoAcceptTcpCliet(IAsyncResult iar) { //還原原始的TcpListner對象 TcpListener listener = (TcpListener)iar.AsyncState; //完成連接的動作,并返回新的TcpClient TcpClient client = listener.EndAcceptTcpClient(iar); Console.WriteLine("連接成功"); }
代碼的處理邏輯為:
(1)調(diào)用BeginAccetpTcpClient方法開開始連接新的連接,當(dāng)連接視圖發(fā)生時,回調(diào)函數(shù)被調(diào)用以完成連接操作;
(2)上面DoAcceptTcpCliet方法通過AsyncState屬性獲得由BeginAcceptTcpClient傳入的listner實例;
(3)在得到listener對象后,用它調(diào)用EndAcceptTcpClient方法,該方法返回新的包含客戶端信息的TcpClient。
BeginConnect方法和EndConnect方法可用于客戶端嘗試建立與服務(wù)端的連接,這里和第一種方法并無區(qū)別。下面看實例:
public void doBeginConnect(IAsyncResult iar) { Socket client=(Socket)iar.AsyncState; //開始與遠(yuǎn)程主機(jī)進(jìn)行連接 client.BeginConnect(serverIP[0],13000,requestCallBack,client); Console.WriteLine("開始與服務(wù)器進(jìn)行連接"); } private void requestCallBack(IAsyncResult iar) { try { //還原原始的TcpClient對象 TcpClient client=(TcpClient)iar.AsyncState; // client.EndConnect(iar); Console.WriteLine("與服務(wù)器{0}連接成功",client.Client.RemoteEndPoint); } catch(Exception e) { Console.WriteLine(e.ToString()); } finally { } }
以上是建立連接的兩種方法??筛鶕?jù)需要選擇使用。
3.2 發(fā)送與接受數(shù)據(jù)
在建立了套接字的連接后,就可以服務(wù)器端和客戶端之間進(jìn)行數(shù)據(jù)通信了。異步套接字用BeginSend和EndSend方法來負(fù)責(zé)數(shù)據(jù)的發(fā)送。注意在調(diào)用BeginSend方法前要確保雙方都已經(jīng)建立連接,否則會出異常。下面演示代碼:
private static void Send(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, new AsyncCallback(SendCallback), handler); } private static void SendCallback(IAsyncResult ar) { try { // Retrieve the socket from the state object. Socket handler = (Socket)ar.AsyncState; // Complete sending the data to the remote device. int bytesSent = handler.EndSend(ar); Console.WriteLine("Sent {0} bytes to client.", bytesSent); handler.Shutdown(SocketShutdown.Both); handler.Close(); } catch (Exception e) { Console.WriteLine(e.ToString()); } }
接收數(shù)據(jù)是通過BeginReceive和EndReceive方法:
private static void Receive(Socket client) { try { // Create the state object. StateObject state = new StateObject(); state.workSocket = client; // Begin receiving the data from the remote device. client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state); } catch (Exception e) { Console.WriteLine(e.ToString()); } } private static void ReceiveCallback(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. int bytesRead = 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, new AsyncCallback(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)首先處理連接的回調(diào)函數(shù)里得到的通訊套接字client,接著開始接收數(shù)據(jù);
(2)當(dāng)數(shù)據(jù)發(fā)送到緩沖區(qū)中,BeginReceive方法試圖從buffer數(shù)組中讀取長度為buffer.length的數(shù)據(jù)塊,并返回接收到的數(shù)據(jù)量bytesRead。最后接收并打印數(shù)據(jù)。
除了上述方法外,還可以使用基于NetworkStream相關(guān)的異步發(fā)送和接收方法,下面是基于NetworkStream相關(guān)的異步發(fā)送和接收方法的使用介紹。
NetworkStream使用BeginRead和EndRead方法進(jìn)行讀操作,使用BeginWreite和EndWrete方法進(jìn)行寫操作,下面看實例:
static void DataHandle(TcpClient client) { TcpClient tcpClient = client; //使用TcpClient的GetStream方法獲取網(wǎng)絡(luò)流 NetworkStream ns = tcpClient.GetStream(); //檢查網(wǎng)絡(luò)流是否可讀 if(ns.CanRead) { //定義緩沖區(qū) byte[] read = new byte[1024]; ns.BeginRead(read,0,read.Length,new AsyncCallback(myReadCallBack),ns); } else { Console.WriteLine("無法從網(wǎng)絡(luò)中讀取流數(shù)據(jù)"); } } public static void myReadCallBack(IAsyncResult iar) { NetworkStream ns = (NetworkStream)iar.AsyncState; byte[] read = new byte[1024]; String data = ""; int recv; recv = ns.EndRead(iar); data = String.Concat(data, Encoding.ASCII.GetString(read, 0, recv)); //接收到的消息長度可能大于緩沖區(qū)總大小,反復(fù)循環(huán)直到讀完為止 while (ns.DataAvailable) { ns.BeginRead(read, 0, read.Length, new AsyncCallback(myReadCallBack), ns); } //打印 Console.WriteLine("您收到的信息是" + data); }
3.3 程序阻塞與異步中的同步問題
.Net里提供了EventWaitHandle類來表示一個線程的同步事件。EventWaitHandle即事件等待句柄,他允許線程通過操作系統(tǒng)互發(fā)信號和等待彼此的信號來達(dá)到線程同步的目的。這個類有2個子類,分別為AutoRestEevnt(自動重置)和ManualRestEvent(手動重置)。下面是線程同步的幾個方法:
(1)Rset方法:將事件狀態(tài)設(shè)為非終止?fàn)顟B(tài),導(dǎo)致線程阻塞。這里的線程阻塞是指允許其他需要等待的線程進(jìn)行阻塞即讓含WaitOne()方法的線程阻塞;
(2)Set方法:將事件狀態(tài)設(shè)為終止?fàn)顟B(tài),允許一個或多個等待線程繼續(xù)。該方法發(fā)送一個信號給操作系統(tǒng),讓處于等待的某個線程從阻塞狀態(tài)轉(zhuǎn)換為繼續(xù)運行,即WaitOne方法的線程不在阻塞;
(3)WaitOne方法:阻塞當(dāng)前線程,直到當(dāng)前的等待句柄收到信號。此方法將一直使本線程處于阻塞狀態(tài)直到收到信號為止,即當(dāng)其他非阻塞進(jìn)程調(diào)用set方法時可以繼續(xù)執(zhí)行。
public static void StartListening() { // Data buffer for incoming data. byte[] bytes = new Byte[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 = new IPEndPoint(ipAddress, 11000); // Create a TCP/IP socket. Socket listener = new Socket(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(new AsyncCallback(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對象創(chuàng)建一個等待句柄,在調(diào)用BeginAccept方法前使用Rest方法允許其他線程阻塞;
(2)為了防止在連接完成之前對套接字進(jìn)行讀寫操作,務(wù)必要在BeginAccept方法后調(diào)用WaitOne來讓線程進(jìn)入阻塞狀態(tài)。
當(dāng)有連接接入后系統(tǒng)會自動調(diào)用會調(diào)用回調(diào)函數(shù),所以當(dāng)代碼執(zhí)行到回調(diào)函數(shù)時說明連接已經(jīng)成功,并在函數(shù)的第一句就調(diào)用Set方法讓處于等待的線程可以繼續(xù)執(zhí)行。
四、實例
下面是一個實例,客戶端請求連接,服務(wù)器端偵聽端口,當(dāng)連接建立之后,服務(wù)器發(fā)送字符串給客戶端,客戶端收到后并回發(fā)給服務(wù)器端。
服務(wù)器端代碼:
using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; // State object for reading client data asynchronously public class StateObject { // Client socket. public Socket workSocket = null; // Size of receive buffer. public const int BufferSize = 1024; // Receive buffer. public byte[] buffer = new byte[BufferSize]; // Received data string. public StringBuilder sb = new StringBuilder(); } public class AsynchronousSocketListener { // Thread signal. public static ManualResetEvent allDone = new ManualResetEvent(false); public AsynchronousSocketListener() { } public static void StartListening() { // Data buffer for incoming data. byte[] bytes = new Byte[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 = new IPEndPoint(ipAddress, 11000); // Create a TCP/IP socket. Socket listener = new Socket(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(new AsyncCallback(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(); } public static void AcceptCallback(IAsyncResult ar) { // Signal the main thread to continue. allDone.Set(); // Get the socket that handles the client request. Socket listener = (Socket)ar.AsyncState; Socket handler = listener.EndAccept(ar); // Create the state object. StateObject state = new StateObject(); state.workSocket = handler; handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state); } public static void ReadCallback(IAsyncResult ar) { String content = String.Empty; // Retrieve the state object and the handler socket // from the asynchronous state object. StateObject state = (StateObject)ar.AsyncState; Socket handler = state.workSocket; // Read data from the client socket. int bytesRead = handler.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)); // Check for end-of-file tag. If it is not there, read // more data. content = state.sb.ToString(); if (content.IndexOf("<EOF>") > -1) { // All the data has been read from the // client. Display it on the console. Console.WriteLine("Read {0} bytes from socket. \n Data : {1}", content.Length, content); // Echo the data back to the client. Send(handler, content); } else { // Not all data received. Get more. handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state); } } } private static void Send(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, new AsyncCallback(SendCallback), handler); } private static void SendCallback(IAsyncResult ar) { try { // Retrieve the socket from the state object. Socket handler = (Socket)ar.AsyncState; // Complete sending the data to the remote device. int bytesSent = handler.EndSend(ar); Console.WriteLine("Sent {0} bytes to client.", bytesSent); handler.Shutdown(SocketShutdown.Both); handler.Close(); } catch (Exception e) { Console.WriteLine(e.ToString()); } } public static int Main(String[] args) { StartListening(); return 0; } }
客戶端代碼:
using System; using System.Net; using System.Net.Sockets; using System.Threading; using System.Text; // State object for receiving data from remote device. public class StateObject { // Client socket. public Socket workSocket = null; // Size of receive buffer. public const int BufferSize = 256; // Receive buffer. public byte[] buffer = new byte[BufferSize]; // Received data string. public StringBuilder sb = new StringBuilder(); } public class AsynchronousClient { // The port number for the remote device. private const int port = 11000; // ManualResetEvent instances signal completion. private static ManualResetEvent connectDone = new ManualResetEvent(false); private static ManualResetEvent sendDone = new ManualResetEvent(false); private static ManualResetEvent receiveDone = new ManualResetEvent(false); // The response from the remote device. private static String response = String.Empty; private static void StartClient() { // Connect to a remote device. try { // Establish the remote endpoint for the socket. // The name of the // remote device is "host.contoso.com". //IPHostEntry ipHostInfo = Dns.Resolve("user"); //IPAddress ipAddress = ipHostInfo.AddressList[0]; IPAddress ipAddress = IPAddress.Parse("127.0.0.1"); IPEndPoint remoteEP = new IPEndPoint(ipAddress, port); // Create a TCP/IP socket. Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // Connect to the remote endpoint. client.BeginConnect(remoteEP, new AsyncCallback(ConnectCallback), client); connectDone.WaitOne(); // Send test data to the remote device. Send(client, "This is a test<EOF>"); sendDone.WaitOne(); // Receive the response from the remote device. Receive(client); receiveDone.WaitOne(); // Write the response to the console. Console.WriteLine("Response received : {0}", response); // Release the socket. client.Shutdown(SocketShutdown.Both); client.Close(); Console.ReadLine(); } catch (Exception e) { Console.WriteLine(e.ToString()); } } private static void ConnectCallback(IAsyncResult ar) { try { // Retrieve the socket from the state object. Socket client = (Socket)ar.AsyncState; // Complete the connection. client.EndConnect(ar); Console.WriteLine("Socket connected to {0}", client.RemoteEndPoint.ToString()); // Signal that the connection has been made. connectDone.Set(); } catch (Exception e) { Console.WriteLine(e.ToString()); } } private static void Receive(Socket client) { try { // Create the state object. StateObject state = new StateObject(); state.workSocket = client; // Begin receiving the data from the remote device. client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state); } catch (Exception e) { Console.WriteLine(e.ToString()); } } private static void ReceiveCallback(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. int bytesRead = 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, new AsyncCallback(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()); } } private static void Send(Socket client, 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. client.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), client); } private static void SendCallback(IAsyncResult ar) { try { // Retrieve the socket from the state object. Socket client = (Socket)ar.AsyncState; // Complete sending the data to the remote device. int bytesSent = client.EndSend(ar); Console.WriteLine("Sent {0} bytes to server.", bytesSent); // Signal that all bytes have been sent. sendDone.Set(); } catch (Exception e) { Console.WriteLine(e.ToString()); } } public static int Main(String[] args) { StartClient(); return 0; } }
五、實驗結(jié)果
圖1 服務(wù)器端界面
圖2 客戶端界面
相關(guān)文章
無法從 int? 轉(zhuǎn)換為 int 運行時出現(xiàn)錯誤
無法從"int?"轉(zhuǎn)換為"int" ,在運行時會出現(xiàn)錯誤,通過強(qiáng)制類型轉(zhuǎn)換(int)便可解決2014-05-05Winform界面中實現(xiàn)菜單列表的動態(tài)個性化配置管理方法
下面小編就為大家分享一篇Winform界面中實現(xiàn)菜單列表的動態(tài)個性化配置管理方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-11-11C#模擬http 發(fā)送post或get請求的簡單實例
下面小編就為大家?guī)硪黄狢#模擬http 發(fā)送post或get請求的簡單實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-06-06winform實現(xiàn)創(chuàng)建最前端窗體的方法
這篇文章主要介紹了winform實現(xiàn)創(chuàng)建最前端窗體的方法,涉及C#窗體屬性設(shè)置的相關(guān)技巧,非常具有實用價值,需要的朋友可以參考下2015-08-08