們在講解Socket編程前,先看幾個和Socket編程緊密相關的概念:
- 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大類
(1)公認端口(WellKnownPorts):從0到1023,它們緊密綁定(binding)于一些服務。通常這些端口的通訊明確表明了某種服務的協議。例如:80端口實際上總是HTTP通訊。
(2)注冊端口(RegisteredPorts):從1024到49151。它們松散地綁定于一些服務。也就是說有許多服務綁定于這些端口,這些端口同樣用于許多其它目的。例如:許多系統處理動態端口從1024左右開始。
(3)動態和/或私有端口(Dynamicand/orPrivatePorts):從49152到65535。理論上,不應為服務分配這些端口。實際上,機器通常從1024起分配動態端口。
3.TCP和UDP報文
? 下面一起來看下TCP和UDP的報文圖
?
? ? ? 從圖中我們可以看出TCP和UDP中都有校驗和,但是在UDP報文中,一般不使用校驗和,這樣可以加快數據傳輸的速度,但是數據的準確性可能會受到影響。換句話說,Tcp協議都有校驗和,為了保證傳輸數據的準確性。
3.Socket
? ? ?Socket包括Ip地址和端口號兩部分,程序通過Socket來通信,Socket相當于操作系統的一個組件。Socket作為進程之間通信機制,通常也稱作”套接字”,用于描述IP地址和端口號,是一個通信鏈的句柄。說白了,就是兩個程序通信用的。
生活案例對比:
? ? ? Socket之間的通信可以類比生活中打電話的案例。任何用戶在通話之前,首先要占有一部電話機,相當于申請一個Socket,同時要知道對方的號碼,相當于對方有一個固定的Socket,然后向對方撥號呼叫,相當于發出連接請求。假如對方在場并空閑,拿起 電話話筒,雙方就可以進行通話了。雙方的通話過程,是一方向電話機發出信號和對方從電話機接收信號的過程,相當于向socket發送數據和從socket接收數據。通話結束后,一方掛起電話機,相當于關閉socket,撤銷連接。
?????注意:Socket不僅可以在兩臺電腦之間通信,還可以在同一臺電腦上的兩個程序間通信。
4,端口進階(深入)
? ??通過IP地址確定了網絡中的一臺電腦后,該電腦上可能提供很多提供服務的應用,每一個應用都對應一個端口。
在Internet上有很多這樣的主機,這些主機一般運行了多個服務軟件 ,同時提供幾種服務,每種服務都打開一個Socket,并綁定到一個端口上,不同的端口對應于不同的服務(應用程序)
? ? 例如:http 使用80端口,?? ftp使用21端口???? smtp使用25端口
5.Socket分類
? ? ?Socket主要有兩種類型:
- 流式Socket
? ? ? ? ? 是一種面向連接的Socket,針對于面向連接的TCP服務應用,安全,但是效率低
? ? ?2,數據報式Socket
? ? ? ? ? 是一種無連接的Socket,對應于無連接的UDP服務應用,不安全,但效率高
?6. Socket一般應用模式(服務器端和客戶端)
? ? ?服務器端的Socket(至少需要兩個)
? ? ? ? 01.一個負責接收客戶端連接請求(但不負責與客戶端通信)
? ? ? ?02.每成功接收到客戶端的連接便在服務器端產生一個對應的復雜通信的Socket
? ? ? ? ? 021.在接收到客戶端連接時創建
? ? ? ? ?022. 為每個連接成功的客戶端請求在服務器端都創建一個對應的Socket(負責和客戶端通信)
? ? 客戶端的Socket
- 必須指定要連接的服務器地址和端口
- 通過創建一個Socket對象來初始化一個到服務器端的TCP連接
?
? ? ? 通過上圖,我們可以看出,首先服務器會創建一個負責監聽的socket,然后客戶端通過socket連接到服務器指定端口,最后服務器端負責監聽的socket,監聽到客戶端有連接過來了,就創建一個負責和客戶端通信的socket。
下面我們來看下Socket更具體的通信過程:
Socket的通訊過程
? 服務器端:
? ? 01,申請一個socket
? ? 02,綁定到一個IP地址和一個端口上
? ? 03,開啟偵聽,等待接收連接
? 客戶端:
? ? 01,申請一個socket
? ?02,連接服務器(指明IP地址和端口號)
? ?服務器端接收到連接請求后,產生一個新的socket(端口大于1024)與客戶端建立連接并進行通信,原監聽socket繼續監聽。
??注意:負責通信的Socket不能無限創建,創建的數量和操作系統有關。
?7.Socket的構造函數
? ? Public Socket(AddressFamily addressFamily,SocketType? socketType,ProtocolType? protocolTYpe)
? ? AddressFamily:指定Socket用來解析地址的尋址方案。例如:InterNetWork指示當Socket使用一個IP版本4地址連接
? ?SocketType:定義要打開的Socket的類型
? ?Socket類使用ProtocolType枚舉向Windows? Sockets? API通知所請求的協議
注意:
? ?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,銷毀連接
? 接下來,我們同一個簡單的服務器和客戶端通信的案例,來看下Sokcet的具體用法,效果圖如下:
服務器端代碼:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;namespace 服務器
{public partial class FServer : Form{IPAddress ip;IPEndPoint point;public FServer(){InitializeComponent();ServerListen();}private void ServerListen() {//ip地址 ip = IPAddress.Parse(txtIP.Text);// IPAddress ip = IPAddress.Any;//端口號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); //同一個時間點過來60個客戶端,排隊 socket.Listen(60); 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 { //創建通信用的SocketSocket tSocket = socket.Accept();string point = tSocket.RemoteEndPoint.ToString();//IPEndPoint endPoint = (IPEndPoint)client.RemoteEndPoint;//string me = Dns.GetHostName();//得到本機名稱//MessageBox.Show(me);ShowMsg(point + "連接成功!");ComboBoxAddItems(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){if (!client.Connected) ComboBoxRoMoveItems(client.RemoteEndPoint.ToString());ShowMsg(ex.Message); break;}}}delegate void SetTextCallback(string msg);public void ShowMsg(string msg){if (this.txtLog.InvokeRequired){SetTextCallback d = new SetTextCallback(ShowMsg);this.Invoke(d, new object[] { msg });}else{this.txtLog.AppendText(msg + "\r\n");}}delegate void SetComboBox(String msg);public void ComboBoxAddItems(string msg){if (this.cboIpPort.InvokeRequired){SetTextCallback d = new SetTextCallback(ComboBoxAddItems);this.Invoke(d, new object[] { msg });}else{this.cboIpPort.Items.Add(msg);}}public void ComboBoxRoMoveItems(string msg){if (this.cboIpPort.InvokeRequired){SetTextCallback d = new SetTextCallback(ComboBoxRoMoveItems);this.Invoke(d, new object[] { msg });}else{this.cboIpPort.Items.Remove(msg);cboIpPort.SelectedIndex = -1;}}//給客戶端發送消息private void btnSend_Click(object sender, EventArgs e){try{ string ip = cboIpPort.Text;if (cboIpPort.Text.Length > 5){ byte[] buffer = Encoding.UTF8.GetBytes(txtMsg.Text);dic[ip].Send(buffer);ShowMsg(point.ToString() + ":" + txtMsg.Text);}else{}}catch (Exception ex){ShowMsg(ex.Message);}}private void Clearbtn_Click(object sender, EventArgs e){this.txtLog.Text = "";}}
}
?
客戶端代碼:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;
using System.Threading;namespace 客戶端
{public partial class FClient : Form{Socket client;IPAddress ip;IPEndPoint point;bool isExits = false;public FClient(){InitializeComponent();}private void FClient_Load(object sender, EventArgs e){ip = IPAddress.Parse(txtIP.Text); point = new IPEndPoint(ip, int.Parse(txtPort.Text));Thread serverConnetThread = new Thread(ServerConnetThread);serverConnetThread.IsBackground = true;serverConnetThread.Start();}private void ServerConnet(){try{//連接到服務器 if (client != null) client.Close();client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);client.Connect(point);ShowMsg("連接成功");ShowMsg("服務器:" + client.RemoteEndPoint.ToString());ShowMsg("客戶端:" + client.LocalEndPoint.ToString());//連接成功后,就可以接收服務器發送的信息了 Thread th = new Thread(ReceiveMsg);th.IsBackground = true;th.Start();}catch (Exception ex){ShowMsg(ex.Message);}}private void ServerConnetThread() {while (!isExits){if (client == null || !client.Connected) {ServerConnet();}Thread.Sleep(5000);} }//接收服務器的消息void ReceiveMsg(){while (true){try {byte[] buffer = new byte[1024 * 1024];int n = client.Receive(buffer);string s = Encoding.UTF8.GetString(buffer, 0, n);ShowMsg(point.ToString() + ":" + s);}catch (Exception ex){ShowMsg(ex.Message);break;}} }delegate void SetTextCallback(string msg);public void ShowMsg(string msg){if (this.txtInfo.InvokeRequired){SetTextCallback d = new SetTextCallback(ShowMsg);this.Invoke(d, new object[] { msg });}else{this.txtInfo.AppendText(msg + "\r\n");}}private void btnSend_Click(object sender, EventArgs e){//客戶端給服務器發消息 if (client != null) { try{ShowMsg(client.RemoteEndPoint + ":" + txtMsg.Text);byte[] buffer = Encoding.UTF8.GetBytes(txtMsg.Text); client.Send(buffer); } catch (Exception ex){ ShowMsg(ex.Message); } }}private void Clearbtn_Click(object sender, EventArgs e){this.txtInfo.Text = "";}}
}
?