📄 serverframe.java
字号:
import javax.swing.JFrame;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.BorderLayout;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JButton;
import java.awt.Color;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.PrintStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.StringTokenizer;
public class ServerFrame extends JFrame implements ActionListener{
JScrollPane scrollPane=new JScrollPane();
JTextArea logTextArea=new JTextArea(); //记录服务器事务的文本编辑框
JButton shutdownButton=new JButton("关闭服务器"); //关闭按钮
//
int serverInPort=6817; //服务器的输入端口
int serverOutPort=6818; //服务器的输出端口
ServerSocket sInSocket; //输入套接字
ServerSocket sOutSocket; //输出套接字
//
SRThread srThread;
//初始化对弈情况表(共两张台,每张台有两张椅子,初始状态为未入座,IP、Socket等暂时为null.)
TableStatus[] tableStatus={new TableStatus(),new TableStatus()};
public ServerFrame(){
setTitle("互联网多人联机对弈(井字棋)游戏服务器");
setSize(500,300);
//添加带水平、垂直滚动条的滚动面板
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
getContentPane().add(scrollPane,BorderLayout.CENTER);
//设置文本框属性
logTextArea.setEditable(false); //只读
logTextArea.setBackground(Color.black); //背景为黑色
logTextArea.setForeground(Color.white); //字体为白色
scrollPane.getViewport().setView(logTextArea); //使文本编辑框具有滚动条
//添加"关闭服务器"按钮
getContentPane().add(shutdownButton,BorderLayout.SOUTH);
show();
//
try{
sInSocket=new ServerSocket(serverInPort); //创建用于服务器输入的套接字
sOutSocket=new ServerSocket(serverOutPort); //创建用于服务器输出的套接字
}catch(IOException e){
System.out.println("创建服务器ServerSocket失败");
System.exit(1); //退出服务器
}
//启动读数据线程
srThread=new SRThread();
srThread.start();
//添加"关闭服务器"按钮的单击事件监听器
shutdownButton.addActionListener(this);
}
public void actionPerformed(ActionEvent e){
dispose();
System.exit(0);
}
public void finalize(){
try{
sInSocket.close();
sOutSocket.close();
}catch(IOException e2){System.out.println("关闭套接字失败");}
sInSocket=null;
sOutSocket=null;
srThread=null;
}
public static void main(String[] args){
ServerFrame serverFrame=new ServerFrame();
}
public void log(String s){ //在文本框显示服务器工作日志
logTextArea.append(s+"\n"); //主动换行
}
public void setChairIdle(int tableNum,int chairNum){
tableStatus[tableNum].chairStatus[chairNum]=0;
tableStatus[tableNum].ip[chairNum]=null;
tableStatus[tableNum].sWriter[chairNum]=null;
}
public void setChairOn(int tableNum,int chairNum,String sIp,PrintStream writer){
tableStatus[tableNum].chairStatus[chairNum]=1; //已被坐上
tableStatus[tableNum].ip[chairNum]=sIp;
tableStatus[tableNum].sWriter[chairNum]=writer;
}
public void sendOnSeatMan(String msg){ //将msg消息发送到已坐下,但还未开始对弈的所有客户.
for(int tNum=0;tNum<2;tNum++){
for(int cNum=0;cNum<2;cNum++){
if(tableStatus[tNum].chairStatus[cNum]==1 && tableStatus[tNum].playStatus!=3 && tableStatus[tNum].playStatus!=4){
tableStatus[tNum].sWriter[cNum].println(msg);
}
}
}
}
class TableStatus{
int[] chairStatus={0,0}; //对应两张椅子的入座状态(0表示空出,1表示已入座)
int playStatus=2; //台面状态(2表示未开始对弈,0表示上方座位下先手棋,1则表示下方座位为先手棋,3表示正申请重新对弈,4表示有一方已离座)
String[] ip={null,null}; //对应两张椅子的入座机器IP
PrintStream[] sWriter={null,null}; //对应于两张椅子上服务器端的数据输出流
}
class SRThread extends Thread{ //Server Read 读线程
public void run(){
while(true){
try{ //利用ServerSocket套接字监听客户端,并创建一个与客户端连接对应的套接字。
synchronized(this){
Socket cInSocket=sInSocket.accept();
Socket cOutSocket=sOutSocket.accept();
ChildThread childThread=new ChildThread(cInSocket,cOutSocket);
childThread.start();
}
}catch(IOException e){
System.out.println("创建与客户端连接的套接字失败.");
System.exit(2); //退出服务器
}
}
}
}
class ChildThread extends Thread{
Socket cInSocket; //用于读入客户端数据的服务器端套接字
Socket cOutSocket; //用于写入客户端数据的服务器端套接字
PrintStream sWriter;
BufferedReader sReader;
String receiveMsg;
StringTokenizer st;
int tableNum,chairNum; //此线程所对应的客户的台号、椅子号
int cellIndex; //棋格号
String sIp;
ChildThread(Socket cInSocket,Socket cOutSocket){
this.cInSocket=cInSocket;
this.cOutSocket=cOutSocket;
//取得对应的输出流,发送入座信息到客户端,以初始化客户端界面。
try{
sWriter=new PrintStream(cOutSocket.getOutputStream());
synchronized(tableStatus){ //同步该语句块
for(int tNum=0;tNum<2;tNum++){ //发送格式"seat:台号,椅子号,入座状态"数据到客户端
for(int cNum=0;cNum<2;cNum++){
sWriter.println("seat:"+tNum+","+cNum+","+tableStatus[tNum].chairStatus[cNum]);
}
}
}
log(cInSocket.getInetAddress().getHostAddress()+"连接上来,但未入座."); //写服务器日志
//初始化输入流sReader
sReader=new BufferedReader(new InputStreamReader(cInSocket.getInputStream()));
}catch(IOException e){
System.out.println("线程创建输入输出流失败"+e);
}
}
public void run(){
while(true){
try{
receiveMsg=sReader.readLine();
}catch(IOException e){
System.out.println("读数据IO出错.");
break; //使线程死亡
}
if(receiveMsg.startsWith("logout:")){ //注销座位
receiveMsg=receiveMsg.substring(7,receiveMsg.length());
st=new StringTokenizer(receiveMsg,",");
try{
while(st.hasMoreTokens()){
tableNum=Integer.parseInt(st.nextToken());
chairNum=Integer.parseInt(st.nextToken());
}
}catch(NumberFormatException e){}
if(tableNum!=-1 && chairNum!=-1){ //不是首次入座位
synchronized(tableStatus){ //同步该语句块
sendOnSeatMan("seat:"+tableNum+","+chairNum+",0"); //刷新在坐的客户台面
setChairIdle(tableNum,chairNum); //设置座位空闲
}
}
}
else if(receiveMsg.startsWith("login:")){ //座位申请注册
receiveMsg=receiveMsg.substring(6,receiveMsg.length());
st=new StringTokenizer(receiveMsg,",");
try{
while(st.hasMoreTokens()){
tableNum=Integer.parseInt(st.nextToken());
chairNum=Integer.parseInt(st.nextToken());
}
}catch(NumberFormatException e){}
sIp=cInSocket.getInetAddress().getHostAddress();
synchronized(tableStatus){ //同步该语句块
if(tableStatus[tableNum].chairStatus[chairNum]==0){ //该座位未被坐下
//登记对弈情况表的tableNum号台、chairNum号椅子的信息
setChairOn(tableNum,chairNum,sIp,sWriter);
//立即刷新入坐者
sendOnSeatMan("seat:"+tableNum+","+chairNum+",1");
log(sIp+"申请座位成功.已坐下"+tableNum+"号台,"+chairNum+"号椅子.");
}
else{ //如果客户端申请一个已被占用的座位,login失败.
sWriter.println("no seat!"); //向此客户端发送座位被占用信息
}
}
}
else if(receiveMsg.startsWith("play!")){ //处理对弈请求
synchronized(tableStatus){ //同步该语句块
//对方已入座,才可开始对弈,并且该台号仍为未开始对弈状态(这个判断可以阻止双方竞争先手棋).
if(tableStatus[tableNum].chairStatus[1-chairNum]==1 && tableStatus[tableNum].playStatus==2){
tableStatus[tableNum].playStatus=chairNum; //设置正在对弈状态和下先手棋的一方
//发送台号到对弈双方,表示对弈开始.
sWriter.println("start:"+chairNum); //发送给请求对弈者,chairNum表示先下棋的椅子号.
tableStatus[tableNum].sWriter[1-chairNum].println("start:"+chairNum); //发送给对方
}
}
}
else if(receiveMsg.startsWith("go:")){ //处理对方棋步
receiveMsg=receiveMsg.substring(3,receiveMsg.length());
try{
cellIndex=Integer.parseInt(receiveMsg);
}catch(NumberFormatException e){}
tableStatus[tableNum].sWriter[1-chairNum].println("chess:"+cellIndex); //向对弈方转发落子所在的棋格号
}
else if(receiveMsg.startsWith("win:")){ //收到嬴棋信息
receiveMsg=receiveMsg.substring(4,receiveMsg.length());
try{
cellIndex=Integer.parseInt(receiveMsg);
}catch(NumberFormatException e){}
tableStatus[tableNum].sWriter[1-chairNum].println("defeat:"+cellIndex); //转发对弈方输棋信息
}
else if(receiveMsg.startsWith("peace:")){ //收到和局信息
receiveMsg=receiveMsg.substring(6,receiveMsg.length());
try{
cellIndex=Integer.parseInt(receiveMsg);
}catch(NumberFormatException e){}
tableStatus[tableNum].sWriter[1-chairNum].println("chess:"+cellIndex);
tableStatus[tableNum].sWriter[1-chairNum].println("peace!"); //通知这局棋和了
}
else if(receiveMsg.startsWith("talk:")){ //收到棋盘满子的信息
receiveMsg=receiveMsg.substring(5,receiveMsg.length());
tableStatus[tableNum].sWriter[1-chairNum].println("message:"+receiveMsg); //转发对弈方转发聊天话本
}
else if(receiveMsg.startsWith("restart!")){ //收到重新开棋对弈请求
if(tableStatus[tableNum].playStatus!=3 && tableStatus[tableNum].playStatus!=4){ //对方并未申请重新开棋或对方未离开(避免双方竞争重新开棋)
tableStatus[tableNum].playStatus=3; //设置该台正被申请重新对弈的状态
tableStatus[tableNum].sWriter[1-chairNum].println("again?"); //向对弈方发送重新开棋请求
}
}
else if(receiveMsg.startsWith("agree!")){ //收到同意重新开棋信息
receiveMsg=receiveMsg.substring(6,receiveMsg.length());
tableStatus[tableNum].playStatus=1-chairNum; //表明1-chairNum椅子执子先行的(实际使该状态不为3).
tableStatus[tableNum].sWriter[1-chairNum].println("agree!"); //向请求者转发同意重新开局信息
}
else if(receiveMsg.startsWith("go back!")){ //收到返回游戏大厅信息
tableStatus[tableNum].sWriter[1-chairNum].println("go back!"); //向对弈方也发送返回信息
//该台号的两张椅子恢复可用,但不关闭Socket等资源.
setChairIdle(tableNum,chairNum);
setChairIdle(tableNum,1-chairNum);
tableStatus[tableNum].playStatus=2; //恢复该台号状态为未开始对弈
//通知在坐、但还未开棋的客户该台面(两张椅子)已可用
sendOnSeatMan("seat:"+tableNum+","+chairNum+",0");
sendOnSeatMan("seat:"+tableNum+","+(1-chairNum)+",0");
}
else if(receiveMsg.startsWith("refresh!")){ //收到刷新客户端台面请求
synchronized(tableStatus){ //同步该语句块
for(int tNum=0;tNum<2;tNum++){ //发送所有台面、椅子的入座信息
for(int cNum=0;cNum<2;cNum++){
sWriter.println("refresh:"+tNum+","+cNum+","+tableStatus[tNum].chairStatus[cNum]);
}
}
}
}
else if(receiveMsg.startsWith("exit:")){ //收到客户退出信息
receiveMsg=receiveMsg.substring(5,receiveMsg.length());
st=new StringTokenizer(receiveMsg,",");
try{
while(st.hasMoreTokens()){
tableNum=Integer.parseInt(st.nextToken());
chairNum=Integer.parseInt(st.nextToken());
}
}catch(NumberFormatException e){}
if(tableNum!=-1 && chairNum!=-1){ //此客户已坐下的情形
if(tableStatus[tableNum].playStatus==0 || tableStatus[tableNum].playStatus==1){ //客户已开始对弈的情形
//让同台的对弈方返回游戏大厅
tableStatus[tableNum].sWriter[1-chairNum].println("she exit!");
}
//让其他已入座、但未开始对弈的客户刷新台面(不发给自己)
for(int tNum=0;tNum<2;tNum++){
for(int cNum=0;cNum<2;cNum++){
if(((tNum!=tableNum)||(cNum!=chairNum)) && (tableStatus[tNum].chairStatus[cNum]==1 && tableStatus[tNum].playStatus!=3 && tableStatus[tNum].playStatus!=4)){
tableStatus[tNum].sWriter[cNum].println("seat:"+tableNum+","+chairNum+",0");
}
}
}
//恢复该客户的台面、椅子未入座
tableStatus[tableNum].sWriter[chairNum].close();
setChairIdle(tableNum,chairNum); //设置退出者座位为空
tableStatus[tableNum].playStatus=2; //该台还未开始对弈
log(sIp+"客户:("+tableNum+"号台,"+chairNum+"号椅子)退出了游戏");
break; //退出客户线程
}
else{ //该客户并未取得座位
break; //直接退出客户线程
}
}
}
}
public void finalize(){ //释放线程资源
try{
cInSocket.close();
cOutSocket.close();
sReader.close();
sWriter.close();
}catch(IOException e){
System.out.println("关闭线程的资源出现错误"+e);
}
cInSocket=null;
cOutSocket=null;
sReader=null;
sWriter=null;
}
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -