此server端代码可以分别在console运行和或者GUI界面运行。如果需要开发其他测试工具,比如一个模拟第三方的测试桩,可以根据代码做修改协议和添加交互消息。
GUI界面继承JFrame 类。
运行效果如图:
ServerGUI.class
GUI使用JTextArea。使用BorderLayout布局,EAST有一个JTextField控件显示服务器要监听的端口号和两个开始/停止服务器的按键。CENTER区域有两个JScrollPane控件分别包含了一个JTextArea控件。第一个JTextArea控件显示聊天室的消息,客户端可见。第二个JTextArea控件显示事件消息:谁登录了,谁退出了,错误信息等。
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
//这是一个GUI方式的server
public class ServerGUI extends JFrame implements ActionListener, WindowListener {
private static final long serialVersionUID =1L;
private JButton stopStart;
//JTextArea 的聊天室和事件
private JTextArea chatWindow,eventWindow;
//端口号
private JTextField tPortNumber;
private Server server;
//server构造,端口用于接收连接
ServerGUI(int port){
super("QQ Server");
server=null;
// 开关键和端口号在窗口右侧
JPanel ServerWindow=new JPanel();
ServerWindow.add(new JLabel("Port number: "));
ServerWindow.setPreferredSize(new Dimension(150, 50));
tPortNumber=new JTextField(" "+port);
tPortNumber.setBackground(Color.GREEN);
ServerWindow.add(tPortNumber);
//to stop or start the server, we start with "Start"
stopStart=new JButton("开始监听");
stopStart.addActionListener(this);
ServerWindow.add(stopStart);
add(ServerWindow,BorderLayout.EAST);
// 聊天窗口
JPanel center=new JPanel(new GridLayout(2,6));
chatWindow=new JTextArea(40,40);
chatWindow.setEditable(false);
appendRoom("聊天室:\n");
center.add(new JScrollPane(chatWindow));
//事件窗口
eventWindow=new JTextArea(50,50);
eventWindow.setEditable(false);
appendEvent("事件日志:\n");
center.add(new JScrollPane(eventWindow));
add(center);
// need to be informed when the user click the close button on the frame
addWindowListener(this);
setSize(600,400);
setVisible(true);
}
// 添加并显示消息到如下两个消息窗口JTextArea
// position at the end
void appendRoom(String str) {
chatWindow.append(str);
chatWindow.setCaretPosition(chatWindow.getText().length()-1);
}
void appendEvent(String str) {
eventWindow.append(str);
eventWindow.setCaretPosition(chatWindow.getText().length()-1);
}
//点击开启和关闭的按键
public void actionPerformed(ActionEvent e) {
if(server!=null) {//server在运行 状态,先停止
server.stop();
server=null;
tPortNumber.setEditable(true);
stopStart.setText("开始监听");
return;
}
//启动server
int port;
try {
port = Integer.parseInt(tPortNumber.getText().trim());
}catch(Exception er) {
appendEvent("Invalid port number.");
return;
}
//创建一个新server
server=new Server(port,this);
new ServerRunning().start();//以一个线程的方式启动server
stopStart.setText("停止监听");
tPortNumber.setEditable(false);
}
//启动server的入口
public static void main(String[] arg) {
new ServerGUI(2500);
}
//一个启动server的线程
class ServerRunning extends Thread{
public void run() {
server.start();//一直执行直到启动失败
stopStart.setText("开始监听");
tPortNumber.setEditable(true);
appendEvent("Server Crashed.\n");
server=null;
}
}
//如果用户点击关闭,需要释放端口
public void windowClosing(WindowEvent e) {
//如果server存在
if(server!=null) {
try {
server.stop();
}catch (Exception eClose) {
}
server=null;
}
//dispose the frame
dispose();
System.exit(0);
}
//忽略其他WindowListener的方法
public void windowClosed(WindowEvent e) {}
public void windowOpened(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
}
说明:extends是继承父类,只要那个类不是声明为final或者那个类定义为abstract的就能继承,JAVA中不支持多重继承,但是可以用接口来实现,这样就要用到implements,继承只能继承一个类,但implements可以实现多个接口,用逗号分开就行了 ,比如 class A extends B implements C,D,E
Server.class
如下为控制台console运行的server
首先你可以输入以下的命令来执行服务器程序:
java Server
它会运行在命令行模式,并且监听2500端口,等待连接。如果你想使用其他的端口,可以附加端口参数:
java Server 1200
代码中id=++uniqueId;为唯一标示ID。(a++是先执行运算或比较再赋值增加1,而++a是先赋值增加1,再运算比较)
import java.io.*;
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.*;
public class Server {
private static int uniqueId;
//保存客户端的数组
private ArrayList<ClientThread> al;
private ServerGUI sg;
private SimpleDateFormat sdf;//显示时间
private int port;
private boolean keepGoing;//是否停止server开关
private Object String;
/** 构造server的console端口参数 */
public Server(int port) {
this(port,null);
}
public Server(int port,ServerGUI sg) {
//判断是否为GUI
this.sg=sg;
this.port=port;
sdf=new SimpleDateFormat("HH:mm:ss");
//数组存放客户端client list
al=new ArrayList<ClientThread>();
}
public void start() {
keepGoing=true;
//创建socket server 等待连接请求
try {//server使用的socket
ServerSocket serverSocket=new ServerSocket(port);
//无线循环 等待连接
while(keepGoing) {
//格式化消息 提示正在等待连接
display("Server is listenning on port "+port+" for new connections.");
Socket socket=serverSocket.accept();//接收连接
if(!keepGoing)
break;
ClientThread t=new ClientThread(socket);//给连接创建一个线程
al.add(t);//在数组list中保存线程
t.start();
}
//被要求关闭
try {
serverSocket.close();
for(int i=0;i<al.size();++i) {
ClientThread tc=al.get(i);
try {
tc.sInput.close();
tc.sOutput.close();
tc.socket.close();
}catch(IOException ioE) {
//还没打算做啥
}
}
}catch(Exception e) {
display("关闭服务器或者客户端失败:"+e);
}
}catch(IOException e) {
String msg=sdf.format(new Date())+"Exception on new ServerSocket: "+e+"\n";
display(msg);
}
}
/** 在GUI停止server */
protected void stop() {
keepGoing=false;
try {
new Socket("localhost",port);
}catch(Exception e) {
//预留操作
}
}
/** 展示一个事件到console或者GUI */
private void display(String msg) {
String time = sdf.format(new Date())+" "+msg;
//String sss=new String ("hello world");
String sss="aaa";
if(sg==null)
System.out.println(time);
else
sg.appendEvent(time+"\n");
}
/** 发布广播给所有客户端 */
private synchronized void broadcast(String message) {
//添加时间给广播消息
String time=sdf.format(new Date());
String messageLF=time+" "+message+"\n";
//把消息在console或者GUI上显示
if(sg==null)
System.out.println(messageLF);
else
sg.appendRoom(messageLF);//在GUI的聊天窗口显示
//用倒序方法发送消息,防止遇到断连的客户端,以便移除
for(int i=al.size();--i>=0;) {
ClientThread ct=al.get(i);
//发送消息给client,如果无法发送 则移除client
if(!ct.writeMsg(messageLF)) {
al.remove(i);
display("Disconnected Client " + ct.username + " removed from list.");
}
}
}
//当客户端使用logout消息退出登录
synchronized void remove(int id) {
//扫描数组,找到该id
for(int i=0;i<al.size();i++) {
ClientThread ct=al.get(i);
if(ct.id==id) {
al.remove(i);
return;
}
}
}
/** 以console application的形式运行,只需要打开 一个console 窗口
* > java Server
* > java Server portNumber
* */
public static void main(String[] args) {
int portNumber=1500;
switch(args.length) {
case 1:
try {
portNumber=Integer.parseInt(args[0]);
}catch(Exception e) {
System.out.println("无效端口.");
System.out.println("java Server [portNumber]");
return;
}
case 0:
break;
default:
System.out.println("java Server [portNumber]");
return;
}
Server server=new Server(portNumber);
server.start();
}
/** One instance of this thread will run for each client */
class ClientThread extends Thread{
//监听和讲话的socket
Socket socket;
ObjectInputStream sInput;
ObjectOutputStream sOutput;
int id;//my unique id
String username;
ChatMessage cm;
String date;
//构造函数
ClientThread(Socket socket){
id=++uniqueId;//a++是先执行运算或比较再赋值增加1,而++a是先赋值增加1,再运算比较
this.socket=socket;
/* Creating both Data Stream 创建两个数据流*/
System.out.println("Thread trying to create Object Input/Output Streams");
try {
sOutput = new ObjectOutputStream(socket.getOutputStream());
sInput = new ObjectInputStream(socket.getInputStream());
username=(String) sInput.readObject();//读取username
display(username+" successfully connected.");
}catch (IOException e) {
display("Exception creating new Input/output Streams: "+e);
return;
}//必须抓取ClassNotFoundException错误,but I read a String, I am sure it will work
catch (ClassNotFoundException e) {
}
date=new Date().toString()+"\n";
}
//永远run状态,直到logout
public void run() {
boolean keepGoing=true;
while(keepGoing) {
//读取一个String
try {
cm=(ChatMessage) sInput.readObject();
}catch (IOException e) {
display(username+" Exception reading Streams:"+e);
}catch (ClassNotFoundException e2) {
break;
}
String message=cm.getMessage();
//打开开关
switch(cm.getType()) {
case ChatMessage.MESSAGE:
broadcast(username+": "+message);
case ChatMessage.LOGOUT:
display(username+" disconnected with server with Logout");
keepGoing = false;
break;
case ChatMessage.WHOISIN:
writeMsg("List of the users connected at " + sdf.format(new Date()) + "\n");
for(int i = 0; i < al.size(); ++i) {
ClientThread ct = al.get(i);
writeMsg((i+1) + ") " + ct.username + " since " + ct.date);
}
break;
}
}//从数组中移除
remove(id);
close();
}//try to close everything
private void close() {
//关闭连接
try {
if(sOutput!=null) sOutput.close();
}catch (Exception e) {}
try {
if(sInput!=null) sInput.close();
}catch (Exception e) {}
try {
if(socket != null) socket.close();
}
catch (Exception e) {}
}
private boolean writeMsg(String msg){
//如果客户端一直在连接状态,则发送消息
if(!socket.isConnected()) {
close();
return false;
}//发送消息到stream
try {
sOutput.writeObject(msg);
}//如果发送失败,不用退出,直接提醒用户
catch(IOException e) {
display("Error sending message to "+username);
display(e.toString());
}
return true;
}
}
}