們在講解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
{
//建立通信用的Socket
Socket 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 = "";
}
}
}