目录
创建服务端 建立ServerSocket服务端。
接下来就是服务端线程的编写
前端ui登录界面
客户端线程
群聊界面
package server;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class Server {
//定义一个集合容器存储所有登录进来的客户端管道,以便将来群发消息给他们
// 定义一个Map集合,键是存储客户端的管道,值是这个管道的用户名称。
public static final Map<Socket,String> onlinesSockets=new HashMap<>();
public static void main(String[] args) {
System.out.println("启动服务端程序...");
try {
ServerSocket serverSocket = new ServerSocket(Constant.SERVER_PORT);
while (true) {
System.out.println("等待客户端连接...");
Socket socket = serverSocket.accept();
System.out.println("一个客户端连接成功...");
new ServerReaderThread(socket).start();
}
}catch (Exception e)
{
e.printStackTrace();
}
}
}
创建服务端 建立ServerSocket服务端。
使用while循环不断接受客户端发来的请求,再将获得的管道交给一个线程独立完成各自的任务,实现多个客户端访问。将所有管道放在一个map集合中,以便将来群发消息给他们。
接下来就是服务端线程的编写
package server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
public class ServerReaderThread extends Thread {
private Socket s;
public ServerReaderThread(Socket s)
{
this.s = s;//设置有参构造器,将socket传入
}
@Override
public void run() {
try {
//接收的消息可能有很多中类型:1登录消息(包含名称)2 群聊消息 3 私聊消息
//所以客户端必须声明协议发送消息,
//客户端发1、2、3代表登录消息、群聊消息、私聊消息
//先从socket管道中接受客户端发送来的消息类型编号
DataInputStream dis = new DataInputStream(s.getInputStream());
while (true) {
int type = dis.readInt();
switch (type) {
case 1:
//登录消息,接下来接收名称数据,在更新全部在线客户端的在线人数列表。
String nickname = dis.readUTF();
Server.onlinesSockets.put(s, nickname);
//更新在线人数列表
updateClientOnlineCUserslist();
break;
case 2:
//群聊消息 接下来接受群聊消息内容,再把群聊消息发送给全部在线客户端。
String msg = dis.readUTF();
sendMsgAll(msg);
break;
case 3:
//私聊消息 接受私聊消息内容,再把私聊消息发送给指定的客户端。
break;
}
}
} catch (Exception e) {
System.out.println("客户端下线了:" + s.getInetAddress().getHostAddress());
Server.onlinesSockets.remove(s);
updateClientOnlineCUserslist();
}
}
private void updateClientOnlineCUserslist()
{
Collection<String>onLineUsers = Server.onlinesSockets.values();
for(Socket socket:Server.onlinesSockets.keySet())
{
try {
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(1);//告诉群聊列表消息的类型
dos.writeInt(onLineUsers.size());
for(String nickname:onLineUsers)
{
dos.writeUTF(nickname);
}
dos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void sendMsgAll(String msg)
{
StringBuilder sb=new StringBuilder();
String name=Server.onlinesSockets.get(s);
//获取当前时间
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss EEE a");
String time = dtf.format(now);
String sb1=sb.append(name).append(" ").append(time).append("\r\n").append(msg).append("\r\n").toString();
for(Socket socket:Server.onlinesSockets.keySet())
{
try {
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(2);//告诉群聊消息的类型
dos.writeUTF(sb1);
dos.flush();
}catch (Exception e)
{
e.printStackTrace();
}
}
}
}
前端ui登录界面
package com.acheng.ui;
import Client.ConstantClient;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataOutputStream;
import java.net.Socket;
public class ChatEntryFrame extends JFrame {
private JTextField nicknameField;
private JButton enterButton;
private JButton cancelButton;
private Socket socket;
public ChatEntryFrame() {
// 设置窗口标题
setTitle("局域网聊天 - 进入界面");
setSize(300, 150);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null); // 窗口居中
// 创建面板
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(3, 1)); // 3行1列的网格布局
// 创建昵称输入框
nicknameField = new JTextField();
nicknameField.setBorder(BorderFactory.createTitledBorder("昵称"));
panel.add(nicknameField);
// 创建按钮面板
JPanel buttonPanel = new JPanel();
enterButton = new JButton("登录");
cancelButton = new JButton("取消");
// 添加按钮事件
enterButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String nickname = nicknameField.getText();//获取昵称
if (!nickname.isEmpty()) {
// TODO: 处理进入聊天逻辑
//立即发送登录消息给服务端程序。
//请求一个socket管道,建立与服务端的连接。
try {
login(nickname);
new ChatApp(nickname, socket);
}catch (Exception e1)
{
e1.printStackTrace();
}
System.out.println("进入聊天,昵称: " + nickname);
dispose(); // 关闭当前窗口
} else {
JOptionPane.showMessageDialog(null, "请输入昵称!", "错误", JOptionPane.ERROR_MESSAGE);
}
}
});
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0); // 退出程序
}
});
buttonPanel.add(enterButton);
buttonPanel.add(cancelButton);
panel.add(buttonPanel);
// 将面板添加到窗口
add(panel);
setVisible(true);
}
public void login(String nickname) throws Exception
{
socket=new Socket(ConstantClient.CLIENT_IP, ConstantClient.CLIENT_PORT);
DataOutputStream dos=new DataOutputStream(socket.getOutputStream());
dos.writeInt(1);
dos.writeUTF(nickname);
dos.flush();
}
}
客户端线程
package com.acheng.ui;
import java.io.DataInputStream;
import java.net.Socket;
public class ClientReaderThread extends Thread {
private Socket s;
private ChatApp frame;
private DataInputStream dis;
public ClientReaderThread(Socket s, ChatApp frame )
{
this.s = s;
this.frame = frame;
}
@Override
public void run()
{
try {
dis = new DataInputStream(s.getInputStream());
while (true) {
//从socket管道中读取服务端发送来的消息
//客户端发1、在线人数更新的数据 2 群聊消息
//先从socket管道中接受客户端发送来的消息类型编号
int type = dis.readInt();
//根据消息类型编号,判断客户端发送的是哪种消息
switch (type)
{
case 1:
//服务端发来的在线人数更新消息
updateClientOnlineUserList(dis);
break;
case 2:
//群聊消息,从socket管道中读取消息内容
getMsgtoWin();
//将群聊消息显示到聊天界面上
break;
case 3:
//私聊消息,从socket管道中读取消息内容
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
private void updateClientOnlineUserList(DataInputStream dis)throws Exception
{
//1、先从socket管道中读取在线人数
//读取有多少个在线用户
int count =dis.readInt();
//循环读取多个用户信息
String[] names = new String[count];
for(int i=0;i<count;i++)
{
String nickname = dis.readUTF();
names[i] = nickname;
}
//将集合中的数据显示到客户端的在线用户列表中
frame.updateOnlineUsers(names);
}
private void getMsgtoWin()throws Exception
{
String msg = dis.readUTF();
frame.setMsgTowin(msg);
}
}
群聊界面
package com.acheng.ui;
import javax.swing.*;
import java.awt.*;
import java.io.DataOutputStream;
import java.net.Socket;
public class ChatApp extends JFrame {
private JFrame frame;
private JTextArea messageDisplayArea;
private JList<String> onlineUsersList;
private DefaultListModel<String> onlineUsersListModel;
private JTextField messageInputField;
private JButton sendButton;
private Socket socket;
private String name;
public ChatApp(String name, Socket socket) {
// 创建主框架
this.socket = socket;
this.name = name; // 保存昵称
frame = new JFrame(name+"的群聊界面");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600, 400);
frame.setLayout(new BorderLayout());
// 消息展示框
messageDisplayArea = new JTextArea();
messageDisplayArea.setEditable(false);
JScrollPane messageScrollPane = new JScrollPane(messageDisplayArea);
frame.add(messageScrollPane, BorderLayout.CENTER);
// 在线人数展示框
onlineUsersListModel = new DefaultListModel<>();
onlineUsersList = new JList<>(onlineUsersListModel);
JScrollPane onlineUsersScrollPane = new JScrollPane(onlineUsersList);
onlineUsersScrollPane.setPreferredSize(new Dimension(150, 0));
frame.add(onlineUsersScrollPane, BorderLayout.EAST);
// 消息发送框和发送按钮
JPanel inputPanel = new JPanel();
inputPanel.setLayout(new BorderLayout());
messageInputField = new JTextField();
sendButton = new JButton("发送");
inputPanel.add(messageInputField, BorderLayout.CENTER);
inputPanel.add(sendButton, BorderLayout.EAST);
frame.add(inputPanel, BorderLayout.SOUTH);
// 发送按钮的事件处理
sendButton.addActionListener(e -> {
String message = messageInputField.getText(); // 获取输入框中的文本
if (!message.isEmpty()) {
sendMessage(message);
messageInputField.setText(""); // 清空输入框
}
});
// 显示框架
frame.setVisible(true);
new ClientReaderThread(socket, this).start();
}
public void sendMessage(String message) {
try {
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(2); // 消息类型
dos.writeUTF(message);
dos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
public void updateOnlineUsers(String[] onLineNames) {
// 更新在线用户列表
onlineUsersList.setListData(onLineNames);
}
public void setMsgTowin(String msg) {
SwingUtilities.invokeLater(() -> {
messageDisplayArea.append(msg + "\n");
});
}
}
package Client;
public class ConstantClient {
public static final int CLIENT_PORT = 6666;
public static final String CLIENT_IP = "127.0.0.1";
}
package server;
public class Constant {
public static final int SERVER_PORT = 6666;
}
import com.acheng.ui.ChatEntryFrame;
public class App {
public static void main(String[] args) {
new ChatEntryFrame();
}
}
界面展示