首    页 界面/窗口 网络/通讯 数据库 组件开发 图像/多媒体 NET/Web 其它技术 源码下载 资料下载 软件共享 软件外包 曲艺杂谈
栏目导航:  首    页  |  网络/通讯  |  棋牌游戏


网络棋牌游戏系统架构篇【附演示+代码+全套图】~~极力推荐C#程序员阅读


原作者:不详    源出处:博客园    发布者:施昌权    发布类型:转载    发布日期:2009-03-21



源链接:http://www.cnblogs.com/yibinboy/articles/1236260.html  

老温评语:在我写这篇文章之前,我的心情比较沉重,不知道会不会影响我现在的写作情绪,之前由于我发表了几篇文章而大受欢迎同时也有网友
对我进行炮轰,认为我是在炒作.我真的感到非常悲哀,我真的是撞猪上了;而且给Brain看到了顺便笑了我一把!苍天啊,上帝啊,佛祖啊.但话又说回
来没有风雨怎么能见彩虹,一定要保持乐观精神,引用伟大领袖毛主席的话:一切反动派都是纸老虎!我相信只要用事实说话,那么是金子就一定
会发光,黑暗是掩盖不了它的.也有些“高手”指明我的文章没有技术含量,的确我自认为相比其他而言是没有一点技术含量,难道我有在强奸ing你
的眼睛吗,你为什么看在眼里都不舒服?是什么驱使你一颗丑陋的心在吞吐恶臭的气体而影响它人的健康呼吸与成长,您犯不着和我这无名小卒
在呕气呀,根本不值得!每个人都有属于自己的一片天空,人不犯我我不犯人,人诺犯我,我必宰了你(男人要对自己狠点)。

上次的文章<地图编辑器>篇受到了如此众多朋友的青睐,我在这儿有必要再作一次说明:该地图编辑器的确像某些人所说的没有任何技术含量,
无非就是用GDI+结合一些矩阵知识及数学公式等进行地图几个逻辑坐标世界坐标和物理坐标的转换及图形计算罢了,我所做的就是把C++下面的
工程转为C#工程,让其评测C#的力量,至于不能开源的原因也就在此,博客园的任何一个程序员都可以实现它,那篇我只是展示并不是一篇技术
文献,有机会我将会重新组织一篇讲述如何实现它,以及其中的算法!我还是那句话:我没有在强奸ing你的眼睛和眼镜,你任为不适合你,完全
可以不理会我,但是这并不代表其它人的想法呀~OK,止住~~多说有害,整就一个娘们!而现在面对博客园这样的巨大压力之下,关系到我在博客园
的发展,我最后决定我还是有必要把我之前一直想要发表的文章继续发布下去,不能因为某些个人行为而阻碍了我们千千万万的求知者.最重要的是我
们一起交流学习!如果您认为我是在作秀,或者作为我是在炒作那么我想我的全身每个器官都不欢迎您,博客园所有求知者也不欢迎您,就此打住,好吗?
算我老温恳请您了!!.好了我尽在讲废话,走自己的路写自己的代码和大家一块学习.切入正点:这篇文章主要是和大家一起来谈谈如何用C#架构网络
棋牌游戏系统,包括客户端、服务端、游戏逻辑判断、图形处理、棋牌游戏规则、传输安全等等问题,其中我还会和大家一起展示我的一个DEMO,老
温我大约足足花了数十个通宵才搞定了该项目,使工程变牵成C#平台,再次体验我甜心(C#)的锐利.希望您阅读本文后可以进一步提高对C#的热情,
用它来实现自己的梦想.知识是通用的,不管用什么语言最重要的是实现它的方法与手段!

撰写本文的目的:希望有更多的爱好者加入C#开发行列,更重要的是让已经把C#作为开发工具的人对C#进一步提升FANS热情包括我自己,不要质疑它
的威力,同时也给博客园注入一股新的C#异形生命力!让我们一起扣开棋牌游戏之门,我在此顺便再次感谢dudu和博客园的朋友们能够支持我的文章,
其实是大家共同学习罢了,忠心的祝福你们,天下最可敬的就是求知者和你们!.

好了,各位朋友坐好了,您到火星的旅程即将出发,请绑好安全带!!!!(~!~!~!~星际之旅?老温到底要干啥……..)

我为什么要撰写这篇文章:

我相信不仅仅是我,你也和我一样拥有同一个梦想,一个非常想实现它而又胆怯的想法,那就是想写一套网络棋牌游戏系统,对,就是它,用C#来实
现!现在网络上多火爆呀而且对于这类项目一直是DELPHI,C++的专属领域。因此你的人脑(不叫大脑,那是对动物的叫法)会有所顾忌,那么我在
这儿可以很明确的告诉你我们用C#完全可以实现,要是实现不了你把我老温拉出去凌迟处死!!不信你往下看,一定会读到你在博客园中很难获取的
知识,保证让你大开眼界,接下来我们会一起交流如何用C#处理这类系统,让你更加得到老板的赏识,更重要的是你已经学会如何开发这类项目了。
在我写这篇文章之前我搜索了一些资料,关于这类系统的开发文章几乎少的令人抽涕,更不用说用C#来实现了,那么我写这篇文章将更加具有实际意
义!希望也给刚刚入行的朋友作为一个参考对象,少走弯路; 下面我就抖胆献丑了,不管你干不干还是有没有兴趣你已经起程了~~~往火星飞啦!

什么是游戏?

其实,游戏这个词的历史远远比电子游戏程序要早得多,早在宋·《教战守》一文中,苏轼就有“游戏酒食”一词,本意为“嬉戏”的意思。而在电子游戏
程序,没有出现时,游戏一词已经被人们广为使用了,象是“幼儿园老师:‘小朋友,今天我们来做个游戏,好不好?’”或是“我们来做个小小的智力游
戏?”之类的。说白了就是玩,游戏就是玩,玩了玩了就上瘾了;我很不情愿的叫它Game。

什么是网络棋牌游戏?

首先我们来谈谈传统的棋牌室,什么是棋牌室?我想大家都可以在自己所在的城市中看到某某棋牌室,从字面上就可以看出它是供人打牌、下棋娱乐的
地方,那么它的结构是怎么样的呢?首先你要到一家棋牌室,然后它里面有好几个房间,有一楼前屋,后屋,二楼前屋后屋,你首先就是选择一个屋,
因为不同的屋会玩不同的牌,比如一个屋专门用来打麻将的,一个屋专门用来下棋的,所以你要选择一个屋依自己今天想玩什么而决定。然后你进入这
个屋后里面可能有好几桌,那么你就会选择一桌,或者在旁边观看! 这就是传统的棋牌室结构。OK,做稳了,我马上就要启动亚光速引擎了,飞入时
空隧道…….网络中的棋牌系统与传统的棋牌室是很相似的,首先我们要有大厅就是一个总的棋牌室,然后大厅里有很多个房间,每个房间都是专供某种
娱乐项目的,然后这个房间里会有很多桌子。了解了这种结构之后那么我们对这种系统体系就有了初步的认识。

接下来我们就一起层层划分网络棋牌游戏系统模块,逐一击迫,胜利就在眼前!

 

我们先从实质性的层次划分它,首先要有玩家,那么我们就设计玩家的一个结构,先打住~~STOP,亚光速引擎先停止!!! 其实我们所做的工作就是把
传统的对象实体设计成计算机面向对象的实体,然后给这些实体一些操作方法,就这么简单,不要被那些软文给讲蒙了,就是这么一回事!啥单件模式,
三层模式,观察者模式最终都离不开这个出发点,OK,继续启动~~ 首先我们肯定要有玩家对吧,没有玩家咱玩呀~那么我们就先设计一个玩家类,如下:

Class 或 Struct 都可以,如果是Class最好加上抽象(abstract)声明至于为什么要加你问别人吧,我在使用C++的时候都被C#的完全面向对象所吸引,真是我的甜
心! 然后我们给这个类赋一些属性,天呐,C#下面实现这些属性太棒了get 和 set 以及那么成熟高级的特性(什么你不会设计这样一个类,那么就此打住先
收藏本文学习这部分知识之后再来阅读)不像C++下面的那样古老级的做法,不过你可不要忘记哦一切源自C++的设计灵感才会有现在的C#~. 我称C++可是
杀手锏~~~~


 

游戏玩家类

{

属性:

   玩家在系统的唯一标识

   玩家的帐户名

   玩家的昵称

   玩家的积分

   玩家的注册时间

   玩家的当前登录时间

   玩家的当前登录IP

   玩家的等级

   玩家的信誉度

   是男是女还是人妖

   是不是喜欢老温

   ……..略

}

 这儿讲句题外话,你在设计属性时,对Set操作时一定要进行判断规则,这样才能从基层保证了数据是OK的,合法的,比如玩家的等级不可能
小于0标识,最好做个判断,玩家的金币不可能超出多少钱做个判断,如果不符合就抛出异常,我好像从哪本EFFCT C++啥中看到的,英文单
词肯定打错啦,管它个屁,学到的知识就是我的!

具体有哪些属性要看你自己的发挥了,要知道这一切都是由你来决定,没有人能够阻止你!切记,自由很重要,人类为了追求自由流了多少鲜血呀!
我怎么竟讲蠢话。上面的这个类就是一个纯的玩家实体类,没有啥功能,就是一个概念级的东西描述符罢了。也就是一个玩家就有这些特性,够简单
吧!好了,这是一个玩家,那么我要是有上百个玩家进行管理呢,怎么组织它们,瞧,数据结构发挥了它魅力的部分了,太帅了。 你可以自己写一个
线性链表进行组织它们,一个类就代表一个玩家,一个个玩家链在一起就方便我们程序员管理它们了,再次感谢微软,感谢老盖,C#下面实现了这些数
据结构类,什么你不知道,LIST类总知道吧,连排序方法都帮你搞定了。打个比方,我们要把用户一个个组织串起来,然后就依次对它取一个出来操
作或遍历它或删除它等。也就是玩家退出大厅和登录大厅这样的情况下的操作罢了。

我们有了一个玩家类还不够,我们得给它生命,让他学会叫,学会叛逆才行。那么这一切工作就需要我们编写一个操作它的类,我就叫它逻辑类(生命
类)吧,我又要告诉您,不要在乎别人怎么讲,游戏设计是很自由的,这个世界属于你自己的! 由这个逻辑类负责给这个玩家实体类产生生命的源泉!
嘿它是单性繁殖的。

在这儿我有必要提出另一种设计方法:我一开始还是比较倾向把这些实现逻辑部分的代码放进这个实体类本身,因为按照面向对象的原则性是这样
做的,一只猫,一只狗都有自己的属性而且都有自己的方法,所以它是一体的!但是从另一层次讲可以把这些叫方法独立开来,让一只猫就是一只
猫,需要叫,需要吃都由另外负责类处理!这一点我在WEB应用程序中一直这么做! 其实你在明白这些道理之后你就会对啥工厂模式啦,啥三层模
式啦都明白啦,就是那个味,只是给它套上了一个专业术语,有些还是啥英文的,奶奶的咱们中国民族的文化可比你们强多了,啥哈日族哈美族哈韩
族统统见鬼去吧~瞧你 那孙样留着金毛~ 可惜我们现在所学的知识仍然完全是由老外控制的,哎~~讲这话我腰酸呀!

不好意思,我忘了启动星际之亚光速引擎了,HOHOHOHO………….狂飞向火星!

那么这个负责处理玩家的类需要哪些方法呢?

玩家需要登录就有一个 登录方法();玩家需要退出游戏 就需要一个 退出方法();哦我的天呐,有一次我在我的项目中偿试我的方法都用汉字
来定义差点被团友给扔出去(你不会不知道C#是可以用汉字来命名的吧)。如果想要得到一位玩家资料就有一个获取方法();删除玩家方法();给
玩家发送通知方法();让玩家强制下线方法();玩家注册方法() 等等;发挥你的想象力,但是一定要拥有一个原则,它是围绕玩家类来实现的。
这一切都会在你设计你的项目时就已经很清晰的画出来了,要不然你就哭吧,痛快的哭吧!

好的,亲爱的旅客我们已经离开了地球,坐好了!我会开启双核亚光速引擎,速度提高双倍!尽可能快的到达火星; 听人家说美国的的火星探车程序
用JAVA编写用来通讯的!

经过以上部分的阅读,我们已经初步有了一个概念,那么接下来就是很重要的部分,我们来分析一下这种游戏的总体架构模型,我认为很重要,不知
道你认为重要不重要,重不重要得看了才知道哟!Let’s GO!

我将这个系统划分为这几个重要组成部分:

1) 客户端大厅程序

2) 游戏服务器(集群)

3) WEB游戏站点

4) 辅助GM工具

(本来我得画一幅图的,可惜PS不好,又没有漂亮的PS MM帮我!只能用文字组织,累就累点呗~)

我们逐一讲解,先看最简单的部分就是WEB游戏站点,既然我们选择了C#还干嘛要用PHP,JSP呢,可惜人家PHP是FREE的呀,羡慕ing,不过我
用盗版,嘿嘿!等我做大了还怕出不起这几个版权费用嘛,最贵的可是SQL系统哦,好几万啊,所有的预算加起来要十几万啊才一个解决方案! WEB
游戏站点我用ASP.NET实现,我们新建一个解决方案,再建立一个网站项目,我想做网站任何人都比我来的强,这个网站就是给用户查询积分,在
线交流,新闻发布,用户注册,修改密码,互动娱乐等等作为一个推广的平台!这中间没啥好讲的,唯一能够打交道的就是数据库,因为你和游戏
系统 交互纽带就是通过数据库。既然讲到数据库,那么我们就顺便讲一下如何设计数据表结构吧,这中间我不想用更多的废话讲,很简单,把你的
策划与需求分析好,转成数据表,规划好你的数据完整约束,建立好你的索引,命名好你的规范,合理设计好你的用户表等等;其实前面所讲到的
玩家类就是一个数据表的体现。博客园的人谁不会呀!

OK,我们在双核亚光速的旅途中已经建立好了游戏站点,玩家可以申请帐号,登录管理中心等等操作!让地球的玩家通过WAP连接进来吧,什么这
种无线技术延时……….你现在很振奋吧,我们的棋牌游戏站点架设完毕了!一切看上去都是那么的真实美妙,只有自己知道我们还没有做出棋牌游戏
来呢?,不急,往下看~~~~ 飞往火星的风景真美,一片漆黑!哇还有月兔呢?(有时候我感觉自己挺傻的)

伟大的C#,我们在做游戏站点的时候已经拥有了sqlhelper类,通常为了能够给游戏中使用将它单独划入一个公用类库中,我想大家也是这么做的。
一家的东西就是好,通用性强呀!

这儿我将这个数据帮助类已经写好了,下载即可,只有几个方法很简单也够全面,几乎我可以用这几个方法操作所有的需求,学习过那个叫什么Pet
什么的工程就清楚了。什么你看不懂这段话吗? SqlHelper类你不知道吗,网上一大把呀,你做Asp.net不用它吗? 这位旅客请下船,什么下船还要拿
走SqlHelper类? 那就看完这篇文章再下吧,下载地址在下面呢?

接着我们继续来观看辅助GM工具的设计,其实这类工具未必要做成WINFORM形式的,我们完全有必要集成在游戏站点的后台中,依你的情况与心情
决定吧!它主要负责的就是给一些用户增加积分,删除一些用户,注册一些用户,禁用一些用户,配置一些游戏参数,积分双倍,活动定义,游戏公告,
游戏通知,广告通知等等操作!这其中涉及到的一些操作你可能不是十分完全明白,不知道怎么和游戏结合,OK,你只要把你的操作保存到DB中即可,
我不是说过了吗DB就是纽带,如果你是单独用WINFORM操作也可以把它保存到INI文件或XML文件中,注意这可是配置服务参数,不要搞进客户端哦! 

对以上的言论我想我有必要和你做一个了段,该交钱的就得交钱(呵呵):我们已经完成了数据表的设计,游戏站点的设计!辅助工具的编写,接下来就
是我们的最后冲刺部分,启动四核亚光速引擎全面提速~~~~~~~~~~~~啗~~CN国际空间站看不到了~~~远离了我的地球.

 

我们现在开始全面讲解游戏服务器方面的知识

哥们要是不耽误您抽烟的时间请先麻烦看一下我之前发表的那篇《当C#遭遇老温》之网络通讯编程篇
地址为:http://www.cnblogs.com/wenweifeng/archive/2008/07/03/1234783.html

该篇有很多网友提出了正确的方法,在此感谢!请你吃饭好吗~~

游戏服务器是非常重要的组成部分,游戏的性能以及安全和稳定全靠它来支撑,所以设计一个合理的游戏服务器是非常重要的~这是一个很严肃的
问题,因为太阳风暴就要来临了,搞不好咱们全玩了,回不了地球了~~很严肃,注意看!

在这类设计中,通常会使用C/S的应用程序模型。客户端不会处理逻辑判断,仅仅是一个收发器,如果你把重要的逻辑判断放在客户端处理你就完了,
那还有什么信任可言呀,客户随时可以搞个外挂模拟提交~~隔位刺杀,极速外挂!!全乱套了。客户端负责与玩家的交互,接收玩家的操作事件,然
后发送给服务端,服务端负责处理判断,然后再发给客户端,客户端收到服务器的消息后再转成UI展现出来,就是这么一个流程,如果想要让通信安
全把这些数据包经过一层加密或数字签名~其实所有的游戏都是这么做的,客户端就是存储着些声音图形等界面性的东西。

好了,我花了数十个通宵才完成的一个例子,现在先展示它,一层层剖析,可能我这不是最好的方案,但是它代表了我一家的风范,你有权拒绝也有
权接受,老温我是讲人权的呀~~

看下图:


 

由一个DEMO解决方案组成,下面有Chess、ChessServer、Common、DataServer、GameClient、HallServer、LoginServer、OnlyServer八大部分组成。
其中Chess是一个棋类游戏的实现,也就是房间,ChessServer是负责这个棋类游戏的服务端逻辑判断部分,Common是存放公存类库的,DataServer是
专门用来处理数据库层的服务,GameClient是游戏的客户端是一个Windows应用程序,HallServer是一个负责处理大厅事务请求的服务器服务,
LoginServer是负责处理用户登录的服务器服务,OnlyServer是仅给服务器应用程序提供的类库,为了安全起见不能将它与Common弄在一起,要不然
发布的时候客户端中也会有这类服务逻辑处理代码,那样就很危险了!

好了介绍完了这个工程下面我就开始讲解这些服务器之间是如何协同工作的,以及为什么要采用这种设计模式?

之所以采用这种设计模式是有很肯定的原因的,废话,要不然这么做干啥!首先做为客户端它不可能知道有这么多服务器的存在,也就是客户端它
只要知道我登录到哪里,也就是用户要知道我要登录到哪个服务器呀,用户需要选择一个最近的服务器,看下图:




   

我做的这个客户端是一个典型的休闲棋牌游戏客户端,其中分二层第一层就是浮在上面的登录层,必须要进行登录后才可以进入第二层;其中登录
层是可以托动的,实现了不规则窗体透明效果,这些在C# Windows开发下还是比较方便实现的!如下图:

我做的这个客户端是一个典型的休闲棋牌游戏客户端,其中分二层第一层就是浮在上面的登录层,必须要进行登录后才可以进入第二层;其中登录层
是可以托动的,实现了不规则窗体透明效果,这些在C# Windows开发下还是比较方便实现的!如下图:


 

大家看到首先会让用户选择服务器,不同的服务器根据用户距离而决定,其次就是这中间还会加上一层智能判断,来协调哪个登录服务器是爆满的
哪个登录服务器是空闲的,这个方法利用计数器就可以实现,其中大家可以看到这些登录按钮都是双个图片进行变色的鼠标移上去就会发生变换。
上面灰色部分是准备嵌套一个公告网页的,这样就可以动态告知用户系统举行了哪些活动等;还可以收入广告费用~~其它系统都不是这样设计的吗





HO 我的天呐我输入的帐号非法,呵呵。其实这个弹出框就是一个窗体,我封装了一个MessageBox窗体类,专门用它来处理与用话对话返回确定,
取消还有其它种类的返回值!
再来看一张我点击了关闭后弹出的对话框:


 

OK,到目前为止我们已经知道了用户第一步将要做的是什么,要么去注册,要么登录,你不登录后面的大厅就是无法使用的!能与用户交互的就是
一个登录服务器,登录服务器判断用户的帐号和密码是否正确,正确将返回正确的数据通知客户端程序验证成功,及其它协调的服务器,在在线列
表中保存住该玩家的状态! 然后下一步工作登录服务器就完成了它的使命了,断开与客户的线程链接交由HallServer服务器处理,然后由HallServer
与客户端进行一次链接,HallServer并返回所有的游戏数据列表给客户端,并且我这儿设计了30分钟更新一次玩家的数据列表,因为要反应出哪些房
间是多少人,是否爆满等情况! 注意:这儿客户与LoginServer是进行短链接的线程交互所以LoginServer可以很轻松的支持更多的玩家通信,还有一
点我需要指明,在线程操作中线程的创建与切换是很耗资源的。那么玩家要是断线了或者不小心死机了怎么判断它呢? 其实这很好办,我这儿在客户
端实现了一个定时器,它起到了“心跳包”的作用,定时向HallServer发送心跳数据,说我在线,我在线。该服务器收到后,嗯,这丫的确在线,如果在
一二分钟内没有收到,该丫已经死了~~哈哈,这儿的时间完全由你自己来掌握,一切要把握好!关系到性能哦~

OK,上面我已经比较清晰的描述了客户端在登录之前和登录时的那一个过程服务器是如何处理的,那么假如我这儿假设用户已经通过验证,就是输入
了正确的帐号和密码会发生什么情况? 首先HallServer将最新的房间列表发送给客户端,客户端此时已经能够操作大厅了,并且与HallServer保持着密
切的交互关系,什么房间列表你不知道如何用数据结构描述它,天呐,就一个树形结构呗,对!就是树形结构,不过要你自己组织好哦,C# windows
默认的树形可能不够适应你哦。得自己专门花点心思处理一下!也是很Easy的事情啦。关于对客户端的界面处理最好还是请一个美工朋友帮你做,
我花了不少心思在这上面,不过美工朋友们可能不知道,你要切成不同等块的才行,因为程序中要考虑不同浏览器的分辨率,我现在用的是19寸分
辨自然很大,所以要考虑百分比的概念,就像做网页中的居中显示还是按百分比来显示,不过这儿要难的多了!!

讲了上面这么多你已经知道客户端是怎么第一时间与开放的服务器通信的,但是你还不知道服务器之间是如何通信的,也就是对用户而言看不到的
那层服务器集群是如何通信的下面我就来继续解说!

看下图,首先需要启动的第一个服务器是数据管理服务器





 

看界面就已经很明白它要完成哪些功能了,下面我再看一下代码:

        private TcpListener myListener;

        private IPAddress localAddress;

        private Service service;

        private UserOperat pubuserop = new UserOperat(System.Configuration.
ConfigurationManager.AppSettings["Sqlconnstring"].ToString());//单独线程用于处理用户在线;

        private System.Collections.Generic.List<User> userList = new List<User>();

以上代码就是一些声前其中我们把各个服务器也视为User,之所以这儿用的是单线程是有原因的,因为总共服务器才几个,我这个数据管理器只
为这几个服务器服务的,没必要用多线程,至于那几个服务器放在哪里我不管,管它放宇宙的哪个角落,只要能访问的到我就可以了。

我们继续看初始化部分的代码:

Code Form1()
        {
            InitializeComponent();
            this.timer1.Enabled = false;
            this.timer1.Interval = 1000 * 60;//每一分钟进行用户在线检测
            this.listBox1.HorizontalScrollbar = true;

        }


        private void Form1_Load(object sender, EventArgs e)
        {
          
            this.Text = "数据管理服务器";
            //初始化得到本地计算机IP与默认端口
            IPAddress[] addrIP = Dns.GetHostAddresses(Dns.GetHostName());
            localAddress = addrIP[0];
            this.serverip.Items.Add(localAddress.ToString());
            this.serverip.SelectedIndex = 0;
            this.port.Text = "5001";//登录服务器端口
            this.button2.Visible = false;
            this.statusStrip1.Items[0].Text = "服务已停止..";
            service = new Service(listBox1);
          
            
        }


 

这中间不过就是做了一些界面初始的显示和取一些IP等数据罢了,service = new Service(listBox1);你可能感到奇怪这个是干什么的,这个类就是负责
专门用来把处理的结果显示到界面上的,其中用到了委托,因为C#中线程是不允许直接访问控件的!

我们再来看看其它代码,好长呀。我这儿就再贴出一个核心的部分:

Code接收客户端连接的线程
        private void ListenClientConnection()
        {
            while (true)
            {
                TcpClient newClient = null;
                try
                {
                    //等待用户进入
                    newClient = myListener.AcceptTcpClient();
                }

                catch
                {
                    //当单击[停止监听]或者退出此窗体时AcceptTcpClient会产生异常
                    //因此可以利用此异常退出循环
                    break;
                }

                //每接受一个客户端的连接,就创建一个对应的线程循环接收该项客户端发来的信息
                ParameterizedThreadStart pts = new ParameterizedThreadStart(ReceiveData);
                Thread threadReceive = new Thread(pts);
                User user = new User(newClient);
                threadReceive.Start(user);
                userList.Add(user);
                service.SetListBox(string.Format("{0}进入", newClient.Client.RemoteEndPoint));
                service.SetListBox(string.Format("当前连接用户数:{0}", userList.Count));
            }

        }



这个函数是等待用户加入的功能,一但有用户进入就要进行一系列的工作的开始!一系列的工作就由下面来处理:
//接收、处理客户端信息的线程式,每客户1个线程,参数用于区分是哪能个客户
        private void ReceiveData(object obj)
        {
            UserOperat userop = new UserOperat(System.Configuration.ConfigurationManager.AppSettings["Sqlconnstring"].ToString());
            MessageData mess = new MessageData();
            User user = (User)obj;
            TcpClient client = user.client;

            //是否正常退出接收线程
            bool normalExit = false;
            //用于控制是否退出循环
            bool exitWhile = false;
            while (exitWhile == false)
            {
                //保存接收的命令字符串
                string receiveString = null;
                //每条命令均带有一个参数,值为true或false,表示是否有紧跟的字符数组
                string[] splitString = null;
                byte[] receiveBytes = null;
                try
                {
                    //从网络流中读出命令字符串
                    //此方法会自动判断字符串长度前缀,并根据长度前缀读出字符串
                    receiveString = user.br.ReadString();
                    splitString = receiveString.Split(',');
                    if (splitString[1] == "true")
                    {
                        //先从网络流中读出32位的长度前缀
                        int bytesLength = user.br.ReadInt32();
                        //然后读出指定长度的内容保存到字节数组中
                        receiveBytes = user.br.ReadBytes(bytesLength);
                    }

                }

                catch
                {
                    //底层套接字不存在时会出现异常
                    service.SetListBox("接收数据失败");

                }

                if (receiveString == null)
                {
                    if (normalExit == false)
                    {
                        //如果停止了监听,Connected为false
                        if (client.Connected == true)
                        {
                            service.SetListBox(string.Format("与{0}失去了联系,已终止接收该用户信息", client.Client.RemoteEndPoint));
                        }

                    }

                    break;
                }

                service.SetListBox(string.Format("来自{0}:{1}", user.client.Client.RemoteEndPoint, receiveString));
                if (receiveBytes != null)
                {
                    service.SetListBox(string.Format("来自{0}:{1}", user.client.Client.RemoteEndPoint, Encoding.Default.GetString(receiveBytes)));
                }

                switch (splitString[0])
                {
                    case "rsaPublicKey":
                        //使用传递过来的公钥重新初始化该客户端
                        //对应的RSACryptoServiceProvider对象
                        //然后就可以使用这个对象加密对称加密的密钥了
                        user.rsa.FromXmlString(Encoding.Default.GetString(receiveBytes));
                        //加密对称加密的私钥
                        try
                        {
                            //使用RSA算法加密对称加密算法的密钥Key
                            byte[] encryptedKey = user.rsa.Encrypt(user.tdes.Key, false);
                            service.SendToClient(user, "tdesKey,true", encryptedKey);
                            //加密IV
                            byte[] encryptedIV = user.rsa.Encrypt(user.tdes.IV, false);
                            service.SendToClient(user, "tdesIV,true", encryptedIV);

                        }

                        catch (Exception err)
                        {
                            MessageBox.Show(err.Message);
                        }

                        break;

                    case "dsaPublicKey":
                        //使用传递过来的公钥重新初始化该客户端的DSA对象
                        //然后就可以使用这个对象对这个发送者进行数字签名的验证
                        user.dsac.FromXmlString(Encoding.Default.GetString(receiveBytes));
                        byte[] ServerdsaPubkey = Encoding.Default.GetBytes(user.Serverdsac.ToXmlString(false));
                        //将服务器的数字答名公钥发给该客户端
                        service.SendToClient(user, "dsaPublicKey,true", ServerdsaPubkey);
                        break;

                    case "Logout":
                        //格式:Logout
                        service.SetListBox(string.Format("[{0}]退出", user.client.Client.RemoteEndPoint));
                        normalExit = true;
                        exitWhile = true;
                        break;
                    case "GetSqlconnect":
                        //向该客户端发送SQL连接字符串

                        string sqlconnt = System.Configuration.ConfigurationManager.AppSettings["Sqlconnstring"].ToString();
                        mess.EncryptMessage = TripleDES.EncryptText(sqlconnt, user.tdes.Key, user.tdes.IV);
                        mess.Datalength = TripleDES.GetStrLength(sqlconnt);
                        mess.SignDataMessage = user.Serverdsac.SignData(Encoding.Default.GetBytes(TripleDES.MD5(sqlconnt)));
                        byte[] buffer = SerializeData.Serialize(mess);
                        if (buffer != null)
                        {
                            //发送注册信息
                            service.SendToClient(user, "Getsqlconnect,true", buffer);
                        }

                        break;
                    default:
                        service.SetListBox("什么意思啊:" + receiveString);
                        break;
                }



            }

            user.br.Close();
            user.bw.Close();
            user.dsac.Clear();
            user.rsa.Clear();
            user.tdes.Clear();
            user.Serverdsac.Clear();
            userList.Remove(user);
            client.Close();
            service.SetListBox(string.Format("当前用户连接数{0}", userList.Count));
        }


 

关于这部分的代码我不想作过多的解释,因为解释的话我得再写N天也写不完,希望大家能够仔细自己阅读,如果有凝问再和我讲~其中你可能会莫
名其妙的用到一些加密的东西,在之前我写的那篇网络通讯篇中就已经指出了加密与解密的原理。这中间的消息我会专门用一个消息类来进行传递,
然后对这个类进行序列化后进行传输,再次感谢C#,多好的技术呀帮我们实现了。

消息类如下:

Code System;
using System.Collections.Generic;
using System.Text;

namespace GameServer
{
  [Serializable]
  public class MessageData
    {
        private byte[] messdata;
        private byte[]  signdata;
        private int datalength;
        public byte[] EncryptMessage
        {
            get
            {
                return messdata;
             
            }

            set
            {
               this.messdata = value;
            }

        }


      public byte[] SignDataMessage
        {
            get
            {
                return signdata;
            }

            set
            {
               this.signdata = value;
            }

        }

      public int Datalength
      {
          get
          {
              return datalength;
          }

          set
          {
              this.datalength = value;
          }

      }


    }

   
    
}

 

TripleDES加密类和序列化类我在上篇中也已经指出,大家可以从那儿获取!在这儿我再次提出我的加密方式:客户端和服务器,以及服务器与服务器
之间都会采用公有加密和私有加密二种方法的结合,因为公有加密安全但速度慢,适合加密少量的数据,我用它来加密私有密钥,然后利用私有加密
技术加密网络传输数据,通常就是一系列的命令罢了,看到了吗,就是我上面的代码片断中的一些CASE后面的字符,通常我把它叫做游戏协议!

我无法再忍受贴代码的痛苦了,又长又烦,还是大伙自己下载过去看吗?

好了我们继续看下面一个服务端:

大厅服务器处理程序(HallServer)


 

它是用来与用户交互的,同时也会与数据服务器发生交互!其中实现的代码基本上和数据服务器一样,但是需要注意的一个问题就是,一定要注意多
线程中的临界资源问题,要不然锁死你~~。代码我在这儿就不贴了。

我们继续往下看,因为我们马上就要到火星了,抓紧时间。

这是LoginServer:



   

继续看下面的这图,这是客户端,我们来讲讲客户端,其中一个很重要的表现手法就是完全用自己的方法去实现不规则的窗体,这一点还是比较好做
的,第二点就是负责把接收到的服务器消息展示出去


客户端的登录部分:


 

其中这些都不太重要,我们关注的是代码级的东西和实现它的部分!

大家接下来继续看Chess项目:如图:


 

看到Table 和 Room了吗,其中Table代表了一个个桌子,而Room代表一个人房间,之前我傻到用控件来显示它,后来我采用了GDI+来绘图,性能
提了很多倍,讲到这儿不得不面对一个现实,我们在处理这部分图形编程的时候,不管你在Directx还是GDI中,都要解决闪屏问题?我们来讲一
下这个闪屏的概念和如何解决~~你需要解决的就是要先知道什么是双缓冲,什么是离屏技术,这类文章网上都讲疯了,我现在只关注我怎么解决
C#闪屏问题,伟大的C#啊,他已经帮我们实现了,只要启用这个就可以了,就一句话:

this.SetStyle(ControlStyles.OptimizedDoubleBuffer |

                    ControlStyles.ResizeRedraw |

                    ControlStyles.AllPaintingInWmPaint, true);

我们只要设置支持它就可以!至于我们如何把图片桌子等这些组织起来或画到屏幕上,我想我此时此也一时讲不清,我会把我所用到的所有
素材打包上传,提供下载。

首先是软件的皮肤:


 

然后是一张张桌子和人物:






 

还有好多图我没有贴出来,大家下载过去看!可以直接放在你自己的项目中使用。

好了,我就先贴这些了,更多的部分就看我提供的下载包中获取吧! OK,这些图片很容易处理,我现在关注就是如何把这些图片与桌子无缝的渲染
在一起,太Easy了,因为我们拥有了C#,只要将利用GDI+就可以搞定它们的图与图的切换等等实现手段,认真的朋友看桌子的图和看人就已经看出名
堂来了,叠在一起就是完整的人坐在椅子上手放桌子上!

你只要花些时间学习一下这方面的知识就可以了。

哎,我写了这么多好累,真的是累了。!~ 本来还想把我的最终房间的效果图贴一下的,那真的很漂亮,会令你的荷尔蒙极速上升,现在看来我没有
足够的精力来做了,因为我的DB环境坏了,无法通信,哪个丫动了我的电脑,差点把我写作此篇的文档找不到了。谢天谢地~~~




   

这是我完整的项目,VS2005+SQL!   各位旅客我们已经到达火星,胜利啦!!!!!

结束语:想必看完此篇文章后您拥有无比的兴奋,迫不及待想获取代码是吗?想实现自己的一款棋牌游戏吗?想真正用C#来开发吗?现在还等什么
啊,完全可以,我这不是已经做出来了吗,举起我们伟大的旗帜,向前向前再向前,我们照样可以用C#写出非常优秀,非常棒,代码简洁,完全面
向对象,多层服务器分布式架构的高性能棋牌系统。告诉您,请您先仔细阅读本文之后再下载,由于经过本人上次的《地图编辑器》的事件之后,
我会更加谨慎的发放源代码,要不然别人又说我作秀了。哎~这什么世道啊!好了,还是老样子,希望想从事这方面研究的朋友请留下您的EMAIL,
我批量发送,如果想学习棋牌游戏开发的朋友很多的话,我将提供在线全部工程文件下载!!!

如果您感觉这篇文章有什么意见或错误之处请您发表! 非常感谢您的阅读~~~~~

希望老温我这篇文章能够带给您激发起梦想的欲望!!!

我先去火星到处溜达溜达!!朋友,一起走逛不?

文中涉及到的素材及全部代码下载地址:http://files.cnblogs.com/wenweifeng/laowen_Game.rar
解压密码:laowen  如果您的浏览器无法观看全景图,请在新窗口观看最大图

 

发表评论
  回复  引用    
#1楼 2008-11-13 10:27 | 江东 [未注册用户]
帅哥,下不到呀
  回复  引用    
#2楼 2008-12-03 11:24 | 肥猫3.0 [未注册用户]
小弟我正在学习编写网络棋牌,能否发一份代码给小弟研究研究
邮箱:fanwj@hotmail.com
  回复  引用    
#3楼 2008-12-10 10:12 | 平月 [未注册用户]
学习编写棋牌类游戏,老温兄能否发一份源码看看,
拜读一下,不胜感激
  回复  引用    
#4楼 2008-12-10 15:14 | PIGZHU [未注册用户]
哥们,给我来一份,正研究这个,好东西!
  回复  引用    
#5楼 2008-12-10 15:14 | PIGZHU [未注册用户]
哥们,给我来一份,正研究这个,好东西!
zwei803@163.com
  回复  引用    
#6楼 2008-12-12 20:03 | 晨琰 [未注册用户]
给我发一份吧,想拜读一下,谢谢。
  回复  引用    
#7楼 2008-12-15 17:36 | 寒潭 [未注册用户]
哥们,给我来一份,公司近来有一个项目是做这一方面的.看了你的文章有恩大的启发,希望能得到代码继续学习中.
  回复  引用    
#8楼 2008-12-17 17:05 | 天下皆为公 [未注册用户]
给我发一份源代码哈,想拜读一下,谢谢。
  回复  引用    
#9楼 2008-12-18 12:05 | null9615 [未注册用户]
小弟正在学习,能否发一份代码
邮箱:null9615@yahoo.com.cn
  回复  引用    
#10楼 2008-12-22 16:06 | 似的 [未注册用户]
您好:我是一个喜欢编程的菜鸟,希望能看看您这个的代码。
邮箱:304857216@qq.com
谢谢!!!
  回复  引用    
#11楼 2008-12-25 10:36 | 有缘 [未注册用户]
给我发一份源代码哈,想拜读一下,谢谢。

  回复  引用    
#12楼 2008-12-27 11:56 | gongjing [未注册用户]
牛人·给我也来份·13561387gj@163.com谢谢·另外能不能向你请教问题啊·我为你建个超级群·你来教教象我这类的人吧。可以吗?
  回复  引用    
#13楼 2009-01-12 10:58 | xiao-0301 [未注册用户]
学习ing xiao-0301@163.com 谢了先
  回复  引用    
#14楼 2009-01-20 10:48 | gerfon [未注册用户]
温老大.下不了啊.劳驾帮我发一份.学习学习.Email:kksd.long@163.com
看就是骗人的家伙。搞显示,不发代码的!
  回复  引用    
#16楼 2009-01-20 12:15 | 恩 [未注册用户]
楼上的说的对!
  回复  引用    
#17楼 2009-01-20 12:17 | 恩 [未注册用户]
我只想问一下 客户端你是怎么处理每张牌的显示的?
是PICTUREBOX做还是 用的GDI+这里我不知道怎么让他的速度快起来,给点建议即可!
  回复  引用    
#18楼 2009-01-31 01:26 | 小陈888 [未注册用户]
温哥下不了呀,能否发一份给小弟我呀,谢谢了,学习中
  回复  引用  查看    
#19楼 2009-02-02 13:23 | 郭英杰      
多谢 兄弟 指点!!
  回复  引用  查看    
#20楼 2009-02-02 13:31 | 郭英杰      
你好 我正在研究网络棋牌游戏 但有些迷茫,不知道该怎么弄,希望你能发给我一份代码!
enjay@126.com
多谢了!!
  回复  引用    
#21楼 2009-02-16 14:28 | 李洪宇 [未注册用户]
如果有棋牌平台开发经验的能人 可以和我联系
我正想组个小团队 来开发我需要的棋牌平台
qq 249042758
  回复  引用    
#22楼 2009-02-16 14:29 | 李洪宇 [未注册用户]
噢 对了 忘记说了 本人在武汉 开发的工作地点也在武汉
  回复  引用    
#23楼 2009-02-18 15:21 | # [未注册用户]
楼主看到后发一份我啊 EMAIL kdalan@163.com 很想学习一下
  回复  引用    
#24楼 2009-02-23 17:30 | 林小鹏 [未注册用户]
温哥。。小弟最近正在学习这个。。劳驾您发一份给我吧。。
  回复  引用  查看    
#25楼 2009-02-27 12:55 | 影子tom      
跟你学习了。你写得不错呀。发一份源码给我好了。多谢。137188467@qq.com
  回复  引用  查看    
#26楼 2009-02-27 12:55 | 影子tom      
学习了。很好发一份给我呀。137188467@qq.com



关于我们 版权声明 广告服务 联系我们 友情链接 加入收藏
站长:施昌权    Email:scq2099yt@163.com    MSN:scq2099yt@live.cn    QQ:14046300    本站QQ群:67202409
Copyright © 2008     卓为VC(www.joyvc.cn)    All Rights Reserved    建议分辨率 1024×768
本站由施昌权制作维护
京ICP备09012297号