溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Socket Basic Concepts

發(fā)布時間:2020-05-30 09:35:34 來源:網(wǎng)絡(luò) 閱讀:372 作者:skydxd 欄目:編程語言

Socket Basic Concepts


Socket Basic Concepts 

首先介紹Socket的一些基本概念

Socket是操作系統(tǒng)提供的一系列網(wǎng)絡(luò)編程接口。

網(wǎng)絡(luò)模型分若干層,也有一些協(xié)議,比如TCP協(xié)議,UDP協(xié)議等,這些都是抽象的定義,在硬件以及操作系統(tǒng)級別上有一些對應(yīng)的實現(xiàn),Socket可以看做操作系統(tǒng)為開發(fā)人員提供的一系列網(wǎng)絡(luò)編程接口,它封裝了一些協(xié)議的細節(jié),比如怎么組織數(shù)據(jù)包,怎么發(fā)送數(shù)據(jù)之類的。

Socket編程的幾個基本概念 

Endpoint
Endpoin指定要連接到哪里,Endpoint包括兩部分內(nèi)容,IP和Port,IP地址和端口組合起來才能唯一指定遠程的通信端。

AddressFamily
怎么尋址,有了IP地址之后就是如何尋址的問題,常用的尋址方案是IP V4和IP V6兩種類型,windows操作系統(tǒng)從VISTA和Windows 20008起默認(rèn)支持IPV6。

Protocol
使用什么協(xié)議進行通信,比如TCP協(xié)議或者UDP協(xié)議,下面介紹Socket類型的時候還會涉及TCP和UDP等協(xié)議的介紹。

Socket類型

Socket有三種常用類型:Stream, Dgram, Raw

Stream流類型,支持可靠、雙向、基于連接的字節(jié)流,使用TCP協(xié)議。

Dgram數(shù)據(jù)報類型,支持?jǐn)?shù)據(jù)報,即最大長度固定的無連接、不可靠消息。消息可能會丟失或重復(fù)并可能在到達時不按順序排列,使用UDP協(xié)議。

Raw類型支持對基礎(chǔ)傳輸協(xié)議的訪問,需要自己生成數(shù)據(jù)包。網(wǎng)上有一些RAW的例子,比如D.O.S***,ARP***,網(wǎng)絡(luò)監(jiān)控之類的。

本文只討論Stream類型的Socket編程,RAW和Dgram不在討論之列,也就是只討論基于TCP協(xié)議的編程。

一些常見的概念問題

Socket和TCP/IP有什么關(guān)系?

Socket和TCP/IP不是一個層面的概念,Socket是操作系統(tǒng)提供的操作TCP數(shù)據(jù)的編程接口。

Sockets V4、Sockets V5有什么區(qū)別?

經(jīng)??吹揭恍┸浖梢栽O(shè)置Sockets4/Sockets5代理,簡單說他們是客戶端與外網(wǎng)服務(wù)器之間通訊的協(xié)議,Sockets是位于應(yīng)用層與傳輸層之間的中間層。 Sockets V4支持TCP, Sockets V5支持TCP/UDP,支持安全認(rèn)證,支持IPV6。

Socket能夠同時接受和發(fā)送數(shù)據(jù)嗎?

TCP協(xié)議是雙工的

Socket如何保證數(shù)據(jù)按順序到達?

TCP協(xié)議來保證

Socket的基本通信模型模型

客戶端:

Socket()
Connect
Send
Close

服務(wù)器端:

Socket()
Bind
Listen
Accept
Receive
Send
Close

客戶端和服務(wù)器端模型是不一樣的,兩邊是非對稱的。

 .Net Socket API


下面是.Net Socket編程最基本的幾個類,位于命名空間System.Net.Sockets

Socket Socket接口類
TcpClient TCP客戶端類
TcpListener TCP偵聽類
NetworkStream 用于網(wǎng)絡(luò)訪問的基礎(chǔ)數(shù)據(jù)流

其他經(jīng)常用到的輔助類,位于命名空間System.Net

Dns 域名解析
EndPoint  標(biāo)識網(wǎng)絡(luò)地址
IPAddress  IP地址。
NetworkCredential 基于密碼的身份驗證方案,不支持基于公鑰的身份驗證方法(比如ssl)

一個Socket的簡單例子

輸入網(wǎng)址,獲得HTML頁面的一段演示代碼,只是演示Socket對象的幾個主要功能,不具有實用價值。

基本步驟為:建立Socket對象,連接服務(wù)器,發(fā)送數(shù)據(jù),然后接受數(shù)據(jù),對應(yīng)上一章介紹的Socket通信模型。

代碼

01 <span style="font-size: 10pt;">private string DownloadPage(string path)
02         {
03             Uri uri = new Uri(path);
04             Encoding encoding = Encoding.UTF8;// .GetEncoding("gb2312");
05  
06             string requestHeader = BuildRequestHeader(uri);
07             byte[] requestBytes = encoding.GetBytes(requestHeader);
08             byte[] receivedBytes = new byte[1024 * 100];
09  
10             Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
11             socket.Connect(uri.Host,uri.Port);
12             socket.Send(requestBytes);
13  
14             int receivedBytesLength = socket.Receive(receivedBytes);
15             socket.Shutdown(SocketShutdown.Both);
16             socket.Close();
17  
18             string html = string.Empty;
19             if (receivedBytesLength &gt; 0)
20             {
21                 html = encoding.GetString(receivedBytes, 0, receivedBytesLength);
22             }
23             return html;
24         }</span>


構(gòu)造HTTP Header的代碼如下,注意不要忘了Http頭模板的最后一行

01 <span style="font-size: 10pt;">private string BuildRequestHeader(Uri uri)
02         {
03             string httpHeaderTemplate = @"GET {url} HTTP/1.1
04 Connection: Close
05 Host: {host}
06  
07 ";
08             return httpHeaderTemplate.Replace("{url}", uri.AbsolutePath)
09                 .Replace("{host}", uri.Host);
10  
11         }</span>


30秒思考題:這段簡單代碼有什么問題?

我們定義的用來接收數(shù)據(jù)的數(shù)組大小是固定的,如果要接受的數(shù)據(jù)超過數(shù)組大小怎么辦? 可以定義一個緩沖區(qū),每次接受固定大小的數(shù)據(jù),直到接收完成為止。示例代碼如下:

01 <span style="font-size: 10pt;">MemoryStream ms = new MemoryStream();
02             while (true)
03             {
04                 Console.WriteLine("Available :{0}", socket.Available);
05                 int receivedBytesLength = socket.Receive(receivedBytes, 0, receivedBytes.Length, SocketFlags.None);
06                 if (receivedBytesLength &gt; 0)
07                 {
08                     ms.Write(receivedBytes, 0, receivedBytesLength);
09                 }
10                 else
11                 {
12                     break;
13                 }
14             }
15  
16             string html = string.Empty;
17             if (ms.Length &gt; 0)
18             {
19                 html = encoding.GetString(ms.ToArray(), 0, (int)ms.Length);
20             }</span>


Socket的緩沖區(qū)

Socket接收數(shù)據(jù)時,操作系統(tǒng)先把數(shù)據(jù)接收到緩沖區(qū),然后通知程序,socket.Available 是從已經(jīng)從網(wǎng)絡(luò)接收的、可供讀取的數(shù)據(jù)的字節(jié)數(shù),這個值是指緩沖區(qū)中已接收數(shù)據(jù)的字節(jié)數(shù),不是實際的數(shù)據(jù)大小。而且如果網(wǎng)絡(luò)有延遲,Send之后馬上讀取Available屬性不一定能讀到正確的值,所以不能利用socket.Available來判斷總共要接受的字節(jié)數(shù)。

在上面的方法中,如果沒有可讀取的數(shù)據(jù),則 Receive 方法將一直處于阻止?fàn)顟B(tài),直到有數(shù)據(jù)可用,如果Server端也沒有正確關(guān)閉連接,程序很容易死在這里,可以通過Socket.ReceiveTimeout來設(shè)置Socket對象接受數(shù)據(jù)的超時時間。

30秒思考題:為什么這樣下載的頁面有時候和瀏覽器下載的頁面不一樣?
>>gzip,chunked編碼,重定向等

NetworkStream的例子

前面講過基于TCP協(xié)議的Socket是Steam類型的,在操作系統(tǒng)中,為了簡化編程,把設(shè)備、文件等都看作流對象,統(tǒng)一編程接口。NetworkStream類提供了在阻止模式下通過Socket套接字發(fā)送和接收數(shù)據(jù)的方法,.Net還提供了TcpClient和TcpListener類,用于簡化同步阻止模式下通過TCP協(xié)議連接、發(fā)送和接收流數(shù)據(jù)。下面的例子是這幾個對象的簡單介紹,省略了一些細節(jié),也不具有實用價值。

這個例子模擬計算機遠程控制,先新起一個線程模擬服務(wù)進程,在這個線程中創(chuàng)建一個TcpListener對象,等待客戶端連接。用戶在客戶端界面點了“連接”按鈕后,UI線程創(chuàng)建TcpClient對象,等待用戶輸入dos命令,用戶輸入dos命令,按執(zhí)行按鈕,這時TcpClient對象把用戶輸入的命令發(fā)送給TcpListener對象,服務(wù)進程執(zhí)行完命令后,將執(zhí)行結(jié)果反饋給TcpClient對象。

部分代碼。
       

01 <span style="font-size: 10pt;">private void StartServer()
02         {
03             TcpListener server = new TcpListener(IPAddress.Any, 10000);
04             server.Start();
05             Debug.WriteLine("Server: start");
06  
07             TcpClient client = server.AcceptTcpClient();
08             Debug.WriteLine("Server : connection accept");
09             NetworkStream stream = client.GetStream();
10  
11             Process process = new Process();
12             process.StartInfo.FileName = "cmd.exe";
13             process.StartInfo.UseShellExecute = false;
14             process.StartInfo.RedirectStandardInput = true;
15             process.StartInfo.RedirectStandardOutput = true;
16             process.StartInfo.RedirectStandardError = true;
17             process.StartInfo.CreateNoWindow = false;
18             process.Start();
19  
20             process.OutputDataReceived += (Object sender, DataReceivedEventArgs e) =>
21             {
22                 byte[] bytes = Encoding.GetEncoding("gb2312").GetBytes(e.Data + "\r\n");
23                 stream.Write(bytes, 0, bytes.Length);
24             };
25             process.BeginOutputReadLine();
26              
27             while (true)
28             {
29                 Byte[] buffer = new Byte[1024 * 10];
30                 int length = stream.Read(buffer, 0, buffer.Length);
31                 if (length == 0)
32                 {
33                     Debug.WriteLine("Server: read 0 byte");
34                     break;
35                 }
36  
37                 string command = Encoding.GetEncoding("gb2312").GetString(buffer, 0, length);
38                 Debug.WriteLine("Server: receive {0} ", command);
39  
40                 StreamWriter Writer = process.StandardInput;
41                 Writer.WriteLine(command);
42                 Writer.Flush();
43             }
44  
45             server.Stop();
46             process.WaitForExit(1000);
47             process.Close();
48             Debug.WriteLine("Server: close");
49         }
50  
51         TcpClient client;
52         private void ConnectButton_Click(object sender, EventArgs e)
53         {
54             client = new TcpClient();
55             client.Connect("localhost", 10000);
56             Console.WriteLine("Client: connect");
57             StartButton.Enabled = true;
58         }
59  
60         private void StartButton_Click(object sender, EventArgs e)
61         {
62             if (CommandTextBox.Text.Trim().Length == 0)
63             {
64                 return;
65             }
66  
67             string request = CommandTextBox.Text.Trim();
68             byte[] bytes = Encoding.GetEncoding("gb2312").GetBytes(request);
69             NetworkStream stream = client.GetStream();
70             stream.Write(bytes, 0, bytes.Length);
71              
72             Console.WriteLine("Client: request {0}", request);
73             MessageTextBox.AppendText("\r\nresponse from server:\r\n");
74              
75             byte[] buffer = new byte[1024];
76             do{
77                 int receivedBytesLength = stream.Read(buffer, 0, buffer.Length);
78                 if(receivedBytesLength > 0)
79                 {
80                     string text = Encoding.GetEncoding("gb2312").GetString(buffer, 0, receivedBytesLength);
81                     MessageTextBox.AppendText(text);
82                     MessageTextBox.AppendText("\r\n");
83                 }
84                 else
85                 {
86                     break;
87                 }
88             }while(stream.DataAvailable);
89         }</span>


向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI