你得学会并且学得会的Socket编程基础知识
這一篇文章,我將圖文并茂地介紹Socket編程的基礎知識,我相信,如果你按照步驟做完實驗,一定可以對Socket編程有更好地理解。
本文源代碼,可以通過這里下載?http://files.cnblogs.com/chenxizhang/SocketWorkshop.rar
?
第一步:創建解決方案
第二步:創建服務端程序
這里可以選擇“Console Application”這個類型,比較方便調試
然后編寫如下代碼,實現服務器的基本功能
using System; using System.Collections.Generic; using System.Linq; using System.Text;//額外導入的兩個命名空間 using System.Net.Sockets; using System.Net;namespace SocketServer {class Program{/// <summary>/// Socket Server 演示/// 作者:陳希章/// </summary>/// <param name="args"></param>static void Main(string[] args){//創建一個新的Socket,這里我們使用最常用的基于TCP的Stream Socket(流式套接字)var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//將該socket綁定到主機上面的某個端口//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspxsocket.Bind(new IPEndPoint(IPAddress.Any, 4530));//啟動監聽,并且設置一個最大的隊列長度//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspxsocket.Listen(4);Console.WriteLine("Server is ready!");Console.Read();}} }?
現在可以啟動調試一下看看效果如何,正常情況下應該會看到一個提示,因為我們需要在TCP 4530端口進行監聽,所以防火墻會有提示。
點擊“Allow access”
這樣,我們的服務器就可以開始監聽了。但是這有什么用呢?是的,沒有什么用。
我們還需要為服務器添加一些功能,例如接受傳入的請求,給客戶端發送消息,或者從客戶端接收消息等等
第三步:接受傳入的請求
我們需要通過Accept,或者(BeginAccept)來接受傳入的請求,請注意下面代碼中的紅色部分
?
using System; using System.Collections.Generic; using System.Linq; using System.Text;//額外導入的兩個命名空間 using System.Net.Sockets; using System.Net;namespace SocketServer {class Program{/// <summary>/// Socket Server 演示/// 作者:陳希章/// </summary>/// <param name="args"></param>static void Main(string[] args){//創建一個新的Socket,這里我們使用最常用的基于TCP的Stream Socket(流式套接字)var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//將該socket綁定到主機上面的某個端口//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspxsocket.Bind(new IPEndPoint(IPAddress.Any, 4530));//啟動監聽,并且設置一個最大的隊列長度//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspxsocket.Listen(4); //開始接受客戶端連接請求//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspxsocket.BeginAccept(new AsyncCallback((ar) =>{//這就是客戶端的Socket實例,我們后續可以將其保存起來var client = socket.EndAccept(ar);//給客戶端發送一個歡迎消息client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at "+DateTime.Now.ToString()));}), null);Console.WriteLine("Server is ready!");Console.Read();}} }?
wow,看起來不錯對吧,我們趕緊做一個客戶端來測試一下吧
?
第四步:創建客戶端
我們還是使用一個Console Application
添加如下的代碼,并且創建客戶端連接
using System; using System.Collections.Generic; using System.Linq; using System.Text;//導入的命名空間 using System.Net.Sockets;namespace SocketClient {class Program{/// <summary>/// Socket Server 演示/// 作者:陳希章/// </summary>/// <param name="args"></param>static void Main(string[] args){//創建一個Socketvar socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//連接到指定服務器的指定端口//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connect.aspxsocket.Connect("localhost", 4530);Console.WriteLine("connect to the server");Console.Read();}} }
依次選擇SocketServer和SocketClient這兩個項目,分別將其啟動為調試狀態(右鍵菜單,Debug=>Start new instance)
我們看到兩個程序都工作正常。
但是,在客戶端怎么沒有收到服務器發過來的消息呢?那是因為,我們沒有在客戶端提供這方面的功能。
?
第五步:在客戶端中實現接受消息的方法
using System; using System.Collections.Generic; using System.Linq; using System.Text;//導入的命名空間 using System.Net.Sockets;namespace SocketClient {class Program{/// <summary>/// Socket Server 演示/// 作者:陳希章/// </summary>/// <param name="args"></param>static void Main(string[] args){//創建一個Socketvar socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//連接到指定服務器的指定端口//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connect.aspxsocket.Connect("localhost", 4530);//實現接受消息的方法 var buffer = new byte[1024];//設置一個緩沖區,用來保存數據//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginreceive.aspxsocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback((ar) =>{//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspxvar length = socket.EndReceive(ar);//讀取出來消息內容var message = Encoding.Unicode.GetString(buffer, 0, length);//顯示消息Console.WriteLine(message);}), null);Console.WriteLine("connect to the server");Console.Read();}} }請注意以上紅色的部分,我們用了BeginReceive方法進行異步的消息偵聽,如果收到了,我們就打印出來
看起來已經實現了我們需求了:服務器給客戶端發了一個消息,而客戶端也已經收到了。
但是,這遠遠不夠,因為它們之間的通訊不僅僅是一次性的,那么如果服務器要不斷地給客戶端發消息,例如每隔兩秒鐘就發送一個消息,如何實現呢?
?
第六步:實現服務器定期向客戶端發消息
using System; using System.Collections.Generic; using System.Linq; using System.Text;//額外導入的兩個命名空間 using System.Net.Sockets; using System.Net;namespace SocketServer {class Program{/// <summary>/// Socket Server 演示/// 作者:陳希章/// </summary>/// <param name="args"></param>static void Main(string[] args){//創建一個新的Socket,這里我們使用最常用的基于TCP的Stream Socket(流式套接字)var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//將該socket綁定到主機上面的某個端口//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspxsocket.Bind(new IPEndPoint(IPAddress.Any, 4530));//啟動監聽,并且設置一個最大的隊列長度//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspxsocket.Listen(4);//開始接受客戶端連接請求//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspxsocket.BeginAccept(new AsyncCallback((ar) =>{//這就是客戶端的Socket實例,我們后續可以將其保存起來var client = socket.EndAccept(ar);//給客戶端發送一個歡迎消息client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at "+DateTime.Now.ToString())); //實現每隔兩秒鐘給服務器發一個消息//這里我們使用了一個定時器var timer = new System.Timers.Timer();timer.Interval = 2000D;timer.Enabled = true;timer.Elapsed += (o, a) =>{client.Send(Encoding.Unicode.GetBytes("Message from server at " +DateTime.Now.ToString()));};timer.Start();}), null);Console.WriteLine("Server is ready!");Console.Read();}} }?
我們還要實現在客戶端一直監聽消息的機制,而不是一次性接收.請注意下面紅色的部分
?
using System; using System.Collections.Generic; using System.Linq; using System.Text;//導入的命名空間 using System.Net.Sockets;namespace SocketClient {class Program{/// <summary>/// Socket Server 演示/// 作者:陳希章/// </summary>/// <param name="args"></param>static void Main(string[] args){//創建一個Socketvar socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//連接到指定服務器的指定端口//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connect.aspxsocket.Connect("localhost", 4530);Console.WriteLine("connect to the server");//實現接受消息的方法//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginreceive.aspx socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage),socket);Console.Read();} static byte[] buffer = new byte[1024];public static void ReceiveMessage(IAsyncResult ar){try{var socket = ar.AsyncState as Socket;//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspxvar length = socket.EndReceive(ar);//讀取出來消息內容var message = Encoding.Unicode.GetString(buffer, 0, length);//顯示消息Console.WriteLine(message);//接收下一個消息(因為這是一個遞歸的調用,所以這樣就可以一直接收消息了)socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);}catch(Exception ex){Console.WriteLine(ex.Message);}}} }重新調試起來,看起來的效果如下圖所示
我們繼續做下面的實驗,一步一步地研究Socket通訊中可能遇到的一些問題
請先關閉掉客戶端這個程序,而不要關閉服務端程序,這時會發現一個錯誤
這個錯誤很容易理解,因為客戶端已經關閉,也就是客戶端那個Socket已經不存在了,服務器還繼續向它發送消息當然會出錯。所以,從可靠性方面的考慮,我們必須在發送消息之前檢測Socket的活動狀態
?
第七步:檢測客戶端的活動狀態
using System; using System.Collections.Generic; using System.Linq; using System.Text;//額外導入的兩個命名空間 using System.Net.Sockets; using System.Net;namespace SocketServer {class Program{/// <summary>/// Socket Server 演示/// 作者:陳希章/// </summary>/// <param name="args"></param>static void Main(string[] args){//創建一個新的Socket,這里我們使用最常用的基于TCP的Stream Socket(流式套接字)var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//將該socket綁定到主機上面的某個端口//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspxsocket.Bind(new IPEndPoint(IPAddress.Any, 4530));//啟動監聽,并且設置一個最大的隊列長度//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspxsocket.Listen(4);//開始接受客戶端連接請求//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspxsocket.BeginAccept(new AsyncCallback((ar) =>{//這就是客戶端的Socket實例,我們后續可以將其保存起來var client = socket.EndAccept(ar);//給客戶端發送一個歡迎消息client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at "+DateTime.Now.ToString()));//實現每隔兩秒鐘給服務器發一個消息//這里我們使用了一個定時器var timer = new System.Timers.Timer();timer.Interval = 2000D;timer.Enabled = true;timer.Elapsed += (o, a) =>{ //檢測客戶端Socket的狀態if(client.Connected){try{client.Send(Encoding.Unicode.GetBytes("Message from server at " + DateTime.Now.ToString()));}catch(SocketException ex){Console.WriteLine(ex.Message);}}else{timer.Stop();timer.Enabled = false;Console.WriteLine("Client is disconnected, the timer is stop.");}};timer.Start();}), null);Console.WriteLine("Server is ready!");Console.Read();}} }上面代碼的邏輯很清楚,但有時候還是會觸發那個SocketException。為什么呢?這是因為我們的Timer是每隔兩秒鐘檢查一次,那么就很可能有一種情況,我們檢查的時候,它還是連接狀態,消息發出去之后,它斷開了。這種情況肯定是存在的。所以要用Try..catch的結構
?
目前我們實現的場景很簡單,服務器只管發消息,客戶端只管收消息。但實際工作中,可能希望服務器和客戶端都能收發消息。請看下一節
?
第八步:實現雙向收發消息
先看服務端的修改
?
using System; using System.Collections.Generic; using System.Linq; using System.Text;//額外導入的兩個命名空間 using System.Net.Sockets; using System.Net;namespace SocketServer {class Program{/// <summary>/// Socket Server 演示/// 作者:陳希章/// </summary>/// <param name="args"></param>static void Main(string[] args){//創建一個新的Socket,這里我們使用最常用的基于TCP的Stream Socket(流式套接字)var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//將該socket綁定到主機上面的某個端口//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspxsocket.Bind(new IPEndPoint(IPAddress.Any, 4530));//啟動監聽,并且設置一個最大的隊列長度//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspxsocket.Listen(4);//開始接受客戶端連接請求//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspxsocket.BeginAccept(new AsyncCallback((ar) =>{//這就是客戶端的Socket實例,我們后續可以將其保存起來var client = socket.EndAccept(ar);//給客戶端發送一個歡迎消息client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at "+DateTime.Now.ToString()));//實現每隔兩秒鐘給服務器發一個消息//這里我們使用了一個定時器var timer = new System.Timers.Timer();timer.Interval = 2000D;timer.Enabled = true;timer.Elapsed += (o, a) =>{//檢測客戶端Socket的狀態if(client.Connected){try{client.Send(Encoding.Unicode.GetBytes("Message from server at " + DateTime.Now.ToString()));}catch(SocketException ex){Console.WriteLine(ex.Message);}}else{timer.Stop();timer.Enabled = false;Console.WriteLine("Client is disconnected, the timer is stop.");}};timer.Start(); //接收客戶端的消息(這個和在客戶端實現的方式是一樣的)client.BeginReceive(buffer,0,buffer.Length,SocketFlags.None,new AsyncCallback(ReceiveMessage),client);}), null);Console.WriteLine("Server is ready!");Console.Read();} static byte[] buffer = new byte[1024];public static void ReceiveMessage(IAsyncResult ar){try{var socket = ar.AsyncState as Socket;//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspxvar length = socket.EndReceive(ar);//讀取出來消息內容var message = Encoding.Unicode.GetString(buffer, 0, length);//顯示消息Console.WriteLine(message);//接收下一個消息(因為這是一個遞歸的調用,所以這樣就可以一直接收消息了)socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);}catch(Exception ex){Console.WriteLine(ex.Message);}}} }可以看出來,為了讓服務器可以接受消息,其實并不需要什么特別的設計,與客戶端接受消息其實可以是一樣的
?
再來看看客戶端的修改
using System; using System.Collections.Generic; using System.Linq; using System.Text;//導入的命名空間 using System.Net.Sockets;namespace SocketClient {class Program{/// <summary>/// Socket Server 演示/// 作者:陳希章/// </summary>/// <param name="args"></param>static void Main(string[] args){//創建一個Socketvar socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//連接到指定服務器的指定端口//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connect.aspxsocket.Connect("localhost", 4530);Console.WriteLine("connect to the server");//實現接受消息的方法//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginreceive.aspxsocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket); //接受用戶輸入,將消息發送給服務器端while(true){var message = "Message from client : " + Console.ReadLine();var outputBuffer = Encoding.Unicode.GetBytes(message);socket.BeginSend(outputBuffer, 0, outputBuffer.Length, SocketFlags.None, null, null);}}static byte[] buffer = new byte[1024];public static void ReceiveMessage(IAsyncResult ar){try{var socket = ar.AsyncState as Socket;//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspxvar length = socket.EndReceive(ar);//讀取出來消息內容var message = Encoding.Unicode.GetString(buffer, 0, length);//顯示消息Console.WriteLine(message);//接收下一個消息(因為這是一個遞歸的調用,所以這樣就可以一直接收消息了)socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);}catch(Exception ex){Console.WriteLine(ex.Message);}}} }我在這里做了一個死循環,用戶可以不斷地輸入,這些消息會被發送給服務器。如下圖所示
【備注】因為服務器每隔兩秒鐘會發送新消息過來,所以在輸入的時候,動作要稍快一點啦
?
本文最后探討一個問題,就是如何讓我們的服務器可以支持多個客戶端
?
第九步:支持多個客戶端
這個步驟只需要修改服務端程序即可
using System; using System.Collections.Generic; using System.Linq; using System.Text;//額外導入的兩個命名空間 using System.Net.Sockets; using System.Net;namespace SocketServer {class Program{/// <summary>/// Socket Server 演示/// 作者:陳希章/// </summary>/// <param name="args"></param>static void Main(string[] args){//創建一個新的Socket,這里我們使用最常用的基于TCP的Stream Socket(流式套接字)var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//將該socket綁定到主機上面的某個端口//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspxsocket.Bind(new IPEndPoint(IPAddress.Any, 4530));//啟動監聽,并且設置一個最大的隊列長度//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspxsocket.Listen(4);//開始接受客戶端連接請求//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspx socket.BeginAccept(new AsyncCallback(ClientAccepted), socket);Console.WriteLine("Server is ready!");Console.Read();} public static void ClientAccepted(IAsyncResult ar){var socket = ar.AsyncState as Socket;//這就是客戶端的Socket實例,我們后續可以將其保存起來var client = socket.EndAccept(ar);//給客戶端發送一個歡迎消息client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at " + DateTime.Now.ToString()));//實現每隔兩秒鐘給服務器發一個消息//這里我們使用了一個定時器var timer = new System.Timers.Timer();timer.Interval = 2000D;timer.Enabled = true;timer.Elapsed += (o, a) =>{//檢測客戶端Socket的狀態if(client.Connected){try{client.Send(Encoding.Unicode.GetBytes("Message from server at " + DateTime.Now.ToString()));}catch(SocketException ex){Console.WriteLine(ex.Message);}}else{timer.Stop();timer.Enabled = false;Console.WriteLine("Client is disconnected, the timer is stop.");}};timer.Start();//接收客戶端的消息(這個和在客戶端實現的方式是一樣的)client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), client);//準備接受下一個客戶端請求socket.BeginAccept(new AsyncCallback(ClientAccepted), socket);}static byte[] buffer = new byte[1024];public static void ReceiveMessage(IAsyncResult ar){try{var socket = ar.AsyncState as Socket;//方法參考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspxvar length = socket.EndReceive(ar);//讀取出來消息內容var message = Encoding.Unicode.GetString(buffer, 0, length);//顯示消息Console.WriteLine(message);//接收下一個消息(因為這是一個遞歸的調用,所以這樣就可以一直接收消息了)socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);}catch(Exception ex){Console.WriteLine(ex.Message);}}} }最后調試起來看到的效果如下圖
?
本文源代碼,可以通過這里下載?http://files.cnblogs.com/chenxizhang/SocketWorkshop.rar
from:?http://www.cnblogs.com/chenxizhang/archive/2011/09/10/2172994.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的你得学会并且学得会的Socket编程基础知识的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何制作chm格式的帮助文件
- 下一篇: Ukbench图像数据集