源码上传csdn https://download.csdn.net/download/x1135299049/80391295
一.【设计目的】
1. 熟悉开发工具(eclipse)的基本操作;
2. 掌握应用程序的编写过程;
3. 对于 Socket 编程建立初步的概念。
二.【设计要求】
1. 熟悉 Sock API 主要函数的使用;
2. 掌握相应开发工具对 Socket API 的封装;
3. 设计并实现一对一网络版小游戏,本次课程设计实现的是五子棋。
三.【工作原理】
- 实现环境
语言:java,jdk 1.8
编译器 eclipse
使用Socket和Thread进行服务器和客户端的通信和同步。
- 功能分析
对用户来说,通过启动客户端后应该实现的功能有:
- 与服务器连接。
- 完成连接后,显示服务器当前的用户。
- 完成连接后,与服务器当前用户聊天,实现私聊和公聊。
- 完成连接后,创建游戏进入匹配状态。
- 完成连接后,加入别的用户创建的游戏。
- 匹配成功后,与对手一起下棋。
- 实现放弃游戏即弃权功能。
- 实现离开游戏功能即断开连接退出程序。
对客户端来说,应该实现功能有:
- 与服务器连接。
- 实现下棋的棋盘。
- 对用户操作进行反馈和提醒。
- 完成连接后,显示服务器当前的用户。
- 捕获用户输入的消息和对象,处理后显示在相应用户的相应面板上。
- 收到用户创建游戏指令后,向服务器发送相应消息,实现功能。
- 收到用户加入游戏指令后,向服务器发送相应消息,实现功能。
- 开始游戏后,捕获用户下棋指令并实现。
- 实现游戏双方的同步。
对服务端来说,应该实现功能有:
- 接收客户端的连接,并为之分配单独的线程。
- 显示当前的连接信息,如连接数,服务器状态。
- 显示从客户端收到的信息和处理结果,并能刷新消息列表。
- 处理客户端的信息,并输出结果给客户端。
- 实现游戏双方的同步。
- 断开所有连接关闭服务端。
- 模块划分
根据功能分析划分如下模块
网络版五子棋小游戏 | ||
模块名 | 文件名 | 功能描述 |
用户面板模块 | UserChatPanel.java | 用户聊天面板 |
UserControlPanel.java | 用户操作如创建、加入 | |
UserInputPanel | 用户输入消息 | |
UserListPanel | 用户列表 | |
棋盘面板模块 | Pad | 棋盘panel |
PadThread | 操作棋盘线程 | |
PointBlack | 画黑棋 | |
PointWhite | 画白棋 | |
客户端模块 | ClientServer | 客户端界面 |
ClientThread | 客户端线程 | |
服务器模块 | GameServer | 服务器界面 |
ServerThread | 服务器线程 |
如图
四.【设计内容】
- 客户端端设计
- 用户面板模块
- UserChatPanel
继承JPanel,内含JTextArea实现用户聊天模块。
- UserControlPanel
继承JPanel,内含五个JButton对象实现连接服务器、创建游戏、加入游戏、放弃游戏、退出程序功能,和一个JTextField捕获用户输入的服务器IP地址。
- UserInputPanel
继承JPanel,内含JTextField和JComboBox实现用户选中对象后聊天。
- UserListPanel
继承Panel,含List组件实现用户列表显示。
- 棋盘面板模块
-
- Pad
继承Panel,含有一TextField作为对用户操作的提醒和反馈,放在setBounds(40, 5, 360, 24),同时实现棋盘功能。
Pad.java类所声明的常量
首先要画出棋盘,重写继承的Paint(Graphics g)方法通过drawLine画棋盘,每个线间隔20。
然后编写connectServer(String ServerIP, int ServerPort)连接到服务器获取Socket和输入输出流以及启动padThread,因为要向服务器发送棋盘消息实现同步。
编写setLocation(int x,int y,int chessColor)把棋子棋盘位置转换成页面位置并且按颜色加入相应颜色的数组且对应棋子数量+1;编写checkWinStatus判断下这一步后我方是否胜利:编写setWinStatus设定胜利时的棋盘状态,棋盘清零输出胜负。
重写mousePressed(MouseEvent e)捕获鼠标左键事件,获取当前位置判断是否在正确位置上,是调用paintPoint(int x,int y,int chessColor)画棋子,通过isMouseEnable判断是否在自己回合,在自己回合才能在相应位置画棋子同时,同时线程发送信息给服务器,实现同步。
sendMessage("/"+chessPeerName+" /chess "+x+" "+y+" "+chessColor);
然后checkWinStatus若胜利调用setWinStatus;否则TextField显示下棋位置
编写paintNetPad(int x,int y,int chessColor)画网络棋盘,padThread线程在收到服务器发送对手行动后消息后调用该方法画出对手下的位置,同样进行胜利判断,若对手胜利线程想服务器发送对手胜利消息;
sendMessage("/"+chessPeerName+" /victory "+chessColor);
否则TextField显示下棋位置。
-
- PointWhite
继承Canvas,重写paint方法画白棋,棋盘pad添加主件即可显示。
-
- PointBlack
继承Canvas,重写paint方法画黑棋,棋盘pad添加主件即可显示。与画白棋基本相同
-
- PadThread
负责接收和处理服务端发来的信息,以及向服务端发消息。
sendMessage向服务端发消息currPad.outputStream.writeUTF(sendMessage);
dealWithMsg处理服务端发来的信息,服务端发来的信息处理的种类有:
/chess,表示对手下棋信息,获得位置同步调用paintNetPad方法画对方棋子到本地的棋盘上。
/yourname,表示服务器发来的针对该socket的命名,即对currPad.chessSelfName。
/error 用户不存在错误。
- 客户端模块
- 客户端界面
继承JFrame实现 ActionListener,KeyListener接口。把用户面板模块和棋盘面板加载到该框架,并创建相应的布尔变量判断当前用户所处状态:是否在聊天,游戏是否创建,是否在下棋,是否是游戏创建者,是否是游戏加入者。有如下变量:
为相应组件添加事件监听器并重写actionPerformed(ActionEvent)方法和keyPressed(KeyEvent)实现相应组件功能。
若用户点击链接服务器Button调用connectToServer(host, PORT)实现链接服务器功能;
若点击创建游戏,调用棋盘connectServer(String ServerIP, int ServerPort)连接到服务器,再用sendMessage向服务器发送消息"/creatgame "+"[inchess]"+clientName;
若点击加入游戏,首先获取用户选择的对手是否符合规范,不能空不能是自己,不能是游戏中的对手;然后调用棋盘connectServer(String ServerIP, int ServerPort)连接到服务器sendMessage("/joingame "+selectedUser+" "+clientName);
若点击放弃游戏,首先判断是在匹配阶段还是下棋阶段,在匹配阶段面板线程发送"/giveupcreategame "+clientName删除服务端匹配信息;若在下棋阶段判断对方胜利sendMessage("/giveup "+clientName)给服务器同步胜利消息。
若点击退出游戏按钮,关闭clientSocket和padsocket,退出程序。
- 客户端线程
接受服务端消息并处理,处理消息种类有
"/userlist ",取得的信息为获取用户列表,并刷新用户聊天和选择用户加入游戏面板。
"/yourname "收到的信息为服务器赋予的用户名时,更改客户端用户名。
"/reject"收到的信息为拒绝用户时 ,服务器处理用户加入游戏请求时若发现对手不符合条件,发送该信息。
"/peer "收到信息为游戏匹配成功时,为双方分配棋子颜色。
"/youwin"收到信息为胜利信息时胜利次数加一,清空棋盘
"/OK"收到信息为成功创建游戏
"/giveupcreategame"放弃创建游戏
"/error"收到信息为错误
其他消息为聊天消息,显示在聊天框
界面如下
- 服务器设计
- 服务器界面
继承JFrame实现ActionListener接口,完成服务器gui设计。
声明3个哈希表关联和识别接收到的客户端消息来源,来实现同步
clientDataHash//将客户端套接口和输出流绑定
clientNameHash//将客户端套接口和客户名绑定
ChessPeerHash//将游戏创建者和游戏加入者绑定
其他变量如下:
编写ServerMsgPanel extends Panel内部类显示收到的客户端发来的消息和当前连接数。
声明三个按钮清空列表、服务器状态、关闭服务器添加监视器并实现对应的事件操作。
createServer()方法搭建服务端的Socket以及对客户端socket接收,同时编写一个ServerForClientThread extends Thread内部类为接收到的每个客户端socket分配单独的线程,同时在run方法中为哈希表添加用户信息。
- 服务器线程
继承Thread类,初始化时继承服务器界面收到的客户端Socket和3个哈希表以及ServerMsgPanel 。
在run方法中无限循环接受客户端消息并通过dealWithMsg处理,直到接受到的客户端Socket关闭。
dealWithMsg方法进行消息处理,收到的消息分为两大类以\开头的命令信息和用户的公共聊天信息,针对用户的公共聊天信息通过sendPublicMsg方法发送给所有在线的用户;
针对命令信息有:
"/list"收到的信息为更新用户列表,首先通过getUserList()获取目前用户,再通过Feedback把获得的信息发送给客户端。
"/creategame [inchess]"收到的信息为创建游戏,首先clientNameHash和chessPeerHash加入对应的信息,然后反馈给客户端"/OK"消息,在通过sendPublicMsg方法更新所有在线用户的用户列表。
"/giveupcreategame "收到的信息为放弃创建游戏,使用closeClient方法去除创建游戏时哈希表添加的元素并关闭接受pad发来的Socket。
"/joingame " 收到的信息为加入游戏时,从消息获取谁是创建者谁是加入者,判断要加入的创建者是否符合条件,符合对应的哈希表添加对应元素,通过sendGamePeerMsg方法给对应客户端:游戏加入者和游戏创建者发送"/peer "+"[inchess]"+gameCreatorName,并使用sendPublicMsg更新在线用户的用户列表;要是不符合拒绝加入者加入游戏,发送"/reject"消息,通过closeClient关闭接受pad发来的Socket和删除初始化信息。
"/[inchess]"收到的信息为游戏中时 ,处理棋盘下棋同步,因为客户端线程收到/peer消息时会给对手命赋值,所以游戏开始后双方对手的名字都带有[inchess],这样服务端收到的消息会议[inchess]开头,后面为对手名字和下棋位置,再通过sendGamePeerMsg给对应对手发送下棋位置,对手收到后调用画网络棋盘方法实现在本地棋盘上画对方棋子实现同步。
"/giveup "收到的信息为放弃游戏时判断胜利的为哪一方后,发送胜利信息 "/youwin",和删除chessPeerHash哈希表对应位置。
收到的信息为其它命令时 判断格式是否规范,不规范为无效命令,剩余的为针对某一用户的私聊消息,调用sendGamePeerMsg发送私人消息,不能发送给自己。格式为"/"+userInputPad.userChoice.getSelectedItem()+" "+inputwords.getText()
界面如下
五.【思考题】
1. 如何实现游戏双方的协同?
游戏双方的协同主要有两个方面:
第一游戏匹配的协同,为了实现这个方面把游戏匹配分为两个方面,创造游戏和加入游戏,发送不同的消息给服务器,服务器再把创建游戏时更新的哈希表在处理加入游戏信息时进行查找,找到合适的对象,即对双方发送消息,完成匹配同步。
第二下棋棋盘同步的协同,要实现棋盘的同步,首先要实现获取用户下棋的位置,判断位置合规后在用户客户端先画出下的这一步,在发送给服务器这一步的同时在本地进行胜负判断,要是胜利调用setWinStatus方法设定好胜利时棋盘清零和对应棋子数组归零; 服务器收到用户发来消息,查找哈希表找到对手的输出流,发送消息给对手,对手收到后调用paintNetPad方法画网络棋盘,同样进行胜负判断但不需要进行位置合规判断,同样胜利后显示胜负比和棋盘设置为结束游戏状态,向服务器发送胜利消息(但可以不需要进行处理,因为胜负的状态对方已经画出来了),这样一来就实现了游戏中棋盘的同步。
流程图如下:
- 总结感悟
通过本次课程设计,我学习到了java中套接字Socket和ServerSocket的使用,如何使用socket创建服务端和客户端,以及在服务端和客户端通信中多线程Thread的使用。学习了通过分析用户操作为客户端编写对应的线程和对服务器要发出的message,以及服务器如何处理客户端发来的message并发送什么信息给客户端,最后客户端处理服务端发来的消息,实现游戏的同步。