上一篇文章介紹了內存映射文件,這篇文章咱們介紹一種用得更加普遍的方式——Socket
通訊git
Socket
稱爲」套接字」,它分爲流式套接字和用戶數據報套接字,分別對應網絡中的 TCP 和 UDP 協議。這兩種都可以實現進程間通訊(不管是不是同一機器)github
TCP 協議是面向鏈接的協議,提供穩定的雙向通訊功能,TCP鏈接的創建是經過三次握手才能完成,穩定性高,建立鏈接的效率相對UDP較低
UDP協議是面向無鏈接的,效率高,但不保證數據必定可以正確傳輸(順序、丟包等)網絡
咱們應該選擇 UDP 仍是 TCP?併發
但實際項目中,這樣「純粹」的場景並非那麼多,所以,每每採用的方案都是 TCP、UDP 相結合的方式來實現。固然爲了保證數據的可靠及業務的穩定性,不少框架都不只僅只有這麼兩種技術框架
框架的複雜、輕量與否,與其應對的業務場景是相關的。咱們須要根據不一樣的場景,來選擇適合本身項目的框架。在 C# 中,有 FastSocket
、SuperSocket
等 Socket
框架供你們選擇。其中 SuperSocket
支持 IOCP
,它能夠實現高性能、高併發。其餘語言有 Netty
、HP-Socket
等,這些也有 .NET 的移植版本tcp
通常狀況下,不建議各位朋友本身去寫一個 Socket
框架來支持項目的業務場景,用現有的框架更加妥當。若是不知道選擇什麼框架,能夠去 Github 上搜索相關的開源框架微服務
選擇 Github 中的框架時,咱們應該注意高併發
Socket
通訊,是市面上不少框架的基礎,所以咱們有必要介紹下它的使用方式,及在開發過程當中須要注意的事項性能
在 C# 中,不管是 TCP 協議,仍是 UDP 協議,都封裝在了 Socket
這個類中。使用時,只須要咱們指定不一樣的參數便可學習
TCP 與 UDP 區別
在大部分狀況下(針對性能而言),咱們沒法感受到這二者之間的差別;而在高併發的場景下,咱們就能很容易體會到(由於訪問量大了以後,任何細小的變化都能累積起來從而形成巨大的影響)
使用 TCP 面臨的一個主要問題就是粘包,業界主流的解決方案可概括以下
另外,若是以爲自定義協議太麻煩,咱們也能夠根據 MQTT
協議來寫一套符合它的解決方案
針對 TCP 的使用,咱們給出一個例子。其中咱們採用 Jil
來實現序列化
/// <summary>
/// 傳輸使用的包
/// </summary>
public class Packet {
public const int TYPE_LOGIN = 10001;
public const int TYPE_MSG = 10000;
public const int TYPE_LOGOUT = 10002;
public const int TYPE_INVALID = 40000;
/// <summary>
/// 這個包的類型。在實際業務場景中,通常會使用 int、short 等來表示,而不是 enum
/// </summary>
public int Type { get; set; }
/// <summary>
/// 具體的業務數據
/// </summary>
public string Data { get; set; }
}
複製代碼
如下爲服務端代碼
using Jil;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace App {
class Program {
static void Main(string[] args) {
TcpListener tcpListener = new TcpListener(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9999));
tcpListener.Start();
/// 此處僅僅用於處理客戶端的鏈接
/// 而不涉及具體的業務邏輯
while (true) {
TcpClient remoteClient = tcpListener.AcceptTcpClient();
ClientPacketHandlers packetHandlers = new ClientPacketHandlers(remoteClient);
}
}
}
/// <summary>
/// 將業務邏輯處理分開
/// </summary>
public class ClientPacketHandlers {
Dictionary<int, Action<NetworkStream, string>> clientHandlers = new Dictionary<int, Action<NetworkStream, string>>();
TcpClient remoteClient;
NetworkStream stream;
Task processTask;
CancellationTokenSource cancellationTokenSource;
public ClientPacketHandlers(TcpClient client) {
this.remoteClient = client;
this.stream = remoteClient.GetStream();
// 這個能夠經過配置文件來添加處理器
clientHandlers.Add(Packet.TYPE_LOGIN, HandleLogin);
clientHandlers.Add(Packet.TYPE_MSG, HandleMsg);
clientHandlers.Add(Packet.TYPE_LOGOUT, HandleLogout);
cancellationTokenSource = new CancellationTokenSource();
// 爲該客戶端開闢一個 Task,用於與該客戶端通訊
// 在高併發場景中,每每不會這樣作。而是採用 IOCP 或者其餘的高性能的方式
// 爲每一個客戶端開闢一個 Task 不合理,也很浪費系統資源(由於不是每一個客戶端都會頻繁發送消息)
processTask = Task.Run(() => {
byte[] buffer = new byte[1024];
while (true) {
int bytesRead = stream.Read(buffer, 0, 1024);
if (bytesRead > 0) {
byte[] realBytes = new byte[bytesRead];
Buffer.BlockCopy(buffer, 0, realBytes, 0, bytesRead);
Packet packet = JSON.Deserialize<Packet>(Encoding.UTF8.GetString(realBytes));
if (packet != null) {
if (clientHandlers.ContainsKey(packet.Type)) {
clientHandlers[packet.Type].Invoke(stream, packet.Data);
} else {
SendPacket(stream, new Packet() { Type = Packet.TYPE_INVALID, Data = "No handlers for your type" });
}
}
}
if (cancellationTokenSource == null || cancellationTokenSource.IsCancellationRequested) {
break;
}
}
}, cancellationTokenSource.Token);
}
public void HandleLogin(NetworkStream stream, string data) {
if (stream == null || string.IsNullOrEmpty(data)) return;
SendPacket(stream, new Packet() { Type = Packet.TYPE_LOGIN, Data = $"Hello, {data}" });
}
public void HandleMsg(NetworkStream stream, string data) {
if (stream == null || string.IsNullOrEmpty(data)) return;
SendPacket(stream, new Packet() { Type = Packet.TYPE_MSG, Data = $"Received Msg : {data}" });
}
public void HandleLogout(NetworkStream stream, string data) {
if (stream == null || string.IsNullOrEmpty(data)) return;
SendPacket(stream, new Packet() { Type = Packet.TYPE_LOGOUT, Data = $"Logout, {data}" });
try {
if (cancellationTokenSource != null) {
cancellationTokenSource.Cancel();
cancellationTokenSource.Dispose();
}
} catch (Exception e) {
} finally {
cancellationTokenSource = null;
}
}
public void SendPacket(NetworkStream stream, Packet packet) {
byte[] packetBytes = Encoding.UTF8.GetBytes(JSON.Serialize(packet));
stream.Write(packetBytes, 0, packetBytes.Length);
}
}
}
複製代碼
如下爲客戶端代碼
using Jil;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace App {
class Program {
static void Main(string[] args) {
TcpClient tcpClient = new TcpClient();
tcpClient.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9999));
NetworkStream networkStream = tcpClient.GetStream();
Task.Run(() => {
byte[] buffer = new byte[1024];
while (true) {
int bytesRead = networkStream.Read(buffer, 0, 1024);
if (bytesRead > 0) {
byte[] realBytes = new byte[bytesRead];
Buffer.BlockCopy(buffer, 0, realBytes, 0, bytesRead);
Packet packet = JSON.Deserialize<Packet>(Encoding.UTF8.GetString(realBytes));
if (packet != null) {
Console.WriteLine($"RECEIVED DATA: {packet.Data}");
}
}
}
});
while (true) {
string line = Console.ReadLine();
string[] strs = line.Split(':');
if(strs.Length >= 2) {
if(strs[0] == "login") {
SendPacket(networkStream, new Packet() { Type = Packet.TYPE_LOGIN, Data = strs[1] });
} else if (strs[0] == "msg") {
SendPacket(networkStream, new Packet() { Type = Packet.TYPE_MSG, Data = strs[1] });
} else if (strs[0] == "logout") {
SendPacket(networkStream, new Packet() { Type = Packet.TYPE_LOGOUT, Data = strs[1] });
}
}
}
}
private static void SendPacket(NetworkStream networkStream, Packet packet) {
byte[] packetBytes = Encoding.UTF8.GetBytes(JSON.Serialize(packet));
networkStream.Write(packetBytes, 0, packetBytes.Length);
}
}
}
複製代碼
這即是 TCP 通訊的基礎示例了,在更復雜的場景中,系統的設計將會更加複雜。但宗旨都只有一個,提供更加穩定可靠的服務
UDP 的使用與 TCP 相似,所以就不一一舉例了
IOCP
等框架。除了避免系統資源的浪費,更是爲了提高系統的響應能力