第 15 章
网络五子棋游戏模块
(Socket 编程+UDP 协议实现)
随着计算机信息技术的发展,网络游戏已成为人们生活中的重要组
成部分,由于网络游戏占用的资源比较大,易受到病毒的攻击,所以局
域网游戏受到众多游戏爱好者的亲睐。五子棋游戏因其简单的规则、漂
亮的界面,而深受广大玩家的喜爱,而对于初步探索网络编程的编程爱
好者来说,五子棋游戏因其规则简单而很受欢迎。本章将制作一个具有
游戏大厅和多人对话功能的网络五子棋游戏。通过本章的学习,读者能
够学到:
UDP 协议的使用
如何实现带图片的下拉列表
进入房间的指定座位
显示当前房间的人员信息列表
显示当前房间的人员信息
在对决窗体中下棋
判断五子棋的输赢情况
局域网互相发送信息
C#典型模块与项目实战大全
15.1 网络五子棋游戏模块概述
15.1.1 模块概述
网络五子棋游戏模块的主要实现目标如下:
操作简单方便、界面简洁美观;
在注册时设置注册人员的头像和性别;
进入某区的房间时,显示当前房间的人员信息;
进入和退出座位时,显示座位状态;
在对决双方的棋盘上显示棋子;
在下完棋子后,高亮度显示最后下的棋子;
按五子棋的规则判断对决双方的输赢;
在服务器端对人员的分数进行排序。
了解网络五子棋游戏模块的主要实现目标之后,接下来开始制作五子棋游戏,但在制
作之前,必须要有一个大体的思路。
制作网络五子棋游戏模块,首先需要创建两个 Windows 应用程序,分别用来作为客
户端和服务器端(即注册、登录、大厅及对决窗体),其中,客户端主要用于实现用户注
册、登录、发送信息和五子棋对决等功能;服务器端主要用于显示在线人员的状态,并作
为客户端向远程客户端发送信息的一个中转站。然后创建一个 Windows 类库,主要用于
记录传递信息的结构。图 15.1 所示为网络五子棋游戏模块的业务流程图。
2
图 15.1 网络五子棋游戏模块的业务流程图
第 15 章 网络五子棋游戏模块(Socket 编程+UDP 协议实现)
实
践
真
知
技巧
客户端与服务器端所使用的 UDP 协议是同一个协议,如果使用两个 UDP
协议进行传输,它们的程序集将不匹配,无法进行通信。
15.1.2 功能结构
网络五子棋游戏模块的功能结构如图 15.2 所示。
网络五子棋游戏模块
服务器端
客户端
开
启
服
务
停
止
服
务
查
看
分
数
排
名
用
户
注
册
系
统
登
录
显
示
人
员
列
表
进
入
指
定
座
位
五
子
棋
对
决
判
断
五
子
棋
输
赢
局
域
网
聊
天
增
加
\减
少
分
数
图 15.2 网络五子棋游戏模块的功能结构图
15.1.3 程序预览
网络五子棋游戏模块是由多个窗体组成,下面列出几个典型窗体。
服务器窗体如图 15.3 所示,该窗体主要用于控制 UDP 协议的开启和关闭。客户端注
册窗体如图 15.4 所示,该窗体主要用于注册用户,并将注册的信息发送给服务器端进行
记录。
图 15.3 服务器窗 图 15.4 客户端注册窗体
3
C#典型模块与项目实战大全
五子棋大厅窗体如图 15.5 所示,该窗体主要用于显示五子棋的房间人员情况及在线
用户信息。
对决窗体如图 15.6 所示,该窗体主要用于实现五子棋对决功能。
图 15.5 五子棋大厅窗体
图 15.6 对决窗体
15.2 关键技术
15.2.1 在下拉列表中绘制图片
4
本模块在对游戏用户进行注册时,为了便于在游戏中区分用户,自制了一个带有图片
的下拉列表,通过该列表用户可以随意设置头像,如图 15.7 所示。
第 15 章 网络五子棋游戏模块(Socket 编程+UDP 协议实现)
图 15.7 带有图片的下拉列表
在下拉列表中绘制图片是使用 ComboBox 控件和 GDI+技术来实现的,在 ComboBox
控件的列表项中绘制图片前要对 DrawMode 和 DropDownStyle 属性进行设置。下面对这两
个属性进行详细说明。
(1)DrawMode 属性。该属性用于获取或设置一个值,该值指示是由代码还是由操
作系统来处理列表中元素的绘制。
语法:
public DrawMode DrawMode { get; set; }
说明:它的属性值为 DrawMode 枚举值之一,默认为 Normal。DrawMode 的枚举值
如表 15.1 所示。
表 15.1 DrawMode 的枚举值
枚 举 值
说 明
Normal
控件中的所有元素都由操作系统绘制,并且元素大小都相等
OwnerDrawFixed
控件中的所有元素都由手动绘制,并且元素大小都相等
OwnerDrawVariable
控件中的所有元素都由手动绘制,元素大小可能不相等
(2)DropDownStyle 属性。该属性用于确定用户能否在文本部分中输入新值以及列
表部分是否总显示。
语法:
public ComboBoxStyle DropDownStyle { get; set; }
说明:它的属性值为 ComboBoxStyle 枚举值之一,默认为 DropDown。ComboBoxStyle
的枚举值如表 15.2 所示。
枚 举 值
DropDown
DropDownList
Simple
表 15.2 ComboBoxStyle 的枚举值
说 明
文本部分可编辑,用户必须单击箭头按钮来显示列表部分。这是默认样式
用户不能直接编辑文本部分,用户必须单击箭头按钮来显示列表部分
文本部分可编辑,列表部分总可见
在 ComboBox 控件的列表项中绘制图片,主要是在该控件的 DrawItem 事件中进行的,
该事件在 ComboBox 控件的可视方位更改时发生。
下面介绍在 ComboBox 控件中绘制图片的相关步骤。
(1)首先在窗体的 Shown(第一次显示)事件中设置 ComboBox 控件的 DrawMode
和 DropDownStyle 属性,使该控件的下拉项可以进行手动绘制,并且不能在文本部分输入
新值。代码如下:
5
C#典型模块与项目实战大全
private void F_SerSetup_Shown(object sender, EventArgs e)
{
comboBox_CPhoto.Items.Clear();
comboBox_CPhoto.DrawMode = DrawMode.OwnerDrawFixed;//所有元素是手动绘制的
comboBox_CPhoto.DropDownStyle = ComboBoxStyle.DropDownList;
//根据绘制图片的个数,添加空的列表项
for (int i = 0; i < imageList1.Images.Count; i++)
{
comboBox_CPhoto.Items.Add("");
}
}
(2)然后在 ComboBox 控件的 DrawItem 事件中,将 ImageList 组件所存储的图片绘
制到下拉列表项中。代码如下:
private void comboBox_CPhoto_DrawItem(object sender, DrawItemEventArgs e)
{
Graphics g = e.Graphics;
Rectangle r = e.Bounds;
Size imageSize = imageList1.ImageSize;
if (e.Index >= 0)
{
string s = (string)comboBox_CPhoto.Items[e.Index];
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Near;
//如果当前项有焦点或有键盘加速键
if (e.State == (DrawItemState.NoAccelerator | DrawItemState.NoFocusRect))
{
imageList1.Draw(e.Graphics, r.Left, r.Top, e.Index); //绘制图像
e.DrawFocusRectangle();
//显示取得焦点时的虚线框
}
else
{
e.Graphics.FillRectangle(new SolidBrush(Color.White), r);
imageList1.Draw(e.Graphics, r.Left, r.Top, e.Index);
e.DrawFocusRectangle();
}
}
}
//设置文本的对齐方式
//设置各项的背景颜色
//绘制图片
//显示取得焦点时的虚线框
15.2.2 UDP 协议的使用
UDP(User Datagram Protocol)协议,即“用户数据报协议”,它是一种无连接协议,
在用该协议进行数据传输时,发送方只需要知道对方的 IP 地址和端口号就可以发送数
据,并不需要进行连接,当连接的远程主机端口号处于监听状态时,则 UDP 必须处于
连接状态。
使用 C#发送和接收 UDP 数据包主要用到 UdpClient 类的 Send 方法和 Receive 方法,
下面分别对它们进行介绍。
6
1.Send 方法
该方法将 UDP 数据报发送给指定的远程计算机,其语法格式如下:
public int Send ( byte[] dgram , int bytes , IPEndPoint endPoint );
第 15 章 网络五子棋游戏模块(Socket 编程+UDP 协议实现)
Send 方法的参数说明如表 15.3 所示。
表 15.3 Send 方法的参数说明
参 数
说 明
dgram
bytes
endPoint
filePath
返回值
要发送的 UDP 数据文报(以字节数组表示)
数据文报中的字节数
一个 IPEndPoint,表示要将数据文报发送到的主机和端口
文件所在路径
返回已发送的字节数
下面使用 Send 方法向远程计算机发送消息,代码如下:
byte[] Data = Encoding.Unicode.GetBytes(richTextBox1.Rtf);
UdpClient server = new UdpClient();
IPAddress IP = IPAddress.Parse("192.168.1.230");
IPEndPoint receivePoint = new IPEndPoint(IP, 11000);
server.Send(Data, Data.Length, receivePoint);
2.Receive 方法
该方法用于返回远程主机发送的 UDP 数据报,其语法格式如下:
public byte [] Receive ( ref IPEndPoint remoteEP ) ;
说明:RemoteEP 表示一个 IPEndPoint 类的实例,表示网络中发送此数据包的节点。
它的返回值为一个 Byte 类型的数组,它包含数据报数据。
如果指定了远程计算机发送到本地计算机的端口号,可以通过监听本地端口号来实现
对数据的获取,下面就是通过监听本地计算机的端口号“11000”来获取信息的相关代码:
UdpClient server = new UdpClient();
IPAddress IP = IPAddress.Parse("127.0.0.1");
IPEndPoint receivePoint = new IPEndPoint(IP, 11000);
byte[] recData = server.Receive(ref receivePoint);
15.2.3 用 Socket 实现消息传递的必备条件
在刚开始编写网络五子棋游戏模块时,客户端和服务器端分别调用 GobangClass 类库
(主要是使用自定义控件 UDPSocket 实现局域网的通信),在客户端向服务器端发送消息
时,会弹出错误提示,具体提示为:无法找到程序集“GobangClass, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null”。
那么,如何解决上面出现的这种问题呢?开发人员只需要同时将类库(主要是 UDP
协议)添加到客户端和服务器端的引用中,使客户端和服务器端调用同一个类库即可。
实
践
真
知
技巧
开发网络五子棋游戏模块时,为了便于对程序的编写和调试,将客户端解
7
决方案和类库都添加到了服务器端解决方案中。
C#典型模块与项目实战大全
15.2.4 自定义事件的设置
本模块在设置五子棋的下棋过程中,设置了几个自定义事件来完成在单击棋盘时添加
棋子的操作。
事件是类和对象向外界发出的消息,事件的执行是通过事件委托的方式,调用已准备
好的处理方法,它是在消息之前响应的。要响应事件并针对某些事件执行已定义的方法,
需要做到以下几步:
//发布事件的类
//定义事件参数类
(1)声明事件委托;
(2)声明事件;
(3)添加事件的触发方法;
(4)添加事件的处理程序(响应事件的方法);
(5)将指定的事件处理程序绑定到要处理的事件上(订阅事件);
(6)用户信息操作并触发事件(调用事件的触发方法);
(7)通过事件委托的回调,执行需要的事件处理程序。
下面列举一个简单的自定义事件处理程序的示例,代码如下:
public class TestEventSource
{
public class TestEventArgs : EventArgs
{
public readonly char KeyToRaiseEvent;
public TestEventArgs(char keyToRaiseEvent)
{
KeyToRaiseEvent = keyToRaiseEvent;
}
}
//定义 delegate
public delegate void TestEventHandler(object sender, TestEventArgs e);
public event TestEventHandler TestEvent;
//用 event 关键字声明事件对象
protected virtual void OnTestEvent(TestEventArgs e)//事件触发方法
{
if (TestEvent != null)
TestEvent(this, e);
}
public void RaiseEvent(char keyToRaiseEvent)
{
TestEventArgs e = new TestEventArgs(keyToRaiseEvent);
OnTestEvent(e);
}
}
public class TestEventListener
{
//定义处理事件的方法,与声明事件的 delegate 具有相同的参数和返回值类型
public void KeyPressed(object sender, TestEventSource.TestEventArgs e)
{
Console.WriteLine(" 发 送 者 : {0} , 所 按 的 键 为 : {1}", sender,
}
public void Subscribe(TestEventSource evenSource) //订阅事件
{
evenSource.TestEvent += new TestEventSource.TestEventHandler(KeyPressed);
}
//监听事件的类
//引发事件
8
e.KeyToRaiseEvent);