C#開發(fā)之Socket網(wǎng)絡(luò)編程TCP/IP層次模型、端口及報(bào)文等探討
1、TCP/IP層次模型
當(dāng)然這里我們只討論重要的四層
01,應(yīng)用層(Application):應(yīng)用層是個(gè)很廣泛的概念,有一些基本相同的系統(tǒng)級(jí)TCP/IP應(yīng)用以及應(yīng)用協(xié)議,也有許多的企業(yè)應(yīng)用和互聯(lián)網(wǎng)應(yīng)用。http協(xié)議在應(yīng)用層運(yùn)行。
02,傳輸層(Tanspot):傳輸層包括UDP和TCP,UDP幾乎不對(duì)報(bào)文進(jìn)行檢查,而TCP提供傳輸保證。
03,網(wǎng)絡(luò)層(Netwok):網(wǎng)絡(luò)層協(xié)議由一系列協(xié)議組成,包括ICMP、IGMP、RIP、OSPF、IP(v4,v6)等。
04,鏈路層(Link):又稱為物理數(shù)據(jù)網(wǎng)絡(luò)接口層,負(fù)責(zé)報(bào)文傳輸。
然后我們來看下tcp層次模型圖
從上圖中可以看出,應(yīng)用程序在應(yīng)用層運(yùn)行,在傳輸層,在數(shù)據(jù)前加上了TCP頭,在
網(wǎng)絡(luò)層加上的IP頭,在數(shù)據(jù)鏈路層加上了幀。
2、端口
端口號(hào)范圍:0-65535,總共能表示65536個(gè)數(shù)。
按端口號(hào)可分為3大類
(1)公認(rèn)端口(WellKnownPorts):從0到1023,它們緊密綁定(binding)于一些服務(wù)。通常這些端口的通訊明確表明了某種服務(wù)的協(xié)議。例如:80端口實(shí)際上總是HTTP通訊。
?。?)注冊(cè)端口(RegisteredPorts):從1024到49151。它們松散地綁定于一些服務(wù)。也就是說有許多服務(wù)綁定于這些端口,這些端口同樣用于許多其它目的。例如:許多系統(tǒng)處理動(dòng)態(tài)端口從1024左右開始。
?。?)動(dòng)態(tài)和/或私有端口(Dynamicand/orPrivatePorts):從49152到65535。理論上,不應(yīng)為服務(wù)分配這些端口。實(shí)際上,機(jī)器通常從1024起分配動(dòng)態(tài)端口。
3.TCP和UDP報(bào)文
下面一起來看下TCP和UDP的報(bào)文圖
從圖中我們可以看出TCP和UDP中都有校驗(yàn)和,但是在UDP報(bào)文中,一般不使用校驗(yàn)和,這樣可以加快數(shù)據(jù)傳輸?shù)乃俣?,但是?shù)據(jù)的準(zhǔn)確性可能會(huì)受到影響。換句話說,Tcp協(xié)議都有校驗(yàn)和,為了保證傳輸數(shù)據(jù)的準(zhǔn)確性。
3.Socket
Socket包括Ip地址和端口號(hào)兩部分,程序通過Socket來通信,Socket相當(dāng)于操作系統(tǒng)的一個(gè)組件。Socket作為進(jìn)程之間通信機(jī)制,通常也稱作”套接字”,用于描述IP地址和端口號(hào),是一個(gè)通信鏈的句柄。說白了,就是兩個(gè)程序通信用的。
生活案例對(duì)比:
Socket之間的通信可以類比生活中打電話的案例。任何用戶在通話之前,首先要占有一部電話機(jī),相當(dāng)于申請(qǐng)一個(gè)Socket,同時(shí)要知道對(duì)方的號(hào)碼,相當(dāng)于對(duì)方有一個(gè)固定的Socket,然后向?qū)Ψ綋芴?hào)呼叫,相當(dāng)于發(fā)出連接請(qǐng)求。假如對(duì)方在場并空閑,拿起 電話話筒,雙方就可以進(jìn)行通話了。雙方的通話過程,是一方向電話機(jī)發(fā)出信號(hào)和對(duì)方從電話機(jī)接收信號(hào)的過程,相當(dāng)于向socket發(fā)送數(shù)據(jù)和從socket接收數(shù)據(jù)。通話結(jié)束后,一方掛起電話機(jī),相當(dāng)于關(guān)閉socket,撤銷連接。
注意:Socket不僅可以在兩臺(tái)電腦之間通信,還可以在同一臺(tái)電腦上的兩個(gè)程序間通信。
4,端口進(jìn)階(深入)
通過IP地址確定了網(wǎng)絡(luò)中的一臺(tái)電腦后,該電腦上可能提供很多提供服務(wù)的應(yīng)用,每一個(gè)應(yīng)用都對(duì)應(yīng)一個(gè)端口。
在Internet上有很多這樣的主機(jī),這些主機(jī)一般運(yùn)行了多個(gè)服務(wù)軟件 ,同時(shí)提供幾種服務(wù),每種服務(wù)都打開一個(gè)Socket,并綁定到一個(gè)端口上,不同的端口對(duì)應(yīng)于不同的服務(wù)(應(yīng)用程序)
例如:http 使用80端口, ftp使用21端口 smtp使用25端口
5.Socket分類
Socket主要有兩種類型:
流式Socket
是一種面向連接的Socket,針對(duì)于面向連接的TCP服務(wù)應(yīng)用,安全,但是效率低
2,數(shù)據(jù)報(bào)式Socket
是一種無連接的Socket,對(duì)應(yīng)于無連接的UDP服務(wù)應(yīng)用,不安全,但效率高
6. Socket一般應(yīng)用模式(服務(wù)器端和客戶端)
服務(wù)器端的Socket(至少需要兩個(gè))
01.一個(gè)負(fù)責(zé)接收客戶端連接請(qǐng)求(但不負(fù)責(zé)與客戶端通信)
02.每成功接收到客戶端的連接便在服務(wù)器端產(chǎn)生一個(gè)對(duì)應(yīng)的復(fù)雜通信的Socket
021.在接收到客戶端連接時(shí)創(chuàng)建
022. 為每個(gè)連接成功的客戶端請(qǐng)求在服務(wù)器端都創(chuàng)建一個(gè)對(duì)應(yīng)的Socket(負(fù)責(zé)和客戶端通信)
客戶端的Socket
必須指定要連接的服務(wù)器地址和端口
通過創(chuàng)建一個(gè)Socket對(duì)象來初始化一個(gè)到服務(wù)器端的TCP連接
通過上圖,我們可以看出,首先服務(wù)器會(huì)創(chuàng)建一個(gè)負(fù)責(zé)監(jiān)聽的socket,然后客戶端通過socket連接到服務(wù)器指定端口,最后服務(wù)器端負(fù)責(zé)監(jiān)聽的socket,監(jiān)聽到客戶端有連接過來了,就創(chuàng)建一個(gè)負(fù)責(zé)和客戶端通信的socket。
下面我們來看下Socket更具體的通信過程:
Socket的通訊過程
服務(wù)器端:
01,申請(qǐng)一個(gè)socket
02,綁定到一個(gè)IP地址和一個(gè)端口上
03,開啟偵聽,等待接收連接
客戶端:
01,申請(qǐng)一個(gè)socket
02,連接服務(wù)器(指明IP地址和端口號(hào))
服務(wù)器端接收到連接請(qǐng)求后,產(chǎn)生一個(gè)新的socket(端口大于1024)與客戶端建立連接并進(jìn)行通信,原監(jiān)聽socket繼續(xù)監(jiān)聽。
注意:負(fù)責(zé)通信的Socket不能無限創(chuàng)建,創(chuàng)建的數(shù)量和操作系統(tǒng)有關(guān)。
7.Socket的構(gòu)造函數(shù)
Public Socket(AddressFamily addressFamily,SocketType socketType,ProtocolType protocolTYpe)
AddressFamily:指定Socket用來解析地址的尋址方案。例如:InterNetWork指示當(dāng)Socket使用一個(gè)IP版本4地址連接
SocketType:定義要打開的Socket的類型
Socket類使用ProtocolType枚舉向Windows Sockets API通知所請(qǐng)求的協(xié)議
注意:
1,端口號(hào)必須在 1 和 65535之間,最好在1024以后。
2,要連接的遠(yuǎn)程主機(jī)必須正在監(jiān)聽指定端口,也就是說你無法隨意連接遠(yuǎn)程主機(jī)。
如:
IPAddress addr = IPAddress.Parse("127.0.0.1");
IPEndPoint endp = new IPEndPoint(addr,,9000);
服務(wù)端先綁定:serverWelcomeSocket.Bind(endp)
客戶端再連接:clientSocket.Connect(endp)
3,一個(gè)Socket一次只能連接一臺(tái)主機(jī)
4,Socket關(guān)閉后無法再次使用
5,每個(gè)Socket對(duì)象只能與一臺(tái)遠(yuǎn)程主機(jī)連接。如果你想連接到多臺(tái)遠(yuǎn)程主機(jī),你必須創(chuàng)建多個(gè)Socket對(duì)象。
8.Socket常用類和方法
相關(guān)類:
IPAddress:包含了一個(gè)IP地址
IPEndPoint:包含了一對(duì)IP地址和端口號(hào)
方法:
Socket():創(chuàng)建一個(gè)Socket
Bind():綁定一個(gè)本地的IP和端口號(hào)(IPEndPoint)
Listen():讓Socket偵聽傳入的連接吃那個(gè)病,并指定偵聽隊(duì)列容量
Connect():初始化與另一個(gè)Socket的連接
Accept():接收連接并返回一個(gè)新的Socket
Send():輸出數(shù)據(jù)到Socket
Receive():從Socket中讀取數(shù)據(jù)
Close():關(guān)閉Socket,銷毀連接
接下來,我們同一個(gè)簡單的服務(wù)器和客戶端通信的案例,來看下Sokcet的具體用法,效果圖如下:
關(guān)鍵代碼:
服務(wù)器端代碼:
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;
//端口號(hào)
IPEndPoint point=new IPEndPoint(ip,int.Parse(txtPort.Text));
//創(chuàng)建監(jiān)聽用的Socket
/*
* AddressFamily.InterNetWork:使用 IP4地址。
SocketType.Stream:支持可靠、雙向、基于連接的字節(jié)流,而不重復(fù)數(shù)據(jù)。此類型的 Socket 與單個(gè)對(duì)方主機(jī)進(jìn)行通信,并且在通信開始之前需要遠(yuǎn)程主機(jī)連接。Stream 使用傳輸控制協(xié)議 (Tcp) ProtocolType 和 InterNetworkAddressFamily。
ProtocolType.Tcp:使用傳輸控制協(xié)議。
*/
//使用IPv4地址,流式socket方式,tcp協(xié)議傳遞數(shù)據(jù)
Socket socket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
//創(chuàng)建好socket后,必須告訴socket綁定的IP地址和端口號(hào)。
//讓socket監(jiān)聽point
try
{
//socket監(jiān)聽哪個(gè)端口
socket.Bind(point);
//同一個(gè)時(shí)間點(diǎn)過來10個(gè)客戶端,排隊(duì)
socket.Listen(10);
ShowMsg("服務(wù)器開始監(jiān)聽");
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
{
//創(chuàng)建通信用的Socket
Socket tSocket = socket.Accept();
string point = tSocket.RemoteEndPoint.ToString();
//IPEndPoint endPoint = (IPEndPoint)client.RemoteEndPoint;
//string me = Dns.GetHostName();//得到本機(jī)名稱
//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)
{
//接收客戶端發(fā)送過來的數(shù)據(jù)
try
{
//定義byte數(shù)組存放從客戶端接收過來的數(shù)據(jù)
byte[] buffer = new byte[1024 * 1024];
//將接收過來的數(shù)據(jù)放到buffer中,并返回實(shí)際接受數(shù)據(jù)的長度
int n = client.Receive(buffer);
//將字節(jié)轉(zhuǎn)換成字符串
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)
{
//主窗體關(guān)閉時(shí)關(guān)閉子線程
}
//給客戶端發(fā)送消息
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);
}
}
客戶端代碼:
Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
private void btnConnection_Click(object sender, EventArgs e)
{
//連接到的目標(biāo)IP
IPAddress ip = IPAddress.Parse(txtIP.Text);
//IPAddress ip = IPAddress.Any;
//連接到目標(biāo)IP的哪個(gè)應(yīng)用(端口號(hào)!)
IPEndPoint point=new IPEndPoint(ip,int.Parse(txtPort.Text));
try
{
//連接到服務(wù)器
client.Connect(point);
ShowMsg("連接成功");
ShowMsg("服務(wù)器" + client.RemoteEndPoint.ToString());
ShowMsg("客戶端:" + client.LocalEndPoint.ToString());
//連接成功后,就可以接收服務(wù)器發(fā)送的信息了
Thread th=new Thread(ReceiveMsg);
th.IsBackground = true;
th.Start();
}
catch (Exception ex)
{
ShowMsg(ex.Message);
}
}
//接收服務(wù)器的消息
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(client.RemoteEndPoint.ToString() + ":" + s);
}
catch (Exception ex)
{
ShowMsg(ex.Message);
break;
}
}
}
void ShowMsg(string msg)
{
txtInfo.AppendText(msg+"\r\n");
}
private void btnSend_Click(object sender, EventArgs e)
{
//客戶端給服務(wù)器發(fā)消息
if (client!=null)
{
try
{
ShowMsg(txtMsg.Text);
byte[] buffer = Encoding.UTF8.GetBytes(txtMsg.Text);
client.Send(buffer);
}
catch (Exception ex)
{
ShowMsg(ex.Message);
}
}
}
private void ClientForm_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls = false;
}
好了,到這里我們對(duì)Socket的討論就告一個(gè)段落了。
- C# TcpClient網(wǎng)絡(luò)編程傳輸文件的示例
- C# 網(wǎng)絡(luò)編程之UDP
- c# 網(wǎng)絡(luò)編程之tcp
- c# 網(wǎng)絡(luò)編程之http
- 深入學(xué)習(xí)C#網(wǎng)絡(luò)編程之HTTP應(yīng)用編程(下)
- 深入學(xué)習(xí)C#網(wǎng)絡(luò)編程之HTTP應(yīng)用編程(上)
- 淺談C#網(wǎng)絡(luò)編程詳解篇
- 詳解C# 網(wǎng)絡(luò)編程系列:實(shí)現(xiàn)類似QQ的即時(shí)通信程序
- 總結(jié)C#網(wǎng)絡(luò)編程中對(duì)于Cookie的設(shè)定要點(diǎn)
- C# Socket網(wǎng)絡(luò)編程實(shí)例
- C#網(wǎng)絡(luò)編程基礎(chǔ)之進(jìn)程和線程詳解
- c# socket網(wǎng)絡(luò)編程接收發(fā)送數(shù)據(jù)示例代碼
- C#網(wǎng)絡(luò)編程中常用特性介紹
相關(guān)文章
C#實(shí)現(xiàn)將選中復(fù)選框的信息返回給用戶的方法
這篇文章主要介紹了C#實(shí)現(xiàn)將選中復(fù)選框的信息返回給用戶的方法,涉及C#針對(duì)復(fù)選框操作的相關(guān)技巧,需要的朋友可以參考下2015-06-06C#基于HttpWebRequest實(shí)現(xiàn)發(fā)送HTTP請(qǐng)求的方法分析
這篇文章主要介紹了C#基于HttpWebRequest實(shí)現(xiàn)發(fā)送HTTP請(qǐng)求的方法,結(jié)合實(shí)例形式分析了C#使用HttpWebRequest類與System.IO類實(shí)現(xiàn)發(fā)送HTTP請(qǐng)求相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2019-02-02