本次作业要求设计服务器和客户端,由于之前对网络编程是一窍不通,上上节课听宗学长讲述Tcp的时候心里想这个东西还真是高大上啊一点儿都听不懂,但是上个周末看了看C#网络编程的博客和书之后,发现这个东西入门其实很简单.经过几天的需求分析代码维护之后,我们的作业已经初具成果,先展示一下效果.
服务器:
客户端:
是不是很华丽的说。。。
感觉就像做了一个实验室的小项目一样。。。代码量有一千的说。。。当然如果要做的更好还可以加很多优化。。。但是(见最后吐槽)。。
下面根据作业要求来展示一下我们的设计:
多人游戏, 每个选手有一个客户程序在运行, 和一个服务器通过互联网的某种协议交互。
我们采用的是TCP编程,遵循TCP/IP协议。
在有网络接的地方就可以玩 (LAN, WiFi, 3G 都应该可以)。
这个。。。明显可以保证吧,我们获得主机的地址然后连接
客户程序要先向服务器注册, 确保在一个游戏的过程中, 注册信息能保证一个(并且只有一个)客户以某确定的用户名参与游戏。
首先为了方便演示,我们用随机数生成Player+num形式的用户名,单击登录按钮之后,会发送一个Login请求,这时服务器会判断userList中是否存在该用户名,如果存在,那么会返回一个sorry消息,告诉该客户端无法登录;如果不存在,那么就将该用户加入userList中,同时返回一个success消息,告诉客户端登录成功.
服务器可以规定一次锦标赛有多少轮, 每轮的具体提交数据的格式是什么。 在服务器规定的时间范围内, 每个客户程序向服务器提交黄金点数据。 客户程序之间不交流。
我们在服务器的界面上进行了如下设计:
我们人为的将允许同时参加游戏的最多人数设定好,为了放置服务器过载?(这里当然满足这门课60人的需求是绰绰有余了。。),游戏轮数,每轮游戏的时间可以自行设定,游戏规则采用radiobutton来设定.同时服务器的这些设定在单击开始游戏之后就会变为enabled=false,防止中途更改.同时这些设定也会传给客户端,同样客户端也只能显示设定而无法更改设定
在一轮介绍后, 服务器把此轮的结果 (参与用户名, 提交的数据,第一名,G-number) 公布在服务器上。 每个客户程序都能自由获取。
每轮结束后,我们会将本轮游戏结果发送给客户端,同时将本轮排名和黄金点变化曲线实时更新在服务器上,当然想在客户端方面获取只要在添加一个webbrowser控件链接到我们的服务器相应地址就可以.
一轮的优胜程序可以得 10 分, 一轮的最后一名得 (–1)分, 其余选手得 0 分。 在规定时间内不能提交数字的客户程序得 (-5) 分。 如果有并列第一名, 则服务器取提交数字较早的客户程序作为优胜者。 如果有并列最后一名, 则并列者都得 (-1) 分。
这个老师的判定这是奇怪…每个User类设计一个grade属性,同时开一个list来存每轮的最后一名.
每次锦标赛事先规定好是采取下列哪一个模式:
-
每个客户程序必须提交一个有理数。
-
每个客户程序必须提交两个有理数。 (任何一个数字最接近G-number 则此客户程序就是优胜。)
这个就是简单的判断吧…规则在游戏一开始就能确定了..上述已经提到这一点,另外实现起来就是send,number1,number2的问题而已
客户程序可以用任何语言编写, 只要它能够按要求和服务器交互即可。
我们用了C#,话说这学期就跟C#打交道了,而且C#网络编程很方便,很多东西都已经包装的很好了.
服务器必须实时地通过一个网页显示每个用户的得分 (人可以从这里看进展,客户端程序会从这里拿数据进行分析)。
我们的Webbrowser控件就是通过读取我们的网页来显示得分的…另外我们的曲线图使用bitmap绘制的(C#的chart控件还没有开始学..),然后将bitmap存为图片同时html显示该图片就好.这里遇到了一个很大的问题是图片开始时一直是X,搜了好多资料之后告诉我是图像色彩模式不统一?后来索性把图片格式从Jpg改为Png,问题解决.据说说rpg转cmyk的问题?...
服务器必须能避免接口拥堵, DDOS 等意外情况。
哈哈.我们相信我们的服务器.
接下来从代码方面分析一下我们的设计
服务器:
我们设计了一个User类来存用户信息,
一个Service类来用来实时显示服务器状态(addItem)和向客户端发送消息
至于老师提到的接口设计,我们放到FormServer类里,不过想不太明白的是,我们设计接口不就是为了大家设计客户端程序提交黄金点么…这样的话不就是每个人写一个productNumber()来生成自己的答案就好了么…客户端所能获取到的就是上一轮的黄金点数据…然后就靠各位当作一个数学题+博弈题来考虑了..
我们的FormServer中,开始游戏后会开一个线程来监听tcp连接,当有连接后会分配一个线程进行相应处理.对于每轮游戏的时间问题我们用了Timer,在每一个timerElapsed事件里进行先发送一个pause消息来暂停游戏,进行相应结果处理后,再重新发送start消息开始游戏.同时我们还用了获取当前事件DateTime同时利用while(now.AddSeconds(some seconds)>DateTime.now){}的方法进行延时处理,不用Thread.sleep的原因是直觉感觉这样会有问题...
消息处理总结如下:
1 switch (command) 2 { 3 case "login": 4 if (userList.Count > maxUsers) 5 { 6 sendString = "sorry"; 7 service.sendToOne(user, sendString); 8 service.addItem("人数已满,拒绝" + splitString[1] + "进入游戏"); 9 isExist = false ; 10 } 11 else 12 { 13 bool flag = true; 14 for (int i = 0; i < userList.Count; i++) 15 { 16 if (userList[i].userName == splitString[1]) 17 { 18 flag = false; 19 break; 20 } 21 } 22 if (!flag) 23 { 24 sendString = "sorry"; 25 service.sendToOne(user, sendString); 26 service.addItem("用户名已存在,拒绝" + splitString[1] + "进入游戏"); 27 isExist = false; 28 } 29 else 30 { 31 user.pos = num++;//用户进入先后顺序 32 userList.Add(user); 33 user.userName = splitString[1]; 34 sendString = "success"; 35 service.sendToOne(user, sendString); 36 service.addItem(string.Format("用户 {0} 加入游戏成功!", user.userName)); 37 } 38 } 39 break; 40 case "logout": 41 service.addItem(string.Format("{0}退出游戏", user.userName)); 42 normalExit = true; 43 isExist = false; 44 break; 45 case "send": 46 service.addItem(string.Format("{0}提交数据: {1},提交成功", user.userName, splitString[1])); 47 sendString = string.Format("success"); 48 service.sendToOne(user, sendString); 49 user.number = double.Parse(splitString[1]); 50 if (splitString.Length == 3) 51 { 52 user.number2 = double.Parse(splitString[2]); 53 } 54 user.hasSend = true; 55 break; 56 default: 57 service.sendToOne(user, "无效指令: " + receiveString); 58 break;
客户端:
客户端也拥有一个Service类,用来显示服务器和客户端状态
客户端的主体是FormClient类,这里对于消息的处理我们总结如下:
1 switch (command) 2 { 3 case "sorry": 4 MessageBox.Show("连接成功,但游戏人数已满或者用户名已存在,无法进入."); 5 isExist = false; 6 break; 7 case "pause": 8 buttonSend.Enabled = false; 9 break; 10 case "start": 11 service.addItemToListBox("收到: " + receiveString); 12 if (isRandom) 13 { 14 randomSend(); 15 } 16 else 17 { 18 buttonSend.Enabled = true; 19 } 20 break; 21 case "success": 22 service.addItemToListBox("本轮向服务器提交答案成功,等待下一轮游戏开始"); 23 buttonSend.Enabled = false; 24 break; 25 case "result": 26 service.addItemToListBox(string.Format("上一轮游戏结果: Gnum is {0}, winner is {1}.", splitString[1], splitString[2])); 27 break; 28 case "mode": 29 if (splitString[1] == "0") 30 { 31 mode = 0; 32 radioButtonMode1.Checked = true; 33 radioButtonMode2.Checked = false; 34 radioButtonMode1.Enabled = false; 35 radioButtonMode2.Enabled = false; 36 textBoxNumber2.Enabled = false; 37 service.addItemToListBox(string.Format("本轮游戏模式为: 每个客户程序必须提交一个有理数")); 38 } 39 else 40 { 41 mode = 1; 42 radioButtonMode1.Checked = false; 43 radioButtonMode2.Checked = true; 44 radioButtonMode1.Enabled = false; 45 radioButtonMode2.Enabled = false; 46 textBoxNumber2.Enabled = true; 47 service.addItemToListBox(string.Format("本轮游戏模式为: 每个客户程序必须提交两个有理数(任何一个数字最接近G-number 则此客户程序就是优胜.)")); 48 } 49 break; 50 default: break;
最后,强烈吐槽一句。。。
超喜欢这门课,学到了好多东西,比如我之前从未接触过C#,比如我之前根本不了解网络编程,比如我之前做UI一点都不熟练,比如我之前根本不知道可以将代码打包成类库,比如我之前不会单例模式,比如我之前不知道单元测试代码覆盖率什么的,比如我对VS....用的越来越熟练了....
强烈建议这门课成为必修课。。。而且是学分很重的必修课。。。因为现在我每周花时间最多的课就是这门了。。。。。。
而无论我有多么喜欢这门课。。。接下来的时间里必然会将大部分时间献给五千行甚至八千行的编译大作业。。。献给我们的cocos2d-x游戏项目。。。献给我们的数据库大作业。。。这门课。。。接下来。。。尽量搞一些讲座什么的吧。。。。。。