TCP、UDP、Socket 通信(原)


      說明:本隨筆主要演示自己給自己發送消息例子,分別使用了TCP協議、UDP協議以及socket套接字通信。使用socket套接字了模擬TCP、UDP通信實現原理。其中有些源碼都來自《C#高級編程 第7版》,並附加了自己的理解,有的也進行了一些簡單的拓展。

      第一次原創隨筆,很多地方可能考慮不周或理解有誤,希望大家留言指正,與大家共同進步。也希望大家不喜勿噴,給點鼓勵還是比較好的~~ 閑話少說,進入主題!

一、TCP 類

  TCP是基於連接的一種通信模式,我們需要創建客戶端與服務器端來進行通信。在客戶端讀取文件,並將文件內容發送到服務器端進行顯示。

      客戶端:

 1 using System;
 2 using System.Windows.Forms;
 3 using System.Net;
 4 using System.Net.Sockets;
 5 using System.IO;
 6 
 7 namespace TcpSender
 8 {
 9     public partial class Form1 : Form
10     {
11         public Form1()
12         {
13             InitializeComponent();
14         }
15 
16         private void btnSend_Click(object sender, EventArgs e)
17         {
18             TcpClient tcpClient = new TcpClient("127.0.0.1",2112);  // 配置主機號及端口 127.0.0.1 2112
19             NetworkStream nws = tcpClient.GetStream(); // 獲取連接流
20             string path = txbFile.Text; // 配置文件路徑
21             FileStream fs = new FileStream(path, FileMode.Open);
22             int data = fs.ReadByte();
23 
24             while (data != -1)
25             {
26                 nws.WriteByte((byte)data);
27                 data = fs.ReadByte();
28             }
29             fs.Close();
30             nws.Close();
31             tcpClient.Close();
32         }
33     }
34 }

  服務器:

 1 using System;
 2 using System.Net;
 3 using System.Net.Sockets;
 4 using System.IO;
 5 using System.Threading;
 6 using System.Windows.Forms;
 7 
 8 namespace TcpReceiver
 9 {
10     public partial class Form1 : Form
11     {
12         public Form1()
13         {
14             InitializeComponent();
15             Thread thread = new Thread(new ThreadStart(Listen)); // 防止主線程在監聽到請求之前處於卡死狀態
16             thread.Start();
17         }
18 
19         // 監聽功能
20         public void Listen()
21         {
22             IPAddress localAddr = IPAddress.Parse("127.0.0.1");
23             Int32 port = 2112;
24             TcpListener tcpListener = new TcpListener(localAddr, port); // 也可以僅綁定端口,因為發送者與接收者均是本地
25             tcpListener.Start();
26 
27             while (true) // 持續監聽
28             {
29                 TcpClient tcpClient = tcpListener.AcceptTcpClient(); // 監聽到請求前處於阻塞狀態
30                 NetworkStream nws = tcpClient.GetStream(); // 獲取監聽流
31 
32                 StreamReader sr = new StreamReader(nws, System.Text.Encoding.UTF8);
33                 string content = sr.ReadToEnd();
34 
35                 // ★★★ very important ★★★
36                 Invoke(new Action<string>(UpdateDisplay), new object[] { content }); // 該線程擁有用戶界面的句柄,因此無需從后台線程直接訪問對話框
37                 tcpClient.Close();
38             }
39             // tcpListener.Stop();
40         }
41  
42         // 將接收到內容顯示到控件中
43         public void UpdateDisplay(string text)
44         {
45             txbContent.Clear();
46             txbContent.Text = text;
47         }
48     }
49 }

  運行效果圖:

客戶端

 

服務器

二、UDP

  UDP是一個無連接的協議,因此可以在一個程序中實現收發消息。

  情形1:使用同一個 UdpClient 實例實現收發消息

 1 using System;
 2 using System.Net;
 3 using System.Net.Sockets;
 4 using System.Text;
 5 using System.Threading;
 6 using System.IO;
 7 
 8 namespace UDPSender
 9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14             #region 模擬自己發送自己 使用UDPClient(通過)
15             UdpClient client = new UdpClient(11000); //相當於給socket實例指定端口,參見socket模擬UDP實現原理(后續代碼)       
16             IPEndPoint iep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000); // 服務器網址及端口
17 
18             Console.WriteLine("Type in what you want to send :");
19             string sendMsg = Console.ReadLine();
20             byte[] sendBytes = Encoding.ASCII.GetBytes(sendMsg);
21             client.Send(sendBytes, sendBytes.Length, iep); //將消息推送到 127.0.0.1:11000
22 
23 
24             byte[] rcvBytes = client.Receive(ref iep);   // 接收127.0.0.1:11000 端口消息,非連接的消息需要緩存存放
25             Console.WriteLine("Received the following message:");
26             string rcvMessage = Encoding.ASCII.GetString(rcvBytes, 0, rcvBytes.Length);
27             Console.WriteLine(rcvMessage);
28             Console.ReadKey();
29             #endregion
30           }
31     }
32 }

  情形2:使用一個 UdpClient 實例發送消息, 使用另一個 UdpClient 實例接收消息

 1 using System;
 2 using System.Net;
 3 using System.Net.Sockets;
 4 using System.Text;
 5 using System.Threading;
 6 using System.IO;
 7 
 8 namespace UDPSender
 9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14             #region 模擬自己發送自己 使用UDPClient(通過)
15             UdpClient client = new UdpClient(); // 沒有為socket指定端口,則在client.Send()時,系統默認為socket分配一個端口,參見Socket模擬UDP(后續代碼)
16             // IPEndPoint remote = new IPEndPoint(IPAddress.Any, 0);
17             UdpClient server = new UdpClient(11000); // 在客戶端發送之前確保服務器已經進行端口綁定,為客戶端發送的消息創建載體
18 
19             IPEndPoint iep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000); // 192.168.106.231 服務器網址及端口
20 
21             Console.WriteLine("Type in what you want to send :");
22             string sendMsg = Console.ReadLine();
23             byte[] sendBytes = Encoding.ASCII.GetBytes(sendMsg);
24             client.Send(sendBytes, sendBytes.Length, iep); // 將消息推送到 127.0.0.1:11000
25 
26 
27             byte[] rcvBytes = server.Receive(ref iep);   // 接收127.0.0.1:11000 端口消息,也可以使用 remote 端點,表示接收所有端口消息
28             Console.WriteLine("Received the following message:");
29             string rcvMessage = Encoding.ASCII.GetString(rcvBytes, 0, rcvBytes.Length);
30             Console.WriteLine(rcvMessage);
31             Console.ReadKey();
32             #endregion
33         }
34     }
35 }

  運行效果圖:

 

三、Socket 通信

  TCP、UDP 底層通信都是通過 socket 套接字實現。

  1、模擬 TCP 通信實現

  客戶端:

 1 using System;
 2 using System.Net;
 3 using System.Net.Sockets;
 4 using System.Text;
 5 
 6 namespace SocketSender
 7 {
 8     // 使用socket類模擬TCP發送原理
 9     class Program
10     {
11         static void Main(string[] args)
12         {
13             byte[] ReceiveBytes = new byte[1024];
14             IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2112); // 創建遠程端點
15 
16             Console.WriteLine("Starting: Creating Socket Object ");
17             while (true)
18             {
19                 // 創建socket對象
20                 Socket SocketSender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
21                 // 連接遠程端點
22                 SocketSender.Connect(ipEndPoint); // 連接到遠程端點,如果服務器端沒有響應,則此處會掛起。若超時,則拋出遠程服務器拒絕的異常。                    
23 
24                 Console.WriteLine("Successfully Connected to {0}",SocketSender.RemoteEndPoint);
25 
26                 Console.WriteLine("Input the Message you want to send :");
27                 string SendingMessage = Console.ReadLine();
28                 byte[] ForwardMessage = Encoding.ASCII.GetBytes(SendingMessage + "[FINAL]");
29 
30                 // 發送消息
31                 SocketSender.Send(ForwardMessage);
32 
33                 // 接收從服務器傳回相應消息,因為是基於連接的,因此使用同一個socket實例直接接收即可
34                 int TotalBytesReceived = SocketSender.Receive(ReceiveBytes);
35 
36                 // 打印輸出接收到的消息
37                 Console.WriteLine("Message Provided By Server: {0}", Encoding.ASCII.GetString(ReceiveBytes, 0, TotalBytesReceived));
38                 Console.WriteLine(Environment.NewLine);
39                 SocketSender.Shutdown(SocketShutdown.Both); // 關閉該socket的發送和接收功能
40                 SocketSender.Close();
41             }
42             //Console.ReadKey();
43         }
44     }
45 }
46     

  服務器端:

 1 using System;
 2 using System.Text;
 3 using System.Net;
 4 using System.Net.Sockets;
 5 
 6 namespace SocketReceiver
 7 {
 8     // 使用socket模擬TCP接收原理
 9     class Program
10     {
11         static void Main(string[] args)
12         {
13             // 本實例是一個基於連接的socket通信,模擬 TCPClient 進行通信
14             Console.WriteLine("Starting: Creating Socket object");
15             Socket Listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
16 
17             Listener.Bind(new IPEndPoint(IPAddress.Any, 2112)); // 創建了基於本機IP地址的,端口號為任意值的,可用於所有網絡接口(網線、電話線及其他形式網絡接口)的綁定
18             Listener.Listen(10); // 掛起連接隊列的最大長度
19 
20             while (true)
21             {
22                 Console.WriteLine("Waiting for connetion on port 2112 ");
23                 Socket socket = Listener.Accept();  // 若無socket,則處於阻塞狀態,等待有效的socket實例
24                 string ReceiveValue = string.Empty;
25 
26                 while (true)
27                 {
28                     byte[] ReceivedByte = new byte[1024]; // 緩存區
29                     int NumBytes = socket.Receive(ReceivedByte); // 接收到的是二進制數據,將接收到的數據放入到緩存中。也可以直接讀到流中,使用 Stream stream = new networkstream(socket)
30                     Console.WriteLine("Receiving . . .");
31                     ReceiveValue += Encoding.ASCII.GetString(ReceivedByte, 0, NumBytes);
32                     if (ReceiveValue.IndexOf("[FINAL]") > -1)
33                     {
34                         break;
35                     }
36                 }
37                 Console.WriteLine("Received Value: {0}", ReceiveValue);
38                 Console.WriteLine(Environment.NewLine);
39                 string ReplyValue = "Message successfully Received.";
40                 byte[] ReplyByte = Encoding.ASCII.GetBytes(ReplyValue);
41                 socket.Send(ReplyByte); // 發送的是二進制數據
42 
43                 // 對於面向連接的協議,建議先調用 Shutdown,然后再調用 Close。 這可以確保在已連接的套接字關閉之前,已發送和接收該套接字上的所有數據。
44                 socket.Shutdown(SocketShutdown.Both); // 關閉該socket的關閉和接收功能
45                 socket.Close();
46             }
47             // Listener.Close();
48         }
49     }
50 }

  運行效果圖:

客戶端

服務器端

  說明:基於鏈接的通信,應先啟動服務器端程序進行監聽,后啟動客戶端程序。否則客戶端在嘗試連接時,沒有服務器進行相應,嘗試一定的時間后,若一直無響應,則拋出遠程主機拒絕的異常。

  2、模擬 UDP 通信實現

  不基於連接的通信,只需要一個程序端即可。

 1 using System;
 2 using System.Net;
 3 using System.Net.Sockets;
 4 using System.Text;
 5 using System.Threading;
 6 using System.IO;
 7 
 8 namespace UDPSender
 9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14             #region 模擬自己發送給自己,使用socket模擬UDP實現原理(通過)
15 
16             // Server
17             Thread server = new Thread(new ThreadStart(ReceiveServer));
18             server.Start();
19 
20             // ★★★確保服務端已建立網絡監聽★★★
21             Thread.Sleep(50);
22 
23             // Client
24             IPHostEntry ipHostEntry = Dns.GetHostEntry("127.0.0.1");
25             IPAddress ipAddress = ipHostEntry.AddressList[0];
26             IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 2112);
27 
28             while (true)
29             {
30                 // UDP 的socket類型不支持stream
31                 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
32                 //socket.Bind(ipEndPoint);
33                 socket.Connect(ipEndPoint); // 若在此之前不給socket手動綁定一個端口,則程序會在此時默認給socket隨機綁定一個端口
34                 Console.WriteLine("Type in what you want to send:");
35                 string SendString = Console.ReadLine();
36                 byte[] SendBytes = Encoding.Default.GetBytes(SendString);
37                 //Thread.Sleep(3000);
38                 socket.Send(SendBytes);               
39                 // 網絡接收有延時,因此需要等待服務器進行接收
40                 socket.Close(100);//等待 timeout 秒以發送所有剩余數據,然后關閉該套接字。
41                 // 等同於socket.Close(100) 的效果
42                 //Thread.Sleep(100);
43                 //socket.Close();
44 
45             }
46             #endregion
47         }
48 
49         public static void ReceiveServer()
50         {
51             IPHostEntry ipHostEntry = Dns.GetHostEntry("127.0.0.1");
52             IPAddress ipAddress = ipHostEntry.AddressList[0];
53             IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 2112);
54 
55             #region 使用緩存接收數據
56             // Receive Message
57             Socket socketListen = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);  // set the protocol
58             socketListen.Bind(ipEndPoint); // binding to the local point which need to be listened
59 
60             try
61             {
62                 // UDP 協議不需要監聽,直接接收消息即可
63                 //socketListen.Listen(5); // 最多監聽到5個隊列的請求
64                 while (true)
65                 {
66                     string result = string.Empty;
67                     if (socketListen.Available < 1) // 判斷是否有數據,若沒有數據而直接進行receive,則會使線程阻塞等待數據的到來。return 無:0 有:>0 
68                     {
69                         Thread.Sleep(20);
70                     }
71                     else
72                     {
73                         byte[] data = new byte[1024];
74                         int NumBytes = socketListen.Receive(data);  // 如果網絡監聽不到數據,則該語句會使線程處於阻塞狀態而等待消息,因此要添加判斷語句判斷數據是否有效   
75                         Console.WriteLine("Receiving..");
76                         if (NumBytes > 0)
77                         {
78                             result = Encoding.Default.GetString(data, 0, NumBytes);
79                             Console.WriteLine("Server Received the follow message:");
80                             Console.WriteLine(result);
81                             Console.WriteLine(Environment.NewLine);
82                             //Thread.Sleep(50000);
83                         }
84                     }
85                 }
86             }
87             catch (SocketException e)
88             {
89                 string a = e.ErrorCode.ToString();
90                 Console.WriteLine(e.Message + e.ErrorCode);
91             }
92             #endregion
93         }
94     }
95 }

  運行效果圖:

 

  注意:對於非連接的通信,在服務器端socket綁定端口(Bind()方法,而非Receive()方法,注意區分)之前,若客戶端發送了消息A,則在服務器端口綁定后收不到該消息A,只能收到在綁定端口后客戶端發送來的消息!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM