线程池一个游戏
输出结果
客户端设计
客户机代码
package cn.edu.ldu;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import javax.swing.JOptionPane;
public class ClientUI extends javax.swing.JFrame {
private Socket clientSocket=null; //客户机套接字
private BufferedReader in; //网络输入流
private PrintWriter out; //网络输出流
/**
* Creates new form ClientUI
*/
public ClientUI() {
initComponents();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
topPanel = new javax.swing.JPanel();
jLabel1 = new javax.swing.JLabel();
txtRemoteName = new javax.swing.JTextField();
jLabel2 = new javax.swing.JLabel();
txtRemotePort = new javax.swing.JTextField();
btnConnect = new javax.swing.JButton();
midPanel = new javax.swing.JPanel();
jScrollPane1 = new javax.swing.JScrollPane();
txtArea = new javax.swing.JTextArea();
txtInput = new javax.swing.JTextField();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
topPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("连接服务器面板"));
jLabel1.setText("服务器主机名");
txtRemoteName.setText("localhost");
jLabel2.setText("服务器端口");
txtRemotePort.setText("5000");
btnConnect.setText("连接服务器");
btnConnect.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
btnConnectActionPerformed(evt);
}
});
javax.swing.GroupLayout topPanelLayout = new javax.swing.GroupLayout(topPanel);
topPanel.setLayout(topPanelLayout);
topPanelLayout.setHorizontalGroup(
topPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(topPanelLayout.createSequentialGroup()
.addComponent(jLabel1)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(txtRemoteName, javax.swing.GroupLayout.PREFERRED_SIZE, 143, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(40, 40, 40)
.addComponent(jLabel2)
.addGap(18, 18, 18)
.addComponent(txtRemotePort, javax.swing.GroupLayout.PREFERRED_SIZE, 131, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(61, 61, 61)
.addComponent(btnConnect)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
topPanelLayout.setVerticalGroup(
topPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(topPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(topPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel1)
.addComponent(txtRemoteName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jLabel2)
.addComponent(txtRemotePort, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(btnConnect))
.addContainerGap(64, Short.MAX_VALUE))
);
getContentPane().add(topPanel, java.awt.BorderLayout.PAGE_START);
midPanel.setLayout(new java.awt.BorderLayout());
txtArea.setColumns(20);
txtArea.setRows(5);
txtArea.setBorder(javax.swing.BorderFactory.createTitledBorder("客户机对话面板,客户机扮演“门内人”角色"));
jScrollPane1.setViewportView(txtArea);
midPanel.add(jScrollPane1, java.awt.BorderLayout.CENTER);
txtInput.setText("此处输入回话内容...");
txtInput.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusGained(java.awt.event.FocusEvent evt) {
txtInputFocusGained(evt);
}
});
txtInput.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
txtInputActionPerformed(evt);
}
});
midPanel.add(txtInput, java.awt.BorderLayout.PAGE_END);
getContentPane().add(midPanel, java.awt.BorderLayout.CENTER);
pack();
}// </editor-fold>
private void btnConnectActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
try {
//获取参数
String remoteName=txtRemoteName.getText();
int remotePort=Integer.parseInt(txtRemotePort.getText());
//构建套接字格式地址
SocketAddress remoteAddr=new InetSocketAddress(remoteName,remotePort);
//1. 创建套接字clientSocket(Socket)并连接到远程服务器
clientSocket=new Socket();
clientSocket.connect(remoteAddr);
//2. 创建绑定到套接字clientSocket上的网络输入流与输出流
in=new BufferedReader(new InputStreamReader(clientSocket.getInputStream(),"UTF-8"));
out=new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream(),"UTF-8"),true);
String fromOutdoor=in.readLine();//接受服务器消息
txtArea.append("outdoor: "+fromOutdoor+"\n"); //显示服务器消息
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, ex.getMessage(), "连接错误", JOptionPane.ERROR_MESSAGE);
return;
}
txtRemoteName.setEnabled(false);
txtRemotePort.setEnabled(false);
btnConnect.setEnabled(false);
}
private void txtInputActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
String fromOutdoor;
String fromIndoor;
if (clientSocket==null) {
JOptionPane.showMessageDialog(null, "请先连接服务器!", "连接错误", JOptionPane.ERROR_MESSAGE);
return;
}
try {
fromIndoor=txtInput.getText();
if (!fromIndoor.equals("")) {
out.println(fromIndoor);
txtArea.append("indoor: "+fromIndoor+"\n");
txtInput.setText("");
} else {
JOptionPane.showMessageDialog(null, "问话内容为空,请重新输入!", "输入错误", JOptionPane.ERROR_MESSAGE);
return;
}//end if
fromOutdoor=in.readLine();
txtArea.append("outdoor: "+fromOutdoor+"\n");
if (fromOutdoor.endsWith("Goodbye!")) {
txtRemoteName.setEnabled(true);
txtRemotePort.setEnabled(true);
btnConnect.setEnabled(true);//激活按钮
//4. 关闭并销毁网络流
//5. 关闭并销毁套接字
try {
if (in!=null) in.close();
if (out!=null) out.close();
if (clientSocket!=null)clientSocket.close();
} catch (IOException ex) { }
}//end if
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, ex.getMessage(), "接收数据错误", JOptionPane.ERROR_MESSAGE);
}//end try
}
private void txtInputFocusGained(java.awt.event.FocusEvent evt) {
// TODO add your handling code here:
txtInput.setText("");
}
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
/* Set the Nimbus look and feel */
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
/* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
* For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
*/
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(ClientUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(ClientUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(ClientUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(ClientUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
//</editor-fold>
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new ClientUI().setVisible(true);
}
});
}
// Variables declaration - do not modify
private javax.swing.JButton btnConnect;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JPanel midPanel;
private javax.swing.JPanel topPanel;
private javax.swing.JTextArea txtArea;
private javax.swing.JTextField txtInput;
private javax.swing.JTextField txtRemoteName;
private javax.swing.JTextField txtRemotePort;
// End of variables declaration
}
//处理与客户机的数据交换ClientThread 继承 Thread
package cn.edu.ldu;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.List;
import javax.swing.SwingWorker;
/**
*
* @author World
*/
//处理与客户机的数据交换
public class ClientThread extends Thread {
private final Socket toClientSocket; //与客户机对话的套接字
private BufferedReader in; //网络输入流
private PrintWriter out; //网络输出流
private Protocol protocol; //会话协议
private int clientCounts; //客户机数量
public ClientThread(Socket toClientSocket,int clientCounts) { //构造函数
this.toClientSocket=toClientSocket;
this.clientCounts=clientCounts;
}
@Override
public void run() {
try {
//创建绑定到套接字toClientSocket上的网络输入流与输出流
in=new BufferedReader(new InputStreamReader(toClientSocket.getInputStream(),"UTF-8"));
out=new PrintWriter(new OutputStreamWriter(toClientSocket.getOutputStream(),"UTF-8"),true);
long currentId=Thread.currentThread().getId();
ServerUI.txtArea.append("当前会话线程ID:"+currentId+"\n"); //发布到process
//根据服务器协议,在网络流上进行读写操作
protocol=new Protocol(); //生成协议对象
String outdoorStr; //门外人的回答
String indoorStr; //门内人的问话
outdoorStr=protocol.protocolWorking(null); //根据协议生成门外人的问话
out.println(outdoorStr); //向客户机发起会话
ServerUI.txtArea.append("outdoor"+clientCounts+":"+outdoorStr+"\n"); //发布门外人的话到process处理
while ((indoorStr=in.readLine())!=null) { //只要客户机不断开连接则反复读
ServerUI.txtArea.append("indoor"+clientCounts+":"+indoorStr+"\n"); //发布门内人的话到process处理
//根据协议生成回答消息
outdoorStr=protocol.protocolWorking(indoorStr);
out.println(outdoorStr); //向客户机发送回答
ServerUI.txtArea.append("outdoor"+clientCounts+":"+outdoorStr+"\n"); //发布门外人的话到process处理
if (outdoorStr.endsWith("Goodbye!")) //结束游戏
break;
}//end while
ServerUI.clientCounts--; //客户机总数减1
//因为客户机断开了连接,所以释放资源
if (in!=null) in.close();
if (out!=null) out.close();
if (toClientSocket!=null) toClientSocket.close();
} catch (IOException ex) {}
}//end run
}//end class ClientThread
//游戏内的设计
package cn.edu.ldu;
public class Protocol {
//游戏包含敲门人和门内人两个角色。门内人的问话和回答有固定格式,敲门人的回答是变化的,所以用以下常量表示敲门人会话进程和状态
//游戏状态
private static final int WAITING=0; //等待 游戏还未开始
private static final int SENTKNOCKKNOCK=1; //敲门完成 服务器向客户机发送Knock
private static final int SENTCLUE=2; //第一遍线索回答完成 服务器向客户机发送单词线索
private static final int SENTANSWER=3; //第二遍终极回答完成 服务器向客户机发送幽默回答
private static final int NUMJOKES=8; //游戏总局数
private int state=WAITING; //会话状态
private int currentJoke=0; //计数
//以下两个数组分别存储敲门人的两次回答
private String[] clues={"Buster","Orange","Ice cream","Tunis","Old lady","Yah","Dishes","Amish"};//单词线索
private String[] answers={"Buster Cherry! Is your daughter home?","Orange you going to answer the door?","Ice cream if you don't let me in!",
"Tunis company, three's a crowd!","Wow I didn't know you could yodel.",
"Naaah, bro, I prefer google.","Dishes the Police come out with your hands up.","Awwww How sweet. I miss you too."};//幽默回答
public String protocolWorking(String question) { //question门内人的问话客户机 String服务器
String answer=null; //敲门人的回答
switch (state) {
case WAITING: //开始敲门
answer="Knock! Knock!";
state=SENTKNOCKKNOCK;
break;
case SENTKNOCKKNOCK: //谁在敲门?问答
if (question.equalsIgnoreCase("Who's there?")) {
answer=clues[currentJoke];
state=SENTCLUE;//发送完成
}else {
answer="你应该问:\"Who's there?\""+"重新开始:Knock! Knock!";
}
break;
case SENTCLUE: //追问敲门人问答
if (question.equalsIgnoreCase(clues[currentJoke]+" Who?")) {
answer=answers[currentJoke]+" 是否继续 ?(y / n ?)";
state=SENTANSWER;
}else {
answer="你应该问:\""+clues[currentJoke]+" Who?\""+"重新开始:Knock! Knock!";
state=SENTKNOCKKNOCK;
}
break;
case SENTANSWER: //询问门内人是否继续游戏?
if (question.equalsIgnoreCase("y")) {
answer="Knock! Knock!";
if (currentJoke==NUMJOKES-1) {
currentJoke=0;
}else {
currentJoke++;
}
state=SENTKNOCKKNOCK;
}else {
answer="Game Over! Goodbye!";
state=WAITING;
}
break;
}
return answer;
}//end protocolWorking
}
//自定义协议
服务器设计
package cn.edu.ldu;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.swing.JOptionPane;
import javax.swing.SwingWorker;
public class ServerUI extends javax.swing.JFrame {
private ExecutorService fixedPool; //线程池
private ServerSocket listenSocket; //侦听套接字
private Socket toClientSocket; //与客户机对话的套接字
public static int clientCounts=0; //客户机数量
/**
* Creates new form Server
*/
public ServerUI() {
initComponents();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
topPanel = new javax.swing.JPanel();
jLabel1 = new javax.swing.JLabel();
txtHostName = new javax.swing.JTextField();
jLabel2 = new javax.swing.JLabel();
txtHostPort = new javax.swing.JTextField();
btnStart = new javax.swing.JButton();
midPanel = new javax.swing.JPanel();
jScrollPane2 = new javax.swing.JScrollPane();
txtArea = new javax.swing.JTextArea();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent evt) {
formWindowClosing(evt);
}
});
topPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("启动服务器面板"));
jLabel1.setText("服务器主机名");
txtHostName.setText("localhost");
jLabel2.setText("服务器端口");
txtHostPort.setText("5000");
btnStart.setText("启动服务器");
btnStart.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
btnStartActionPerformed(evt);
}
});
javax.swing.GroupLayout topPanelLayout = new javax.swing.GroupLayout(topPanel);
topPanel.setLayout(topPanelLayout);
topPanelLayout.setHorizontalGroup(
topPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(topPanelLayout.createSequentialGroup()
.addComponent(jLabel1)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(txtHostName, javax.swing.GroupLayout.PREFERRED_SIZE, 139, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(44, 44, 44)
.addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, 104, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(txtHostPort, javax.swing.GroupLayout.PREFERRED_SIZE, 89, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(39, 39, 39)
.addComponent(btnStart)
.addGap(0, 19, Short.MAX_VALUE))
);
topPanelLayout.setVerticalGroup(
topPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(topPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(topPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel1)
.addComponent(txtHostName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jLabel2)
.addComponent(txtHostPort, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(btnStart))
.addContainerGap(64, Short.MAX_VALUE))
);
getContentPane().add(topPanel, java.awt.BorderLayout.PAGE_START);
midPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("服务器消息面板,服务器扮演“门外人”角色"));
midPanel.setLayout(new java.awt.BorderLayout());
txtArea.setColumns(20);
txtArea.setRows(5);
jScrollPane2.setViewportView(txtArea);
midPanel.add(jScrollPane2, java.awt.BorderLayout.CENTER);
getContentPane().add(midPanel, java.awt.BorderLayout.CENTER);
pack();
}// </editor-fold>
private void btnStartActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
try {
btnStart.setEnabled(false);
txtHostName.setEnabled(false);
txtHostPort.setEnabled(false);
//获取启动参数
String hostName=txtHostName.getText();
int hostPort=Integer.parseInt(txtHostPort.getText());
//构建套接字格式的地址
SocketAddress serverAddr=new InetSocketAddress(InetAddress.getByName(hostName),hostPort);
listenSocket=new ServerSocket(); //创建服务器侦听套接字
listenSocket.bind(serverAddr); //绑定到工作地址
int processors=Runtime.getRuntime().availableProcessors();//CPU数
fixedPool=Executors.newFixedThreadPool(processors*2);//创建固定大小线程池
long currentId=Thread.currentThread().getId();//获取编号
txtArea.append("服务器CPU数:"+processors+"\n 固定线程池大小:"+processors*2+"\n 当前侦听线程ID:"+currentId+"\n 服务器正等待客户机连接...\n");
} catch (IOException ex) { }
new Thread(new Runnable() {
public void run() {
try {
while (true) { //处理所有客户机连接
toClientSocket=listenSocket.accept();//如果无连接,则阻塞,否则接受连接并创建新的会话套接字
clientCounts++;
txtArea.append(toClientSocket.getRemoteSocketAddress()+" 客户机编号:"+clientCounts+" 连接到服务器,会话开始...\n");
//客户会话线程为SwingWorker类型的后台工作线程
Thread clientThread=new ClientThread(toClientSocket,clientCounts); //创建客户线程
fixedPool.execute(clientThread); //用线程池调度客户线程运行
//clientThread.start(); //这样做就是一客户一线程
}//end while
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, ex.getMessage(), "错误提示", JOptionPane.ERROR_MESSAGE);
}//end try catch
}//end run()
}).start();
}
private void formWindowClosing(java.awt.event.WindowEvent evt) {
// TODO add your handling code here:
//关闭套接字和线程池
try {
if (toClientSocket!=null) toClientSocket.close();//关闭套接字
if (listenSocket!=null) listenSocket.close();
if (fixedPool==null) return;
fixedPool.shutdown(); //线程池开始关闭
if (!fixedPool.awaitTermination(60, TimeUnit.SECONDS)) {
fixedPool.shutdownNow();//强制关闭正执行的线程
if (!fixedPool.awaitTermination(60, TimeUnit.SECONDS)) {
fixedPool.shutdownNow();//强制关闭正执行的线程
}//超过60秒有线程未关闭执行
}
} catch (IOException | InterruptedException ex) {
fixedPool.shutdownNow();//强制关闭正执行的线程
Thread.currentThread().interrupt();//关闭主线程
}
}
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
/* Set the Nimbus look and feel */
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
/* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
* For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
*/
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(ServerUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(ServerUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(ServerUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(ServerUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
//</editor-fold>
//</editor-fold>
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new ServerUI().setVisible(true);
}
});
}
// Variables declaration - do not modify
private javax.swing.JButton btnStart;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JScrollPane jScrollPane2;
private javax.swing.JPanel midPanel;
private javax.swing.JPanel topPanel;
public static javax.swing.JTextArea txtArea;
private javax.swing.JTextField txtHostName;
private javax.swing.JTextField txtHostPort;
// End of variables declaration
private static class MyThread extends Thread{
public void run() {
System.out.println(Thread.currentThread().getName()+"正在运行");
}
}
}