声明:本文参考多篇java聊天室设计的文章。
java简易聊天室制作要求
1. 具有简易的GUI界面
2. 能实现一个聊天室中多人聊天
3. 可以两人私聊
提示:使用socket通信
1.0版本只考虑到了多人聊天室的功能,升级版本则考虑加入私聊功能和展现在线用户的页面。
- 所应用的原理:TCP协议--Socket通信
基于TCP的聊天室,支持多个用户同时登陆服务器进行聊天。(相当于群)
Socket编程,做一对多的多线程,必然要用到多线程,保证多个客户端(并行)登陆服务器时同时进行聊天。
TCP协议:用户传输协议、可靠连接、三次握手四次分手。
socket提供一组函数接口,套接字(socket)是一个抽象层,应用程序可以通过它发送或接受数据,可对其进行像对文件一样打开、读写和关闭等操作。网络套接字是IP地址与端口的组合。套接字Socket=(IP地址:端口号)
TCP通信步骤:
服务器端:
- 创建socket套接字;
- 确定端口号;
- 绑定socket套接字和网络通信地址;
- 设置监听listen();
- 等待客户端连接;
- 连接后,一个客户端对应一个线程。
客户端:
- 创建socket套接字;
- 准备网络通信地址(服务器的IP和端口);
- 连接服务器。
二话不说,先上代码。
Client.java
package client;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.StringTokenizer;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;
import server.User;
/**
* @功能描述:多人聊天室客户端
* @创建作者:炸奶糕
* @创建日期:2021年12月22日上午10:53:58
*/
public class Client {
private JFrame frame;
private JTextArea textArea;
private JTextField textField;
private JTextField txt_port;
private JTextField txt_hostIp;
private JTextField txt_name;
private JButton start;//启动按钮
private JButton stop;//停止按钮
private JButton send;//发送按钮
private JRadioButton groupchat;//群聊按钮
private JRadioButton privatechat;//私聊按钮
private ButtonGroup buttongroup;//按钮组
private JPanel buttonPanel;
private JPanel northPanel;
private JPanel southPanel;
private JScrollPane rightScroll;//右滚动窗口
private JScrollPane leftScroll;//左滚动窗格
private JSplitPane centerSplit;//分割面板
private JList<String> userList;//用户列表可多行显示
private DefaultListModel<String> listModel;//用一个特别的类构造JList,对这个类的内容添加或减少,JList可以很快做出响应
private boolean isConnected = false;//客户端连接上服务器的判断符号
private Socket socket;
private PrintWriter writer;//写入
private BufferedReader reader;//读入
private MessageThread messageThread;// 负责接收消息的线程
private Map<String, User> onLineUsers = new HashMap<String, User>();// 所有在线用户,可以和他们私聊
//主方法
public static void main(String[] args) {
new Client();
}
//构造方法
public Client(){
//初始化客户端窗口
textArea = new JTextArea();
textArea.setEditable(false);
textField = new JTextField();
txt_port = new JTextField("12345");//端口号
txt_hostIp = new JTextField("127.0.0.1");//IP地址
Random rand = new Random();
txt_name = new JTextField("用户" + rand.nextInt(100));//默认昵称为:“用户+100内随机数”。
start = new JButton("登录");
stop = new JButton("注销");
send = new JButton("发送");
listModel = new DefaultListModel<String>();
userList = new JList<String>(listModel);
//添加北部容器的内容
northPanel = new JPanel();
northPanel.setLayout(new GridLayout(1, 7));
northPanel.add(new JLabel("端口"));
northPanel.add(txt_port);
northPanel.add(new JLabel("服务器IP"));
northPanel.add(txt_hostIp);
northPanel.add(new JLabel("昵称"));
northPanel.add(txt_name);
northPanel.add(start);
northPanel.add(stop);
northPanel.setBorder(new TitledBorder("连接信息"));
//定义滚动窗口的内容(右:消息显示区;左:在线用户列表)
rightScroll = new JScrollPane(textArea);
rightScroll.setBorder(new TitledBorder("消息显示区"));
leftScroll = new JScrollPane(userList);
leftScroll.setBorder(new TitledBorder("在线用户"));
//禁止textArea里用光标输入文字,文字显示区域不能编辑
textArea.setEditable(false);
//定义群聊or私聊二选一选项按钮
groupchat=new JRadioButton("群聊");
privatechat=new JRadioButton("私聊");
//将按钮归纳为按钮组并将其容器定为流布局
privatechat.setSelected(true);
buttongroup=new ButtonGroup();
buttongroup.add(groupchat);
buttongroup.add(privatechat);
buttonPanel=new JPanel();
buttonPanel.setLayout(new FlowLayout());
buttonPanel.add(groupchat);
buttonPanel.add(privatechat);
//添加组件至南部容器
southPanel = new JPanel(new BorderLayout());
southPanel.add(buttonPanel, "North");
southPanel.add(textField, "Center");
southPanel.add(send, "East");
southPanel.setBorder(new TitledBorder("写消息"));
//左右分隔栏
centerSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftScroll,rightScroll);
centerSplit.setDividerLocation(100);
//用JFrame的子类frame定义布局大小及位置
frame = new JFrame("客户端");
frame.setLayout(new BorderLayout());
frame.add(northPanel, "North");
frame.add(centerSplit, "Center");
frame.add(southPanel, "South");
frame.setSize(600, 400);
frame.setVisible(true);
// 单击 发送 按钮事件
send.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
send();
}
});
// 单击 连接 按钮事件
start.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int port;
if (isConnected) {
JOptionPane.showMessageDialog(frame, "已处于连接上状态,不要重复连接!",
"错误", JOptionPane.ERROR_MESSAGE);
return;
}
try {
try {
port = Integer.parseInt(txt_port.getText().trim());
} catch (NumberFormatException e2) {
throw new Exception("端口号不符合要求!端口为整数!");
}
String hostIp = txt_hostIp.getText().trim();
String name = txt_name.getText().trim();
if (name.equals("") || hostIp.equals("")) {
throw new Exception("昵称、服务器IP不能为空!");
}
boolean flag = connectServer(port, hostIp, name);
if (flag == false) {
throw new Exception("与服务器连接失败!");
}
frame.setTitle(name);
JOptionPane.showMessageDialog(frame, "成功连接!");
} catch (Exception exc) {
JOptionPane.showMessageDialog(frame, exc.getMessage(),
"错误", JOptionPane.ERROR_MESSAGE);
}
}
});
// 单击 断开 按钮时事件
stop.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (!isConnected) {
JOptionPane.showMessageDialog(frame, "已处于断开状态,不要重复断开!",
"错误", JOptionPane.ERROR_MESSAGE);
return;
}
try {
boolean flag = closeConnection();// 断开连接
if (flag == false) {
throw new Exception("断开连接发生异常!");
}
JOptionPane.showMessageDialog(frame, "成功断开!");
} catch (Exception exc) {
JOptionPane.showMessageDialog(frame, exc.getMessage(),
"错误", JOptionPane.ERROR_MESSAGE);
}
}
});
// 关闭 窗口时 事件
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
if (isConnected) {
closeConnection();// 关闭与服务器的连接
}
System.exit(0);// 退出程序
}
});
}
// 执行 发送
public void send() {
if (!isConnected) {
JOptionPane.showMessageDialog(frame, "还没有连接服务器,无法发送消息!", "错误",
JOptionPane.ERROR_MESSAGE);
return;
}
String message=textField.getText().trim();
String selectUser="所有人";
if (message == null || message.equals("")) {
JOptionPane.showMessageDialog(frame, "消息不能为空!", "错误",
JOptionPane.ERROR_MESSAGE);
}
//如果点击群聊按钮
if(groupchat.isSelected()){
sendMessage("TOALL@"+frame.getTitle()+"@"+selectUser+"@"+message);
// System.out.println("群聊发送");
textField.setText("");
}
//如果点击私聊按钮
if(privatechat.isSelected()){
//选择私聊用户
selectUser=(String)userList.getSelectedValue();
//必须点击一个在线用户才能进行私聊,因此此处进行判断
if(selectUser==null){
JOptionPane.showMessageDialog(frame, "请选择想私聊的用户!");
return;
}
sendMessage("TOONE@"+frame.getTitle()+"@"+selectUser+"@"+message);
String t="我@"+selectUser+"说:"+message+"\r\n";
textArea.append(t);
//textArea.setForeground(Color.BLUE);
textField.setText("");
}
}
//此处连接服务器
public boolean connectServer(int port, String hostIp, String name) {
// 连接服务器
try {
socket = new Socket(hostIp, port);// 根据端口号和服务器IP建立连接(Socket通信)
writer = new PrintWriter(socket.getOutputStream());
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 发送客户端用户基本信息至服务器(用户名和IP地址)
sendMessage(name + "@" + socket.getLocalAddress().toString());
// 开启接收消息的线程
messageThread = new MessageThread(reader, textArea);
messageThread.start();
isConnected = true;// 已经连接上了,从而点击连接按钮事件时能根据此处进行判断
return true;
}catch (Exception e) {
textArea.append("与端口号为:" + port + " IP地址为:" + hostIp
+ " 的服务器连接失败!" + "\r\n");
isConnected = false;//与服务器连接失败
return false;
}
}
//发送消息的方法
public void sendMessage(String message) {
writer.println(message);
writer.flush();
}
//客户端主动关闭连接
@SuppressWarnings("deprecation")
public synchronized boolean closeConnection() {
try {
sendMessage("CLOSE");// 此处发送断开连接命令给服务器
messageThread.stop();// 客户端停止接受消息线程
//客户端 释放资源
if (reader != null) {
reader.close();
}
if (writer != null) {
writer.close();
}
if (socket != null) {
socket.close();
}
isConnected = false;
return true;
} catch (IOException e1) {
e1.printStackTrace();
isConnected = true;
return false;
}
}
// 持续接收消息的线程
class MessageThread extends Thread {
private BufferedReader reader;
private JTextArea textArea;
String username=textField.getName();
// 此处为接收消息线程的构造方法
public MessageThread(BufferedReader reader, JTextArea textArea) {
this.reader = reader;
this.textArea = textArea;
}
// 客户端被动的关闭连接(即服务器关闭,断开连接)
public synchronized void closeCon() throws Exception {
// 清空 用户列表
listModel.removeAllElements();
// 被动的关闭连接释放资源
if (reader != null) {
reader.close();
}
if (writer != null) {
writer.close();
}
if (socket != null) {
socket.close();
}
isConnected = false;// 修改用户状态为断开
}
public void run() {
String message = null;
while (true) {
try {
message = reader.readLine();
StringTokenizer st=new StringTokenizer(message,"/@");
//命令
String parts=st.nextToken();
switch(parts){
case "CLOSE":{
textArea.append("服务器已关闭!\r\n");
closeCon();// 被动的关闭连接
return;//结束线程
}
//用户列表事件
case "USERLIST":{
int size=Integer.parseInt(st.nextToken());
String username=null;
String userIp=null;
for(int i=0;i<size;i++){
username=st.nextToken();
userIp=st.nextToken();
User user=new User(username,userIp);
onLineUsers.put(username, user);
listModel.addElement(username);
}
break;
}
//有新用户加入时的事件
case "ADD":{
String username = "";
String userIp = "";
if ((username = st.nextToken()) != null
&& (userIp = st.nextToken()) != null) {
User user = new User(username, userIp);
onLineUsers.put(username, user);
listModel.addElement(username);
textArea.append("系统提示:"+username+"上线!\r\n");
}
break;
}
//有用户断开连接下线时
case "DELETE":{
String username=st.nextToken();
// System.out.println(username+"下线");
User user = (User) onLineUsers.get(username);
onLineUsers.remove(user);
listModel.removeElement(username);
textArea.append("系统提示:"+username+"下线!\r\n");
userList.setModel(listModel);
break;
}
//当有用户选择群聊时
case "TOALL":{
textArea.append(st.nextToken()+"\r\n");
//System.out.println("群聊");
break;
}
//当有用户选择两人私聊时
case "TOONE":{
textArea.append(st.nextToken()+"\r\n");
//System.out.println("私聊");
//textArea.setForeground(color.blue);//私聊的消息为蓝色
break;
}
default:
textArea.append(message+"\r\n");
break;
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
Server.java
package server;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;
/**
* @功能描述:多人聊天室服务端
* @创建作者:炸奶糕
* @创建日期:2021年12月22日上午10:56:03
*/
public class Server extends JFrame {
private static final long serialVersionUID = 1L;
private ServerSocket serveSocket;
private ServerThread serverThread;
private ArrayList<ClientThread> clients;
private JFrame frame;
private JTextArea txt1;
private JTextField txt_message;
private JTextField txt_port;
private JButton start;
private JButton send;
private JButton stop;
private JPanel northPanle;
private JPanel southPanle;
private JScrollPane leftPanle;
private JScrollPane rightPanle;
private JSplitPane centerSplit;
private JList<String> userList;
private DefaultListModel<String> listModel;
private boolean isStart=false;
//主函数
public static void main(String[] args) {
new Server();
}
//构造函数
public Server(){
frame=new JFrame("服务器");
txt1=new JTextArea();
txt_message=new JTextField(30);
txt_port=new JTextField("12345");
start=new JButton("监听此端口");
stop=new JButton("停止服务器");
send=new JButton("发送");
listModel=new DefaultListModel<String>();
userList=new JList<String>(listModel);
southPanle=new JPanel(new BorderLayout());
southPanle.setBorder(new TitledBorder("写消息"));
southPanle.add(txt_message,BorderLayout.CENTER);
southPanle.add(send,BorderLayout.EAST);
leftPanle=new JScrollPane(userList);
leftPanle.setBorder(new TitledBorder("在线用户"));
rightPanle=new JScrollPane(txt1);
rightPanle.setBorder(new TitledBorder("消息显示区"));
//禁止txt1里用光标输入文字,文字显示区域不能编辑
txt1.setEditable(false);
centerSplit=new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,leftPanle,rightPanle);
centerSplit.setDividerLocation(100);
northPanle=new JPanel();
northPanle.setLayout(new GridLayout(1,6));
northPanle.add(start);
northPanle.add(txt_port);
northPanle.add(stop);
frame.setLayout(new BorderLayout());
frame.add(northPanle, BorderLayout.NORTH);
frame.add(centerSplit, BorderLayout.CENTER);
frame.add(southPanle, BorderLayout.SOUTH);
frame.setSize(600, 400);
frame.setVisible(true);
// 关闭窗口事件
frame.addWindowListener(new WindowAdapter() {
@SuppressWarnings("unused")
public void windowclosing(WindowEvent e){
if(isStart){
closeServer();//关闭服务器
}
System.exit(0);//退出程序
}
});
@SuppressWarnings("unused")
int port=Integer.parseInt(txt_port.getText());
// 监听端口事件,即启动服务器
start.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if(isStart){
JOptionPane.showMessageDialog(frame, "服务器已处于启动状态","错误",JOptionPane.ERROR_MESSAGE);
return;
}
int port ;
try{
try{
port= Integer.parseInt(txt_port.getText());
}catch(Exception e1){
throw new Exception("端口号 为正整数!");
}
if (port<= 0) {
throw new Exception("端口号 为正整数!");
}
serverStart(port);
txt1.append("服务器已启动,端口:"+port+",正在等待客户端连接...\r\n");
JOptionPane.showMessageDialog(frame, "服务器成功启动");
start.setEnabled(false);
txt_port.setEnabled(false);
stop.setEnabled(true);
} catch (Exception ee) {
JOptionPane.showMessageDialog(frame, ee.getMessage(),"错误",JOptionPane.ERROR_MESSAGE);
}
}
});
// 单击停止服务器按钮时事件
stop.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(!isStart){
JOptionPane.showMessageDialog(frame, "服务器还未启动!", "错误",JOptionPane.ERROR_MESSAGE);
return;
}
try {
closeServer();
start.setEnabled(true);
txt_port.setEnabled(true);
stop.setEnabled(false);
txt1.append("服务器成功停止!\r\n");
JOptionPane.showMessageDialog(frame, "服务器成功停止!");
} catch (Exception exc) {
JOptionPane.showMessageDialog(frame, "停止服务器发生异常!", "错误",
JOptionPane.ERROR_MESSAGE);
}
}
});
// 单击发送按钮时事件
send.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
send();
}
});
}
//启动服务器
public void serverStart(int port)throws java.net.BindException {
try {
clients=new ArrayList<ClientThread>();
serveSocket = new ServerSocket(port);
serverThread = new ServerThread(serveSocket);
serverThread.start();
isStart=true;
}catch(BindException e){
isStart=false;
throw new BindException("端口号已被占用,换一个");
}catch (Exception e) {
e.printStackTrace();
isStart=false;
throw new BindException("启动服务器异常");
}
}
//关闭服务器
@SuppressWarnings("deprecation")
public void closeServer() {
try {
if (serverThread != null)
serverThread.stop();// 停止服务器线程
for (int i = clients.size() - 1; i >= 0; i--) {
// 给所有在线用户发送关闭命令
clients.get(i).getWriter().println("CLOSE");
clients.get(i).getWriter().flush();
// 释放资源
clients.get(i).stop();// 停止此条为客户端服务的线程
clients.get(i).reader.close();
clients.get(i).writer.close();
clients.get(i).socket.close();
clients.remove(i);
}
if (serveSocket != null) {
serveSocket.close();// 关闭服务器端连接
}
listModel.removeAllElements();// 清空用户列表
isStart=false;
} catch (IOException e) {
e.printStackTrace();
isStart=true;
}
}
// 执行消息发送
public void send() {
if(!isStart){
JOptionPane.showMessageDialog(frame, "服务器未启动,不能发送消息!","错误",JOptionPane.ERROR_MESSAGE);
return;
}
if (clients.size() == 0) {
JOptionPane.showMessageDialog(frame, "没有用户在线,不能发送消息!", "错误",JOptionPane.ERROR_MESSAGE);
return;
}
String message = txt_message.getText().trim();
if (message == null || message.equals("")) {
JOptionPane.showMessageDialog(frame, "消息不能为空!", "错误",JOptionPane.ERROR_MESSAGE);
return;
}
sendServerMessage(message);// 群发服务器消息
txt1.append("服务器提示:" + txt_message.getText() + "\r\n");//服务器说的话显示在服务器界面
txt_message.setText(null);
}
//把后台消息发送给各个客户端
public void sendServerMessage(String message) {
for (int i = clients.size() - 1; i >= 0; i--) {
clients.get(i).getWriter().println("系统提示:" + message+"(群发)");//服务器获得的输出流发送给客户端界面
clients.get(i).getWriter().flush();
}
}
//每个连接到服务器的客户端,又有与之对应的一个线程来单独处理,收发消息
class ClientThread extends Thread{
Socket socket;
BufferedReader reader;
PrintWriter writer;
private User user;
public BufferedReader getReader(){
return reader;
}
public PrintWriter getWriter(){
return writer;
}
public User getUser(){
return user;
}
//每个客户端对应一个客户端线程处理
public ClientThread(Socket socket){
try {
this.socket=socket;
reader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer=new PrintWriter(socket.getOutputStream());
//接收客户端的基本信息
String line=reader.readLine();
String[] st=line.split("@");
user=new User(st[0],st[1]);
//反馈连接成功消息
writer.println("系统提示:"+user.getName()+user.getIp()+"与服务器连接成功!");
//System.out.println(user.getName()+".."+user.getIp());
//反馈当前用户信息
if(clients.size()>0){
String temp="";
for(int i=clients.size()-1;i>=0;i--){
temp+=(clients.get(i).getUser().getName()+"/"+clients.get(i).getUser().getIp())+"@";
}
writer.println("USERLIST@"+clients.size()+"@"+temp);
writer.flush();
}
System.out.println(st[0]+",服务器显示上线"+st[1]);
//向所有在线用户发送该用户上线命令,即把新上线的用户添加在在线用户列表中
for(int i=clients.size()-1;i>=0;i--){
clients.get(i).getWriter().println("ADD@"+user.getName()+"@"+user.getIp());
clients.get(i).getWriter().flush();
}
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
@SuppressWarnings("deprecation")
public void run(){//不断接受客户端的消息进行处理
String message=null;
while(true){
try {
message=reader.readLine();//接收客户端消息
if (message.equals("CLOSE"))// 下线命令
{
txt1.append(this.getUser().getName()+ this.getUser().getIp() + "下线!\r\n");
// 断开连接释放资源
reader.close();
writer.close();
socket.close();
// 向所有在线用户发送该用户的下线命令
for (int i = clients.size() - 1; i >= 0; i--) {
clients.get(i).getWriter().println("DELETE@" + user.getName());
clients.get(i).getWriter().flush();
}
listModel.removeElement(user.getName());// 更新在线列表
// 删除此条客户端服务线程
for (int i = clients.size() - 1; i >= 0; i--) {
if (clients.get(i).getUser() == user) {
ClientThread temp = clients.get(i);
clients.remove(i);// 删除此用户的服务线程
temp.stop();// 停止这条服务线程
return;
}
}
}else{
dispatcherMessage(message);// 转发消息
}
} catch (Exception e) {
// TODO: handle exception
}
}
}
private void dispatcherMessage(String message) {
String[] parts=message.split("@");
String string=parts[1]+"对"+parts[2]+"说:"+parts[3];
if (parts[0].equals("TOALL")) {// 群发
for (int i = clients.size() - 1; i >= 0; i--) {
clients.get(i).getWriter().println("TOALL@"+string);
clients.get(i).getWriter().flush();
//System.out.println("群聊消息发送");
}
txt1.append(string + "\r\n");
}
if(parts[0].equals("TOONE")){//私发
for(int i=0;i<clients.size();i++){
if(parts[2].equals((clients.get(i).getUser().getName())))
{
string=parts[1]+"对我说:"+parts[3];
clients.get(i).getWriter().println("TOONE@"+string);
clients.get(i).getWriter().flush();
//System.out.println("私聊消息发送");
}
txt1.append( parts[1]+"对"+parts[2]+"说:"+parts[3]+ "\r\n");
}
}
}
}
//服务器线程
class ServerThread extends Thread {
private ServerSocket serverSocket;
// 服务器线程的构造方法
public ServerThread(ServerSocket serverSocket) {
this.serverSocket = serverSocket;
}
public void run() {
while (true) {// 不停的等待客户端的链接
try {
Socket socket = serverSocket.accept();
ClientThread client = new ClientThread(socket);
client.start();// 开启对此客户端服务的线程
clients.add(client);
listModel.addElement(client.getUser().getName());// 更新在线列表
txt1.append(client.getUser().getName()+ client.getUser().getIp() + "上线!\r\n");
}catch (SocketException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
User.java
package server;
public class User {
private String name;
private String ip;
public User(String name,String ip){
this.name=name;
this.ip=ip;
}
public String getName() {
return name;
}
public void setName(String name){
this.name=name;
}
public String getIp() {
return ip;
}
public void setIp(String ip){
this.ip=ip;
}
}
(1)用户设置:
功能描述:用户可在客户端页面未按连接按钮之前,自定义自己的昵称,成功设置后,服务端能接受到新设置的用户名;
(2)登录功能:
功能描述:在服务端开启的情况下,用户可以通过自己定义或者默认登录名和登录地址上线聊天室,并在服务器显示登录状态;
- 群聊功能:
功能描述:用户在客户端窗口中可以选择“群聊”发送信息,同在聊天室的所有用户进行聊天;
- 私聊功能:
功能描述:用户可以点击其他任意一个在线用户进行私聊,且双方的聊天记录是对其他用户不可见的;
- 系统消息:
功能描述:服务器正常运行的过程中,在聊天室的每一个用户都能接受服务器端发送的提示信息;
- 用户的状态:
功能描述:每一个客户端用户可以实时看到看到其他在线用户的状态;
- 用户注销:
功能描述:每一个客户端用户都可以注销自己的账号,重新登记昵称;
- 用户退出:
功能描述:用户可以在正常情况下退出自己的聊天室;
- 服务端控制:
功能描述:只有在服务端开启的情况下,用户才能登陆成功和实现聊天功能;用户设置的地址端口也必须与服务器的侦听端口一致。
该简易聊天室分为聊天室服务器和聊天室客户端,服务端和客户端之间通过Socket(TCP)连接,进行信息传输及转发。服务器采用多线程以满足多个用户同时在线聊天的需求,并通过创建一个socket对象监听来自客户端的连接请求,默认IP地址为127.0.0.1,默认端口号为12345。
端口服务:启动开始侦听的窗口,默认端口号为12345;
启动服务:启动服务器,准备连接客户端,连接成功后,可通过与服务器通信,建立管道,与其他在线用户进行聊天;
停止服务:关闭服务器,侦听结束。客户端将不能再聊天;
发送系统消息:服务端给所有人发送信息或者命令;
消息显示:可以显示所有用户的聊天记录,和上线、下线的记录;
退出服务器:退出程序,并停止服务。
(2)客户端模块图:
用户设置:在连接服务端之前,用户可以自定义昵称;
连接设置:用户可以设置自己服务器的端口号和IP地址,默认连接地址为127.0.0.1:12345;
用户登录:只有在服务端运行的情况下,用户才能成功登录,用户登录之后才能开始聊天,登录之后就不能再更新用户设置和连接设置;
用户注销(主动断开):用户点击注销后,会显示下线,退出聊天,此时可以修改用户设置和连接设置;
发送、接受:用户可以群发或者私聊,也可以看到其他人发给自己或者所有人的聊天记录;
退出窗口:关闭所有聊天及退出窗口。
服务器端由一个package.server组成,内含Server.java和User.java、Server.java内有三个类:Server类、ServerThread类和ClientThread类,其中ServerThread、ClientThread类是Server的内部类。User.java只有一个类User.java。
Server类的主要功能是设置服务端窗口的GUI界面、管理窗口关闭事件、管理监听启动事件、管理点击按钮事件、设置启动、停止的方法(方便调用多线程)、发送功能(将服务端输入的信息发送到所有在线用户)、发送系统消息或命令(后台命令)。
ClientThread类作为Server类的内部类,继承自Thread类,每个连接到服务器的客户端,又有与之对应的一个线程来单独处理,收发消息。它可以接收客户端的基本信息、反馈连接成功的信息、向所有在线用户发送该用户上线命令,即把新上线的用户添加在在线用户列表中、不断接受客户端的消息进行处理、对于私聊和群聊两种不同聊天方式采用private定义了dispatcherMessage()方法,在此方法中对这两种方式进行判断,在进行不同的处理。
ServerThread类也是Server类的内部类,同样继承自Thread类,用于等待接收新客户端的连接进而开启其对应的ClientThread客户端线程,更新在线用户列表。
User类是用户类,只有一个构造方法User(),其主要用于Sever.java中登录系统时对端口及IP地址进行判定,该类则为登录程序的模型(model)。