文章目錄
- 前言
- 一、網口通信概念
- 二、使用網口通信準備
- 三、使用步驟
前言
C#上位機之網口通信與協議!
一、網口通信概念
定義 :Socket 可以理解為一個通信端點,它提供了應用程序與網絡之間的接口,使得應用程序能夠在網絡上發送和接收數據。通過 Socket,不同設備上的應用程序可以建立連接,實現數據的交換。
地址族 :用于確定 Socket 能夠使用的協議類型,常見的有 AF_INET(IPv4 地址族)、AF_INET6(IPv6 地址族)、AF_UNIX(本地通信的 UNIX 域協議)等。
類型
流式套接字(SOCK_STREAM) :提供面向連接的、可靠的、雙向的字節流服務,基于 TCP 協議。數據傳輸保證順序、完整且無重復,適用于對數據可靠性要求較高的場景,如文件傳輸、遠程登錄等。
數據報套接字(SOCK_DGRAM) :提供無連接的、不可靠的、基于數據報的包傳輸服務,基于 UDP 協議。數據報獨立傳輸,可能存在丟失、重復或亂序的情況,但傳輸效率較高,適用于對實時性要求較高、少量丟包可以接受的場景,如視頻直播、在線游戲等。
原始套接字(SOCK_RAW) :允許對低層協議進行訪問和操作,可直接處理 IP 數據報或更低層協議的數據,通常用于網絡工具開發、協議研究等特殊場景。
二、使用網口通信準備
1、下載網絡調試助手軟件
2、Socket概念
地址族(AddressFamily):如 InterNetwork (IPv4)、InterNetworkV6 (IPv6)。
套接字類型(SocketType):如 Stream (TCP)、Dgram (UDP)。
協議類型(ProtocolType):如 Tcp 、Udp 。
IPEndPoint:表示網絡終結點(IP 地址 + 端口)。
3、Socket工作流程
階段 | 服務器端操作 | 客戶端操作 |
---|---|---|
創建套接字(Socket) | 創建一個 Socket 對象,指定地址族、套接字類型和協議類型。 | 創建一個 Socket 對象,指定地址族、套接字類型和協議類型。 |
綁定地址(Bind) | 使用 Bind 方法將套接字綁定到本地的 IP 地址和端口號。 | 通常不需要顯式綁定,除非需要指定本地地址和端口。 |
監聽連接(Listen) | 調用 Listen 方法開始監聽來自客戶端的連接請求,進入監聽狀態,等待客戶端連接。(僅TCP) | 無此操作。 |
建立連接(Accept) | 調用 Accept 方法接受客戶端的連接請求,建立與客戶端之間的連接,返回一個新的套接字用于與客戶端通信(僅TCP)。 | 調用 Connect 方法向服務器端發起連接請求,嘗試與服務器端建立連接。 |
數據傳輸 | 通過返回的套接字使用 Receive 方法接收客戶端發送的數據,使用 Send 方法向客戶端發送數據。 | 通過套接字使用 Send 方法向服務器端發送數據,使用 Receive 方法接收服務器端發送的數據。 |
關閉連接(Close) | 數據傳輸完成后,調用 Shutdown 方法關閉套接字的發送和接收功能,然后調用 Close 方法釋放套接字資源,關閉連接。 | 數據傳輸完成后,調用 Shutdown 方法關閉套接字的發送和接收功能,然后調用 Close 方法釋放套接字資源,關閉連接。 |
三、使用步驟
TCP服務器
C#創建socket服務端,但這種方法只能接收一個客戶端連接和處理一次接收到的信息。解釋:當啟動服務端后程序會停在Socket socketClient = socketServer.Accept();處等待客戶端連接,當有客戶端來連接程序就會停在int bytesReader = socketClient.Receive(recvData);等待客戶端發送消息。
// 1、創建Socket
Socket socketServer = new Socket(AddressFamily.InterNetwork,// 地址族IPv4SocketType.Stream, // 套接字類型ProtocolType.Tcp// TCP協議);
// 2、綁定IP和端口
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8888);
socketServer.Bind(endPoint);// 3、開始監聽
socketServer.Listen();
Debug.WriteLine($"服務器已啟動,等待連接");// 4、接收客戶端連接
Socket socketClient = socketServer.Accept();
Debug.WriteLine($"連接的客戶端:{socketClient.RemoteEndPoint}");// 5、接收數據
byte[] recvData = new byte[1024];
int bytesReader = socketClient.Receive(recvData);// 返回接收到的字節數
Debug.WriteLine($"收到的消息:{Encoding.UTF8.GetString(recvData,0, bytesReader)}");// 6、發送響應
byte[] sendMes = Encoding.UTF8.GetBytes("Hello I am Server");
socketClient.Send(sendMes);// 7、關閉連接
//socketClient.Close();
//socketServer.Close();
如果需要連接多個客戶端和接收信息,可以使用While循環來實現
// 1、創建Socket
Socket socketServer = new Socket(AddressFamily.InterNetwork,// 地址族IPv4SocketType.Stream, // 套接字類型ProtocolType.Tcp// TCP協議);
// 2、綁定IP和端口
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8888);
socketServer.Bind(endPoint);// 3、開始監聽
socketServer.Listen();
Debug.WriteLine($"服務器已啟動,等待連接");
while (true)
{// 4、接收客戶端連接Socket socketClient = socketServer.Accept();Debug.WriteLine($"連接的客戶端:{socketClient.RemoteEndPoint}");while (true){// 5、接收數據byte[] recvData = new byte[1024];int bytesReader = socketClient.Receive(recvData);// 返回接收到的字節數Debug.WriteLine($"收到的消息:{Encoding.UTF8.GetString(recvData, 0, bytesReader)}");// 6、發送響應byte[] sendMes = Encoding.UTF8.GetBytes("Hello I am Server");socketClient.Send(sendMes);}
}
但是使用上述方法程序在
while (true)
{// 5、接收數據byte[] recvData = new byte[1024];int bytesReader = socketClient.Receive(recvData);// 返回接收到的字節數Debug.WriteLine($"收到的消息:{Encoding.UTF8.GetString(recvData, 0, bytesReader)}");// 6、發送響應byte[] sendMes = Encoding.UTF8.GetBytes("Hello I am Server");socketClient.Send(sendMes);
}
這段代碼處陷入死循環,只能重復接收一個客戶端的消息。
如何解決上述的問題呢?
可以使用Task線程來解決。相當于每來一個客戶端就創建一個線程來處理這個客戶端發送的數據。
// 1、創建Socket
Socket socketServer = new Socket(AddressFamily.InterNetwork,// 地址族IPv4SocketType.Stream, // 套接字類型ProtocolType.Tcp// TCP協議);
// 2、綁定IP和端口
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8888);
socketServer.Bind(endPoint);// 3、開始監聽
socketServer.Listen();
Debug.WriteLine($"服務器已啟動,等待連接");
while (true)
{// 4、接收客戶端連接Socket socketClient = socketServer.Accept();Debug.WriteLine($"連接的客戶端:{socketClient.RemoteEndPoint}");Task.Factory.StartNew(() =>{while (true){// 5、接收數據byte[] recvData = new byte[1024];int bytesReader = socketClient.Receive(recvData);// 返回接收到的字節數Debug.WriteLine($"收到的消息:{Encoding.UTF8.GetString(recvData, 0, bytesReader)}");// 6、發送響應byte[] sendMes = Encoding.UTF8.GetBytes("Hello I am Server");socketClient.Send(sendMes);}});
}
// 7、關閉連接
//socketClient.Close();
//socketServer.Close();
部分代碼解讀:
Task.Factory.StartNew 創建一個線程并開啟這個線程。
TCP客戶端
// 1、創建Socket
Socket socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);// 2、連接服務器
IPEndPoint serverPoin = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8888);
socketClient.Connect(serverPoin);
Debug.WriteLine($"已連接到服務器");// 3、發送數據
byte[] senfMsg = Encoding.UTF8.GetBytes("I am Client");
socketClient.Send(senfMsg);// 4、接收響應
byte[] recvMsg = new byte[1024];
int bytesReader = socketClient.Receive(recvMsg);
string response = Encoding.UTF8.GetString(recvMsg, 0,bytesReader);
Debug.WriteLine($"接收到的消息:{response}");// 5、關閉連接
//socketClient.Close();
UDP通信
// 1、創建UDP
Socket udp = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);
// 2.綁定端口
udp.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8888));// 1、指定地址
EndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9999);
// 2、給指定地址發送信息
udp.SendTo(Encoding.UTF8.GetBytes("Hello UDP"),endPoint);// 接收信息的字節數組
byte[] buffer = new byte[1024];
// IPAddress.Any 服務器會接受來自任何網絡接口的連接請求,表示端口號由操作系統自動分配
endPoint = new IPEndPoint(IPAddress.Any, 0);
// 參數ref endPoint指定接收數據的來源
int bytesReader = udp.ReceiveFrom(buffer,ref endPoint);
string message = Encoding.UTF8.GetString(buffer, 0, bytesReader);
Debug.WriteLine(message);// 2、廣播模式
udp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
endPoint = new IPEndPoint(IPAddress.Parse("255.255.255.255"), 9999);
udp.SendTo(Encoding.UTF8.GetBytes("Hello UDP!-BroadCast"), endPoint);
TcpClient,簡化了 TCP 協議的通信流程。
構造方法 | 說明 |
---|---|
TcpClient() | 初始化一個新的 TcpClient 實例,不指定遠程主機和端口。 |
TcpClient(string hostname, int port) | 初始化一個新的 TcpClient 實例,并連接到指定的遠程主機和端口。 |
屬性 | 說明 | 使用方法示例 |
---|---|---|
Client | 獲取底層的 Socket 對象,用于更細粒度的網絡操作。 | Socket socket = tcpClient.Client; |
Connected | 獲取一個布爾值,指示 TcpClient 是否連接到遠程主機。 | if (tcpClient.Connected) { ... } |
Available | 獲取接收緩沖區中等待接收的字節數。 | int availableBytes = tcpClient.Available; |
ReceiveBufferSize | 獲取或設置接收緩沖區的大小。 | int bufferSize = tcpClient.ReceiveBufferSize; 或 tcpClient.ReceiveBufferSize = 1024; |
SendBufferSize | 獲取或設置發送緩沖區的大小。 | int bufferSize = tcpClient.SendBufferSize; 或 tcpClient.SendBufferSize = 1024; |
方法 | 說明 | 使用方法示例 |
---|---|---|
Connect(string host, int port) | 連接到遠程 TCP 服務器的指定主機和端口。 | tcpClient.Connect("127.0.0.1", 8888); |
GetStream() | 返回一個 NetworkStream 對象,用于在 TcpClient 上進行讀寫操作。 | NetworkStream stream = tcpClient.GetStream(); |
Close() | 關閉 TcpClient 和其底層的 Socket 。 | tcpClient.Close(); |
Dispose() | 釋放 TcpClient 使用的資源。 | tcpClient.Dispose(); |
TCP客戶端
// 1、創建TcpClient
using ( TcpClient client = new TcpClient())
{client.Connect("127.0.0.1",8888);Debug.WriteLine("已連接到服務器");// 2、獲取網絡流NetworkStream stream = client.GetStream();// 3、發送數據string message = "Hello Server!";byte[] sentBytes = Encoding.UTF8.GetBytes(message);stream.Write(sentBytes, 0, sentBytes.Length);Debug.WriteLine($"發送:{message}");// 4、接收響應byte[] buffer = new byte[1024];int bytesRead = stream.Read(buffer, 0, buffer.Length);string response = Encoding.UTF8.GetString(buffer);Debug.WriteLine(response);
TCP服務端
// 監聽指定IP和端口
TcpListener server = new TcpListener(IPAddress.Any, 8888);
server.Start();
Debug.WriteLine($"服務器已啟動,等待連接.....");try
{// 接收客戶端連接using (TcpClient client = server.AcceptTcpClient()){Debug.WriteLine($"客戶端已連接:{((IPEndPoint)client.Client.RemoteEndPoint).Address}");// 2、獲取網絡流NetworkStream stream = client.GetStream();// 3、接收數據byte[] buffer = new byte[1024];int bytesRead = stream.Read(buffer, 0, buffer.Length);string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);Debug.WriteLine(message);// 4、發送響應string response = "Hello Client!";byte[] sendData = Encoding.UTF8.GetBytes(response);stream.Write(sendData, 0, sendData.Length);}
}catch(Exception ex)
{Debug.WriteLine($"錯誤:{ex.Message}");
}
finally
{server.Stop();
}