黄金点游戏之客户端(homework-05)

时间:2021-06-27 05:29:04

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显示了状态机、信息收发的情况,正常。通过服务器读取客户端发来的消息,并发送正确指令,测试正常:

  客户端:

  黄金点游戏之客户端(homework-05)

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

  黄金点游戏之客户端(homework-05)

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方法),并使用最小二乘法逼近黄金点曲线的方法来预测数值。