聊天室界面如下:
聊天室源代码链接
一、服务端:
窗体:
package sonyi.server;import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;public class WindowServer {public static JFrame window;public static JTextArea textMessage;//聊天记录public static JList<String> user;//用户列表public static int ports;JButton start,send,exit;JTextField portServer,message,name;//主函数入口public static void main(String[] args) {new WindowServer();}//初始化窗体public WindowServer() {init();}//初始化内容public void init(){//采用绝对布局window = new JFrame("服务端");window.setLayout(null);window.setBounds(200, 200, 500, 400);window.setResizable(false);//不可改变大小JLabel label1 = new JLabel("端口号:");label1.setBounds(10, 8, 50, 30);window.add(label1);portServer = new JTextField();portServer.setBounds(60, 8, 100, 30);portServer.setText("30000");window.add(portServer);JLabel names = new JLabel("用户名:");names.setBounds(180, 8, 55, 30);window.add(names);name = new JTextField();name.setBounds(230, 8, 60, 30);name.setText("服务端");window.add(name);start = new JButton("启动");start.setBounds(300, 8, 80, 30);window.add(start);exit = new JButton("关闭");exit.setBounds(390, 8, 80, 30);window.add(exit);JLabel label2 = new JLabel("用户列表");label2.setBounds(40, 40, 80, 30);window.add(label2);user = new JList<String>();JScrollPane scrollPane = new JScrollPane(user);//添加滚动条scrollPane.setBounds(10, 70, 120, 220);window.add(scrollPane);textMessage = new JTextArea();textMessage.setBounds(135, 70, 340, 220);textMessage.setBorder(new TitledBorder("聊天记录"));//设置标题textMessage.setEditable(false);//不可编辑//文本内容换行的两个需要配合着用textMessage.setLineWrap(true);//设置文本内容自动换行,在超出文本区域时,可能会切断单词textMessage.setWrapStyleWord(true);//设置以自动换行,以单词为整体,保证单词不会被切断JScrollPane scrollPane1 = new JScrollPane(textMessage);//设置滚动条scrollPane1.setBounds(135, 70, 340, 220);window.add(scrollPane1);message = new JTextField();message.setBounds(10, 300, 360, 50);window.add(message);send = new JButton("发送");send.setBounds(380, 305, 70, 40);window.add(send);myEvent(); //添加监听事件window.setVisible(true);}public void myEvent(){window.addWindowListener(new WindowAdapter() {//关闭窗体public void windowClosing(WindowEvent e){//如果有客户端存在,发信息给客户端,并退出if(StartServer.userList != null && StartServer.userList.size() != 0){try {new SendServer(StartServer.userList, "" , "4");//4代表服务端退出} catch (IOException e1) {e1.printStackTrace();}}System.exit(0);//退出窗体}});exit.addActionListener(new ActionListener() { //退出连接public void actionPerformed(ActionEvent e) {if(StartServer.ss == null || StartServer.ss.isClosed()){//如果已退出,弹窗提醒JOptionPane.showMessageDialog(window, "服务器已关闭");}else {//发信息告诉客户端,要退出if(StartServer.userList != null && StartServer.userList.size() != 0){try {new SendServer(StartServer.userList, "" , 4 + ""); } catch (IOException e1) {e1.printStackTrace();}}try {start.setText("启动");exit.setText("已关闭");StartServer.ss.close();//关闭服务端StartServer.ss = null;StartServer.userList = null;StartServer.flag = false;//改变服务端循环标记} catch (IOException e1) {e1.printStackTrace();}} }});//开启服务端start.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {//如果服务端已经开启,弹窗提醒服务端已开启if(StartServer.ss != null && !StartServer.ss.isClosed()){JOptionPane.showMessageDialog(window, "服务器已经启动");}else {ports = getPort();//获取端口号if(ports != 0){try {StartServer.flag = true;//改变服务端接收循环标记new Thread(new StartServer(ports)).start(); //开启服务端接收线程start.setText("已启动");exit.setText("关闭");} catch (IOException e1) {JOptionPane.showMessageDialog(window, "启动失败");}}}}});//点击按钮发送消息send.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {sendMsg();}});//按回车发送消息message.addKeyListener(new KeyAdapter() {public void keyPressed(KeyEvent e) {if(e.getKeyCode() == KeyEvent.VK_ENTER){sendMsg();}}});}//发送消息方法public void sendMsg(){String messages = message.getText();//判断内容是否为空if("".equals(messages)){JOptionPane.showMessageDialog(window, "内容不能为空!");}else if(StartServer.userList == null || StartServer.userList.size() == 0){//判断是否已经连接成功JOptionPane.showMessageDialog(window, "未连接成功,不能发送消息!");}else {try {//将信息发送给所有客户端new SendServer(StartServer.userList, getName() + ":" + messages, 1 + "");//将信息添加到客户端聊天记录中WindowServer.textMessage.append(getName() + ":" + messages + "\r\n");message.setText(null);//消息框设置为空} catch (IOException e1) {JOptionPane.showMessageDialog(window, "发送失败!");}}}//获取端口号public int getPort(){String port = portServer.getText();if("".equals(port)){//判断端口号是否为空JOptionPane.showMessageDialog(window, "端口号为口");return 0;}else {return Integer.parseInt(port);//返回整形的端口号} } //获取服务端名称public String getName(){return name.getText();}
}
发送接收线程:
package sonyi.server;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Vector;import javax.swing.JOptionPane;//启动服务端接收客户端的线程
public class StartServer implements Runnable{private int port;public static ArrayList<Socket> userList = null;public static Vector<String> userName = null;public static ServerSocket ss = null;public static boolean flag = true;//传入端口号public StartServer(int port) throws IOException{this.port = port; }public void run() {Socket s = null;userList = new ArrayList<Socket>();//客户端端口容器userName = new Vector<String>();//用户名称容器//System.out.println("启动服务端");try {ss = new ServerSocket(port);//启动服务端} catch (IOException e1) {e1.printStackTrace();}while (flag) {//开启循环,等待接收客户端try { s = ss.accept();//接收客户端userList.add(s);//将客户端的socket添加到容器中//打印客户端信息String id = s.getInetAddress().getHostName();System.out.println(id + "-----------connected");System.out.println("当前客户端个数为:" + userList.size());//启动与客户端相对应的信息接收线程new Thread(new ReceiveServer(s,userList,userName)).start();} catch (IOException e) {JOptionPane.showMessageDialog(WindowServer.window, "服务端退出!");} }}
}//服务端信息接收线程
class ReceiveServer implements Runnable{private Socket socket;private ArrayList<Socket> userList;private Vector<String> userName;public ReceiveServer(Socket s,ArrayList<Socket> userList,Vector<String> userName) {this.socket = s;this.userList = userList;this.userName = userName;}public void run() { try {//读取信息流BufferedReader brIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));while(true){ char info = (char)brIn.read();//先读取信息流的首字符,并判断信息类型String line = brIn.readLine();//读取信息流的信息内容if(info == '1'){//1代表收到的是信息WindowServer.textMessage.append(line + "\r\n");//将信息添加到服务端聊天记录中//设置消息显示最新一行,也就是滚动条出现在末尾,显示最新一条输入的信息WindowServer.textMessage.setCaretPosition(WindowServer.textMessage.getText().length());new SendServer(userList, line, "1");//将信息转发给客户端}if(info == '2'){//2代表有新客户端建立连接userName.add(line);//将新客户端用户名添加到容器中WindowServer.user.setListData(userName);//更新服务端用户列表new SendServer(userList, userName, "2");//将用户列表以字符串的形式发给客户端}if(info == '3'){//3代表有用户端退出连接userName.remove(line);//移除容器中已退出的客户端用户名userList.remove(socket);//移除容器中已退出的客户端的socketWindowServer.user.setListData(userName);//更新服务端用户列表new SendServer(userList, userName, "3");//将用户列表以字符串的形式发给客户端socket.close();//关闭该客户端的socketbreak;//结束该客户端对于的信息接收线程}} } catch (IOException e) {e.printStackTrace();}}
}//服务端发送信息
class SendServer {SendServer(ArrayList<Socket> userList,Object message,String info) throws IOException{String messages = info + message;//添加信息头标记PrintWriter pwOut = null;for(Socket s : userList){//将信息发送给每个客户端pwOut = new PrintWriter(s.getOutputStream(),true);pwOut.println(messages);}}
}
二、客户端:
窗体:
package sonyi.client;import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.Socket;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;public class WindowClient {JTextField port,name,ip,message;JButton send;public static JFrame window;public static JButton link,exit;public static JTextArea textMessage;public static Socket socket = null;public static JList<String> user;//主函数入口public static void main(String[] args) {new WindowClient();}//初始化窗体public WindowClient() {init();}//窗体初始化内容public void init(){//采用绝对布局window = new JFrame("客户端");window.setLayout(null);window.setBounds(200, 200, 500, 400);window.setResizable(false);JLabel label = new JLabel("主机IP:");label.setBounds(10, 8, 50, 30);window.add(label);ip = new JTextField();ip.setBounds(55, 8, 60, 30);ip.setText("127.0.0.1");window.add(ip);JLabel label1 = new JLabel("端口号:");label1.setBounds(125, 8, 50, 30);window.add(label1);port = new JTextField();port.setBounds(170, 8, 40, 30);port.setText("30000");window.add(port);JLabel names = new JLabel("用户名:");names.setBounds(220, 8, 55, 30);window.add(names);name = new JTextField();name.setBounds(265, 8, 60, 30);name.setText("客户端1");window.add(name);link = new JButton("连接");link.setBounds(335, 8, 75, 30);window.add(link);exit = new JButton("退出");exit.setBounds(415, 8, 75, 30);window.add(exit);JLabel label2 = new JLabel("用户列表");label2.setBounds(40, 40, 80, 30);window.add(label2);user = new JList<String>();JScrollPane scrollPane = new JScrollPane(user);//设置滚动条scrollPane.setBounds(10, 70, 120, 220);window.add(scrollPane);textMessage = new JTextArea();textMessage.setBounds(135, 70, 340, 220);textMessage.setEditable(false);//文本不可编辑textMessage.setBorder(new TitledBorder("聊天记录"));//设置标题//文本内容换行的两个需要配合着用textMessage.setLineWrap(true);//设置文本内容自动换行,在超出文本区域时,可能会切断单词textMessage.setWrapStyleWord(true);//设置以自动换行,以单词为整体,保证单词不会被切断JScrollPane scrollPane1 = new JScrollPane(textMessage);//设置滚动条scrollPane1.setBounds(135, 70, 340, 220);window.add(scrollPane1);message = new JTextField();message.setBounds(10, 300, 360, 50);message.setText(null);window.add(message);send = new JButton("发送");send.setBounds(380, 305, 70, 40);window.add(send);myEvent();//添加监听事件window.setVisible(true);//设置窗体可见 }public void myEvent(){//事件监听window.addWindowListener(new WindowAdapter() {//退出窗体public void windowClosing(WindowEvent e){//如果仍在连接,发信息给服务端,并退出if(socket != null && socket.isConnected()){try {new SendClient(socket, getName(), 3 + "");} catch (IOException e1) {e1.printStackTrace();}}System.exit(0);}});//关闭连接exit.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {//如果仍在连接,将信息发给服务端if(socket == null){JOptionPane.showMessageDialog(window, "已关闭连接");}else if(socket != null && socket.isConnected()){try {new SendClient(socket, getName(), "3");//发送信息给服务端link.setText("连接");exit.setText("已退出");socket.close();//关闭socketsocket = null;} catch (IOException e1) {e1.printStackTrace();}}}});//建立连接link.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {//判断是否已经连接成功if(socket != null && socket.isConnected()){ JOptionPane.showMessageDialog(window, "已经连接成功!");}else {String ipString = ip.getText();//获取ip地址String portClinet = port.getText();//获取端口号if("".equals(ipString) || "".equals(portClinet)){//判断获取内容是否为空JOptionPane.showMessageDialog(window, "ip或端口号为空!");}else {try {int ports = Integer.parseInt(portClinet);//将端口号转为整形socket = new Socket(ipString,ports);//建立连接link.setText("已连接");//更改button显示信息exit.setText("退出");new SendClient(socket, getName(), 2 + "");//发送该客户端名称至服务器new Thread(new ReceiveClient(socket)).start();//启动接收线程} catch (Exception e2) {JOptionPane.showMessageDialog(window, "连接未成功!可能是ip或端口号格式不对,或服务器未开启。");}}}}});//点击按钮发送信息send.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {sendMsg();}}); //按回车发送信息message.addKeyListener(new KeyAdapter() {public void keyPressed(KeyEvent e) {if(e.getKeyCode() == KeyEvent.VK_ENTER){sendMsg();}}});}//发送信息的方法public void sendMsg(){String messages = message.getText();//获取文本框内容if("".equals(messages)){//判断信息是否为空JOptionPane.showMessageDialog(window, "内容不能为空!");}else if(socket == null || !socket.isConnected()){//判断是否已经连接成功JOptionPane.showMessageDialog(window, "未连接成功,不能发送消息!");}else {try {//发送信息new SendClient(socket,getName() + ":" + messages,"1");message.setText(null);//文本框内容设置为空} catch (IOException e1) {JOptionPane.showMessageDialog(window, "信息发送失败!");}} }//获取客户端名称public String getName(){return name.getText();}
}
发送接收线程:
package sonyi.client;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;import javax.swing.JOptionPane;//启动客户端接收线程
public class StartClient {public StartClient(Socket s) throws UnknownHostException, IOException{ new Thread(new ReceiveClient(s)).start();}
}//客户端接收线程
class ReceiveClient implements Runnable{private Socket s;public ReceiveClient(Socket s) {this.s = s;}public void run() {try {//信息接收流BufferedReader brIn = new BufferedReader(new InputStreamReader(s.getInputStream()));while(true){ char info = (char)brIn.read();//读取信息流首字符,判断信息类型String line = brIn.readLine();//读取信息流内容if(info == '1'){//代表发送的是消息WindowClient.textMessage.append(line + "\r\n"); //将消息添加到文本域中//设置消息显示最新一行,也就是滚动条出现在末尾,显示最新一条输入的信息WindowClient.textMessage.setCaretPosition(WindowClient.textMessage.getText().length());}if(info == '2' || info == '3'){//有新用户加入或退出,2为加入,3为退出String sub = line.substring(1, line.length()-1);String[] data = sub.split(",");WindowClient.user.clearSelection();WindowClient.user.setListData(data);}if(info == '4'){//4代表服务端退出WindowClient.link.setText("连接");WindowClient.exit.setText("已退出");WindowClient.socket.close();WindowClient.socket = null;break;}} } catch (IOException e) {JOptionPane.showMessageDialog(WindowClient.window, "客户端已退出连接");}}
}//客户端发送信息类
class SendClient {SendClient(Socket s,Object message,String info) throws IOException{ String messages = info + message;PrintWriter pwOut = new PrintWriter(s.getOutputStream(),true);pwOut.println(messages);}
}
运行过程中应该还有很多bug,希望码友们指正,互相交流。