使用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;
}
}
}
}
}
有需要学习视频欢迎关注微信公众号