赞
踩
.net有诸多版本,所以简单实现了Socket多线程的V1-Framework+Unity版本和V2-Core(Netty)+Unity版本
重点使用了Select{}这个特性
ServerSocket.cs
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
namespace SimServer.Net
{
public class ServerSocket : Singleton<ServerSocket>
{
//公钥
public static string PublicKey = "DongServer_PublicKey";
//密钥,后续可以随时间进行变化
public static string SecretKey = "DongServer_SecretKey";
#if DEBUG
private string m_IpStr = "127.0.0.1";
#else
//对应阿里云或腾讯云的 本地ip地址(不是公共ip地址)
private string m_IpStr = "172.45.756.54";
#endif
private const int m_Port = 8011;
public static long m_PingInterval = 30;
//服务器监听socket
private static Socket m_ListenSocket;
//临时保存所有socket的集合
private static List<Socket> m_CheckReadList = new List<Socket>();
//所有客户端的一个字典
public static Dictionary<Socket, ClientSocket> m_ClientDic = new Dictionary<Socket, ClientSocket>();
public static List<ClientSocket> m_TempList = new List<ClientSocket>();
public void Init()
{
IPAddress ip = IPAddress.Parse(m_IpStr);
IPEndPoint ipEndPoint = new IPEndPoint(ip, m_Port);
m_ListenSocket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
m_ListenSocket.Bind(ipEndPoint);
m_ListenSocket.Listen(50000);
Debug.LogInfo("服务器启动监听{0}成功", m_ListenSocket.LocalEndPoint.ToString());
while (true)
{
//检查是否有读取的socket
//处理找出所有socket
ResetCheckRead();
try
{
//最后等待时间单位是微妙
Socket.Select(m_CheckReadList, null, null, 1000);
}
catch (Exception e)
{
Debug.LogError(e.ToString());
}
for (int i = m_CheckReadList.Count - 1; i >= 0; i--)
{
Socket s = m_CheckReadList[i];
if (s == m_ListenSocket)
{
//说明有客户端链接到服务器了,所以服务器socket可读
ReadListen(s);
}
else
{
//说明链接的客户端可读,证明有信息传上来了
ReadClient(s);
}
}
//检测是否心跳包超时的计算
long timeNow = GetTimeStamp();
m_TempList.Clear();
foreach (ClientSocket clientSocket in m_ClientDic.Values)
{
if (timeNow - clientSocket.LastPingTime > m_PingInterval * 4)
{
Debug.Log("Ping Close" + clientSocket.Socket.RemoteEndPoint.ToString());
m_TempList.Add(clientSocket);
}
}
foreach (ClientSocket clientSocket in m_TempList)
{
CloseClient(clientSocket);
}
m_TempList.Clear();
}
}
public void ResetCheckRead()
{
m_CheckReadList.Clear();
m_CheckReadList.Add(m_ListenSocket);
foreach (Socket s in m_ClientDic.Keys)
{
m_CheckReadList.Add(s);
}
}
void ReadListen(Socket listen)
{
try
{
Socket client = listen.Accept();
ClientSocket clientSocket = new ClientSocket();
clientSocket.Socket = client;
clientSocket.LastPingTime = GetTimeStamp();
m_ClientDic.Add(client, clientSocket);
Debug.Log("一个客户端链接:{0},当前{1}个客户端在线!",client.LocalEndPoint.ToString(),m_ClientDic.Count);
}
catch (SocketException ex)
{
Debug.LogError("Accept fali:" + ex.ToString());
}
}
void ReadClient(Socket client)
{
ClientSocket clientSocket = m_ClientDic[client];
ByteArray readBuff = clientSocket.ReadBuff;
//接受信息,根据信息解析协议,根据协议内容处理消息再下发到客户端
int count = 0;
//如果上一次接收数据刚好占满了1024的数组,
if (readBuff.Remain <= 0)
{
//数据移动到index =0 位置。
OnReceiveData(clientSocket);
readBuff.CheckAndMoveBytes();
//保证到如果数据长度大于默认长度,扩充数据长度,保证信息的正常接收
while (readBuff.Remain <= 0)
{
int expandSize = readBuff.Length < ByteArray.DEFAULT_SIZE ? ByteArray.DEFAULT_SIZE : readBuff.Length;
readBuff.ReSize(expandSize * 2);
}
}
try
{
count = client.Receive(readBuff.Bytes, readBuff.WriteIdx, readBuff.Remain, 0);
}
catch (SocketException ex)
{
Debug.LogError("Receive fali:" + ex);
CloseClient(clientSocket);
return;
}
//代表客户端断开链接了
if (count <= 0)
{
CloseClient(clientSocket);
return;
}
readBuff.WriteIdx += count;
//解析我们的信息
OnReceiveData(clientSocket);
readBuff.CheckAndMoveBytes();
}
/// <summary>
/// 接收数据处理
/// </summary>
/// <param name="clientSocket"></param>
void OnReceiveData(ClientSocket clientSocket)
{
ByteArray readbuff = clientSocket.ReadBuff;
//基本消息长度判断
if (readbuff.Length <= 4 || readbuff.ReadIdx < 0)
{
return;
}
int readIdx = readbuff.ReadIdx;
byte[] bytes = readbuff.Bytes;
int bodyLength = BitConverter.ToInt32(bytes, readIdx);
//判断接收到的信息长度是否小于包体长度+包体头长度,如果小于,代表我们的信息不全,大于代表信息全了(有可能有粘包存在)
if (readbuff.Length < bodyLength + 4)
{
return;
}
readbuff.ReadIdx += 4;
//解析协议名
int nameCount = 0;
ProtocolEnum proto = ProtocolEnum.None;
try
{
proto = MsgBase.DecodeName(readbuff.Bytes, readbuff.ReadIdx, out nameCount);
}
catch (Exception ex)
{
Debug.LogError("解析协议名出错:" + ex);
CloseClient(clientSocket);
return;
}
if (proto == ProtocolEnum.None)
{
Debug.LogError("OnReceiveData MsgBase.DecodeName fail");
CloseClient(clientSocket);
return;
}
readbuff.ReadIdx += nameCount;
//解析协议体
int bodyCount = bodyLength - nameCount;
MsgBase msgBase = null;
try
{
msgBase = MsgBase.Decode(proto, readbuff.Bytes, readbuff.ReadIdx, bodyCount);
if (msgBase == null)
{
Debug.LogError("{0}协议内容解析错误:" ,proto.ToString());
CloseClient(clientSocket);
return;
}
}
catch (Exception ex)
{
Debug.LogError("接收数据协议内容解析错误:" + ex);
CloseClient(clientSocket);
return;
}
readbuff.ReadIdx += bodyCount;
readbuff.CheckAndMoveBytes();
//通过反射分发消息
MethodInfo mi = typeof(MsgHandler).GetMethod(proto.ToString());
object[] o = { clientSocket, msgBase };
if (mi != null)
{
mi.Invoke(null, o);
}
else
{
Debug.LogError("OnReceiveData Invoke fail:" + proto.ToString());
}
//继续读取消息
if (readbuff.Length > 4)
{
OnReceiveData(clientSocket);
}
}
/// <summary>
/// 发送数据
/// </summary>
/// <param name="cs"></param>
/// <param name="msgBase"></param>
public static void Send(ClientSocket cs, MsgBase msgBase)
{
if (cs == null || !cs.Socket.Connected)
{
return;
}
try
{
//分为三部分,头:总协议长度;名字;协议内容。
byte[] nameBytes = MsgBase.EncodeName(msgBase);
byte[] bodyBytes = MsgBase.Encond(msgBase);
int len = nameBytes.Length + bodyBytes.Length;
byte[] byteHead = BitConverter.GetBytes(len);
byte[] sendBytes = new byte[byteHead.Length + len];
Array.Copy(byteHead, 0, sendBytes, 0, byteHead.Length);
Array.Copy(nameBytes, 0, sendBytes, byteHead.Length, nameBytes.Length);
Array.Copy(bodyBytes, 0, sendBytes, byteHead.Length + nameBytes.Length,bodyBytes.Length);
try
{
cs.Socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, null, null);
}
catch(SocketException ex)
{
Debug.LogError("Socket BeginSend Error:" + ex);
}
}
catch(SocketException ex)
{
Debug.LogError("Socket发送数据失败:" + ex);
}
}
public void CloseClient(ClientSocket client)
{
client.Socket.Close();
m_ClientDic.Remove(client.Socket);
Debug.Log("一个客户端断开链接,当前总连接数:{0}", m_ClientDic.Count);
}
public static long GetTimeStamp()
{
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(ts.TotalSeconds);
}
}
}
log4net.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="System.Configuration.IgnoreSectionHandler"/>
</configSections>
<log4net>
<root>
<level value="All"/>
<appender-ref ref="RollingLogFileAppender"/>
<appender-ref ref="ConsoleAppender"/>
</root>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="log\logfile.log"/>
<appendToFile value="true"/>
<Encoding value="UTF-8" />
<!--按日期产生文件夹,文件名[在日期方式与混合方式下使用]日志文件名格式为:2008-08-31.log -->
<param name= "DatePattern" value= "yyyy-MM-dd".log""/>
<!--log保留天数-->
<param name= "MaxSizeRollBackups" value= "10"/>
<!--日志文件名是否是固定不变的(是否只写到一个文件中)-->
<param name= "StaticLogFileName" value= "false"/>
<!--按照何种方式产生多个日志文件(日期[Date],文件大小[Size],混合[Composite])-->
<param name="RollingStyle" value="Date" />
<!--每个文件的大小。只在混合方式与文件大小方式下使用,超出大小的在文件名后自动增加1重新命名-->
<param name="maximumFileSize" value="500KB" />
<layout type="log4net.Layout.PatternLayout">
<!--
%d, %date :表示当然的时间
%p, %level :表示日志的级别
%c, %logger :表示日志产生的主题或名称,通常是所在的类名,便于定位问题
%m, %message :表示日志的具体内容
%n, %newline :换行
%exception :表示异常信息
-->
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m %logger %n" />
</layout>
</appender>
<!-- 控制台显示日志 -->
<appender name="ConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
<!-- 设置不同级别控制台显示的不同颜色 -->
<mapping>
<level value="INFO" />
<foreColor value="Green" />
</mapping>
<mapping>
<level value="DEBUG" />
<foreColor value="White" />
</mapping>
<mapping>
<level value="WARN" />
<foreColor value="Yellow" />
</mapping>
<mapping>
<level value="ERROR" />
<foreColor value="Red, HighIntensity" />
</mapping>
<!-- 记录的格式。 -->
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{HH:mm:ss,fff} [%-5level] %m %n" />
</layout>
<!--
过滤器type有如下几种类型
log4net.Filter.DenyAllFilter 丢弃所有日志事件。
log4net.Filter.LevelMatchFilter 准确匹配事件等级。
log4net.Filter.LevelRangeFilter 匹配一个范围的等级。
log4net.Filter.LoggerMatchFilter 匹配一个日志器名字的开始。
log4net.Filter.PropertyFilter 匹配指定属性名称的子字符串。
log4net.Filter.StringMatchFilter 匹配事件消息的子字符串。
-->
<filter type="log4net.Filter.LevelRangeFilter">
<!-- 控制输出日志的级别范围 -->
<param name="LevelMin" value="DEBUG" />
<param name="LevelMax" value="Error" />
</filter>
</appender>
</log4net>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
</startup>
</configuration>
设置始终复制
AssemblyInfo.cs增加代码使Log4Net生效
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", ConfigFileExtension = "config", Watch = true)]
手写了一个ByteArray.cs(收发[]byte)
using System;
public class ByteArray
{
//默认大小
public const int DEFAULT_SIZE = 1024;
//初始大小
private int m_InitSize = 0;
//缓冲区
public byte[] Bytes;
//读写位置, ReadIdx = 开始读的索引,WriteIdx = 已经写入的索引
public int ReadIdx = 0;
public int WriteIdx = 0;
//容量
private int Capacity = 0;
//剩余空间
public int Remain { get { return Capacity - WriteIdx; } }
//数据长度
public int Length { get { return WriteIdx - ReadIdx; } }
public ByteArray()
{
Bytes = new byte[DEFAULT_SIZE];
Capacity = DEFAULT_SIZE;
m_InitSize = DEFAULT_SIZE;
ReadIdx = 0;
WriteIdx = 0;
}
public ByteArray(byte[] dafalutBytes)
{
Bytes = dafalutBytes;
Capacity = dafalutBytes.Length;
m_InitSize = dafalutBytes.Length;
ReadIdx = 0;
WriteIdx = dafalutBytes.Length;
}
/// <summary>
/// 检测并移动数据
/// </summary>
public void CheckAndMoveBytes()
{
if (Length < 8)
{
MoveBytes();
}
}
/// <summary>
/// 移动数据
/// </summary>
public void MoveBytes()
{
if (ReadIdx < 0)
return;
Array.Copy(Bytes, ReadIdx, Bytes, 0, Length);
WriteIdx = Length;
ReadIdx = 0;
}
/// <summary>
/// 重设尺寸
/// </summary>
/// <param name="size"></param>
public void ReSize(int size)
{
if (ReadIdx < 0) return;
if (size < Length) return;
if (size < m_InitSize) return;
int n = 1024;
while (n < size) n *= 2;
Capacity = n;
byte[] newBytes = new byte[Capacity];
Array.Copy(Bytes, ReadIdx, newBytes, 0, Length);
Bytes = newBytes;
WriteIdx = Length;
ReadIdx = 0;
}
}
MsgBase.cs 封装了proto加密解密
using ProtoBuf;
using System;
using System.IO;
using UnityEngine;
public class MsgBase
{
public virtual ProtocolEnum ProtoType { get; set; }
/// <summary>
/// 编码协议名
/// </summary>
/// <param name="msgBase"></param>
/// <returns></returns>
public static byte[] EncodeName(MsgBase msgBase)
{
byte[] nameBytes = System.Text.Encoding.UTF8.GetBytes(msgBase.ProtoType.ToString());
Int16 len = (Int16)nameBytes.Length;
byte[] bytes = new byte[2 + len];
bytes[0] = (byte)(len % 256);
bytes[1] = (byte)(len / 256);
Array.Copy(nameBytes, 0, bytes, 2, len);
return bytes;
}
/// <summary>
/// 解码协议名
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static ProtocolEnum DecodeName(byte[] bytes, int offset, out int count)
{
count = 0;
if (offset + 2 > bytes.Length) return ProtocolEnum.None;
Int16 len = (Int16)((bytes[offset + 1] << 8) | bytes[offset]);
if (offset + 2 + len > bytes.Length) return ProtocolEnum.None;
count = 2 + len;
try
{
string name = System.Text.Encoding.UTF8.GetString(bytes, offset + 2, len);
return (ProtocolEnum)System.Enum.Parse(typeof(ProtocolEnum), name);
}
catch (Exception ex)
{
Debug.LogError("不存在的协议:" + ex.ToString());
return ProtocolEnum.None;
}
}
/// <summary>
/// 协议序列化及加密
/// </summary>
/// <param name="msgBase"></param>
/// <returns></returns>
public static byte[] Encode(MsgBase msgBase)
{
using (var memory = new MemoryStream())
{
//将我们的协议类进行序列化转换成数组
Serializer.Serialize(memory, msgBase);
byte[] bytes = memory.ToArray();
return bytes;
}
}
/// <summary>
/// 协议解密
/// </summary>
/// <param name="protocol"></param>
/// <param name="bytes"></param>
/// <param name="offset"></param>
/// <param name="count"></param>
/// <returns></returns>
public static MsgBase Decode(ProtocolEnum protocol, byte[] bytes, int offset, int count)
{
if (count <= 0)
{
Debug.LogError("协议解密出错,数据长度为0");
return null;
}
try
{
byte[] newBytes = new byte[count];
Array.Copy(bytes, offset, newBytes, 0, count);
using (var memory = new MemoryStream(newBytes, 0, newBytes.Length))
{
Type t = System.Type.GetType(protocol.ToString());
return (MsgBase)Serializer.NonGeneric.Deserialize(t, memory);
}
}
catch(Exception ex)
{
Debug.LogError("协议解密出错:" + ex.ToString());
return null;
}
}
}
netty中重点使用了ChannelRead
ServerHandler.cs
namespace TcpServer
{
using System;
using System.Linq;
using System.Text;
using DotNetty.Buffers;
using DotNetty.Transport.Channels;
public class EchoServerHandler : ChannelHandlerAdapter
{
public override void ChannelActive(IChannelHandlerContext context)
{
}
public override void ChannelRead(IChannelHandlerContext context, object message)
{
var buffer = message as IByteBuffer;
if (buffer != null)
{
Console.WriteLine("Received from client: " + buffer.ToString(Encoding.UTF8));
}
ReceiveFromClient(context, buffer);
}
public override void ChannelInactive(IChannelHandlerContext context)
{
context.CloseAsync();
}
public override void ChannelReadComplete(IChannelHandlerContext context)
{
// => context.Flush();
}
public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
{
Console.WriteLine("Exception: " + exception);
context.CloseAsync();
}
/// <summary>
/// 发送客户端string
/// </summary>
/// <param name="context"></param>
/// <param name="msg"></param>
void SendToClient(IChannelHandlerContext context,string msg) {
var buffer = ByteBufferUtil.EncodeString(ByteBufferUtil.DefaultAllocator,msg,Encoding.UTF8);
if (buffer != null) {
context.Channel.WriteAndFlushAsync(buffer);
Debug.Instance.LogError("发送消息:" + msg.ToString());
}
}
/// <summary>
/// 发送消息 MsgTest data = new MsgTest();data.RecContent = "这是1个Server回复";
/// </summary>
/// <param name="context"></param>
/// <param name="msg"></param>
void SendToClient(IChannelHandlerContext context, MsgBase msg)
{
byte[] nameBytes = MsgBase.EncodeName(msg);
byte[] bodyBytes = MsgBase.Encode(msg);
byte[] dataBuff = nameBytes.Concat(bodyBytes).ToArray();
IByteBuffer buffer = Unpooled.WrappedBuffer(dataBuff);
context.Channel.WriteAndFlushAsync(buffer);
Debug.Instance.LogError("发送消息:"+msg.ToString());
}
void ReceiveFromClient(IChannelHandlerContext context, IByteBuffer buffer) {
byte[] bytes = new byte[buffer.ReadableBytes];
buffer.ReadBytes(bytes);
int nameCount = 0;
int readIdx = 0;
ProtocolEnum protocol = MsgBase.DecodeName(bytes, readIdx, out nameCount);
if (protocol == ProtocolEnum.None)
{
string s = buffer.ToString(Encoding.UTF8);
Debug.Instance.LogError("收到消息:"+s);
Debug.Instance.LogError("ReceiveFromClient DecodeName Fail");
context.CloseAsync();
return;
}
readIdx += nameCount;
MsgBase msgBase = MsgBase.Decode(protocol, bytes, readIdx, bytes.Length - readIdx);
if (msgBase == null)
{
Debug.Instance.LogError("ReceiveFromClient Decode Fail");
context.CloseAsync();
return;
}
if (msgBase is MsgCSharp)
{
var msg = msgBase as MsgCSharp;
msg.RecContent = "我是3!-server";
SendToClient(context, msgBase);
}
else if (msgBase is MsgLua)
{
var msg = msgBase as MsgLua;
Debug.Instance.Log(msg.LuaJson);
}
else {
// TODO 其他类型支持
}
}
}
}
因为Netty配置的大小端问题,我统一设置为小端,处理Unity相关的与V1有部分不同
public void SendMessage(MsgBase msgBase)
{
if (m_Socket == null || !m_Socket.Connected)
{
return;
}
if (m_Connecting)
{
Debug.LogError("正在链接服务器中,无法发送消息!");
return;
}
if (m_Closing)
{
Debug.LogError("正在关闭链接中,无法发送消息!");
return;
}
try
{
byte[] nameBytes = MsgBase.EncodeName(msgBase);
byte[] bodyBytes = MsgBase.Encode(msgBase);
byte[] dataBuff = nameBytes.Concat(bodyBytes).ToArray();
byte[] lenBuff = BitConverter.GetBytes((ushort) dataBuff.Length);
if (!BitConverter.IsLittleEndian)
{
lenBuff.Reverse();
}
byte[] sendBytes = lenBuff.Concat(dataBuff).ToArray();
ByteArray ba = new ByteArray(sendBytes);
int count = 0;
lock (m_WriteQueue)
{
m_WriteQueue.Enqueue(ba);
count = m_WriteQueue.Count;
}
if (count == 1)
{
m_Socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallBack, m_Socket);
}
}
catch (Exception ex)
{
Debug.LogError("SendMessage error:" + ex.ToString());
Close();
}
}
void OnReceiveData()
{
if (m_ReadBuff.Length <= 2 || m_ReadBuff.ReadIdx < 0)
return;
int readIdx = m_ReadBuff.ReadIdx;
byte[] bytes = m_ReadBuff.Bytes;
//读取协议长度之后进行判断,如果消息长度小于读出来的消息长度,证明是没有一条完整的数据
int bodyLength = BitConverter.ToUInt16(bytes, readIdx);
if (m_ReadBuff.Length < bodyLength + 2)
{
return;
}
string msg = Encoding.UTF8.GetString(m_ReadBuff.Bytes, 2, m_ReadBuff.WriteIdx);
Debug.LogError("收到数据:"+msg);
m_ReadBuff.ReadIdx += 2;
int nameCount = 0;
ProtocolEnum protocol = MsgBase.DecodeName(m_ReadBuff.Bytes, m_ReadBuff.ReadIdx, out nameCount);
if (protocol == ProtocolEnum.None)
{
Debug.LogError("OnReceiveData MsgBase.DecodeName fail");
Close();
return;
}
m_ReadBuff.ReadIdx += nameCount;
//解析协议体
int bodyCount = bodyLength - nameCount;
try
{
MsgBase msgBase = MsgBase.Decode(protocol, m_ReadBuff.Bytes, m_ReadBuff.ReadIdx, bodyCount);
if (msgBase == null)
{
Debug.LogError("接受数据协议内容解析出错");
Close();
return;
}
m_ReadBuff.ReadIdx += bodyCount;
m_ReadBuff.CheckAndMoveBytes();
//协议具体的操作
lock (m_MsgList)
{
m_MsgList.Add(msgBase);
Debug.Log(msgBase.ToString());
}
m_MsgCount++;
//处理粘包
if (m_ReadBuff.Length > 2)
{
OnReceiveData();
}
}
catch (Exception ex)
{
Debug.LogError("Socket OnReceiveData error:" + ex.ToString());
Close();
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。