前置知识:
获取时间:
JDK8之前:
Date date = new Date();//获取当前时间 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置时间格式 System.out.println(sdf.format(date));
JDK8之后:
LocalDateTime now = LocalDateTime.now(); System.out.println(now + String.valueOf(now.getYear())); DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String nowStr = dtf.format(now); System.out.println(nowStr);
StringBuilder高效拼接字符串
直接用“+”拼接,因为字符串变量在内存就一个,拼接就是声明一个新的String对象,开销很大。
BigDecimal解决字符串失真。
具体实施
思路分析:
一共两个页面登陆页面。
首先是登陆页面:
点击登陆按钮,就会触发点击事件。
然后调用login方法:socket与服务端建立连接,然后就是用特殊数据流DataOutputStream发送数据。首先发送“1”,告诉服务器有用户登陆进来了。再发送用户名给服务器。
让我很有收获的是,我在想怎么跳转界面,没想到是直接new一个群聊窗口对象,在new的时候就调用构造器方法,就完成界面初始化了。这里this起到大作用,因为新页面需要登陆界面的管道socket和用户名,本来的构造器接受这两个数据,并且调用兄弟构造器完成界面初始化。
这个就很有收获了,界面在互相跳转的时候,就会带着数据一起数据一起跳。
对了,我们把一些常量,专门用了一个类来存储,接下来很方便变动。
然后就是群聊页面:
在new客户端界面初始化,而且会唤醒一个进程,用来接受客户端的消息,根据消息判断要干什么。
点击发送按钮,就会触发点击事件。
然后调用sendMsgToServer方法,然后DataOutputStream数据流,借用上个页面的管道socket,发送“2”,这样服务器就会知道,发送的群聊消息。
为什么要用特殊数据流呢?因为很方便,发送的数据能直接拿来用。
群聊线程:
会不断接受来自服务器的请求,接受到1,就是更新用户列表。接收到2,就是追加消息。
最后服务器:
服务器会一直死循环,不断的尝试与客户端进行管道socket的连接。一旦有连接,这里多线程好处就体现出来了,直接将socket交给另一个线程处理ServerReaderThread(socket).start()。
接下来就是线程处理了,线程使用管道接受数据,先判断是1、还是2,这涉及到不同的功能。1更新用户列表,2发送消息给所有用户。
那么先说功能1:首先将用户名存储在集合中。然后将集合送入
Server.onLineSockets.put(socket,nickname);由这个方法进行处理,首先会发送1,让客户端进程知道要干嘛,在发送用户的个数,然后遍历发送。
功能2:首先读取发送的消息内容,然后调用sendMsgtoAll(msg),在这个方法中我们要将消息,用户名,时间进行拼接,使用StringBuilder来拼接,为什么要用这个,还有时间的获取,请看上一篇博客。然后发送就行了。
不错,很有收获!
接下来就是源代码
文件结构
public class Server {
//定义一个集合容器存储所有的登录进来的客户端管道,以便未来群发消息给他们
public static final Map<Socket, String> onLineSockets = new HashMap<>();
public static void main(String[] args)
{
System.out.println("-------服务器启动-------");
try {
//注册端口
ServerSocket serverSocket = new ServerSocket(Constant.PORT);
//主线程负责接受客户端请求
while (true) {
System.out.println("等待客户端请求-----");
Socket socket = serverSocket.accept();
new ServerReaderThread(socket).start();
System.out.println("一个客户端请求成功-----");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
public void run() {
try {
DataInputStream dis = new DataInputStream(socket.getInputStream());
while (true) {
int type = dis.readInt();
switch (type) {
case 1:
String nickname = dis.readUTF();
Server.onLineSockets.put(socket,nickname);
//更新全部客户端的在线人数列表
updateClientOnlineList();
break;
case 2:
String msg = dis.readUTF();
sendMsgtoAll(msg);
break;
default:
System.out.println();
}
}
} catch (IOException e) {
System.out.println("客户端退出"+socket.getInetAddress().getAddress());
Server.onLineSockets.remove(socket);//移除该socket
updateClientOnlineList();//更新全部客户端的在线人数列表
}
}
private void sendMsgtoAll(String msg) {
StringBuilder sb = new StringBuilder();
String name = Server.onLineSockets.get(socket);
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a");
String nowStr = dtf.format(now);
String msgResult = sb.append(name).append(" ").append(nowStr).append("\r\n").append(msg).append("\r\n").toString();
for (Socket socket : Server.onLineSockets.keySet()) {
try {
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(2);
dos.writeUTF(msgResult);
dos.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private void updateClientOnlineList() {
Collection<String> onLineUsers = Server.onLineSockets.values();
for (Socket socket : Server.onLineSockets.keySet()) {
try {
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(1);//1
dos.writeInt(Server.onLineSockets.size());
for (String nickname : Server.onLineSockets.values()) {
dos.writeUTF(nickname);
}
dos.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
public class Constant {
public static final int PORT = 6666;
}
public class ClientReaderThread extends Thread {
private Socket socket;
private DataInputStream dis;
private LanGroupChatUI win;
public ClientReaderThread(Socket socket, LanGroupChatUI win) {
this.win = win;
this.socket = socket;
}
public void run() {
try {
//接受消息的种类,1更新在线人数2群聊消息
dis = new DataInputStream(socket.getInputStream());
while (true) {
int type = dis.readInt();
switch (type) {
case 1:
updateClientOnlineUserList();
break;
case 2:
getMsgToWin();
break;
default:
System.out.println();
}
}
} catch (IOException e) {
}
}
private void getMsgToWin() throws IOException {
String msg = dis.readUTF();
win.setMsgToWin(msg);
}
private void updateClientOnlineUserList() throws IOException {
int count = dis.readInt();
String[] onlinenames = new String[count];
for (int i = 0; i < count; i++) {
String nickname = dis.readUTF();
onlinenames[i] = nickname;
}
win.updateClientOnlineUserList(onlinenames);
}
}
public class Constant {
public static final int SERVER_PORT = 6666;
public static final String SERVER_IP = "127.0.0.1";
}
public class LanChatEntryUI extends JFrame {
private JTextField nicknameField;
private JButton enterButton;
private JButton cancelButton;
private Socket socket;
public LanChatEntryUI() {
try {
// 设置界面风格为系统默认风格,使界面更美观
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
// 设置窗口标题
setTitle("局域网聊天 - 进入界面");
// 设置窗口大小
setSize(300, 200);
// 设置窗口关闭操作
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置窗口布局
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 10, 10, 10);
// 昵称标签
JLabel nicknameLabel = new JLabel("昵称:");
nicknameLabel.setFont(new Font("楷体", Font.PLAIN, 16));
gbc.gridx = 0;
gbc.gridy = 0;
add(nicknameLabel, gbc);
// 昵称输入框
nicknameField = new JTextField(20);
nicknameField.setFont(new Font("楷体", Font.PLAIN, 16));
gbc.gridx = 1;
gbc.gridy = 0;
add(nicknameField, gbc);
// 创建一个面板用于放置按钮
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10)); // 水平居中对齐
// 进入按钮
enterButton = new JButton("进入");
enterButton.setFont(new Font("楷体", Font.PLAIN, 16));
enterButton.setOpaque(true); // 确保按钮是不透明的
enterButton.setBackground(Color.GREEN); // 设置背景颜色
enterButton.setForeground(Color.BLACK); // 设置字体颜色
enterButton.setFocusPainted(false); // 去掉焦点边框
enterButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String nickname = nicknameField.getText();
nicknameField.setText("");
if (!nickname.isEmpty()) {
try {
login(nickname);
dispose();
new LanGroupChatUI(nickname, socket);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
} else {
JOptionPane.showMessageDialog(LanChatEntryUI.this, "请输入昵称", "提示", JOptionPane.WARNING_MESSAGE);
}
}
});
buttonPanel.add(enterButton);
// 取消按钮
cancelButton = new JButton("取消");
cancelButton.setFont(new Font("楷体", Font.PLAIN, 16));
cancelButton.setOpaque(true); // 确保按钮是不透明的
cancelButton.setBackground(Color.RED); // 设置背景颜色
cancelButton.setForeground(Color.BLACK); // 设置字体颜色
cancelButton.setFocusPainted(false); // 去掉焦点边框
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0); // 点击取消时退出程序
}
});
buttonPanel.add(cancelButton);
// 将按钮面板添加到主界面
gbc.gridx = 0;
gbc.gridy = 1;
gbc.gridwidth = 2; // 占据两列
gbc.anchor = GridBagConstraints.CENTER; // 居中显示
add(buttonPanel, gbc);
// 居中显示窗口
setLocationRelativeTo(null);
// 显示窗口
setVisible(true);
}
public void login(String nickname) throws IOException {
socket = new Socket(Constant.SERVER_IP, Constant.SERVER_PORT);
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(1);
dos.writeUTF(nickname);
dos.flush();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new LanChatEntryUI();
}
});
}
}
public class LanGroupChatUI extends JFrame {
private JTextArea messageDisplayArea;
private JTextField messageInputField;
private JButton sendButton;
private JList<String> onlineNicknameList;
private DefaultListModel<String> onlineNicknameModel;
private String nickname;
private Socket socket;
public LanGroupChatUI(String nickname, Socket socket){
this();
//立马展示昵称到窗口
this.setTitle(nickname);
this.socket = socket;
new ClientReaderThread(socket,this).start();
}
public void sendMsgToServer(String message) throws IOException {
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(2);
dos.writeUTF(message);
}
public LanGroupChatUI() {
// 设置窗口标题
setTitle("局域网群聊界面");
// 设置窗口大小
setSize(800, 600);
// 设置窗口关闭操作
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置布局为边界布局
setLayout(new BorderLayout());
// 设置字体为楷体
Font kaiTiFont = new Font("楷体", Font.PLAIN, 14);
// 群聊消息展示框
messageDisplayArea = new JTextArea();
messageDisplayArea.setFont(kaiTiFont);
messageDisplayArea.setEditable(false);
JScrollPane messageScrollPane = new JScrollPane(messageDisplayArea);
add(messageScrollPane, BorderLayout.CENTER);
// 消息发送面板
JPanel inputPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
messageInputField = new JTextField();
messageInputField.setFont(kaiTiFont);
// 动态计算输入框高度
int windowHeight = getHeight();
int inputFieldHeight = (int) (windowHeight * 0.2);
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 1.0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.ipady = inputFieldHeight - messageInputField.getPreferredSize().height;
inputPanel.add(messageInputField, gbc);
sendButton = new JButton("发送");
sendButton.setFont(kaiTiFont);
//调整按钮的大小,稍微大一点
sendButton.setPreferredSize(new Dimension(100, inputFieldHeight));
gbc.gridx = 1;
gbc.gridy = 0;
gbc.weightx = 0;//
gbc.fill = GridBagConstraints.NONE;
gbc.ipady = 0;
inputPanel.add(sendButton, gbc);
add(inputPanel, BorderLayout.SOUTH);
// 在线昵称展示
onlineNicknameModel = new DefaultListModel<>();
onlineNicknameList = new JList<>(onlineNicknameModel);
onlineNicknameList.setFont(kaiTiFont);
JScrollPane nicknameScrollPane = new JScrollPane(onlineNicknameList);
//调整宽度
nicknameScrollPane.setPreferredSize(new Dimension(150, getHeight()));
add(nicknameScrollPane, BorderLayout.EAST);
// 发送按钮点击事件
sendButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String message = messageInputField.getText();
//sendButton.setText("");
try {
sendMsgToServer(message);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
});
// 居中显示窗口
setLocationRelativeTo(null);
// 显示窗口
setVisible(true);
}