聊天室界面如下:
聊天室源代码链接
一、服务端:
窗体:
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 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();
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 userList = null;
public static Vector 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();//客户端端口容器
userName = new Vector();//用户名称容器
//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 userList;
private Vector userName;
public ReceiveServer(Socket s,ArrayList userList,Vector 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);//移除容器中已退出的客户端的socket
WindowServer.user.setListData(userName);//更新服务端用户列表
new SendServer(userList, userName, "3");//将用户列表以字符串的形式发给客户端
socket.close();//关闭该客户端的socket
break;//结束该客户端对于的信息接收线程
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//服务端发送信息
class SendServer {
SendServer(ArrayList 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 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();
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();//关闭socket
socket = 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,希望码友们指正,互相交流。