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

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. 主要代码

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

  

  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.Linq;
  7 using System.Text;
  8 using System.Threading.Tasks;
  9 using System.Windows.Forms;
 10 using System.Net;
 11 using System.Net.Sockets;
 12 using System.Threading;
 13 using System.IO;
 14 
 15 namespace AsyncTcpServer
 16 {
 17     public partial class mainForm : Form
 18     {
 19 
 20         const int STATE_REGISTER = 1;
 21         const int STATE_REGISTER_OK = 2;
 22         const int STATE_NEW_ROUND = 3;
 23         const int STATE_SUBMIT = 4;
 24         const int STATE_SUBMIT_OK = 5;
 25         const int STATE_SERVER_STOP = 6;
 26         const int INTERVAL = 3000;
 27 
 28         const string TYPE_REGISTER = "1";
 29         const string TYPE_REGISTER_OK = "2";
 30         const string TYPE_NEW_ROUND = "3";
 31         const string TYPE_SUBMIT = "4";
 32         const string TYPE_SUBMIT_OK = "5";
 33         const string TYPE_SERVER_STOP = "6";
 34 
 35         const string    DEFAULT_SERVER_IP = "192.168.1.2";    
 36         const int       DEFAULT_SERVER_PORT =  51888;
 37         const string    DEFAULT_USER_ID = "11061128";
 38         const string    DEFAULT_USER_PASSWD = "123456";
 39 
 40         private string ip_input = DEFAULT_SERVER_IP;
 41         private int port = DEFAULT_SERVER_PORT;
 42         private string id = DEFAULT_USER_ID;
 43         private string passwd = DEFAULT_USER_PASSWD;
 44 
 45         private int tstate = 2;
 46         private int State = STATE_REGISTER;
 47         private int Round = 0;
 48         private int[] PrevRslt = new int[5000];
 49         private int PrevRslt_idx = 0;
 50         private int GoldPoint;
 51         private string[] rcvs;
 52         private bool isRunning = false;
 53 
 54         private TcpClient client = null;
 55         private StreamWriter sw;
 56         private StreamReader sr;
 57         private Service service;
 58         private NetworkStream netStream;
 59 
 60         Thread threadRegister;
 61         Thread threadSubmit;
 62         Thread threadReceive;
 63         Thread threadWaitNewRound;
 64         Thread threadControl;
 65 
 66         public mainForm()
 67         {
 68             InitializeComponent();
 69             service = new Service(lb_log, sw);
 70         }
 71 
 72         private void btn_start_Click(object sender, EventArgs e)
 73         {
 74             if (isRunning == true)
 75             {
 76                 service.SetListBox("Already running, Press STOP first");
 77                 return;
 78             }
 79 
 80             if (String.Compare(tb_svr_IP.Text, "IP") != 0)
 81             {
 82                 ip_input = tb_svr_IP.Text.Trim();
 83             }
 84             if (String.Compare(tb_svr_port.Text, "PORT") != 0)
 85             {
 86                 port = Int32.Parse(tb_svr_port.Text.Trim());
 87             }
 88             if (String.Compare(tb_id.Text, "ID") != 0)
 89             {
 90                 id = tb_id.Text.Trim();
 91             }
 92             if (String.Compare(tb_passwd.Text, "PASSWORD") != 0)
 93             {
 94                 passwd = tb_passwd.Text.Trim();
 95             }
 96             IPAddress serverIP = IPAddress.Parse(ip_input);
 97             client = new TcpClient();
 98             try
 99             {
100                 client.Connect(serverIP, port);
101             }
102             catch (System.Exception ex)
103             {
104                 service.SetListBox(ex.Message);
105                 return;
106             }
107             try
108             {
109                 netStream = client.GetStream();
110             }
111             catch (System.Exception ex)
112             {
113                 service.SetListBox(ex.Message);
114                 return;
115             }
116             sr = new StreamReader(netStream, System.Text.Encoding.UTF8);
117             sw = new StreamWriter(netStream, System.Text.Encoding.UTF8);
118             service = new Service(lb_log, sw);
119             isRunning = true;
120             threadControl = new Thread(new ThreadStart(control));
121             threadControl.Start();
122         }
123 
124         private void control()
125         {
126             threadRegister = new Thread(new ThreadStart(Register));
127             //threadSubmit = new Thread(new ThreadStart(Cal_and_Submit));
128             threadReceive = new Thread(new ThreadStart(ReceiveData));
129             //threadWaitNewRound = new Thread(new ThreadStart(WaitNewRound));
130             //threadWaitNewRound.Start(); threadReceive.Suspend();
131             //threadSubmit.Start(); threadSubmit.Suspend();
132 
133             while (isRunning == true)
134             {
135                 if (tstate == 1) //wait finish
136                 {
137                     threadWaitNewRound.Abort();
138                     tstate = 0;
139                 }
140                 else if(tstate == 0)//waiting
141                 {
142                     continue;
143                 }
144                 //nothing to wait
145                 switch (State)
146                 {
147                     case STATE_REGISTER:
148                         threadRegister.Start();
149                         threadRegister.Join();
150                         State = STATE_REGISTER_OK;
151                         break;
152                     case STATE_REGISTER_OK:
153                         threadWaitNewRound = new Thread(new ThreadStart(WaitNewRound));
154                         threadWaitNewRound.Start();
155                         threadWaitNewRound.Join();
156                         tstate = 2;
157                         State = STATE_NEW_ROUND;/////
158                         break;
159                     case STATE_NEW_ROUND:
160                         threadSubmit = new Thread(new ThreadStart(Cal_and_Submit));
161                         threadSubmit.Start();
162                         threadSubmit.Join();
163                         State = STATE_SUBMIT_OK;
164                         break;
165                     case STATE_SUBMIT_OK:
166                         threadWaitNewRound = new Thread(new ThreadStart(WaitNewRound));
167                         threadWaitNewRound.Start();
168                         threadWaitNewRound.Join();
169                         State = STATE_NEW_ROUND;
170                         tstate = 2;
171                         break;
172                     case STATE_SERVER_STOP:
173                         service.SetListBox("Server Stop");
174                         endMission();
175                         break;
176                     default:
177                         break;
178                 }
179             }
180         }
181 
182         private void ReceiveData()
183         {
184             while (isRunning == true)
185             {
186                 string receiveString = null;
187                 try
188                 {
189                     receiveString = sr.ReadLine();
190                 }
191                 catch (Exception e)
192                 {
193                     service.SetListBox(e.Message);
194                 }
195 
196                 if (receiveString == null)
197                 {
198                     //
199                     service.SetListBox("wait to re receive");
200                     //
201                     Thread.Sleep(INTERVAL);
202                     continue;
203                 }
204                 //
205                 service.SetListBox("recieved" + receiveString);
206                 //
207                 rcvs = receiveString.Split(';');
208                 switch (rcvs[0])
209                 {
210                     case TYPE_REGISTER_OK:
211                         State = STATE_REGISTER_OK;
212                         break;
213                     case TYPE_SUBMIT_OK:
214                         if (Round == Int32.Parse(rcvs[1]))
215                         {
216                             State = STATE_SUBMIT_OK;
217                         }
218                         break;
219                     case TYPE_NEW_ROUND:
220                         if (Round == Int32.Parse(rcvs[1]) - 1)
221                         {
222                             State = STATE_NEW_ROUND;
223                         }
224                         break;
225                     case TYPE_SERVER_STOP:
226                         State = STATE_SERVER_STOP;
227                         service.SetListBox("Server Stop");
228                         tstate = 2;
229                         endMission();
230                         break;
231                     default:
232                         break;
233                 }
234             }
235         }
236 
237         private void Register()
238         {
239             threadReceive.Start();
240             String str = TYPE_REGISTER + ";" + id + ";" + passwd;
241             service.SendToServer(str);
242             while (isRunning && State != STATE_REGISTER_OK)
243             {
244                 //
245                 service.SetListBox("re register");
246                 //
247                 Thread.Sleep(INTERVAL);
248                 service.SendToServer(str);
249             }
250             threadReceive.Suspend();
251         }
252 
253         private void WaitNewRound()
254         {
255             threadReceive.Resume();
256             while (isRunning && State != STATE_NEW_ROUND)
257             {
258                 //
259                 service.SetListBox("waiting");
260                 //
261                 Thread.Sleep(INTERVAL);
262             }
263             Round++;
264             PrevRslt[PrevRslt_idx++] = Int32.Parse(rcvs[1]);
265             threadReceive.Suspend();
266             tstate = 1;
267         }
268 
269         private void Cal_and_Submit()
270         {
271             threadReceive.Resume();
272             GoldPoint = calculate();
273             string str = TYPE_SUBMIT + ";" + Round.ToString() + ";" + GoldPoint.ToString();
274             service.SendToServer(str);
275             while (isRunning && State != STATE_SUBMIT_OK)
276             {
277                 Thread.Sleep(INTERVAL);
278                 //
279                 service.SetListBox("submitting");
280                 //
281                 service.SendToServer(str);
282             }
283             threadReceive.Suspend();
284         }
285         private int calculate()
286         {
287             //
288             service.SetListBox("calculation");
289             //
290             if (Round == 1)
291             {
292                 return 100;
293             }
294             else
295             return (int)(PrevRslt[PrevRslt_idx] * 0.618);
296         }
297  
298         private void btn_stop_Click(object sender, EventArgs e)
299         {
300             endMission();
301         }
302 
303         private void mainForm_FormClosing(object sender, FormClosingEventArgs e)
304         {
305             endMission();
306         }
307 
308         private void endMission()
309         {
310             //
311             service.SetListBox("ending mission");
312             //
313             if (isRunning)
314             {
315                 netStream.Close();
316                 client.Close();
317             }
318             isRunning = false;
319         }
320     }
321 }

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

posted @ 2013-11-04 11:44  Shone JIN  阅读(346)  评论(2编辑  收藏  举报