当前位置: 首页 > news >正文

[ java SE ] 多人聊天窗口1.0

   目录

一.功能分析:

    (1).客户端功能

   (2).服务器功能

二.代码及详细注释

    1.客户端

(1).聊天窗口

(2).登录窗口

(3).注册窗口

(4).启动多人聊天窗口

(5).涉及到的工具类

2.服务器

     (1).服务器窗口

     (2).启动服务器窗口

三.窗口简易模版

    1.客户端

(1).登录窗口模版

 

(2).聊天窗口模版

(3).注册窗口模版

2.服务器窗口模版

四.代码思路


本篇文章为 java SE 部分的综合训练 ,有助于初学者进一步理解和熟练掌握java SE部分的知识.

一.功能分析:

    (1).客户端功能

         1.创建登录窗口,注册窗口,聊天窗口
         2.点击登录按钮,验证账号和密码,如果没有问题,连接服务器
            如果服务器连接不成功,进行提示;连接成功,打开聊天窗口
         3.在聊天窗口显示当前登录人的信息
         4.输入聊天内容,点击发送按钮发送信息
         5.显示发送聊天的内容
         6.点击关闭聊天窗口,回到登录窗口

   (2).服务器功能

         1.启动服务器
         2.循环监听客户端socket连接,在服务器保存连接到的多个socket
         3.连接到服务器的socket,需要监听自己客户端有没有向服务器端发送消息
         4.一旦客户端向服务器发消息,将此消息转发给其他连接服务器的客户端

二.代码及详细注释

    1.客户端

        (1).聊天窗口

/** Created by JFormDesigner on Sun Mar 02 16:17:47 CST 2025*/package client;import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Date;
public class ChatFrame extends  JFrame {private Socket socket;private String loginfield;//将账户定义成全局变量,在后面就可以直接使用private DataOutputStream dataOutputStream;//输出流 发送聊天内容private DataInputStream dataInputStream;//接受流  接受聊天内容//创建一个有参的构造方法,把账号loginfield和socket传进去public ChatFrame(String loginfield, Socket socket) throws IOException{this.loginfield = loginfield;this.socket = new Socket();this.dataOutputStream = new DataOutputStream(socket.getOutputStream());//输出流从socket里面获取this.dataInputStream  = new DataInputStream(socket.getInputStream());//输入流从socket里面获取initComponents();}private void initComponents() {// JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents  @formatter:offChat = new JFrame();scrollPane1 = new JScrollPane();chatFrame = new JTextArea();infoField = new JLabel();scrollPane2 = new JScrollPane();inputfield = new JTextArea();sendbtn = new JButton();label1 = new JLabel();//======== Chat ========{Chat.setTitle("\u804a\u5929\u7a97\u53e3");Container ChatContentPane = Chat.getContentPane();ChatContentPane.setLayout(null);//======== scrollPane1 ========{scrollPane1.setViewportView(chatFrame);}ChatContentPane.add(scrollPane1);scrollPane1.setBounds(5, 5, 435, 250);//---- infoField ----infoField.setText("text");ChatContentPane.add(infoField);infoField.setBounds(445, 100, 105, 90);//======== scrollPane2 ========{scrollPane2.setViewportView(inputfield);}ChatContentPane.add(scrollPane2);scrollPane2.setBounds(5, 265, 435, 85);//---- sendbtn ----sendbtn.setText("\u53d1\u9001");ChatContentPane.add(sendbtn);sendbtn.setBounds(new Rectangle(new Point(450, 290), sendbtn.getPreferredSize()));//---- label1 ----label1.setText("\u7528\u6237\u4fe1\u606f");ChatContentPane.add(label1);label1.setBounds(new Rectangle(new Point(450, 75), label1.getPreferredSize()));{// compute preferred sizeDimension preferredSize = new Dimension();for(int i = 0; i < ChatContentPane.getComponentCount(); i++) {Rectangle bounds = ChatContentPane.getComponent(i).getBounds();preferredSize.width = Math.max(bounds.x + bounds.width, preferredSize.width);preferredSize.height = Math.max(bounds.y + bounds.height, preferredSize.height);}Insets insets = ChatContentPane.getInsets();preferredSize.width += insets.right;preferredSize.height += insets.bottom;ChatContentPane.setMinimumSize(preferredSize);ChatContentPane.setPreferredSize(preferredSize);}Chat.pack();Chat.setLocationRelativeTo(Chat.getOwner());}// JFormDesigner - End of component initialization  //GEN-END:initComponents  @formatter:on//初始化窗口(设置界面)Chat.setLocationRelativeTo(null);Chat.setResizable(false);//setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭程序Chat.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);//客户关闭窗口时什么都不做infoField.setText(loginfield);//显示用户信息Chat.setVisible(true);//为窗口添加事件监听Chat.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {int res =JOptionPane.showConfirmDialog(null,"您确定要退出吗","来自系统的消息",JOptionPane.OK_CANCEL_OPTION);if (res==0){try {//关闭 socket 输入流 输出流socket.close();dataInputStream.close();dataOutputStream.close();Chat.dispose();//释放当前窗口new LoginFrame();//关闭聊天窗口时返回登录界面} catch (IOException ex) {throw new RuntimeException(ex);}}}});//事件处理 为发送按钮添加事件监听sendbtn.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {//获得输入的聊天的内容String s = inputfield.getText();if (s.length()==0){//判断聊天发送的内容是否为空JOptionPane.showMessageDialog(null,"聊天内容不能为空");return;//一旦为空就return,不能往下走}//发送聊天内容try {//拼接聊天内容---将账号 当前时间 发送内容 拼接到一起//s = loginfield+" "+new Date()+"\n"+s+"\n";//是date里面的默认时间s = loginfield+" "+DateUtil.datetosstring(new Date())+"\n"+s+"\n";dataOutputStream.writeUTF(s);//发送//可能会出现服务器中断,所以要try一下inputfield.setText("  ");//相当于发送完把聊天内容清空} catch (IOException ex) {JOptionPane.showMessageDialog(null,"发送失败");}}});new ReceieveThread().start();}/* 进入聊天窗口后,客户端随时都可能发送消息,需要在聊天的窗口中,循环监听来自服务器发送的消息但不能直接写在主线程main中,死循环会卡死,需要建立线程 */private  class ReceieveThread extends Thread{boolean mark = true;@Overridepublic void run() {while (mark){try {String s = dataInputStream.readUTF();chatFrame.append(s);//settext()会覆盖掉上一次的内容,所以用append()追加到上一次内容后面} catch (IOException e) {mark = false;}}}}// JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables  @formatter:off//插件名称private JFrame Chat;private JScrollPane scrollPane1;private JTextArea chatFrame;private JLabel infoField;private JScrollPane scrollPane2;private JTextArea inputfield;private JButton sendbtn;private JLabel label1;// JFormDesigner - End of variables declaration  //GEN-END:variables  @formatter:on}

   (2).登录窗口

/** Created by JFormDesigner on Sun Mar 02 14:54:14 CST 2025*/package client;import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;/*** @author yang*/
public class LoginFrame extends JFrame {public LoginFrame() {initComponents();}private void initComponents() {// JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents  @formatter:offlabel1 = new JLabel();label2 = new JLabel();passwordField = new JPasswordField();LoginField = new JTextField();LoginBtn = new JButton();ReBtn = new JButton();//======== this ========Container contentPane = getContentPane();contentPane.setLayout(null);//---- label1 ----label1.setText("\u767b\u5f55");label1.setFont(label1.getFont().deriveFont(label1.getFont().getStyle() | Font.BOLD, label1.getFont().getSize() + 5f));contentPane.add(label1);label1.setBounds(new Rectangle(new Point(50, 50), label1.getPreferredSize()));//---- label2 ----label2.setText("\u5bc6\u7801");contentPane.add(label2);label2.setBounds(60, 100, label2.getPreferredSize().width, 17);contentPane.add(passwordField);passwordField.setBounds(105, 95, 185, passwordField.getPreferredSize().height);contentPane.add(LoginField);LoginField.setBounds(105, 50, 180, LoginField.getPreferredSize().height);//---- LoginBtn ----LoginBtn.setText("\u767b\u5f55");contentPane.add(LoginBtn);LoginBtn.setBounds(new Rectangle(new Point(100, 170), LoginBtn.getPreferredSize()));//---- ReBtn ----ReBtn.setText("\u6ce8\u518c");contentPane.add(ReBtn);ReBtn.setBounds(new Rectangle(new Point(210, 170), ReBtn.getPreferredSize()));contentPane.setPreferredSize(new Dimension(350, 290));pack();setLocationRelativeTo(getOwner());// JFormDesigner - End of component initialization  //GEN-END:initComponents  @formatter:onthis.setTitle("欢迎登录");this.setLocationRelativeTo(null);this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);this.setVisible(true);//为登录按钮注册事件监听String account = LoginField.getText();LoginBtn.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {String loginfield = LoginField.getText();//获得账号框内容String passwordfield =passwordField.getText();//获得密码框内容try {//判断账号和密码是否为空if(loginfield.length()==0){JOptionPane.showMessageDialog(null,"账号不能为空","来自系统的提示",JOptionPane.INFORMATION_MESSAGE);return;}if(passwordfield.length()==0){JOptionPane.showMessageDialog(null,"密码不能为空");return;}//预留数据库对接//连接服务器SocketSocket socket = new Socket("127.0.0.1",9999);//连接成功,打开聊天窗口,传递账户loginfield和客户端socketnew ChatFrame(loginfield,socket);//通过构造方法,将用户账号和服务器socket传入dispose();//释放关闭当前窗口}catch(UnknownHostException ex){JOptionPane.showMessageDialog(null,"主机地址不正确");}catch(IOException ex){JOptionPane.showMessageDialog(null,"网络连接失败");}catch(Exception ex){ex.printStackTrace();//打印异常信息JOptionPane.showMessageDialog(null,"请稍后再试");}}});ReBtn.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {new reg();dispose();}});}// JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables  @formatter:offprivate JLabel label1;private JLabel label2;private JPasswordField passwordField;private JTextField LoginField;private JButton LoginBtn;private JButton ReBtn;// JFormDesigner - End of variables declaration  //GEN-END:variables  @formatter:on}

    (3).注册窗口

/** Created by JFormDesigner on Sun Mar 02 15:49:03 CST 2025*/package client;import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;/*** @author yang*/
public class reg extends JFrame {public reg() {initComponents();}private void initComponents() {// JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents  @formatter:offlabel1 = new JLabel();label2 = new JLabel();label3 = new JLabel();textField1 = new JTextField();passwordField1 = new JPasswordField();passwordField2 = new JPasswordField();ReturnBtn = new JButton();ReBtn = new JButton();//======== this ========setAlwaysOnTop(true);Container contentPane = getContentPane();contentPane.setLayout(null);//---- label1 ----label1.setText("\u7528\u6237\u540d");contentPane.add(label1);label1.setBounds(new Rectangle(new Point(75, 25), label1.getPreferredSize()));//---- label2 ----label2.setText("\u8bf7\u8f93\u5165\u5bc6\u7801");contentPane.add(label2);label2.setBounds(new Rectangle(new Point(55, 70), label2.getPreferredSize()));//---- label3 ----label3.setText("\u518d\u6b21\u8f93\u5165\u5bc6\u7801");contentPane.add(label3);label3.setBounds(50, 120, label3.getPreferredSize().width, 25);contentPane.add(textField1);textField1.setBounds(145, 20, 155, textField1.getPreferredSize().height);contentPane.add(passwordField1);passwordField1.setBounds(150, 65, 140, passwordField1.getPreferredSize().height);contentPane.add(passwordField2);passwordField2.setBounds(150, 115, 135, passwordField2.getPreferredSize().height);//---- ReturnBtn ----ReturnBtn.setText("\u8fd4\u56de");contentPane.add(ReturnBtn);ReturnBtn.setBounds(new Rectangle(new Point(90, 165), ReturnBtn.getPreferredSize()));//---- ReBtn ----ReBtn.setText("\u6ce8\u518c");contentPane.add(ReBtn);ReBtn.setBounds(new Rectangle(new Point(220, 165), ReBtn.getPreferredSize()));contentPane.setPreferredSize(new Dimension(385, 275));pack();setLocationRelativeTo(getOwner());// JFormDesigner - End of component initialization  //GEN-END:initComponents  @formatter:onsetVisible(true);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);setLocationRelativeTo(null);ReturnBtn.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {new LoginFrame();dispose();}});}// JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables  @formatter:offprivate JLabel label1;private JLabel label2;private JLabel label3;private JTextField textField1;private JPasswordField passwordField1;private JPasswordField passwordField2;private JButton ReturnBtn;private JButton ReBtn;// JFormDesigner - End of variables declaration  //GEN-END:variables  @formatter:onpublic static void main(String[] args) {new reg();}
}

(4).启动多人聊天窗口

package client;public class ClientRun {public static void main(String[] args) {new LoginFrame();}
}

(5).涉及到的工具类

  在[ java API ]中讲解过,本次直接"拿来主义" 

package client;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;public class DateUtil {public static Date stringtoday(String str) throws ParseException {SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd");return simpleDateFormat.parse(str);}public static  String datetosstring(Date d){SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd");return simpleDateFormat.format(d);}public static void main(String[] args) throws ParseException {System.out.println(stringtoday("2002-2-13"));Date date = new Date();System.out.println(datetosstring(date));}}

2.服务器

    (1).服务器窗口

/** Created by JFormDesigner on Fri Mar 14 18:42:49 CST 2025*/package server;import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.*;/*** @author yang*/
public class ServerFrame extends JFrame {//使用集合来储存链接到服务器端的多个socket对象private ArrayList<Socket> sockets= new ArrayList<>();public ServerFrame() {initComponents();//1.初始化窗口startServer();//2.启动服务器}//2.启动服务器public void startServer(){try {//创建服务器ServerSocket serverSocket= new ServerSocket(9999);System.out.println("服务器连接成功");while (true){//循环监听Socket socket = serverSocket.accept();//监听客户端连接sockets.add(socket);//一旦监听到就放到集合里面System.out.println("客户端链接成功:"+sockets.size());//记录存储的socket个数//为每一个连接到服务器端的socket创建一个线程,让他独立的运行,从而不影响死循环,监听客户端的连接//更新在线人数peoplefield.setText(String.valueOf(sockets.size()));//将size的int类型转化为String类型,通过String.valueof来进行转化new SocketListenerThread(socket).start();//在循环中,启动多个线程独立运行 内部类只需要写一遍}}catch(IOException e){System.out.println("服务器启动失败");}}//3.创建内部类监听private class SocketListenerThread extends Thread{//写一个内部类作为监听线程继承Thread类,一旦有客户端(socket)连接到服务器,就把socket传入到线程中独立运行boolean mark = true;Socket socket;//定义为全局变量private DataInputStream dataInputStream;//读:接收从客户端发送到服务器端的内容//构造方法public  SocketListenerThread(Socket socket) throws IOException {this.socket = socket;this.dataInputStream= new DataInputStream(socket.getInputStream());}//重写run()方法@Overridepublic void run() {while(mark){try {String s = dataInputStream.readUTF();informfield.append(s);//???为啥无法调用append  以及为啥我输出的内容不换行/*for (Socket socket :sockets){//循环sockets集合 转发消息DataOutputStream dataOutputStream =new DataOutputStream(socket.getOutputStream());try {//如果发送失败说明客户端下线,所以要try catchdataOutputStream.writeUTF(s);//转发消息}catch(IOException ex){sockets.remove(socket);//但是增强for循环不能删除,所以选择Iterator迭代器}  }*///将消息转发给其他客户端Iterator<Socket> iterator = sockets.iterator();while (iterator.hasNext()){//有没有下一个,有下一个就拿到Socket socket = iterator.next();DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());try {dataOutputStream.writeUTF(s);}catch(IOException e){sockets.remove(socket);//发送失败,说明客户端下线了,需要从集合中删掉当前客户socketpeoplefield.setText(String.valueOf(sockets.size()));}}}catch(IOException e){mark = false;//出现异常时结束接收消息sockets.remove(socket);//删除的是客户端自己,所以要将socket定义为全局变量和this socket,不然调用用不到,peoplefield.setText(String.valueOf(sockets.size()));}}}}//1.初始化窗口private void initComponents() {// JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents  @formatter:offlabel1 = new JLabel();peoplefield = new JLabel();inputfield = new JTextField();sendbtn = new JButton();scrollPane1 = new JScrollPane();informfield = new JTextArea();//======== this ========Container contentPane = getContentPane();contentPane.setLayout(null);//---- label1 ----label1.setText("\u5728\u7ebf\u4eba\u6570:");contentPane.add(label1);label1.setBounds(15, 10, 70, label1.getPreferredSize().height);contentPane.add(peoplefield);peoplefield.setBounds(85, 5, 65, 25);contentPane.add(inputfield);inputfield.setBounds(15, 35, 245, 40);//---- sendbtn ----sendbtn.setText("\u53d1\u9001");contentPane.add(sendbtn);sendbtn.setBounds(new Rectangle(new Point(290, 40), sendbtn.getPreferredSize()));//======== scrollPane1 ========{scrollPane1.setViewportView(informfield);}contentPane.add(scrollPane1);scrollPane1.setBounds(20, 90, 350, 160);contentPane.setPreferredSize(new Dimension(400, 300));pack();setLocationRelativeTo(getOwner());// JFormDesigner - End of component initialization  //GEN-END:initComponents  @formatter:onsetTitle("服务器");setResizable(false);setVisible(true);this.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {int res =JOptionPane.showConfirmDialog(null,"您确定要退出吗","来自系统的消息",JOptionPane.OK_CANCEL_OPTION);if (res==0){System.exit(0);}}});//为发送按钮添加事件监听sendbtn.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {String  notice = inputfield.getText();//获得通知通告的内容if (notice.length()==0){JOptionPane.showMessageDialog(null,"通知公告不能为空");return;}//向所有连接到服务器的客户端转发通告try {Iterator<Socket> iterator = sockets.iterator();while (iterator.hasNext()){//有没有下一个,有下一个就拿到Socket socket = iterator.next();DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());notice = notice+"\n";dataOutputStream.writeUTF(notice);inputfield.setText(" ");}}catch(IOException ex){}}});}//4.插件名称// JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables  @formatter:offprivate JLabel label1;private JLabel peoplefield;private JTextField inputfield;private JButton sendbtn;private JScrollPane scrollPane1;private JTextArea informfield;// JFormDesigner - End of variables declaration  //GEN-END:variables  @formatter:on
}

     (2).启动服务器窗口

package server;import java.net.ServerSocket;
import java.net.Socket;public class ServerRun {public static void main(String[] args) {ServerFrame serverFrame =  new ServerFrame();//创建服务器窗口serverFrame.startServer();//启动服务器}
}

三.窗口简易模版

    1.客户端

         (1).登录窗口模版

(2).聊天窗口模版

(3).注册窗口模版

2.服务器窗口模版

四.代码思路

 注意 : 写代码一般先构建基础框架,后完善功能 顺着思路一步一步写

http://www.dtcms.com/a/319787.html

相关文章:

  • 强光干扰下裂缝漏检率↓82%!陌讯轻量化模型在道路巡检的落地实践
  • 2深度学习Pytorch-自动微分--梯度计算、梯度上下文控制(累计梯度、梯度清零)
  • Ethereum: 像Uniswap V3贡献者一样开发,克隆、编译与测试v3-core
  • 通过减少回表和增加冗余字段,优化SQL查询效率
  • LSTM 单变量时序预测—pytorch
  • vscode+latex本地英文期刊环境配置
  • VScode使用jupyter notebook,配置内核报错没有torch解决
  • 如何委托第三方检测机构做软件测试?
  • 鸿蒙 - 分享功能
  • 直播预告|鸿蒙生态下的 Flutter 开发实战
  • 非化学冷却塔水处理解决方案:绿色工业时代的革新引擎
  • Elasticsearch 文档分词器
  • 神经网络入门指南:从零理解 PyTorch 的核心思想
  • 2025 五大商旅平台管控力解析:合规要求下的商旅管理新范式
  • Flutter 布局控件使用详解
  • 【java基础|第十六篇】面向对象(六)——抽象和接口
  • Java-JVM探析
  • 参考平面与返回电流
  • BMS保护板测试仪:电池安全管理的“质检卫士”|深圳鑫达能
  • Java爬虫性能优化:多线程抓取JSP动态数据实践
  • 键盘+系统+软件等快捷键大全
  • RK3568笔记九十八:使用Qt实现RTMP拉流显示
  • FluentUI-main的详解
  • MyBatis联合查询
  • windows有一个企业微信安装包,脚本执行并安装到d盘。
  • 我的世界Java版1.21.4的Fabric模组开发教程(十七)自定义维度
  • PCL提取平面上的圆形凸台特征
  • WindowsLinux系统 安装 CUDA 和 cuDNN
  • 从库存一盘货到全域智能铺货:巨益科技全渠道平台助力品牌业财一体化升级
  • 电子基石:硬件工程师的器件手册 (九) - DC-DC拓扑:电能转换的魔术师