C# Socket網路通訊基礎教程
Socket連結的流程
套接字是支援TCP/IP協議網路通訊的基本操作單元可以將套接字看作不同主機間的程序雙向通訊的端點, 它構成了單個主機內及整個網路間的程式設計介面。 套接字存在於通訊域中, 通訊域是為了處理
一般的執行緒通過套接字通訊而引進的一種抽象概念。 套接字通常會和同一個域中的套接字交換資料(資料交換也可能會穿越域的界限, 但這時一定要執行某種解釋程式) 。 各種程序使用這個相同的域用
圖6-14展示的是一套基本的Socket通訊流程。
Socket通訊的基本流程具體步驟如下所示
1.開啟一個連結之前,需要先完成Socket和Bind兩個步驟。Socket是新建一個套接字,Bind指定套接字的IP和埠(客戶端在呼叫Connect時會由系統分配埠,因此可以省去Bind)。
2.服務端通過Listen開啟監聽,等待客戶端接入。
3.客戶端通過Connect連線伺服器, 服務端通過Accept接收客戶端連線。 在connect-accept過程中, 作業系統將會進行三次握手
4.客戶端和服務端通過write和read傳送和接收資料, 作業系統將會完成TCP資料的確認、 重發等步驟。
5.通過close關閉連線, 作業系統會進行四次揮手。
Socket類
System.Net.Sockets名稱空間的Socket類為網路通訊提供了一套豐富的方法和屬性, 表6-6和表6-7列舉了Socket類的一些常用方法和屬性。
編寫服務端程式
伺服器遵照Socket通訊的基本流程, 先建立Socket, 再呼叫Bind繫結IP地址和埠號, 之後呼叫Listen等待客戶端連線。 最後在while迴圈中呼叫Accept接收客戶端的連線, 並回應訊息。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace SocketServerDemo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("hello world!");
//新建一個套接字Socket即建立Socket
//Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) ”這一行建立了一個套接字, 它的3個引數分別代表地址族、 套接字型別和協議。 地址族指明是使用IPv4還是IPv6, 含義如表6-8所示, 本例中使用的是IPv4, 即InterNetwork
Socket listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//Bind指定套接字的IP和埠
//listenfd.Bind(ipEp) 將給listenfd套接字繫結IP和埠。 示例程式中繫結的是本地地址“127.0.0.1”和1234號埠。 127.0.0.1是回送地址, 指本地機, 一般用於測試。 讀者也可以設定成真實的IP地址, 然後在兩臺電腦上分別執行客戶端和服務端程式。
IPAddress ipAdr = IPAddress.Parse("127.0.0.1");//根據IP地址建立IPAddress物件,如IPAddress.Parse("192.168.1.1")
IPEndPoint ipEp = new IPEndPoint(ipAdr, 1234);//用IPAddress指定的地址和埠號初始化
//服務端通過listenfd.Listen(0) 開啟監聽, 等待客戶端連線。 引數backlog指定佇列中最多可容納等待接受的連線數, 0表示不限制
listenfd.Listen(0);
Console.WriteLine("[伺服器]啟動成功");
while(true)
{
//Accept
//開啟監聽後, 伺服器呼叫listenfd.Accept() 接收客戶端連線。 本例使用的所有Socket方法都是阻塞方法, 也就是說當沒有客戶端連線時, 伺服器程式會卡在listenfd.Accept() 處, 而不會往下執行, 直到
//接收了客戶端的連線。 Accept返回一個新客戶端的Socket, 對於伺服器來說, 它有一個監聽Socket(例子中的listenfd) 用來接收(Accept) 客戶端的連線, 對每個客戶端來說還有一個專門的Socket(例子中的
//connfd) 用來處理該客戶端的資料
Socket connfd = listenfd.Accept();
Console.WriteLine("[伺服器]Accept");
//Recv
//伺服器通過connfd.Receive接收客戶端資料, Receive也是阻塞方法, 沒有收到客戶端資料時, 程式將卡在Receive處, 而不會往下執行。 Receive帶有一個byte[]型別的引數, 它將儲存接收到的資料, Receive
//的返回值則指明接收到的資料的長度。 之後使用System.Text.Encoding.UTF8.GetString(readBuff, 0, count) 將byte[] 陣列轉換成字串顯示在螢幕上。
byte[] readBuff = new byte[1024];
int count = connfd.Receive(readBuff);
string str = System.Text.Encoding.UTF8.GetString(readBuff, 0, count);
Console.WriteLine("[伺服器接收]" + str);
//Send
//伺服器通過connfd.Send傳送資料, 它接受一個byte[]型別的引數指明要傳送的內容。 Send的返回值指明發送資料的長度(例子中沒有使用) 。 伺服器程式用System.Text.Encoding.Default.GetBytes(字串)
//把字串轉換成byte[] 陣列, 然後傳送給客戶端(且會在字串前面加上“serv echo”)
byte[] bytes = System.Text.Encoding.Default.GetBytes("serv echo" + str);
connfd.Send(bytes);
}
}
}
}
客戶端程式
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using UnityEngine.UI;
public class UserNet : MonoBehaviour
{
//與服務端的套接字
Socket socket;
//服務端的IP和埠
public InputField hostInput;
public InputField portInput;
//文字框
public Text recvText;
public Text clientText;
//接收緩衝區
const int BUFFER_SIZE = 1024;
byte[] readBuff = new byte[BUFFER_SIZE];
public void Connection()
{
//Socket
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//Connect
string host = hostInput.text;
int port = int.Parse(portInput.text);
socket.Connect(host, port);
clientText.text = "客戶端地址" + socket.LocalEndPoint.ToString();
//Send
string str = "Hello Unity!";
byte[] bytes = System.Text.Encoding.Default.GetBytes(str);
socket.Send(bytes);
//Recv
int count = socket.Receive(readBuff);
str = System.Text.Encoding.UTF8.GetString(readBuff, 0, count);
recvText.text = str;
//Close
socket.Close();
}
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
}