开学了,去图书馆借了几本书,没有找到想要的C++网络编程,却是找到了几本LINUX的书,以及一本《Visual C#经典游戏编程开发》。翻了翻发现里面有个能够联网对弈的中国象棋游戏,一直写ASP.NET的网页,也想试着写写Winform的图形程序了,并且能够了解一下.NET的网络编程相关的内容。编程
象棋方面,先从网上找了张棋盘图片,在PS里把棋子一一扣了出来做为素材。编写对应的棋子类和棋盘类,前者实现棋子身份的识别和棋子图片的绘制,后者实现棋盘绘制、走棋规则、棋谱生成等功能。c#
网络通讯的协议使用的是UDP协议,发送的信息规则是:“命令|参数1|参数2……”,主要命令:join表示用户想要联机,处于接受对方链接的状态;conn表示收到对方联机命令,已准备好开始游戏,对方能够开局了;new表示其中一方提出从新开始游戏;move|id|x|y表示移动棋盘上编号为id的棋子到x,y处;succ|赢方代号 表示此局已分出胜负;exit表示退出游戏。网络
游戏开始后建立了一个线程,在read方法中建立UdpClient对象,循环侦听指定端口中由指定IP主机传来的信息。主要代码是:socket
udpClient = new UdpClient(Convert.ToInt32(txt_port.Text)); int id, x, y; while(readFlag == true) { try { byte[] data = udpClient.Receive(ref remote); string strData = Encoding.UTF8.GetString(data); string[] a = strData.Split('|'); // 分割命令与参数 switch(a[0]) // 判断命令 { case "join": case "conn": //...... } } catch { break; } }
游戏过程当中发送数据则是经过send方法,建立UdpClient网络服务对象,向指定IP的主机发送消息到设定的端口号。完成后发送后,关闭UDP网络服务。主要代码:spa
UdpClient sendUdp = new UdpClient(); UPAddress remoteIP; try { remoteIP = IPAddress.Parse(txt_IP.Text); } catch { MessageBox.Show("请输入正确的IP地址!", "错误"); return; } remote = new IPEndPoint(remoteIP, Convert.ToInt32(txt_remoteport.Text)); byte[] buffer = Encoding.UTF8.GetBytes(str); sendUdp.Send(buffer, buffer.Length, remote); sendUdp.Close();
原书中的代码有一些小问题,因此作了点改动和简化。不过基本的思路仍是和原书上是同样的。线程
通讯过程方面,每次联机都须要双方填写好对方的IP、端口号以及本身的端口号。并且,因为没有明显的游戏过程标记,因此形成了一些通讯问题,致使在不正确的阶段收到对方错误的请求时游戏出现意外的状况。3d
因而在完成后,就开始了对原程序的改造。考虑到内网用户链接外网的问题,就将游戏中的UDP协议通讯改成了使用Socket经过TCP协议进行游戏通讯。添加游戏状态state字段,包括如下几个主要状态:code
public const short WAITING_CLIENT = 1; // 做为主机,等待客户端链接 public const short SERVING = 2; // 做为主机,客户端已链接,游戏中 public const short WAITING_SERVER = 3; // 做为从机,等待主机接受链接 public const short CONNECTING = 4; // 做为从机,链接到主机,游戏中
游戏的命令和监听流程也作了对应的改动。orm
主机创建游戏等待客户端链接的过程主要包括:监听端口、创建链接、开始监听消息,这三个步骤。首先建立线程,在线程中执行acceptClientConnect监听端口等待链接,检测到链接后创建链接,转入Read监听阶段。对象
private void WaitClient() { // 启动一个线程来接受请求 thread = new Thread(acceptClientConnect); thread.Start(); SetState(WAITING_CLIENT); } // 接受请求 private void acceptClientConnect() { IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, DEFAULT_PORT); listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); toolStripStatusLabel1.Text = "正在等待其余玩家链接……"; try { if (timeoutThread != null) { timeoutThread.Abort(); } timeoutThread = new Thread(WaiteTimeOut); timeoutThread.Start(); listener.Bind(localEndPoint); listener.Listen(1); connection = listener.Accept(); listener.Close(); listener = null; Read(); } catch { toolStripStatusLabel1.Text = "链接中断,游戏终止。"; SetState(FREE); } } private void WaiteTimeOut() { Thread.Sleep(30000); if (connection == null || !connection.Connected) { if (listener != null) { SetState(FREE); toolStripStatusLabel1.Text = "链接超时,无玩家链接游戏。"; } } timeoutThread = null; }
预约的监听超时时间是30秒,30秒后没有链接时,转到FREE状态并终止监听。终止的实现思路是在主机自己建立一个TCP协议的Socket对象,向自身端口发送终止链接的消息,listener创建链接后在Read中接受到此消息中止阻塞,结束游戏完全转为FREE状态,线程结束。
private void SetState(int newState) // 支线程 { state = newState; switch (state) { case FREE: btnCreate.Text = "建立游戏"; btnCreate.Enabled = true; btnJoin.Text = "加入游戏"; btnJoin.Enabled = true; if (listener != null) { Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Connect(IPAddress.Parse("127.0.0.1"), DEFAULT_PORT); if (socket.Connected) { socket.Send(Encoding.UTF8.GetBytes("end")); } } if (connection != null) { connection.Close(); connection = null; } break; case WAITING_CLIENT: btnCreate.Text = "关闭游戏"; btnJoin.Enabled = false; break; case SERVING: break; case WAITING_SERVER: btnJoin.Text = "断开游戏"; btnCreate.Enabled = false; break; case CONNECTING: break; } }
接受消息的Read方法和发送消息的Send方法方面,本来的UdpClient收发消息改成经过建立的connection(Socket)链接来通讯。
private void Read() { int t, x1, y1, x2, y2; while (state != FREE) { try { byte[] data = new byte[1024]; int len = connection.Receive(data); string msg = Encoding.UTF8.GetString(data); string[] a = msg.Split('|'); switch (a[0]) { case "end": // 终止listener的监听 if (state != WAITING_CLIENT) continue; connection.Close(); connection = null; SetState(FREE); return; case "join": // 客户端请求加入游戏 break; case "acce": // 服务端接受加入的请求 break; case "exit": // 对方方退出游戏 break; case "new": // 对方方提出从新开局 break; case "succ": // 一方获胜 break; case "lose": // 一方认输 break; case "move": // 对方移动棋子 break; } } catch { break; } } } private void Send(string msg) { if (connection != null) { byte[] buffer = Encoding.UTF8.GetBytes(msg); connection.Send(buffer); } }
具体游戏效果截图:
项目工程下载: