Posted on 2009-12-18 21:51
啥都寫點 閱讀(749)
評論(0) 編輯 收藏 所屬分類:
J2SE
聊天室的服務器端主要用于接收客戶端的連接與斷開請求、廣播聊天室的成員信息、將成員的聊天信息公布給所有成員。
服務器端在某個端口上開啟ServerSocket,接收客戶端的連接請求。根據客戶端的IP地址和賬號,判斷客戶端是否重復登錄,如果不是,便允許它加入聊天室,并將最新的成員列表發送給所有客戶端。
每當收到客戶端的聊天信息時,將聊天信息發送給所有客戶端。
為了處理多個客戶端同時登錄聊天室,采用了多線程的技術,對于每個客戶端,都用一個線程專門處理它。


/** *//**------------------------------------------------------ChatServer.java--------------------------------------------------------------------*/

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.TitledBorder;


/** *//**
* 聊天室的服務器端程序,GUI界面
*/

public class ChatServer extends JFrame
{

// 狀態欄標簽
static JLabel statusBar = new JLabel();
// 顯示客戶端的連接信息的列表
static java.awt.List connectInfoList = new java.awt.List(10);

// 保存當前處理客戶端請求的處理器線程
static Vector clientProcessors = new Vector(10);
// 當前的連接數
static int activeConnects = 0;

// 構造方法

public ChatServer()
{
init();

try
{
// 設置界面為系統默認外觀
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
SwingUtilities.updateComponentTreeUI(this);

} catch (Exception e)
{
e.printStackTrace();
}
}


private void init()
{

this.setTitle("聊天室服務器");
statusBar.setText("");

// 初始化菜單
JMenu fileMenu = new JMenu();
fileMenu.setText("文件");
JMenuItem exitMenuItem = new JMenuItem();
exitMenuItem.setText("退出");

exitMenuItem.addActionListener(new ActionListener()
{

public void actionPerformed(ActionEvent e)
{
exitActionPerformed(e);
}
});
fileMenu.add(exitMenuItem);
JMenuBar menuBar = new JMenuBar();
menuBar.add(fileMenu);
this.setJMenuBar(menuBar);

// 將組件進行布局
JPanel jPanel1 = new JPanel();
jPanel1.setLayout(new BorderLayout());
JScrollPane pane = new JScrollPane(connectInfoList);
pane.setBorder(new TitledBorder(BorderFactory.createEtchedBorder(
Color.white, new Color(134, 134, 134)), "客戶端連接信息"));
jPanel1.add(new JScrollPane(pane), BorderLayout.CENTER);
this.getContentPane().setLayout(new BorderLayout());
this.getContentPane().add(statusBar, BorderLayout.SOUTH);
this.getContentPane().add(jPanel1, BorderLayout.CENTER);

this.pack();
}


/** *//**
* 退出菜單項事件
* @param e
*/

public void exitActionPerformed(ActionEvent e)
{
// 向客戶端發送斷開連接信息
sendMsgToClients(new StringBuffer(Constants.QUIT_IDENTIFER));
// 關閉所有的連接
closeAll();
System.exit(0);
}


/** *//**
* 處理窗口關閉事件
*/

protected void processWindowEvent(WindowEvent e)
{
super.processWindowEvent(e);

if (e.getID() == WindowEvent.WINDOW_CLOSING)
{
exitActionPerformed(null);
}
}


/** *//**
* 刷新聊天室,不斷刷新clientProcessors,制造最新的用戶列表
*/

public static void notifyRoomPeople()
{
StringBuffer people = new StringBuffer(Constants.PEOPLE_IDENTIFER);

for (int i = 0; i < clientProcessors.size(); i++)
{
ClientProcessor c = (ClientProcessor) clientProcessors.elementAt(i);
people.append(Constants.SEPERATOR).append(c.clientName);
}
// 用sendClients方法向客戶端發送用戶列表的信息
sendMsgToClients(people);
}


/** *//**
* 向所有客戶端群發消息
* @param msg
*/

public static synchronized void sendMsgToClients(StringBuffer msg)
{

for (int i = 0; i < clientProcessors.size(); i++)
{
ClientProcessor c = (ClientProcessor) clientProcessors.elementAt(i);
System.out.println("send msg: " + msg);
c.send(msg);
}
}


/** *//**
* 關閉所有連接
*/

public static void closeAll()
{

while (clientProcessors.size() > 0)
{
ClientProcessor c = (ClientProcessor) clientProcessors.firstElement();

try
{
// 關閉socket連接和處理線程
c.socket.close();
c.toStop();

} catch (IOException e)
{
System.out.println("Error:" + e);

} finally
{
clientProcessors.removeElement(c);
}
}
}


/** *//**
* 判斷客戶端是否合法。
* 不允許同一客戶端重復登陸,所謂同一客戶端是指IP和名字都相同。
* @param newclient
* @return
*/

public static boolean checkClient(ClientProcessor newclient)
{

if (clientProcessors.contains(newclient))
{
return false;

} else
{
return true;
}
}


/** *//**
* 斷開某個連接,并且從連接列表中刪除
* @param client
*/

public static void disconnect(ClientProcessor client)
{
disconnect(client, true);
}

/** *//**
* 斷開某個連接,根據要求決定是否從連接列表中刪除
* @param client
* @param toRemoveFromList
*/

public static synchronized void disconnect(ClientProcessor client, boolean toRemoveFromList)
{

try
{
//在服務器端程序的list框中顯示斷開信息
connectInfoList.add(client.clientIP + "斷開連接");

ChatServer.activeConnects--; //將連接數減1
String constr = "目前有" + ChatServer.activeConnects + "客戶相連";
statusBar.setText(constr);
//向客戶發送斷開連接信息
client.send(new StringBuffer(Constants.QUIT_IDENTIFER));
client.socket.close();


} catch (IOException e)
{
System.out.println("Error:" + e);

} finally
{
//從clients數組中刪除此客戶的相關socket等信息, 并停止線程。

if (toRemoveFromList)
{
clientProcessors.removeElement(client);
client.toStop();
}
}
}

public static void main(String[] args)
{

ChatServer chatServer1 = new ChatServer();
chatServer1.setVisible(true);
System.out.println("Server starting
");

ServerSocket server = null;

try
{
// 服務器端開始偵聽
server = new ServerSocket(Constants.SERVER_PORT);

} catch (IOException e)
{
System.out.println("Error:" + e);
System.exit(1);
}

while (true)
{
// 如果當前客戶端數小于MAX_CLIENT個時接受連接請求

if (clientProcessors.size() < Constants.MAX_CLIENT)
{
Socket socket = null;

try
{
// 收到客戶端的請求
socket = server.accept();

if (socket != null)
{
System.out.println(socket + "連接");
}

} catch (IOException e)
{
System.out.println("Error:" + e);
}

// 定義并實例化一個ClientProcessor線程類,用于處理客戶端的消息
ClientProcessor c = new ClientProcessor(socket);

if (checkClient(c))
{
// 添加到列表
clientProcessors.addElement(c);
// 如果客戶端合法,則繼續
int connum = ++ChatServer.activeConnects;
// 在狀態欄里顯示連接數
String constr = "目前有" + connum + "客戶相連";
ChatServer.statusBar.setText(constr);
// 將客戶socket信息寫入list框
ChatServer.connectInfoList.add(c.clientIP + "連接");
c.start();
// 通知所有客戶端用戶列表發生變化
notifyRoomPeople();

} else
{
//如果客戶端不合法
c.ps.println("不允許重復登陸");
disconnect(c, false);
}


} else
{
//如果客戶端超過了MAX_CLIENT個,則等待一段時間再嘗試接受請求

try
{
Thread.sleep(200);

} catch (InterruptedException e)
{
}
}
}
}
}


/** *//**
* 處理客戶端發送的請求的線程
*/

class ClientProcessor extends Thread
{
//存儲一個連接客戶端的socket信息
Socket socket;
//存儲客戶端的連接姓名
String clientName;

//存儲客戶端的ip信息
String clientIP;
//用來實現接受從客戶端發來的數據流
BufferedReader br;
//用來實現向客戶端發送信息的打印流
PrintStream ps;

boolean running = true;

/** *//**
* 構造方法
* @param s
*/

public ClientProcessor(Socket s)
{
socket = s;

try
{
// 初始化輸入輸出流
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
ps = new PrintStream(socket.getOutputStream());
// 讀取收到的信息,第一條信息是客戶端的名字、IP信息
String clientInfo = br.readLine();
// 讀取信息,根據消息分隔符解析消息
StringTokenizer stinfo = new StringTokenizer(clientInfo, Constants.SEPERATOR);
String head = stinfo.nextToken();

if (head.equals(Constants.CONNECT_IDENTIFER))
{

if (stinfo.hasMoreTokens())
{
//關鍵字后的第二段數據是客戶名信息
clientName = stinfo.nextToken();
}

if (stinfo.hasMoreTokens())
{
//關鍵字后的第三段數據是客戶ip信息
clientIP = stinfo.nextToken();
}
System.out.println(head); //在控制臺打印頭信息
}

} catch (IOException e)
{
System.out.println("Error:" + e);
}
}


/** *//**
* 向客戶端發送消息
* @param msg
*/

public void send(StringBuffer msg)
{
ps.println(msg);
ps.flush();
}
//線程運行方法

public void run()
{


while (running)
{
String line = null;

try
{
//讀取客戶端發來的數據流
line = br.readLine();


} catch (IOException e)
{
System.out.println("Error" + e);
ChatServer.disconnect(this);
ChatServer.notifyRoomPeople();
return;
}
//客戶已離開

if (line == null)
{
ChatServer.disconnect(this);
ChatServer.notifyRoomPeople();
return;
}

StringTokenizer st = new StringTokenizer(line, Constants.SEPERATOR);
String keyword = st.nextToken();

// 如果關鍵字是MSG則是客戶端發來的聊天信息

if (keyword.equals(Constants.MSG_IDENTIFER))
{
StringBuffer msg = new StringBuffer(Constants.MSG_IDENTIFER).append(Constants.SEPERATOR);
msg.append(clientName);
msg.append(st.nextToken("\0"));
// 再將某個客戶發來的聊天信息發送到每個連接客戶的聊天欄中
ChatServer.sendMsgToClients(msg);

} else if (keyword.equals(Constants.QUIT_IDENTIFER))
{
// 如果關鍵字是QUIT則是客戶端發來斷開連接的信息
// 服務器斷開與這個客戶的連接
ChatServer.disconnect(this);
// 繼續監聽聊天室并刷新其他客戶的聊天人名list
ChatServer.notifyRoomPeople();
running = false;
}
}
}

public void toStop()
{
running = false;
}
// 覆蓋Object類的equals方法

public boolean equals(Object obj)
{

if (obj instanceof ClientProcessor)
{
ClientProcessor obj1 = (ClientProcessor)obj;
if (obj1.clientIP.equals(this.clientIP) &&

(obj1.clientName.equals(this.clientName)))
{
return true;
}
}
return false;
}
// 覆蓋Object類的hashCode方法

public int hashCode()
{
return (this.clientIP + Constants.SEPERATOR + this.clientName).hashCode();
}
}

/** *//**---------------------------------Constants.java------------------------------------*/

/** *//**
* 定義聊天室程序中用到的常量
*/

public class Constants
{

// 服務器的端口號
public static final int SERVER_PORT = 2525;
public static final int MAX_CLIENT = 10;
// 消息標識符與消息體之間的分隔符
public static final String SEPERATOR = ":";
// 消息信息的標識符
public static final String MSG_IDENTIFER = "MSG";
// 用戶列表信息的標識符
public static final String PEOPLE_IDENTIFER = "PEOPLE";
// 連接服務器信息的標識符
public static final String CONNECT_IDENTIFER = "INFO";
// 退出信息標識符
public static final String QUIT_IDENTIFER = "QUIT";
}
--
學海無涯