当前位置:   article > 正文

Java实验:编写网络聊天程序(图形界面)_java编写简单聊天界面

java编写简单聊天界面

课程名称 高级Java程序设计

 

实验项目 Java网络编程

实验目的:

        使用客户机/服务器模式、基于TCP协议编写一对多“群聊”程序。其中客户机端单击“连接服务器”或“断开连接”按钮,均能即时更新服务器和所有客户机的在线人数和客户名。

实验要求:

设计一对多的网络聊天程序,要求:

  1. 基于TCP/IP设计聊天程序
  2. 采用图形界面设计
  3. 能够进行一对多聊天

项目截图

服务器端代码:

  1. import javax.swing.*;
  2. import javax.swing.border.TitledBorder;
  3. import java.awt.*;
  4. import java.awt.event.ActionEvent;
  5. import java.awt.event.ActionListener;
  6. import java.io.*;
  7. import java.net.ServerSocket;
  8. import java.net.Socket;
  9. import java.util.ArrayList;
  10. import java.util.Vector;
  11. public class Server extends JFrame {
  12. // TODO 该图形界面拥有三块区域,分别位于上、中、下 (up、middle、down)。
  13. private JPanel panUp = new JPanel();
  14. private JPanel panMid = new JPanel();
  15. private JPanel panDown = new JPanel();
  16. // panUp 区域的子节点定义,标签、输入框、按钮
  17. private JLabel lblLocalPort = new JLabel("本机服务器监听端口:");
  18. protected JButton butStart = new JButton("启动服务器");
  19. protected JTextField tfLocalPort = new JTextField(25);
  20. // panMid 区域的子节点定义,显示框 以及 滚动条
  21. protected JTextArea taMsg = new JTextArea(25, 25);
  22. JScrollPane scroll = new JScrollPane(taMsg);
  23. // panDown 区域的子节点定义,lstUsers在线用户界面
  24. JList lstUsers = new JList();
  25. // TODO 以下是存放数据的变量
  26. public static int localPort = 8000; // 默认端口 8000
  27. static int SerialNum = 0; // 用户连接数量
  28. ServerSocket serverSocket; // 服务器端 Socket
  29. ArrayList<AcceptRunnable.Client> clients = new ArrayList<>(); // 用户连接对象数组
  30. Vector<String> clientNames = new Vector<>(); // lstUsers 中存放的数据
  31. // TODO 构造方法
  32. public Server() {
  33. init();
  34. }
  35. // TODO 初始化方法:初始化图形界面布局
  36. private void init() {
  37. // panUp 区域初始化:流式区域
  38. panUp.setLayout(new FlowLayout());
  39. panUp.add(lblLocalPort);
  40. panUp.add(tfLocalPort);
  41. panUp.add(butStart);
  42. tfLocalPort.setText(String.valueOf(localPort));
  43. butStart.addActionListener(new startServerHandler()); // 注册 "启动服务器" 按钮点击事件
  44. // panMid 区域初始化
  45. panMid.setBorder(new TitledBorder("监听消息"));
  46. taMsg.setEditable(false);
  47. panMid.add(scroll);
  48. // panDown 区域初始化
  49. panDown.setBorder(new TitledBorder("在线用户"));
  50. panDown.add(lstUsers);
  51. lstUsers.setVisibleRowCount(10);
  52. // 图形界面的总体初始化 + 启动图形界面
  53. this.setTitle("服务器端");
  54. this.add(panUp, BorderLayout.NORTH);
  55. this.add(panMid, BorderLayout.CENTER);
  56. this.add(panDown, BorderLayout.SOUTH);
  57. this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
  58. this.setPreferredSize(new Dimension(600, 400));
  59. this.pack();
  60. this.setVisible(true);
  61. }
  62. // TODO “启动服务器”按钮的动作事件监听处理类
  63. private class startServerHandler implements ActionListener {
  64. @Override
  65. public void actionPerformed(ActionEvent e) {
  66. try {
  67. // 当点击按钮时,获取端口设置并启动新进程、监听端口
  68. localPort = Integer.parseInt(tfLocalPort.getText());
  69. serverSocket = new ServerSocket(localPort);
  70. Thread acptThrd = new Thread(new AcceptRunnable());
  71. acptThrd.start();
  72. taMsg.append("**** 服务器(端口" + localPort + ")已启动 ****\n");
  73. } catch (Exception ex) {
  74. System.out.println(ex);
  75. }
  76. }
  77. }
  78. // TODO 接受用户连接请求的线程关联类
  79. private class AcceptRunnable implements Runnable {
  80. public void run() {
  81. // 持续监听端口,当有新用户连接时 再开启新进程
  82. while (true) {
  83. try {
  84. Socket socket = serverSocket.accept();
  85. // 新的用户已连接,创建 Client 对象
  86. Client client = new Client(socket);
  87. taMsg.append("——客户【" + client.nickname + "】加入\n");
  88. Thread clientThread = new Thread(client);
  89. clientThread.start();
  90. clients.add(client);
  91. } catch (Exception ex) {
  92. System.out.println(ex);
  93. }
  94. }
  95. }
  96. // TODO 服务器存放用户对象的客户类(主要编程)。每当有新的用户连接时,该类都会被调用
  97. // TODO 该类继承自 Runnable,内部含有 run()方法
  98. private class Client implements Runnable {
  99. private Socket socket; // 用来保存用户的连接对象
  100. private BufferedReader in; // IO 流
  101. private PrintStream out;
  102. private String nickname; // 保存用户昵称
  103. // Client类的构建方法。当有 新用户 连接时会被调用
  104. public Client(Socket socket) throws Exception {
  105. this.socket = socket;
  106. InputStream is = socket.getInputStream();
  107. in = new BufferedReader(new InputStreamReader(is));
  108. OutputStream os = socket.getOutputStream();
  109. out = new PrintStream(os);
  110. nickname = in.readLine(); // 获取用户昵称
  111. for (Client c : clients) { // 将新用户的登录消息发给所有用户
  112. c.out.println("——客户【" + nickname + "】加入\n");
  113. }
  114. }
  115. //客户类线程运行方法
  116. public void run() {
  117. try {
  118. while (true) {
  119. String usermsg = in.readLine(); //读用户发来消息
  120. String secondMsg = usermsg.substring(usermsg.lastIndexOf(":") + 1); // 字符串辅助对象
  121. // 如果用户发过来的消息不为空
  122. if (usermsg != null && usermsg.length() > 0) {
  123. // 如果消息是 bye,则断开与此用户的连接 并 告知所有用户当前信息,跳出循环终止当前进程
  124. if (secondMsg.equals("bye")) {
  125. clients.remove(this);
  126. for (Client c : clients) {
  127. c.out.println(usermsg);
  128. }
  129. taMsg.append("——客户离开:" + nickname + "\n");
  130. // 更新在线用户数量 lstUsers的界面信息
  131. updateUsers();
  132. break;
  133. }
  134. /**
  135. * 每当有新用户连接时,服务器就会接收到 USERS 请求
  136. * 当服务器接收到此请求时,就会要求现在所有用户更新 在线用户数量 的列表
  137. * */
  138. if (usermsg.equals("USERS")) {
  139. updateUsers();
  140. continue;
  141. }
  142. // 当用户发出的消息都不是以上两者时,消息才会被正常发送
  143. for (Client c : clients) {
  144. c.out.println(usermsg);
  145. }
  146. }
  147. }
  148. socket.close();
  149. } catch (Exception ex) {
  150. System.out.println(ex);
  151. }
  152. }
  153. // TODO 更新在线用户数量 lstUsers 信息,并要求所有的用户端同步更新
  154. public void updateUsers() {
  155. // clientNames 是 Vector<String>对象,用来存放所有用户的名字
  156. clientNames.removeAllElements();
  157. StringBuffer allname = new StringBuffer();
  158. for (AcceptRunnable.Client client : clients) {
  159. clientNames.add(0, client.nickname);
  160. allname.insert(0, "|" + client.nickname);
  161. }
  162. panDown.setBorder(new TitledBorder("在线用户(" +clientNames.size() + "个)"));
  163. // 要求所有的用户端同步更新
  164. for (Client c : clients) {
  165. c.out.println(clientNames);
  166. }
  167. lstUsers.setListData(clientNames);
  168. }
  169. }
  170. }
  171. // TODO 主方法
  172. public static void main(String[] args) {
  173. new Server();
  174. }
  175. }

客户端代码:

  1. import javax.swing.*;
  2. import javax.swing.border.TitledBorder;
  3. import java.awt.*;
  4. import java.awt.event.ActionEvent;
  5. import java.awt.event.ActionListener;
  6. import java.awt.event.WindowAdapter;
  7. import java.awt.event.WindowEvent;
  8. import java.io.BufferedReader;
  9. import java.io.InputStreamReader;
  10. import java.io.PrintStream;
  11. import java.net.Socket;
  12. import java.util.Vector;
  13. public class Client extends JFrame { //客户机窗体类
  14. // TODO 该图形界面拥有四块区域,分别位于上、左、中、下 (up、Left、middle、down)。
  15. private JPanel panUp = new JPanel();
  16. private JPanel panLeft = new JPanel();
  17. private JPanel panMid = new JPanel();
  18. private JPanel panDown = new JPanel();
  19. // panUp 区域的子节点定义,3个标签、3个输入框、2个按钮
  20. private JLabel lblLocalPort1 = new JLabel("服务器IP: ");
  21. private JLabel lblLocalPort2 = new JLabel("端口: ");
  22. private JLabel lblLocalPort3 = new JLabel("本人昵称: ");
  23. protected JTextField tfLocalPort1 = new JTextField(15);
  24. protected JTextField tfLocalPort2 = new JTextField(5);
  25. protected JTextField tfLocalPort3 = new JTextField(5);
  26. protected JButton butStart = new JButton("连接服务器");
  27. protected JButton butStop = new JButton("断开服务器");
  28. // TODO
  29. // panLeft 区域的子节点定义,显示框、滚动条
  30. protected JTextArea taMsg = new JTextArea(25, 25);
  31. JScrollPane scroll = new JScrollPane(taMsg);
  32. // panMid 区域的子节点定义,lstUsers在线用户界面
  33. JList lstUsers = new JList();
  34. // panDown 区域的子节点定义,标签,输入框
  35. private JLabel lblLocalPort4 = new JLabel("消息(按回车发送): ");
  36. protected JTextField tfLocalPort4 = new JTextField(20);
  37. /**
  38. * ===== 变量分割 =====
  39. * 上面是图形界面变量,下面是存放数据的变量
  40. */
  41. BufferedReader in;
  42. PrintStream out;
  43. public static int localPort = 8000; // 默认端口
  44. public static String localIP = "127.0.0.1"; // 默认服务器IP地址
  45. public static String nickname = "Cat"; // 默认用户名
  46. public Socket socket;
  47. public static String msg; // 存放本次发送的消息
  48. Vector<String> clientNames = new Vector<>();
  49. // TODO 构造方法
  50. public Client() {
  51. init();
  52. }
  53. // TODO 初始化方法:初始化图形界面
  54. private void init() {
  55. // panUp 区域初始化:流式面板,3个标签、3个输入框,2个按钮
  56. panUp.setLayout(new FlowLayout());
  57. panUp.add(lblLocalPort1);
  58. panUp.add(tfLocalPort1);
  59. panUp.add(lblLocalPort2);
  60. panUp.add(tfLocalPort2);
  61. panUp.add(lblLocalPort3);
  62. panUp.add(tfLocalPort3);
  63. tfLocalPort1.setText(localIP);
  64. tfLocalPort2.setText(String.valueOf(localPort));
  65. tfLocalPort3.setText(nickname);
  66. panUp.add(butStart);
  67. panUp.add(butStop);
  68. butStart.addActionListener(new linkServerHandlerStart());
  69. butStop.addActionListener(new linkServerHandlerStop());
  70. butStop.setEnabled(false); // 断开服务器按钮的初始状态应该为 不可点击,只有连接服务器之后才能点击
  71. // 添加 Left
  72. taMsg.setEditable(false);
  73. panLeft.add(scroll);
  74. panLeft.setBorder(new TitledBorder("聊天——消息区"));
  75. scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
  76. scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
  77. // 添加 Middle
  78. panMid.setBorder(new TitledBorder("在线用户"));
  79. panMid.add(lstUsers);
  80. lstUsers.setVisibleRowCount(20);
  81. // 添加 Down
  82. // TODO 此处注意:JTextField输入框 的回车事件默认存在,无需添加
  83. panDown.setLayout(new FlowLayout());
  84. panDown.add(lblLocalPort4);
  85. panDown.add(tfLocalPort4);
  86. tfLocalPort4.addActionListener(new Client.SendHandler());
  87. // 图形界面的总体初始化 + 启动图形界面
  88. this.setTitle("客户端");
  89. this.add(panUp, BorderLayout.NORTH);
  90. this.add(panLeft, BorderLayout.WEST);
  91. this.add(panMid, BorderLayout.CENTER);
  92. this.add(panDown, BorderLayout.SOUTH);
  93. this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
  94. this.addWindowListener(new WindowHandler());
  95. this.setPreferredSize(new Dimension(800, 600));
  96. this.pack();
  97. this.setVisible(true);
  98. }
  99. // TODO “连接服务器”按钮的动作事件监听处理类:
  100. private class linkServerHandlerStart implements ActionListener {
  101. @Override
  102. public void actionPerformed(ActionEvent e) {
  103. // 当点击"连接服务器"按钮之后,该按钮被禁用(不可重复点击)。同时"断开服务器按钮"被恢复使用
  104. butStart.setEnabled(false);
  105. butStop.setEnabled(true);
  106. localIP = tfLocalPort1.getText();
  107. localPort = Integer.parseInt(tfLocalPort2.getText());
  108. nickname = tfLocalPort3.getText();
  109. linkServer(); // 连接服务器
  110. Thread acceptThread = new Thread(new Client.ReceiveRunnable());
  111. acceptThread.start();
  112. }
  113. }
  114. // TODO “断开服务器”按钮的动作事件监听处理类
  115. private class linkServerHandlerStop implements ActionListener {
  116. /**
  117. * 当点击该按钮之后,断开服务器连接、清空图形界面所有数据
  118. */
  119. @Override
  120. public void actionPerformed(ActionEvent e) {
  121. taMsg.setText("");
  122. clientNames = new Vector<>();
  123. updateUsers();
  124. out.println("——客户【" + nickname + "】离开:bye\n");
  125. butStart.setEnabled(true);
  126. butStop.setEnabled(false);
  127. }
  128. }
  129. // TODO 连接服务器的方法
  130. public void linkServer() {
  131. try {
  132. socket = new Socket(localIP, localPort);
  133. } catch (Exception ex) {
  134. taMsg.append("==== 连接服务器失败~ ====");
  135. }
  136. }
  137. // TODO 接收服务器消息的线程关联类
  138. private class ReceiveRunnable implements Runnable {
  139. public void run() {
  140. try {
  141. in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  142. out = new PrintStream(socket.getOutputStream());
  143. out.println(nickname); // 当用户首次连接服务器时,应该向服务器发送自己的用户名、方便服务器区分
  144. taMsg.append("——本人【" + nickname + "】成功连接到服务器......\n");
  145. out.println("USERS"); // 向服务器发送"神秘代码",请求 当前在线用户 列表
  146. while (true) {
  147. msg = in.readLine(); // 读取服务器端的发送的数据
  148. // 此 if 语句的作用是:过滤服务器发送过来的 更新当前在线用户列表 请求
  149. if (msg.matches(".*\\[.*\\].*")) {
  150. clientNames.removeAllElements();
  151. String[] split = msg.split(",");
  152. for (String single : split) {
  153. clientNames.add(single);
  154. }
  155. updateUsers();
  156. continue;
  157. }
  158. // 更新 "聊天——消息区" 信息
  159. taMsg.append(msg + "\n");
  160. // 此 if 语句作用:与服务器进行握手确认消息。
  161. // 当接收到服务器端发送的确认离开请求bye 的时候,用户真正离线
  162. msg = msg.substring(msg.lastIndexOf(":") + 1);
  163. if (msg.equals(nickname)) {
  164. socket.close();
  165. clientNames.remove(nickname);
  166. updateUsers();
  167. break; // 终止线程
  168. }
  169. }
  170. } catch (Exception e) {
  171. }
  172. }
  173. }
  174. // TODO "发送消息文本框" 的动作事件监听处理类
  175. private class SendHandler implements ActionListener {
  176. @Override
  177. public void actionPerformed(ActionEvent e) {
  178. out.println("【" + nickname + "】:" + tfLocalPort4.getText());
  179. tfLocalPort4.setText(""); // 当按下回车发送消息之后,输入框应该被清空
  180. }
  181. }
  182. // TODO 窗口关闭的动作事件监听处理类
  183. // 当用户点击 "x" 离开窗口时,也会向服务器发送 bye 请求,目的是为了同步更新数据。
  184. private class WindowHandler extends WindowAdapter {
  185. @Override
  186. public void windowClosing(WindowEvent e) {
  187. cutServer();
  188. }
  189. }
  190. private void cutServer() {
  191. out.println("——客户【" + nickname + "】离开:bye");
  192. }
  193. // TODO 更新 "在线用户列表" 的方法
  194. public void updateUsers() {
  195. panMid.setBorder(new TitledBorder("在线用户(" + clientNames.size() + "个)"));
  196. lstUsers.setListData(clientNames);
  197. }
  198. // TODO 主方法
  199. public static void main(String[] args) {
  200. new Client();
  201. }
  202. }

如何同时开启两个客户端进行聊天?

        将上述的 Client 类复制一份,改名为 Client2 ,然后同时启动 Client 和 Client2 程序。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/465550
推荐阅读
相关标签
  

闽ICP备14008679号