开源Unity服务端客户端(双端C#)网络通讯框架(Lidgren)[二]ChatDemo之NetServer

开源Unity服务端客户端(双端C#)网络通讯框架(Lidgren)[二]ChatDemo

还有一点时间,把二也开个头

有服务端和客户端两部分,那先来看服务端,至于到底哪个先哪个后,其实我觉得还是先看服务端。

1.从服务端说起。

创建一个NetServer

创建一个socket需要一些最基础的配置,比如端口号、连接的客户端数等等。NetPeerConfiguration类就是用于配置参数。其中的字符串参数可理解为给它取个名字。就算是ip、port都正确,客户端叫不对名字服务器也不会相应。这样我们可以理解为游戏中常见的:

  • 聊天服务器
  • 战斗服务器
  • 各个副本服务器
NetServer s_server;//声明
NetPeerConfiguration config = new NetPeerConfiguration("chat");//创建一个网络配置类
config.MaximumConnections = 100;//最多有100个连接
			config.Port = 14242;//使用本地14242端口
			s_server = new NetServer(config);//使用配置文件类创建一个socket

2. 开启Server

public static void StartServer()
		{
			s_server.Start();
		}

3 监听消息(msg)

3.1 添加消息处理监听

Application.Idle += new EventHandler(Application_Idle);

3.2 Application_Idle 详解

3.2.1NativeMethods.AppStillIdle
while (NativeMethods.AppStillIdle)
{
	//code1
}

//---------------------------------------------
[StructLayout(LayoutKind.Sequential)]
		public struct PeekMsg
		{
			public IntPtr hWnd;
			public Message msg;
			public IntPtr wParam;
			public IntPtr lParam;
			public uint time;
			public System.Drawing.Point p;
		}
[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
		[DllImport("User32.dll", CharSet = CharSet.Auto)]
		public static extern bool PeekMessage(out PeekMsg msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);

		public static bool AppStillIdle
		{
			get
			{
				PeekMsg msg;
				return !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
			}
		}

PeekMessage(百科):Windows窗体程序或者说整个windows桌面应用程序,都是通过sendmessage这种方式搭建的。
PeekMessage:

  • 返回值表示:消息队列中是否存在消息;
  • 参数1(PeekMsg):获得一条消息(堆栈指针不移动。)
  • 其他参数:巴拉巴拉(看百科)…

3.1.2回归正题 消息解析
				//code1
				NetIncomingMessage im;//声明一条消息变量
				while ((im = s_server.ReadMessage()) != null)//读消息
				{
					// handle incoming message
					switch (im.MessageType)
					{
							//code2
					}
					s_server.Recycle(im);
				}
				Thread.Sleep(1);
		/// <summary>
		/// Read a pending message from any connection, if any
		/// 从等待队列中尝试读取一条消息(非阻塞)
		/// </summary>
		public NetIncomingMessage ReadMessage()
		{
			NetIncomingMessage retval;
			if (m_releasedIncomingMessages.TryDequeue(out retval))
			{
				if (retval.MessageType == NetIncomingMessageType.StatusChanged)
				{
					NetConnectionStatus status = (NetConnectionStatus)retval.PeekByte();
					retval.SenderConnection.m_visibleStatus = status;
				}
			}
			return retval;
		}

NetPeer.cs

从上面代码可以看出来ReadMessage方法是一个非阻塞的方法Thread.Sleep(1);是必要的。

读取消息的应该非为如下几步:

  • NetIncomingMessage im;//声明一个消息变量
  • while ((im = s_server.ReadMessage()) != null) //使用while循环读取当前队列中所有的消息
  • 在while循环中处理读到消息
  • s_server.Recycle(im);// 回收消息(特别棒的操作减少GC)
  • 如此往复以上内容

3.1.3处理消息
//code2
// handle incoming message
					switch (im.MessageType)
					{
						case NetIncomingMessageType.DebugMessage:
						case NetIncomingMessageType.ErrorMessage:
						case NetIncomingMessageType.WarningMessage:
						case NetIncomingMessageType.VerboseDebugMessage:
							string text = im.ReadString();
							Output(text);
							break;

						case NetIncomingMessageType.StatusChanged:
							NetConnectionStatus status = (NetConnectionStatus)im.ReadByte();

							string reason = im.ReadString();
							Output(NetUtility.ToHexString(im.SenderConnection.RemoteUniqueIdentifier) + " " + status + ": " + reason);

							if (status == NetConnectionStatus.Connected)
								Output("Remote hail: " + im.SenderConnection.RemoteHailMessage.ReadString());

							UpdateConnectionsList();
							break;
						case NetIncomingMessageType.Data:
							// incoming chat message from a client
							string chat = im.ReadString();

							Output("Broadcasting '" + chat + "'");

							// broadcast this to all connections, except sender
							List<NetConnection> all = s_server.Connections; // get copy
							all.Remove(im.SenderConnection);

							if (all.Count > 0)
							{
								NetOutgoingMessage om = s_server.CreateMessage();
								om.Write(NetUtility.ToHexString(im.SenderConnection.RemoteUniqueIdentifier) + " said: " + chat);
								s_server.SendMessage(om, all, NetDeliveryMethod.ReliableOrdered, 0);
							}
							break;
						default:
							Output("Unhandled type: " + im.MessageType + " " + im.LengthBytes + " bytes " + im.DeliveryMethod + "|" + im.SequenceChannel);
							break;
					}

借助一下API文档
NetIncomingMessageType

这里要着重理解的内容是:和平时直接使用socket不同,所有的消息都被封装。
例如:
StatusChanged就如它字面意思那样,有客户端连接变化时会受到消息。
Data:这里是程序的主要逻辑实现,需要自定义各种协议完成项目功能。

4.处理消息

4.1处理连接状态消息

							case NetIncomingMessageType.StatusChanged:
							NetConnectionStatus status = (NetConnectionStatus)im.ReadByte();

							string reason = im.ReadString();
							Output(NetUtility.ToHexString(im.SenderConnection.RemoteUniqueIdentifier) + " " + status + ": " + reason);

							if (status == NetConnectionStatus.Connected)
								Output("Remote hail: " + im.SenderConnection.RemoteHailMessage.ReadString());

							UpdateConnectionsList();
							break;
  • NetConnectionStatus 是表示连接状态的枚举,读取一个字节im.ReadByte()得到状态。
  • 获取状态发生改变的原因。string reason = im.ReadString();
  • 如果是新客户端加入if (status == NetConnectionStatus.Connected)
  • RemoteHailMessage表示远端打招呼消息 im.SenderConnection.RemoteHailMessage.ReadString()
  • 更新连接用户列表UpdateConnectionsList();

4.2 处理聊天消息

							case NetIncomingMessageType.Data:
							// incoming chat message from a client
							string chat = im.ReadString();

							Output("Broadcasting '" + chat + "'");

							// broadcast this to all connections, except sender
							List<NetConnection> all = s_server.Connections; // get copy
							all.Remove(im.SenderConnection);

							if (all.Count > 0)
							{
								NetOutgoingMessage om = s_server.CreateMessage();
								om.Write(NetUtility.ToHexString(im.SenderConnection.RemoteUniqueIdentifier) + " said: " + chat);
								s_server.SendMessage(om, all, NetDeliveryMethod.ReliableOrdered, 0);
							}
							break;
  • 收到A客户端发来的内容。string chat = im.ReadString();
  • 获取除A以外的客户端List all = s_server.Connections;all.Remove(im.SenderConnection);
  • 服务端创建一条外发消息 NetOutgoingMessage om = s_server.CreateMessage();
  • 外发消息中写入(A客户端唯一标识_said:_消息内容)om.Write(NetUtility.ToHexString(im.SenderConnection.RemoteUniqueIdentifier) + " said: " + chat);
  • 以顺序可靠的形式发送消息s_server.SendMessage(om, all, NetDeliveryMethod.ReliableOrdered, 0);

题外话(可略)

话题1.代码应该好看易读

看到这消息处理部分一层一层的嵌套,可能例子中看不出来
举例子还是那个StatusChanged(如下图),
示例只处理connected,实际应用中最少还需要处理disconnected。
Data中有自定义的各种消息逻辑那就更多
写在一个Switch中一定很长。

StatusChanged
话题2.为什么不用现成的PhotonServer+PUN2?

PhotonServer+PUN2和unity结合完美,使用简单,是一个非常好的客户端服务端框架。
没有记错的话,PhotonServer底层Socket使用C/C++编写,上层架构使用C#。
对于刚接触网络的同学们来说,PhotonServer完美的封装,只让开发者看到API并不知所以然。
更有可能没有基础的同学们,API中所说内容都不知道是什么,怎么使用。
总结就是:如果需要快速在项目中使用网络功能首选PhotonServer+PUN2。有时间研究的同学,可以使用Lidgren
一步一步完成属于自己的网络框架。

下一篇:ChatDemo之NetClient粗略带过

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zheenyuan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值