0. 摘要

  之前我们玩了2次黄金数游戏,我也幸运的得到了一本《代码大全》,嘿嘿。这次的作业是一个Client/Server程序,自动化完成多轮重复游戏。

我完成了Client部分,使用C#编写。下面简要阐述。

1. 总体设计:

  思考后,我认为这个客户端程序要能满足如下要求:

  1. 保证信息传输到服务器。如果发送的信息没有得到相应,应可以不断重试。

  2. 一定的错误恢复能力,当因网络问题错过某些回合,应该可以跳过而继续运行。

  3. 恰当的算法,提供相对准确的黄金数字预测。

  4. 具有自动获取可用端口能力,使得20个客户端同时开启能够不冲突的与服务器连接。

  除此之外,为了确保游戏执行期间不出现漏洞(如假冒身份发送数字等),客户端还具有如下考虑:

  1. 游戏开始前,使用ID和密码注册。

  2. 等待回合开始;只有当接收到服务器新回合开始的消息,且新回合的序号大于上一次的序号时,才开始计算和条过程。提交信息附带了本客户端的用户名密码。当然,服务器也可以根据客户端的IP和端口判断身份。这里考虑万一不得不更换网络并继续进行回合的情况。

  3. 每次发送消息均要等待服务器返回确认信息,并且是当前消息的确认信息。在未收到反馈时,启用一个线程不断重试发送。

  4. 当服务器停止游戏,客户端不再进入发送线程和等待线程。

  5. 客户端可以停止游戏。不论如何停止客户端,客户端需要正常告知服务器退出游戏。以避免服务器重试发送新回合开始消息。

  6. 客户端提供GUI,可自定义服务器IP和端口,不填写默认为本地。随时显示当前状态,便于观察和调试。

2. 端口设计

  基于上述考虑,我的客户端/服务器通讯协议为:

  str = TYPE + ";" + OP1 + ";" + OP2 + ";" + OP3;

  这是一条类似指令的收发字符串。各各数值使用分好分割。其中TYPE指明消息类型,包含以下信息:

  客户端注册,

  服务器注册确认,

  服务器开始新回合,

  客户端提交数字,

  服务器确认收到提交数字,

  服务器停止游戏。

  具体来说,我们为每一个消息举个例子:

  1. 客户端以ID为11061128,密码123456向服务器注册:

      str = "1;11061128;123456"

  2.服务器收到这个注册,注册成功:

      str = "2"

  3.服务器开始第5个回合,开始提交数字。上一回合的黄金数是17:

      str = "3;5;17"

  4.客户端提交第5回合的数字11,并验证身份:

      str = "4;5;11;11061128;123456"

  5.服务器收到第5回合提交的数字,正在等待他人提交:

      str = "5;5"

  6.服务器停止游戏:

      str = "6";

3. 线程调度

  客户端完成相关逻辑是通过一个控制线程调动其他线程的起止。线程有:

  control线程:循环的状态机,直到停止。此线程一直运行。

  receive线程:需要接受服务器确认结果时候,一直运行直到收到相应信息,完成等待。

  其他线程:完成各自信息发送的功能,与前两者同时进行。当收到相应确认信息时,线程停止,不再重试发送信息。(一遍不断的发信息,一遍看服务器收到没有)。

4. 测试结果

  经过为服务器的测试,客户端已经可以正确运行。下图的客户端Log显示了状态机、信息收发的情况,正常。通过服务器读取客户端发来的消息,并发送正确指令,测试正常:

  客户端:

  

  服务器显示客户端发来的连接和信息:

  

5. 主要代码

  下面是主要代码部分。该部分代码的重点是状态机、线程调度和信息收发:

  

 using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO; namespace AsyncTcpServer
{
public partial class mainForm : Form
{ const int STATE_REGISTER = ;
const int STATE_REGISTER_OK = ;
const int STATE_NEW_ROUND = ;
const int STATE_SUBMIT = ;
const int STATE_SUBMIT_OK = ;
const int STATE_SERVER_STOP = ;
const int INTERVAL = ; const string TYPE_REGISTER = "";
const string TYPE_REGISTER_OK = "";
const string TYPE_NEW_ROUND = "";
const string TYPE_SUBMIT = "";
const string TYPE_SUBMIT_OK = "";
const string TYPE_SERVER_STOP = ""; const string DEFAULT_SERVER_IP = "192.168.1.2";
const int DEFAULT_SERVER_PORT = ;
const string DEFAULT_USER_ID = "";
const string DEFAULT_USER_PASSWD = ""; private string ip_input = DEFAULT_SERVER_IP;
private int port = DEFAULT_SERVER_PORT;
private string id = DEFAULT_USER_ID;
private string passwd = DEFAULT_USER_PASSWD; private int tstate = ;
private int State = STATE_REGISTER;
private int Round = ;
private int[] PrevRslt = new int[];
private int PrevRslt_idx = ;
private int GoldPoint;
private string[] rcvs;
private bool isRunning = false; private TcpClient client = null;
private StreamWriter sw;
private StreamReader sr;
private Service service;
private NetworkStream netStream; Thread threadRegister;
Thread threadSubmit;
Thread threadReceive;
Thread threadWaitNewRound;
Thread threadControl; public mainForm()
{
InitializeComponent();
service = new Service(lb_log, sw);
} private void btn_start_Click(object sender, EventArgs e)
{
if (isRunning == true)
{
service.SetListBox("Already running, Press STOP first");
return;
} if (String.Compare(tb_svr_IP.Text, "IP") != )
{
ip_input = tb_svr_IP.Text.Trim();
}
if (String.Compare(tb_svr_port.Text, "PORT") != )
{
port = Int32.Parse(tb_svr_port.Text.Trim());
}
if (String.Compare(tb_id.Text, "ID") != )
{
id = tb_id.Text.Trim();
}
if (String.Compare(tb_passwd.Text, "PASSWORD") != )
{
passwd = tb_passwd.Text.Trim();
}
IPAddress serverIP = IPAddress.Parse(ip_input);
client = new TcpClient();
try
{
client.Connect(serverIP, port);
}
catch (System.Exception ex)
{
service.SetListBox(ex.Message);
return;
}
try
{
netStream = client.GetStream();
}
catch (System.Exception ex)
{
service.SetListBox(ex.Message);
return;
}
sr = new StreamReader(netStream, System.Text.Encoding.UTF8);
sw = new StreamWriter(netStream, System.Text.Encoding.UTF8);
service = new Service(lb_log, sw);
isRunning = true;
threadControl = new Thread(new ThreadStart(control));
threadControl.Start();
} private void control()
{
threadRegister = new Thread(new ThreadStart(Register));
//threadSubmit = new Thread(new ThreadStart(Cal_and_Submit));
threadReceive = new Thread(new ThreadStart(ReceiveData));
//threadWaitNewRound = new Thread(new ThreadStart(WaitNewRound));
//threadWaitNewRound.Start(); threadReceive.Suspend();
//threadSubmit.Start(); threadSubmit.Suspend(); while (isRunning == true)
{
if (tstate == ) //wait finish
{
threadWaitNewRound.Abort();
tstate = ;
}
else if(tstate == )//waiting
{
continue;
}
//nothing to wait
switch (State)
{
case STATE_REGISTER:
threadRegister.Start();
threadRegister.Join();
State = STATE_REGISTER_OK;
break;
case STATE_REGISTER_OK:
threadWaitNewRound = new Thread(new ThreadStart(WaitNewRound));
threadWaitNewRound.Start();
threadWaitNewRound.Join();
tstate = ;
State = STATE_NEW_ROUND;/////
break;
case STATE_NEW_ROUND:
threadSubmit = new Thread(new ThreadStart(Cal_and_Submit));
threadSubmit.Start();
threadSubmit.Join();
State = STATE_SUBMIT_OK;
break;
case STATE_SUBMIT_OK:
threadWaitNewRound = new Thread(new ThreadStart(WaitNewRound));
threadWaitNewRound.Start();
threadWaitNewRound.Join();
State = STATE_NEW_ROUND;
tstate = ;
break;
case STATE_SERVER_STOP:
service.SetListBox("Server Stop");
endMission();
break;
default:
break;
}
}
} private void ReceiveData()
{
while (isRunning == true)
{
string receiveString = null;
try
{
receiveString = sr.ReadLine();
}
catch (Exception e)
{
service.SetListBox(e.Message);
} if (receiveString == null)
{
//
service.SetListBox("wait to re receive");
//
Thread.Sleep(INTERVAL);
continue;
}
//
service.SetListBox("recieved" + receiveString);
//
rcvs = receiveString.Split(';');
switch (rcvs[])
{
case TYPE_REGISTER_OK:
State = STATE_REGISTER_OK;
break;
case TYPE_SUBMIT_OK:
if (Round == Int32.Parse(rcvs[]))
{
State = STATE_SUBMIT_OK;
}
break;
case TYPE_NEW_ROUND:
if (Round == Int32.Parse(rcvs[]) - )
{
State = STATE_NEW_ROUND;
}
break;
case TYPE_SERVER_STOP:
State = STATE_SERVER_STOP;
service.SetListBox("Server Stop");
tstate = ;
endMission();
break;
default:
break;
}
}
} private void Register()
{
threadReceive.Start();
String str = TYPE_REGISTER + ";" + id + ";" + passwd;
service.SendToServer(str);
while (isRunning && State != STATE_REGISTER_OK)
{
//
service.SetListBox("re register");
//
Thread.Sleep(INTERVAL);
service.SendToServer(str);
}
threadReceive.Suspend();
} private void WaitNewRound()
{
threadReceive.Resume();
while (isRunning && State != STATE_NEW_ROUND)
{
//
service.SetListBox("waiting");
//
Thread.Sleep(INTERVAL);
}
Round++;
PrevRslt[PrevRslt_idx++] = Int32.Parse(rcvs[]);
threadReceive.Suspend();
tstate = ;
} private void Cal_and_Submit()
{
threadReceive.Resume();
GoldPoint = calculate();
string str = TYPE_SUBMIT + ";" + Round.ToString() + ";" + GoldPoint.ToString();
service.SendToServer(str);
while (isRunning && State != STATE_SUBMIT_OK)
{
Thread.Sleep(INTERVAL);
//
service.SetListBox("submitting");
//
service.SendToServer(str);
}
threadReceive.Suspend();
}
private int calculate()
{
//
service.SetListBox("calculation");
//
if (Round == )
{
return ;
}
else
return (int)(PrevRslt[PrevRslt_idx] * 0.618);
} private void btn_stop_Click(object sender, EventArgs e)
{
endMission();
} private void mainForm_FormClosing(object sender, FormClosingEventArgs e)
{
endMission();
} private void endMission()
{
//
service.SetListBox("ending mission");
//
if (isRunning)
{
netStream.Close();
client.Close();
}
isRunning = false;
}
}
}

  下一次作业,客户端将完善线程操作(弃用已经过时的suspend和resume方法),并使用最小二乘法逼近黄金点曲线的方法来预测数值。

黄金点游戏之客户端(homework-05)的更多相关文章

  1. 黄金点游戏(js+css)

    一.项目描述:黄金点游戏 黄金点游戏是一个数字小游戏,其游戏规则是: N个同学(N通常大于10),每人写一个0-100之间的有理数 (不包括0或100),交给裁判,裁判算出所有数字的平均值,然后乘以0 ...

  2. 软件工程 in MSRA 黄金点游戏-第一次结对编程

    简单介绍 第一次结对编程,邹欣老师选择了一个博弈游戏作为题目.博弈论是一门非常有趣的学科.之前竞赛时接触的博弈论大部分都是存在均衡点/必胜策略的.像这次这种多人参与,没有完美策略,你方唱罢我登台的游戏 ...

  3. Python Web实战 - 基于Flask实现的黄金点游戏

    一.简介 团队成员: 领航者:张旭 驾驶员:张国庆 项目简介: 项目名称:基于B/S模式的黄金点游戏 采用技术: 后端:Python + Sqlite3 前端:HTML + CSS + JS + Bo ...

  4. 黄金点游戏 结队i项目

    结对编程——黄金点游戏   本次的结对编程的项目是黄金点游戏,我的结对对象是冯雨倩,我们的编程能力都不太好,而且都对C语言更熟悉些,因此我们决定用C语言来实现. (1)分工:角色分配:冯雨倩是领航员, ...

  5. 结对项目--黄金点游戏(邓乐&曾亮)

    #include<stdio.h> #include<stdlib.h> #include<Windows.h> int result[100][1000000]; ...

  6. ASE "黄金点游戏"

    问题定义 黄金点游戏是源于经济学家Richar Thaler构思的在1997年伦敦金融时报进行了一次公开竞猜活动.MSRA-ASE课程的第一次结对编程中,我们写了一个AI Bot来与大家玩儿这个游戏. ...

  7. 结对编程--基于android平台的黄金点游戏

    游戏内容: 阿超的课都是下午两点钟,这时班上不少的同学都昏昏欲睡,为了让大家兴奋起来,阿超让同学玩一个叫“黄金点”的游戏: N个同学(N通常大于10),每人写一个0~100之间的有理数 (不包括0或1 ...

  8. 结对编程—黄金点游戏WinForm单机版

    本小游戏场景来自邹欣老师的<移山之道>一书: "阿超的课都是下午两点钟,这时班上不少的同学都昏昏欲睡,为了让大家兴奋起来,阿超让同学玩一个叫"黄金点"的游戏: ...

  9. 基于js脚本的单机黄金点游戏

    题目描述 N个同学(N通常大于10),每人写一个0-100之间的有理数 (不包括0或100),交给裁判,裁判算出所有数字的平均值,然后乘以0.618(所谓黄金分割常数),得到G值.提交的数字最靠近G( ...

随机推荐

  1. Vim的可视模式

    可视模式可以看到选中的字符串, 并对其进行操作 v:进入字符选择模式 V:进入行选择模式 ctrl-v(Window是ctrl-q):进入block选择模式 o:移动光标到选择的另一端 O:移动光标到 ...

  2. 在Ubuntu为Android硬件抽象层(HAL)模块编写JNI方法提供Java访问硬件服务接口(老罗学习笔记4)

    在上两篇文章中,我们介绍了如何为Android系统的硬件编写驱动程序,包括如何在Linux内核空间实现内核驱动程序和在用户空间实现硬件抽象层接口.实现这两者的目的是为了向更上一层提供硬件访问接口,即为 ...

  3. UrlRewriter.dll伪静态实现二级域名泛解析

    大家应该知道,微软的URLRewrite能够对URL进行重写,但是也只能对域名之后的部分进行重写,而不能对域名进行重写, 如:可将 http://http://www.115sou.com/qq/  ...

  4. 解析CSS加密技术之“障眼法”

    CSS(Cascading Style Sheet,可译为“层叠样式表”或“级联样式表”)是一组格式设置规则,用于控制Web页面的外观.通过使用CSS样式设置页面的格式,可将页面的内容与表现形式分离. ...

  5. aspx中的表单验证 jquery.validate.js 的使用 以及 jquery.validate相关扩展验证(Jquery表单提交验证插件)

    这一期我们先讲在aspx中使用 jquery.validate插件进行表单的验证, 关于MVC中使用 validate我们在下一期中再讲     上面是效果,下面来说使用步骤 jQuery.Valid ...

  6. WTL汉化版2013.10.15

    汉化内容: 2013.10.15 版本:当前可下载Trunk最新版,wtl-code-467-trunk.zip 汉化内容: 1.应用向导的部分汉化,考虑到部分词汇的表述问题,只汉化无影响部分 2.资 ...

  7. python - 回溯继承树 - 自己实现

    # -*- coding: utf-8 -*- class test(object): pass class test1(test): pass class test2(test1): pass pr ...

  8. zoj 1967 Fiber Network/poj 2570

    题意就是 给你 n个点 m条边 每条边有些公司支持 问 a点到b点的路径有哪些公司可以支持 这里是一条路径中要每段路上都要有该公司支持 才算合格的一个公司// floyd 加 位运算// 将每个字符当 ...

  9. javascript实现map的功能(转载)

    /* * MAP对象,实现MAP功能 * * 接口: * size() 获取MAP元素个数 * isEmpty() 判断MAP是否为空 * clear() 删除MAP所有元素 * put(key, v ...

  10. Ejabberd源码解析前奏--管理

    一.ejabberdctl 使用ejabberdctl命令行管理脚本,你可以执行ejabberdctl命令和一些普通的ejabberd命令(后面会详细解说).这意味着你可以在一个本地或远程ejabbe ...