續上篇《你也能夠寫個聊天程序 - C# Socket學習1》css
這裏說的服務器是Web服務器,是相似IIS、Tomcat之類的,用來響應瀏覽器請求的服務。html
首先瀏覽器的請求是HTTP協議。咱們上一篇說過,HTTP是短鏈接,用完就斷開,是無狀態的。因此咱們在等待響應的時候不須要另外開個線程循環等待。
也就是咱們只須要經過Socket和服務器創建鏈接,而後發送請求,而後接收服務器的響應,這樣就完成了一次請求。
但是,咱們通常訪問網頁的時候都是經過域名,沒有IP也沒有端口,怎麼和服務器創建鏈接了。這裏就須要用到咱們上篇介紹的幾個類了:git
//根據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的地址也是不容易了)
而後咱們代碼實現以下:github
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 = 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)); }
整個流程也就是: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); //三、開啓偵聽(等待客戶機發出的鏈接),並設置最大客戶端鏈接數爲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 = 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協議對寄存器讀和寫。網絡