使用VS2017建立一個類庫。
伺服器端
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MyServer
{
public class SocketPeer
{
private Socket serverSocket;
/// <summary>
/// 信号技術量,線程的擁塞控制
/// </summary>
private Semaphore semaphore;
private ClientPeerPool clientPeerPool;//用戶端連接配接對象池
public void StartServer(string ip,int port,int maxClient)
{
try
{
semaphore = new Semaphore(maxClient, maxClient);
clientPeerPool = new ClientPeerPool(maxClient);
for (int i = 0; i < maxClient; i++)
{
ClientPeer client = new ClientPeer();
client.ReceiveArgs.Completed += ReceiveArgs_Completed;
clientPeerPool.Enqueue(client);
}
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(IPAddress.Parse(ip), port));
serverSocket.Listen(maxClient);
Console.WriteLine("伺服器啟動成功");
//開始接收用戶端的連接配接
StartAccept(null);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
/// <summary>
/// 開始等待用戶端的連接配接
/// </summary>
private void StartAccept(SocketAsyncEventArgs e)
{
if (e==null)
{
e = new SocketAsyncEventArgs();
e.Completed += E_Completed;
}
//result 為true 代表正在接收連接配接,連接配接成功以後會調用E_Completed事件
//為false 代表接收成功
bool result = serverSocket.AcceptAsync(e);
if (result==false)
{
ProcessAccept(e);
}
}
/// <summary>
/// 異步接收連接配接完成後的回調
/// </summary>
private void E_Completed(object sender, SocketAsyncEventArgs e)
{
ProcessAccept(e);
}
/// <summary>
/// 出來用戶端的連接配接
/// </summary>
private void ProcessAccept(SocketAsyncEventArgs e)
{
semaphore.WaitOne();//阻止目前線程,直到有用戶端釋放
ClientPeer client = clientPeerPool.Dequeue();
client.clientSocket = e.AcceptSocket;//得到Client e.AcceptSocket 可以擷取目前連接配接的用戶端Sockt
Console.WriteLine(client.clientSocket.RemoteEndPoint+"用戶端連接配接成功");
//接收消息
StartReceive(client);
e.AcceptSocket = null;
StartAccept(e);//僞遞歸
}
/// <summary>
/// 開始接收消息
/// </summary>
private void StartReceive(ClientPeer client)
{
try
{
//這裡需要一個異步套接字 在ClientPeer中定義一個,在構造方法中指派
bool result = client.clientSocket.ReceiveAsync(client.ReceiveArgs);
if (result == false)
{
ProcessReceive(client.ReceiveArgs);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
/// <summary>
///異步接收消息完畢後的回調
/// </summary>
private void ReceiveArgs_Completed(object sender, SocketAsyncEventArgs e)
{
ProcessReceive(e);
}
/// <summary>
/// 處理資料的接收
/// </summary>
private void ProcessReceive(SocketAsyncEventArgs e)
{
//UserToken 可以得到與這個套接字綁定的程式對象,在ClientPeer進行了綁定設定,這裡得到ClientPeer
ClientPeer client = e.UserToken as ClientPeer;
//判斷資料是否接收成功 SocketError.Success是否連接配接 BytesTransferred 獲得收到資料的長度
if (client.ReceiveArgs.SocketError == SocketError.Success && client.ReceiveArgs.BytesTransferred > 0)
{
byte[] packet = new byte[client.ReceiveArgs.BytesTransferred];//資料的存儲
//client.ReceiveArgs.Buffer 資料緩沖區,接收到的資料存放到這裡
Buffer.BlockCopy(client.ReceiveArgs.Buffer, 0, packet, 0, client.ReceiveArgs.BytesTransferred);
//讓Client自身處理接收到的消息
client.ProcessReceive(packet);
StartReceive(client);//僞遞歸
}
else//資料接收不成功
{
//接收的資料為0 代表斷開連接配接了
if (client.ReceiveArgs.BytesTransferred==0)
{
//用戶端主動斷開連接配接
if (client.ReceiveArgs.SocketError==SocketError.Success)
{
DisConnected(client,"用戶端主動斷開連接配接");
}
else//因為網絡原因,被動斷開連接配接
{
DisConnected(client, e.SocketError.ToString());
}
}
}
}
//斷開連接配接
private void DisConnected(ClientPeer client,string reason)
{
try
{
if (client==null)
{
throw new Exception("用戶端為空,無法斷開連接配接!");
}
Console.WriteLine(client.clientSocket.RemoteEndPoint + "用戶端斷開連接配接,原因:" + reason);
//用戶端自己處理斷開連接配接
client.DisConnected();
clientPeerPool.Enqueue(client);//斷開連接配接的Client放到用戶端連接配接池中
semaphore.Release();//釋放一個Client
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
用戶端
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace MyServer
{
public class ClientPeer
{
public Socket clientSocket { get; set; }
public SocketAsyncEventArgs ReceiveArgs { get; set; }
public ClientPeer()
{
ReceiveArgs = new SocketAsyncEventArgs();
ReceiveArgs.UserToken = this;//綁定UserToken 通過UserToken獲得目前的ClientPeer
ReceiveArgs.SetBuffer(new byte[2048],0,2048);//設定資料緩沖區,資料緩沖區在使用時,需要進行設定
}
/// <summary>
/// 處理資料的接收
/// </summary>
/// <param name="packet"></param>
public void ProcessReceive(byte[] packet)
{
}
/// <summary>
/// 斷開連接配接
/// </summary>
public void DisConnected()
{
}
}
}
用戶端連接配接池
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyServer
{
public class ClientPeerPool
{
public Queue<ClientPeer> clientPeers;
public ClientPeerPool(int maxCount)
{
clientPeers = new Queue<ClientPeer>(maxCount);
}
public void Enqueue(ClientPeer client)
{
clientPeers.Enqueue(client);
}
public ClientPeer Dequeue()
{
return clientPeers.Dequeue();
}
}
}
構造類
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyServer
{
/// <summary>
/// 構造類,避免粘包問題
/// </summary>
public class EncodeTools
{
/// <summary>
/// 構造包 標頭加包尾
/// </summary>
public static byte[] EncodePacket(byte[] packet)
{
//using 不需要手動釋放 會自動釋放
using (MemoryStream ms = new MemoryStream())
{
using (BinaryWriter bw = new BinaryWriter(ms))
{
//寫入標頭(資料長度,前四個位元組)
bw.Write(packet.Length);
//寫入包尾(資料) 解析時:先解析出標頭 得到資料長度 資料長度在和剩下資料長度進行比較
bw.Write(packet);
byte[] data = new byte[ms.Length];//擷取所有寫入流中的總長度
//ms.GetBuffer(); 傳回流中所有建立的無符号位元組的數組
Buffer.BlockCopy(ms.GetBuffer(), 0, data, 0, (int)ms.Length);
return data;
}
}
}
/// <summary>
/// 解析包 這裡修改了緩沖區的位址,需要加引用參數ref
/// </summary>
public static byte[] DecodePacket(ref List<byte> cache)
{
if (cache.Count < 4)//前4位為資料長度,不夠4位傳回null
{
return null;
}
using (MemoryStream ms = new MemoryStream(cache.ToArray()))
{
using (BinaryReader br = new BinaryReader(ms))
{
int length = br.ReadInt32();//ReadInt32 讀取前四個位元組 得到資料長度
//Position擷取目前在流中的所在位置 ReadInt32讀取以後會讓遊标向後移4位 目前的總長度-減去遊标
//得到剩下的長度
int remainLength = (int)(ms.Length - ms.Position);
if (length > remainLength)
{
return null;
}
byte[] data = br.ReadBytes(length);//讀取一個數組
cache.Clear();//更新資料緩存
//得到讀取後的位置
int remainLengthAngin = (int)(ms.Length - ms.Position);
//把後面的資料填充到緩沖區 解析後的清除掉
cache.AddRange(br.ReadBytes(remainLengthAngin));
return data;
}
}
}
}
}
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPB50MBR1TwUEROBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL1UTN1EjMwQTM1ITMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
有需要學習視訊歡迎關注微信公衆号