續上篇《你也可以寫個聊天程式 - C# Socket學習1》
前言
這里說的服務器是Web服務器,是類似IIS、Tomcat之類的,用來回應瀏覽器請求的服務,
Socket模擬瀏覽器的Url Get請求
首先瀏覽器的請求是HTTP協議,我們上一篇說過,HTTP是短連接,用完就斷開,是無狀態的,所以我們在等待回應的時候不需要另外開個執行緒回圈等待,
也就是我們只需要通過Socket和服務器建立連接,然后發送請求,然后接收服務器的回應,這樣就完成了一次請求,
可是,我們一般訪問網頁的時候都是通過域名,沒有IP也沒有埠,怎么和服務器建立連接了,這里就需要用到我們上篇介紹的幾個類了:
//根據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);
對于HTTP沒有顯示埠默認都是80 (為了簡單這里就先不考慮HTTPS了)
知道了IP和埠,連接是可以建立了,為了得到正確的回應,我們應該給服務器發送什么訊息呢?這里就需要用到HTTP協議了,
具體協議這里就不說了,我們先F12看看瀏覽器的請求報文,然后依葫蘆畫瓢試試,以http://fanyi-pro.baidu.com為例,(現在找個非HTTPS的地址也是不容易了)

然后我們代碼實作如下:
void ...()
{
//得到主機資訊
IPHostEntry ipInfo = Dns.GetHostEntry(new Uri("http://fanyi-pro.baidu.com").Host);
//取得IPAddress[]
IPAddress[] ipAddr = ipInfo.AddressList;
//得到服務器ip
IPAddress ip = ipAddr[0];
//組合遠程終結點
IPEndPoint ipEndPoint = new IPEndPoint(ip, 80);
//創建Socket 實體
Socket socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//嘗試連接
socketClient.Connect(ipEndPoint);
//發送請求
Send(socketClient);
//接收服務器的回應
Receive(socketClient);
}
//接收來自服務端的訊息
void Receive(Socket socketClient)
{
byte[] data = https://www.cnblogs.com/zhaopei/p/new byte[1024 * 1024];
while (true)
{
//讀取客戶端發送過來的資料
int readLeng = socketClient.Receive(data, 0, data.Length, SocketFlags.None);
textBox2.AppendText($"{socketClient.RemoteEndPoint}:{Encoding.UTF8.GetString(data, 0, readLeng)}\r\n");
}
}
//發送訊息到服務端
void Send(Socket socketClient)
{
//為了方便演示,僅用請求報文的前兩行即可,(切記:需要嚴格按照報文格式,如,最后需要連續兩次換行)
var msg = $"GET / HTTP/1.1\r\nHost: {new Uri(textBox1.Text).Host}\r\n\r\n";
socketClient.Send(Encoding.UTF8.GetBytes(msg));
}
整個流程也就是:
- 1、dns服務把域名決議成ip
- 2、通過ip和埠和服務器建立連接(三次握手)
- 3、獲取html檔案
- 4、根據檔案里面的鏈接(js、css、img)再重復以上程序
【注意】:發送報文的時候需要嚴格按照報文格式,如,最后需要連續兩次換行、行末不能有空格等,
效果圖:

用Socket實作Web服務器
Web服務器的實作和我們上一篇的Socket聊天服務端其實也差不多,
不同之處就在于,決議請求報文,然后按HTTP協議回復標準的回應報文(我這里為了簡單,就沒有按標準的協議來玩,僅僅只是實作了表面的效果)
代碼如下:
void ...()
{
//1 創建Socket物件
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, 80);
socketServer.Bind(ipEndPoint);
//3、開啟偵聽(等待客戶機發出的連接),并設定最大客戶端連接數為10
socketServer.Listen(10);
//阻塞等待客戶端連接
Task.Run(() => { Accept(socketServer); });
}
//4 阻塞等待客戶端連接
private static void Accept(Socket socketServer)
{
while (true)
{
//阻塞等待客戶端連接
Socket newSocket = socketServer.Accept();
Task.Run(() => { Receive(newSocket); });
}
}
//5 讀取客戶端發送過來的報文
private static void Receive(Socket newSocket)
{
byte[] data = https://www.cnblogs.com/zhaopei/p/new byte[1024 * 1024];
while (newSocket.Connected)
{
//讀取客戶端發送過來的資料
int readLeng = newSocket.Receive(data, 0, data.Length, SocketFlags.None);
//讀取客戶端發來的請求報文
var requst = Encoding.UTF8.GetString(data, 0, readLeng);
//決議請求報文的請求路徑(可以決議請求路徑、請求檔案、檔案型別)
var requstFile = requst.Split("\r\n")[0].Split(" ")[1];
//回復客戶端回應報文
Send(newSocket, requstFile);
}
}
//6 回復客戶端回應報文
private static void Send(Socket newSocket, string requstFile)
{
//這里如果請求的根目錄,默認顯示Index.html
if (requstFile == "/" ) requstFile = "/Index.html";
var msg = File.ReadAllText(Directory.GetCurrentDirectory() + requstFile);
//把訊息內容轉成位元組陣列后發送
newSocket.Send(Encoding.UTF8.GetBytes(msg));
//回復回應后馬上關閉連接
newSocket.Shutdown(SocketShutdown.Both);
newSocket.Close();
}
效果如下:


由此我們知道了.net core為什么可以在不需要iis的情況下,一個黑表單就提供了對網址的訪問,其實也就是KestrelServer通過Socket系結并監聽埠提供的服務,
【注意】:我們系結的ip是127.0.0.1socketServer.Bind(ipEndPoint),所以我們測驗的時候只能在瀏覽器輸入127.0.0.1或者localhost,如果想通過內外ip訪問,我們可以系結任意ipIPAddress.Any,如socketServer.Bind(new IPEndPoint(IPAddress.Any, port)),
為什么不見三次握手
對于HTTP/TCP可能大家多少都聽過三次握手,可是在我們在用Socket撰寫Web服務器的時候并沒有看到相關的東西啊,這是怎么回事,
因為我們在客戶端執行連接socketClient.Connect(ipEndPoint)的時候已經進行了三次握手

具體可細讀小坦克大佬的文章,
也就是說我們在用C#的Socket、TCP、HttpClient的時候根本就不用關注這些細節,
另外套接字有三種不同的型別:流套接字、資料報套接字和原始套接字,前兩者是標準套接字,分別對應TCP和UDP,而原始套接字則更加底層更加牛逼,普通開發人員一般接觸不到,
我們說的HTTP、TCP、UDP之類都是網路協議,那協議到底是什么?通俗的說其實只是你我他之間的一個約定而已,大家都按規定了來那就可以說是協議,
而HTTP又是建立在TCP之上的,也就是說基礎協議之后再加約定又可以成為一種新的協議,下章我們將用Socket來實作ModbusTCP協議對暫存器讀和寫,
結束
- 本文已同步至索引目錄:《物聯網基礎組件IoTClient開發系列》
- 完整demo:https://github.com/zhaopeiym/BlogDemoCode/tree/master/Socket編程/2HTTP
- 推薦閱讀:https://www.cnblogs.com/TankXiao/archive/2012/10/10/2711777.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/103533.html
標籤:.NET Core
上一篇:C#函式(建構式)的多載
