开源Unity服务端客户端(双端C#)网络通讯框架(Lidgren)[二]ChatDemo
还有一点时间,把二也开个头
有服务端和客户端两部分,那先来看服务端,至于到底哪个先哪个后,其实我觉得还是先看服务端。
1.从服务端说起。
创建一个NetServer
创建一个socket需要一些最基础的配置,比如端口号、连接的客户端数等等。NetPeerConfiguration类就是用于配置参数。其中的字符串参数可理解为给它取个名字。就算是ip、port都正确,客户端叫不对名字服务器也不会相应。这样我们可以理解为游戏中常见的:
NetServer s_server;
NetPeerConfiguration config = new NetPeerConfiguration("chat");
config.MaximumConnections = 100;
config.Port = 14242;
s_server = new NetServer(config);
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)
{
}
[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]
[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回归正题 消息解析
NetIncomingMessage im;
while ((im = s_server.ReadMessage()) != null)
{
switch (im.MessageType)
{
}
s_server.Recycle(im);
}
Thread.Sleep(1);
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处理消息
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:
string chat = im.ReadString();
Output("Broadcasting '" + chat + "'");
List<NetConnection> all = s_server.Connections;
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文档
这里要着重理解的内容是:和平时直接使用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:
string chat = im.ReadString();
Output("Broadcasting '" + chat + "'");
List<NetConnection> all = s_server.Connections;
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中一定很长。
话题2.为什么不用现成的PhotonServer+PUN2?
PhotonServer+PUN2和unity结合完美,使用简单,是一个非常好的客户端服务端框架。
没有记错的话,PhotonServer底层Socket使用C/C++编写,上层架构使用C#。
对于刚接触网络的同学们来说,PhotonServer完美的封装,只让开发者看到API并不知所以然。
更有可能没有基础的同学们,API中所说内容都不知道是什么,怎么使用。
总结就是:如果需要快速在项目中使用网络功能首选PhotonServer+PUN2。有时间研究的同学,可以使用Lidgren
一步一步完成属于自己的网络框架。
|