標籤:
我們在講解Socket編程前,先看幾個和Socket編程緊密相關的概念:
1. TCP/IP層次模型
當然這裡我們只討論重要的四層
01,應用程式層(Application):應用程式層是個很廣泛的概念,有一些基本相同的系統級TCP/IP應用以及應用協議,也有許多的公司專屬應用程式和互連網應用。http協議在應用程式層運行。
02,傳輸層(Tanspot):傳輸層包括UDP和TCP,UDP幾乎不對報文進行檢查,而TCP提供傳輸保證。
03,網路層(Netwok):網路層協議由一系列協議組成,包括ICMP、IGMP、RIP、OSPF、IP(v4,v6)等。
04,鏈路層(Link):又稱為物理資料網路介面層,負責報文傳輸。
然後我們來看下tcp層次模型圖
從中可以看出,應用程式在應用程式層運行,在傳輸層,在資料前加上了TCP頭,在網路層加上的IP頭,在資料連結層加上了幀。
2. 連接埠
連接埠號碼範圍:0-65535,總共能表示65536個數。
按連接埠號碼可分為3大類
01,公認連接埠(WellKnownPorts):從0到1023,它們緊密綁定(binding)於一些服務。通常這些連接埠的通訊明確表明了某種服務的協議。例如:80連接埠實際上總是HTTP通訊。
02,註冊連接埠(RegisteredPorts):從1024到49151。它們鬆散地綁定於一些服務。也就是說有許多服務綁定於這些連接埠,這些連接埠同樣用於許多其它目的。例如:許多系統處理動態連接埠從1024左右開始。
03,動態和/或私人連接埠(Dynamicand/orPrivatePorts):從49152到65535。理論上,不應為服務分配這些連接埠。實際上,機器通常從1024起分配動態連接埠。
3. TCP和UDP報文
下面一起來看下TCP和UDP的報文圖
我們可以看出TCP和UDP中都有校正和,但是在UDP報文中,一般不使用校正和,這樣可以加快資料轉送的速度,但是資料的準確性可能會受到影響。換句話說,Tcp協議都有校正和,為了保證傳輸資料的準確性。
4. Socket
Socket包括ip地址和連接埠號碼兩部分,程式通過Socket來通訊,Socket相當於作業系統的一個組件。Socket作為進程之間通訊機制,通常也稱作”通訊端”,用於描述IP地址和連接埠號碼,是一個通訊鏈的控制代碼。說白了,就是兩個程式通訊用的。
生活案例對比:
Socket之間的通訊可以類比生活中打電話的案例。任何使用者在通話之前,首先要佔有一部電話機,相當於申請一個Socket,同時要知道對方的號碼,相當於對方有一個固定的Socket,然後向對方撥號呼叫,相當於發出串連請求。假如對方在場並空閑,拿起 電話話筒,雙方就可以進行通話了。雙方的通話過程,是一方向電話機發出訊號和對方從電話機接收訊號的過程,相當於向socket發送資料和從socket接收資料。通話結束後,一方掛起電話機,相當於關閉socket,撤銷串連。
注意:Socket不僅可以在兩台電腦之間通訊,還可以在同一台電腦上的兩個程式間通訊。
5. 連接埠進階(深入)
通過IP地址確定了網路中的一台電腦後,該電腦上可能提供很多提供服務的應用,每一個應用都對應一個連接埠。
在Internet上有很多這樣的主機,這些主機一般運行了多個服務軟體 ,同時提供幾種服務,每種服務都開啟一個Socket,並綁定到一個連接埠上,不同的連接埠對應於不同的服務(應用程式)
例如:http 使用80連接埠, ftp使用21連接埠 smtp使用25連接埠
6. Socket分類
Socket主要有兩種類型:
01,流式Socket: 是一種連線導向的Socket,針對於連線導向的TCP服務應用,安全,但是效率低
02,資料報式Socket: 是一種不需連線的Socket,對應於不需連線的UDP服務應用,不安全,但效率高
7. Socket一般應用模式(伺服器端和用戶端)
伺服器端的Socket(至少需要兩個)
01. 一個負責接收用戶端串連請求(但不負責與用戶端通訊)
02. 每成功接收到用戶端的串連便在伺服器端產生一個對應的複雜通訊的Socket
021. 在接收到用戶端串連時建立
022. 為每個串連成功的用戶端請求在伺服器端都建立一個對應的Socket(負責和用戶端通訊)
用戶端的Socket
01. 必須指定要已連線的服務器地址和連接埠
02. 通過建立一個Socket對象來初始化一個到伺服器端的TCP串連
通過,我們可以看出,首先伺服器會建立一個負責監聽的socket,然後用戶端通過socket串連到伺服器指定連接埠,最後伺服器端負責監聽的socket,監聽到用戶端有串連過來了,就建立一個負責和用戶端通訊的socket。
8. Socket通訊過程(伺服器端和用戶端)
下面我們來看下Socket更具體的通訊過程:
Socket的通訊過程
伺服器端:
01,申請一個socket
02,綁定到一個IP地址和一個連接埠上
03,開啟偵聽,等待接收串連
用戶端:
01,申請一個socket
02,串連伺服器(指明IP地址和連接埠號碼)
伺服器端接收到串連請求後,產生一個新的socket(連接埠大於1024)與用戶端建立串連並進行通訊,原監聽socket繼續監聽。
注意:負責通訊的Socket不能無限建立,建立的數量和作業系統有關。
9. Socket的建構函式
Public Socket(AddressFamily addressFamily,SocketType socketType,ProtocolType protocolTYpe)
AddressFamily:指定Socket用來解析地址的定址方案。例如:InterNetWork指示當Socket使用一個IP版本4地址串連
SocketType:定義要開啟的Socket的類型
Socket類使用ProtocolType枚舉向Windows Sockets API通知所請求的協議
C#中又多了一層的封裝:TCPClient和TCPListener兩個類又做了一層封裝。
注意:
1,連接埠號碼必須在 1 和 65535之間,最好在1024以後。
2,要串連的遠程主機必須正在監聽指定連接埠,也就是說你無法隨意串連遠程主機。如:
IPAddress addr = IPAddress.Parse("127.0.0.1");
IPEndPoint endp = new IPEndPoint(addr, 9000);
服務端先綁定:serverWelcomeSocket.Bind(endp)
用戶端再串連:clientSocket.Connect(endp)
3,一個Socket一次只能串連一台主機
4,Socket關閉後無法再次使用
5,每個Socket對象只能與一台遠程主機串連。如果你想串連到多台遠程主機,你必須建立多個Socket對象。
8. Socket常用類和方法
相關類:
IPAddress:包含了一個IP地址
IPEndPoint:包含了一對IP地址和連接埠號碼
方法:
Socket():建立一個Socket
Bind():綁定一個本地的IP和連接埠號碼(IPEndPoint)
Listen():讓Socket偵聽傳入的串連,並指定偵聽隊列容量
Connect():初始化與另一個Socket的串連
Accept():接收串連並返回一個新的Socket
Send():輸出資料到Socket
Receive():從Socket中讀取資料
Close():關閉Socket,銷毀串連
private void Form1_Load(object sender, EventArgs e) { Control.CheckForIllegalCrossThreadCalls = false; } private void btnListen_Click(object sender, EventArgs e) { //ip地址 IPAddress ip = IPAddress.Parse(txtIP.Text); // IPAddress ip = IPAddress.Any; //連接埠號碼 IPEndPoint point=new IPEndPoint(ip,int.Parse(txtPort.Text)); //建立監聽用的Socket /* * AddressFamily.InterNetWork:使用 IP4地址。SocketType.Stream:支援可靠、雙向、基於串連的位元組流,而不重複資料。此類型的 Socket 與單個對方主機進行通訊,並且在通訊開始之前需要遠程主機串連。Stream 使用傳輸控制通訊協定 (Tcp) ProtocolType 和 InterNetworkAddressFamily。ProtocolType.Tcp:使用傳輸控制通訊協定。 */ //使用IPv4地址,流式socket方式,tcp協議傳遞資料 Socket socket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); //建立好socket後,必須告訴socket綁定的IP地址和連接埠號碼。 //讓socket監聽point try { //socket監聽哪個連接埠 socket.Bind(point); //同一個時間點過來10個用戶端,排隊 socket.Listen(10); ShowMsg("伺服器開始監聽"); Thread thread = new Thread(AcceptInfo); thread.IsBackground = true; thread.Start(socket); } catch (Exception ex) { ShowMsg(ex.Message); } } //記錄通訊用的Socket Dictionary<string,Socket> dic=new Dictionary<string, Socket>(); // private Socket client; void AcceptInfo(object o) { Socket socket = o as Socket; while (true) { //通訊用socket try { //建立通訊用的Socket Socket tSocket = socket.Accept(); string point = tSocket.RemoteEndPoint.ToString(); //IPEndPoint endPoint = (IPEndPoint)client.RemoteEndPoint; //string me = Dns.GetHostName();//得到本機名稱 //MessageBox.Show(me); ShowMsg(point + "串連成功!"); cboIpPort.Items.Add(point); dic.Add(point, tSocket); //接收訊息 Thread th = new Thread(ReceiveMsg); th.IsBackground = true; th.Start(tSocket); } catch (Exception ex) { ShowMsg(ex.Message); break; } } } //接收訊息 void ReceiveMsg(object o) { Socket client = o as Socket; while (true) { //接收用戶端發送過來的資料 try { //定義byte數組存放從用戶端接收過來的資料 byte[] buffer = new byte[1024 * 1024]; //將接收過來的資料放到buffer中,並返回實際接受資料的長度 int n = client.Receive(buffer); //將位元組轉換成字串 string words = Encoding.UTF8.GetString(buffer, 0, n); ShowMsg(client.RemoteEndPoint.ToString() + ":" + words); } catch (Exception ex) { ShowMsg(ex.Message); break; } } } void ShowMsg(string msg) { txtLog.AppendText(msg+"\r\n"); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { //主表單關閉時關閉子線程 } //給用戶端發送訊息 private void btnSend_Click(object sender, EventArgs e) { try { ShowMsg(txtMsg.Text); string ip = cboIpPort.Text; byte[] buffer = Encoding.UTF8.GetBytes(txtMsg.Text); dic[ip].Send(buffer); // client.Send(buffer); } catch (Exception ex) { ShowMsg(ex.Message); } }
[非技術參考]C# Socket網路編程