Silverlight MMORPG WebGame游戏设计(四)-----Client:Server!房子啥时候装好?我急嫁人啊!
上周有点杂事这篇文章就耽搁下来了。还有上篇文章涉及到我所在的“深蓝WPF/Silverlight群”里的“开心”的代码版权问题,去年我在网上搜到silverlight服务端的源代码,以此基础写了web传奇的服务端。由于不知道是“开心”的源码,还由于里面的bug,虽然我尽我的能力做了修正,还是有处明显的bug没有修正,所以我重写了服务端的代码,特在次感谢“开心”的开源精神,他鼓励我多写下文章。
废话不说了,我们言归正传了。上次说到client急着住新房和server百年好合,但好事多磨,这Server的房子装修不是一天两天,Client的嫁妆也不是很容易就弄好的,谁让这是个物质的社会呢!
Server就找来了装修包工头Socket,说:“你看我这毛坯房也拿到手了,两个仆人也雇来了,我们什么时候开工啊?”Socket说:“有钱好办事,你买本winSocket高级编程贿赂我下。”Server囧了:“房子首付都七姑八爷得借个遍,装修也是买血弄点钱,你就行行好,开工吧。”
Socket被磨得不行了,就开工了。Socket找了个徒弟叫做listener,让他负责房子装修的事.
这个listener不像以前的老工人,有事没事就跑个死循环,日复一日低效干活。

{
TcpListener listener = new TcpListener(nPortListen );
listener.Start();
do
{
byte [] myBuff = new byte[128];
if( listener.Pending() )
{
client = listener.AcceptSocket();
......
else
{
Thread.Sleep( 100 );
}
} while( true );
}
catch( Exception ex )
{
Console.WriteLine ( ex.Message );
}
大家可以看到以前的老工人只要没接到指令(!listener.Pending()),就去睡懒觉(Thread.Sleep( 100 ); ),搞得雇主火冒三丈,这一睡一醒多费事啊,耽搁时间,还搞得身心疲惫,处于假死状态。
这个新的工人listener比较能接受新事物,听说了有异步这个事。觉得很好,我不用全年无休,我动态上班,有活我立马干,没活我就做其他的事。

{
listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(new IPEndPoint(IPAddress.Any, Sport));
listener.Listen(100);
listener.BeginAccept(OnConnectRequest, listener);//当有客户端连接进入时,开始调用OnConnectRequest函数
}

{
Socket listener = (Socket)ar.AsyncState;
NewConnection(listener.EndAccept(ar));//接受这个连接发送的数据
listener.BeginAccept(new AsyncCallback(OnConnectRequest), listener);//数据接收完毕后,继续异步监听,等待下一次连接
}
大家仔细看OnConnectRequest函数,会发现他取代了do while 死循环,在函数最后又异步回调了自己,这样就保持了连续接收客户端连接的状态。
接收每一个客户端传来的数据在NewConnection函数里.

{
SetupRecieveCallback(clientSock);//接收到客户端数据时回调函数
ServerMessage ClientMessage = new ServerMessage();
ClientMessage.Socket = clientSock;
ui.Post(this.uiDisPlay.userEnter, ClientMessage);//通知界面线程有客户端连入
}
{
AsyncCallback recieveData = new AsyncCallback(OnRecievedData);//收到数据时回调数据处理函数
sock.BeginReceive(listenerMess.Buffer, 0, listenerMess.Buffer.Length, SocketFlags.None, out socketerror, recieveData, sock);//把接收到的数据预先分配给服务端byte[]来储存
CatchWithSocketError(socketerror, listenerMess);//处理接收数据时各种异常
}
小tips:这里没有用try catch处理异常,而是用的错误号来处理,这里的socketerror是输出参数,带出各种错误号。这样的好处是对错误号进行处理能比try catch减少系统资源消耗,大家都知道try catch是比较消耗资源的。

/// 根据错误号作出处理
/// </summary>
/// <param name="error"></param>
/// <param name="client"></param>
private void CatchWithSocketError(SocketError error, ServerMessage client)
{
#region 各种错误号
//AccessDenied已试图通过被其访问权限禁止的方式访问 Socket。
//ConnectionAborted此连接由 .NET Framework 或基础套接字提供程序中止。
// Disconnecting正常关机正在进行中。
//Fault 基础套接字提供程序检测到无效的指针地址。
//HostDown 由于远程主机被关闭,操作失败。
//HostNotFound 无法识别这种主机。该名称不是正式的主机名或别名。
//HostUnreachable 没有到指定主机的网络路由。
//InProgress 阻止操作正在进行中。
//Interrupted 已取消阻止 Socket 调用的操作。
//InvalidArgument 给 Socket 成员提供了一个无效参数。
//IOPending 应用程序已启动一个无法立即完成的重叠操作。
//IsConnected Socket 已连接。
//MessageSize 数据报太长。
//NetworkDown 网络不可用。
//NetworkReset 应用程序试图在已超时的连接上设置 KeepAlive。
//NetworkUnreachable 不存在到远程主机的路由。
//NoBufferSpaceAvailable 没有可用于 Socket 操作的可用缓冲区空间。
//NoData 在名称服务器上找不到请求的名称或 IP 地址。
//NoRecovery 错误不可恢复或找不到请求的数据库。
//NotConnected 应用程序试图发送或接收数据,但是 Socket 未连接。
//NotInitialized 尚未初始化基础套接字提供程序。
//NotSocket 对非套接字尝试 Socket 操作。
//OperationAborted 由于 Socket 已关闭,重叠的操作被中止。
//OperationNotSupported 协议族不支持地址族。
//ProcessLimit 正在使用基础套接字提供程序的进程过多。
//ProtocolFamilyNotSupported 未实现或未配置协议族。
//ProtocolNotSupported 未实现或未配置协议。
//ProtocolOption 对 Socket 使用了未知、无效或不受支持的选项或级别。
//ProtocolType 此 Socket 的协议类型不正确。
//Shutdown 发送或接收数据的请求未得到允许,因为 Socket 已被关闭。
//SocketError 发生了未指定的 Socket 错误。
//SocketNotSupported 在此地址族中不存在对指定的套接字类型的支持。
//Success Socket 操作成功。
//SystemNotReady 网络子系统不可用。
//TimedOut 连接尝试超时,或者连接的主机没有响应。
//TooManyOpenSockets 基础套接字提供程序中打开的套接字太多。
//TryAgain 无法解析主机名。请稍后重试。
//TypeNotFound 未找到指定的类。
//VersionNotSupported 基础套接字提供程序的版本超出范围。
//WouldBlock 对非阻止性套接字的操作不能立即完成。
#endregion
if (error == SocketError.Disconnecting || error == SocketError.Fault || error == SocketError.IsConnected || error == SocketError.SocketError)
{
client.Socket.Close();
ui.Post(this.uiDisPlay.delUser, client);
}
if (error == SocketError.MessageSize || error == SocketError.NotConnected || error == SocketError.ProcessLimit || error == SocketError.TooManyOpenSockets)
{
client.Socket.Close();
ui.Post(this.uiDisPlay.delUser, client);
}
}

{
Socket sock = (Socket)ar.AsyncState;
listenerMess.Socket = sock;
int nBytesRec = sock.EndReceive(ar,out socketerror);
CatchWithSocketError(socketerror, listenerMess);
if (nBytesRec > 0)
{
listenerMess.Stream.Write(listenerMess.Buffer, 0, nBytesRec);//把服务端byte[]中接收到的数据交给Stream类的write方法,写入到Stream类的buffer byte[]中。
//...对接收到的数据进行分析处理
}
else
{
sock.Shutdown(SocketShutdown.Both);
sock.Close();
}
}
以上代码没有用一个 do while 循环,也没有用一个try catch,就靠异步回调函数和错误号就完成了服务端接收数据的处理,listener干完这些活后,很得意,“知识就是力量啊!”。
listener还意犹未尽,换了一副画来纪念一下:
Server家装修进展还不错,client那里的嫁妆准备得如何呢,下一篇文章:
Silverlight MMORG WebGame游戏设计(五)-----Client的嫁妆
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库