在上一篇文章中介紹了Socket基礎—TCP與UDP協議和他們之間的區別,這篇文章參考另一位前輩的博文重點記錄下Socket的原理及兩種協議的開發過程。
一、Socket通信簡介
1.按慣例先來介紹下socket
Windows中的很多東西都是從Unix領域借鑒過來的,Socket也是一樣。在Unix中,socket代表了一種文件描述符(在Unix中一切都是以文件為單位),而這里這個描述符則是用於描述網絡訪問的。什么意思呢?就是程序員可以通過socket來發送和接收網絡上的數據。你也可以理解成是一個API。有了它,你就不用直接去操作網卡了,而是通過這個接口,這樣就省了很多復雜的操作。
在C#中,MS為我們提供了 System.Net.Sockets 命名空間,里面包含了Socket類。
2.有了socket,那就可以用它來訪問網絡了
不過你不要高興得太早,要想訪問網絡,還得有些基本的條件(和編程無關的我就不提了):a. 要確定本機的IP和端口,socket只有與某一IP和端口綁定,才能發揮強大的威力。b. 得有協議吧(否則誰認得你這發送到網絡的是什么呀)。想要復雜的,我們可以自己來定協議。但是這個就不在這篇里提了,我這里介紹兩種大家最熟悉不過的協議:TCP & UDP。(別說你不知道,不然...不然...我不告訴你)
如果具備了基本的條件,就可以開始用它們訪問網絡了。來看看步驟吧:
a. 建立一個套接字
b. 綁定本機的IP和端口
c. 如果是TCP,因為是面向連接的,所以要利用Listen()方法來監聽網絡上是否有人給自己發東西;如果是UDP,因為是無連接的,所以來者不拒。
d. TCP情況下,如果監聽到一個連接,就可以使用accept來接收這個連接,然后就可以利用Send/Receive來執行操作了。而UDP,則不需要accept, 直接使用SendTo/ReceiveFrom來執行操作。(看清楚哦,和TCP的執行方法有區別,因為UDP不需要建立連接,所以在發送前並不知道對方的IP和端口,因此需要指定一個發送的節點才能進行正常的發送和接收)
e. 如果你不想繼續發送和接收了,就不要浪費資源了。能close的就close吧。
面向連接的套接字系統調用時序 (TCP)
無連接的套接字系統調用時序(UDP)
二、TCP協議的Socket實例
服務端 后台代碼:

1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.IO; 7 using System.Linq; 8 using System.Net; 9 using System.Net.Sockets; 10 using System.Text; 11 using System.Threading; 12 using System.Threading.Tasks; 13 using System.Windows.Forms; 14 15 namespace Socket通信 16 { 17 public partial class Form1 : Form 18 { 19 20 public Form1() 21 { 22 InitializeComponent(); 23 TextBox.CheckForIllegalCrossThreadCalls = false; 24 25 } 26 Socket socketSend; 27 Thread threadWatch = null; // 負責監聽客戶端連接請求的 線程; 28 Socket socketWatch = null; 29 30 31 /// <summary> 32 /// 監聽客戶端請求的方法; 33 /// </summary> 34 void WatchConnecting() 35 { 36 try 37 { 38 while (true) // 持續不斷的監聽客戶端的連接請求; 39 { 40 // 開始監聽客戶端連接請求,Accept方法會阻斷當前的線程; 41 Socket sokConnection = socketWatch.Accept(); 42 // 一旦監聽到一個客戶端的請求,就返回一個與該客戶端通信的 套接字; 43 // 向列表控件中添加客戶端的IP信息; 44 lbOnline.Items.Add(sokConnection.RemoteEndPoint.ToString()); 45 ShowMsg("客戶端連接成功!"); 46 //開啟一個新線程,執行接收消息方法 47 Thread r_thread = new Thread(Received); 48 r_thread.IsBackground = true; 49 r_thread.Start(sokConnection); 50 } 51 } 52 catch (Exception e) 53 { 54 ShowMsg("異常:" + e.Message); 55 } 56 57 } 58 /// <summary> 59 /// 服務器端不停的接收客戶端發來的消息 60 /// </summary> 61 /// <param name="o"></param> 62 void Received(object o) 63 { 64 try 65 { 66 socketSend = o as Socket; 67 while (true) 68 { 69 //客戶端連接服務器成功后,服務器接收客戶端發送的消息 70 // 定義一個3M的緩存區; 71 byte[] buffer = new byte[1024 * 1024 * 3]; 72 //實際接收到的有效字節數 73 // 將接受到的數據存入到輸入 buffer中; 74 int len = socketSend.Receive(buffer); 75 if (len == 0) 76 { 77 break; 78 } 79 string str = Encoding.UTF8.GetString(buffer, 0, len); 80 ShowMsg("接收到的客戶端數據:" + socketSend.RemoteEndPoint + ":" + str); 81 Send("服務端接收成功(" + str + ")"); 82 83 } 84 } 85 catch (Exception e) 86 { 87 ShowMsg("異常:" + e.Message); 88 } 89 } 90 /// <summary> 91 /// 服務器向客戶端發送消息 92 /// </summary> 93 /// <param name="str"></param> 94 void Send(string str) 95 { 96 byte[] buffer = Encoding.UTF8.GetBytes(str); 97 socketSend.Send(buffer); 98 99 } 100 void ShowMsg(string str) 101 { 102 txtMsg.AppendText(str + "\r\n"); 103 } 104 private void button1_Click_1(object sender, EventArgs e) 105 { 106 // 創建負責監聽的套接字,注意其中的參數; 107 socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 108 // 獲得文本框中的IP對象; 109 IPAddress address = IPAddress.Parse(txtIp.Text.Trim()); 110 // 創建包含ip和端口號的網絡節點對象; 111 IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim())); 112 try 113 { 114 // 將負責監聽的套接字綁定到唯一的ip和端口上; 115 socketWatch.Bind(endPoint); 116 } 117 catch (SocketException se) 118 { 119 MessageBox.Show("異常:" + se.Message); 120 return; 121 } 122 // 設置監聽隊列的長度; 123 socketWatch.Listen(10); 124 // 創建負責監聽的線程; 125 threadWatch = new Thread(WatchConnecting); 126 threadWatch.IsBackground = true; 127 threadWatch.Start(); 128 ShowMsg("服務器啟動監聽成功!"); 129 } 130 } 131 }
客戶端 后台代碼:

1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows.Forms; 10 using System.Net; 11 using System.Net.Sockets; 12 using System.Threading; 13 14 15 namespace Socket通信客戶端 16 { 17 public partial class Socket_client : Form 18 { 19 public Socket_client() 20 { 21 InitializeComponent(); 22 CheckForIllegalCrossThreadCalls = false; 23 } 24 Socket socketSend; 25 void ShowMsg(string str) 26 { 27 txtMsg.AppendText(str + "\r\n"); 28 } 29 /// <summary> 30 /// 接收服務端返回的消息 31 /// </summary> 32 void Received() 33 { 34 while (true) 35 { 36 try 37 { 38 byte[] buffer = new byte[1024 * 1024 * 3]; 39 //實際接收到的有效字節數 40 int len = socketSend.Receive(buffer); 41 if (len == 0) 42 { 43 continue; 44 } 45 string str = Encoding.UTF8.GetString(buffer, 0, len); 46 ShowMsg("接收到的服務端數據:" + socketSend.RemoteEndPoint + ":" + str); 47 } 48 catch 49 { 50 MessageBox.Show("接收失敗,請檢查服務端是否斷開!"); 51 return; 52 } 53 } 54 } 55 56 private void btnDisconnect_Click(object sender, EventArgs e) 57 { 58 socketSend.Close(); 59 ShowMsg("連接已經斷開!"); 60 } 61 void Send(string str) 62 { 63 byte[] buffer = Encoding.UTF8.GetBytes(str); 64 socketSend.Send(buffer); 65 } 66 } 67 }
三、UDP協議的Socket實例

using System; using System.Collections.Generic; using System.Text; using System.Net; using System.Net.Sockets; namespace SimpleUdpSrvr { class Program { static void Main(string[] args) { int recv; byte[] data = new byte[1024]; IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9050);//定義一網絡端點 Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);//定義一個Socket newsock.Bind(ipep);//Socket與本地的一個終結點相關聯 Console.WriteLine("Waiting for a client.."); IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);//定義要發送的計算機的地址 EndPoint Remote = (EndPoint)(sender);// recv = newsock.ReceiveFrom(data, ref Remote);//接受數據 Console.WriteLine("Message received from{0}:", Remote.ToString()); Console.WriteLine(Encoding.ASCII.GetBytes(data,0,recv)); string welcome = "Welcome to my test server!"; data = Encoding.ASCII.GetBytes(welcome); newsock.SendTo(data, data.Length, SocketFlags.None, Remote); while (true) { data = new byte[1024]; recv = newsock.ReceiveFrom(data, ref Remote); Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv)); newsock.SendTo(data, recv, SocketFlags.None, Remote); } } } }

using System; using System.Collections.Generic; using System.Text; using System.Net; using System.Net.Sockets; namespace SimpleUdpClient { class Program { static void Main(string[] args) { byte[] data = new byte[1024];//定義一個數組用來做數據的緩沖區 string input, stringData; IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9050); Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); string welcome = "Hello,are you there?"; data = Encoding.ASCII.GetBytes(welcome); server.SendTo(data, data.Length, SocketFlags.None, ipep);//將數據發送到指定的終結點 IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0); EndPoint Remote = (EndPoint)sender; data = new byte[1024]; int recv = server.ReceiveFrom(data, ref Remote);//接受來自服務器的數據 Console.WriteLine("Message received from{0}:", Remote.ToString()); Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv)); while (true)//讀取數據 { input = Console.ReadLine();//從鍵盤讀取數據 if (input == "text")//結束標記 { break; } server.SendTo(Encoding.ASCII.GetBytes(input), Remote);//將數據發送到指定的終結點Remote data = new byte[1024]; recv = server.ReceiveFrom(data, ref Remote);//從Remote接受數據 stringData = Encoding.ASCII.GetString(data, 0, recv); Console.WriteLine(stringData); } Console.WriteLine("Stopping client"); server.Close(); } } }
上面的示例只是簡單的應用了socket來實現通信,你也可以實現異步socket、IP組播 等等。
MS還為我們提供了幾個助手類:TcpClient類、TcpListener類、UDPClient類。這幾個類簡化了一些操作,所以你也可以利用這幾類來寫上面的代碼,但我個人還是比較習慣直接用socket來寫。
既然快寫完了,那我就再多啰嗦幾句。在需要即時響應的軟件中,我個人更傾向使用UDP來實現通信,因為相比TCP來說,UDP占用更少的資源,且響應速度快,延時低。至於UDP的可靠性,則可以通過在應用層加以控制來滿足。當然如果可靠性要求高的環境下,還是建議使用TCP。