簡述
我們做軟體作業的雖然每天都離不開網路,可網路協議細節卻不是每個人都會接觸和深入了解,我今天就來和大家一起學習下Socket,并寫一個簡單的聊天程式,
一些基礎類
首先我們每天打開瀏覽器訪問網頁資訊都是使用的HTTP/HTTPS協議,而HTTP是通過的TCP建立的連接,TCP底層又是通過的Socket套接字進行的通信,所以他們之間的抽象關系是:

我們在學習Socket編程的時候可能會需要用到IPEndPoint、Dns、IPAddress等類,再往上TCP相關有TcpListener、TcpClient、NetworkStream,再往上就是大家熟悉的HttpClient等,
IPEndPoint、Dns、IPAddress基礎作用如下:
//根據DNS獲取域名系結的IP
foreach (var address in Dns.GetHostEntry("www.baidu.com").AddressList)
{
Console.WriteLine($"百度IP:{address}");
}
//字串轉IP地址
IPAddress ipAddress = IPAddress.Parse("192.168.1.101");
//通過IP和埠構造IPEndPoint物件,用于遠程連接
//通過IP可以確定一臺電腦,通過埠可以確定電腦上的一個程式
IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 80);
利用Socket撰寫聊天程式
我們首先從Socket開始講起,
要實作Socket通信,先得有個服務端的監聽,再有個客戶端的連接,然后客戶端和服務端就可以通信了,如下:

服務端代碼如下:
//1 創建Socket物件
socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2 系結ip和埠
IPAddress ip = IPAddress.Parse("127.0.0.1");
IPEndPoint ipEndPoint = new IPEndPoint(ip, 50001);
socketServer.Bind(ipEndPoint);
//3、開啟偵聽(等待客戶機發出的連接),并設定最大客戶端連接數為10
socketServer.Listen(10);
//4、【阻塞】,等待客戶端連接
Socket newSocket = socketServer.Accept();
//5、【阻塞】,等待讀取客戶端發送過來的資料
byte[] data = https://www.cnblogs.com/zhaopei/p/new byte[1024 * 1024];
int readLeng = newSocket.Receive(data, 0, data.Length, SocketFlags.None);
//6、讀取資料
var msg = Encoding.UTF8.GetString(data, 0, readLeng);
客戶端代碼如下:
//1 創建Socket物件
socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2 連接到服務端
IPAddress ip = IPAddress.Parse("127.0.0.1");
IPEndPoint ipEndPoint = new IPEndPoint(ip, 50001);
socketClient.Connect(ipEndPoint);
//3 發送訊息到服務端
socketClient.Send(Encoding.UTF8.GetBytes("你好,農碼一生"));
到此,我們就可以開啟服務端的服務,并接受客戶端的發來的訊息了,
不過,這里有個很大的問題,服務端只能建立一個客戶端連接和接受一次客戶端發來的訊息,如果想要連接更多的客戶端和接受無數次的訊息,服務端代碼兩處阻塞的地方需要另外開一個執行緒然后包到回圈里面去,
修改后的服務端代碼如下:
void .... ()
{
//1 創建Socket物件
socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2 系結ip和埠
IPAddress ip = IPAddress.Parse("127.0.0.1");
IPEndPoint ipEndPoint = new IPEndPoint(ip, 50001);
socketServer.Bind(ipEndPoint);
//3、開啟偵聽(等待客戶機發出的連接),并設定最大客戶端連接數為10
socketServer.Listen(10);
//開啟新的執行緒,回圈等待新的客戶端連接
Task.Run(() => { Accept(socketServer); });
}
void Accept(Socket socket)
{
while (true)
{
//4、【阻塞】,等待客戶端連接
Socket newSocket = socket.Accept();
//開啟新的執行緒,回圈等待接收新的資料
Task.Run(() => { Receive(newSocket); });
}
}
void Receive(Socket newSocket)
{
while (true)
{
//5、【阻塞】,等待讀取客戶端發送過來的資料
byte[] data = https://www.cnblogs.com/zhaopei/p/new byte[1024 * 1024];
int readLeng = newSocket.Receive(data, 0, data.Length, SocketFlags.None);
//6、讀取資料
var msg = Encoding.UTF8.GetString(data, 0, readLeng);
}
}
到此,服務端就可以接受多個客戶端的連接和接收多次客戶端發來的訊息了,不過我們可能還需客服端能接收服務端發來的訊息,這個你可以自己嘗試下,文末會提供完整的代碼參考,
注意:
用Socket來撰寫聊天軟體是長連接,有狀態的,不確定服務端什么時候會發送訊息過來,我們也可以連續發送訊息而不回應,所以,對于訊息的接收就需要開一個新的執行緒回圈接收,
而對于HTTP來說,雖然它是也是通過TCP建立的通信,但在資料請求完畢后會馬上關閉連接,這個程序很短,每次訪問都會建立一個新的連接,是無狀態的,
對于瀏覽器來說是一問一答的形式,先發送請求(Send),然后接收回應(Receive)所以就可以做到不開啟新的執行緒,直接有序的同步的完成,這個在下一篇《模擬瀏覽器的請求和服務端的回應》會具體分析,
利用TCP撰寫聊天程式
雖然上面我們利用Socket類實作了一個簡單的聊天程式,但是微軟覺得Socket太復雜,為了讓你們早點干完活,早點下班,于是又在Socket的基礎上有封裝了兩個相關的類TcpListener、TcpClient,
利用TcpListener、TcpClient來實作同上面相同的功能,
服務端代碼:
void ...()
{
//1 開啟監聽
TcpListener tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), 9999);
tcpListener.Start(10); //最多同時接收10個用戶連接
//開啟一個執行緒,回圈等待客戶端的連接
Task.Run(() => { Accept(); });
}
//等待客戶端的連接
void Accept()
{
while (true)
{
//2 【阻塞】等待客戶端的連接
TcpClient tcpClient = tcpListener.AcceptTcpClient();
NetworkStream networkStream = tcpClient.GetStream();
//開啟一個新的執行緒 等待新的訊息
Task.Run(() => { Read(networkStream, tcpClient); });
}
}
//接收訊息
void Read(NetworkStream networkStream)
{
while (true)
{
byte[] buffer = new byte[1024 * 1024];
//3 【阻塞】等待接收新的訊息
var readLen = networkStream.Read(buffer, 0, buffer.Length);
var msg = Encoding.UTF8.GetString(buffer, 0, readLen);
}
}
客戶端代碼:
//1 連接服務端
TcpClient tcpClient = new TcpClient();
tcpClient.Connect(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text));
//2 發送訊息到服務端
byte[] buffer = Encoding.UTF8.GetBytes("你好,農碼一生");
networkStream.Write(buffer, 0, buffer.Length);
用TcpListener、TcpClient的實作也算ok了,TcpListener代碼寫的服務端和Socket通信也是完成沒問題的,因為他們最后都是Socket,
對此你有覺得比Socket簡單和容易理解?其實我更習慣Socket,
注意:
// 1、斷開連接使用
socketClient?.Shutdown(SocketShutdown.Both);
socketClient?.Close();
// 2、服務端需要判斷
int readLeng = newSocket.Receive(data, 0, data.Length, SocketFlags.None);
if (readLeng == 0)//客戶端斷開連接
{
//停止會話(禁用Socket上的發送和接收,該方法允許Socket物件一直等待,直到將內部緩沖區的資料發送完為止)
newSocket.Shutdown(SocketShutdown.Both);
//關閉連接
newSocket.Close();
//跳出回圈
return;
}
3、具體請參考文末提供的完整demo
效果圖

結束
- 本文已同步至索引目錄:《物聯網基礎組件IoTClient開發系列》
- 完整Demo:https://github.com/zhaopeiym/BlogDemoCode/tree/master/Socket編程
- 關于demo本來打算用.net core3.0來寫的,可是winform的設計器不爭氣,所以demo還是用的.net fx,只有TcpListener是用的.net core控制臺寫的,
- 為什么要寫這個,一是學習下底層的協議通信,二是為了實作一套物聯網設備通訊協議做準備,類似dathlin這位大佬的Hsl,
- 推薦閱讀
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/107347.html
標籤:.NET Core
下一篇:ABP進階教程8 - 自定義按鈕
