├── out └── production │ ├── Client │ └── com │ │ └── wxy │ │ ├── ClientChat.class │ │ ├── ClientJframe.class │ │ ├── ClientJframe$1.class │ │ └── ClientJframe$Receive.class │ └── Server │ └── com │ └── wxy │ ├── ServerChat.class │ ├── ServerJframe.class │ ├── ServerJframe$1.class │ ├── ServerJframe$2.class │ └── ServerJframe$ClientCoon.class ├── .idea ├── vcs.xml ├── .gitignore ├── misc.xml ├── modules.xml └── inspectionProfiles │ └── Project_Default.xml ├── ChatRoom.iml ├── Client ├── Client.iml └── src │ └── com │ └── wxy │ └── ClientChat.java ├── Server ├── Server.iml └── src │ └── com │ └── wxy │ └── ServerChat.java ├── README.md └── 多人聊天系统 2021.03.02.md /out/production/Client/com/wxy/ClientChat.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuxinyu3366/ChatRoom/HEAD/out/production/Client/com/wxy/ClientChat.class -------------------------------------------------------------------------------- /out/production/Server/com/wxy/ServerChat.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuxinyu3366/ChatRoom/HEAD/out/production/Server/com/wxy/ServerChat.class -------------------------------------------------------------------------------- /out/production/Client/com/wxy/ClientJframe.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuxinyu3366/ChatRoom/HEAD/out/production/Client/com/wxy/ClientJframe.class -------------------------------------------------------------------------------- /out/production/Server/com/wxy/ServerJframe.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuxinyu3366/ChatRoom/HEAD/out/production/Server/com/wxy/ServerJframe.class -------------------------------------------------------------------------------- /out/production/Client/com/wxy/ClientJframe$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuxinyu3366/ChatRoom/HEAD/out/production/Client/com/wxy/ClientJframe$1.class -------------------------------------------------------------------------------- /out/production/Server/com/wxy/ServerJframe$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuxinyu3366/ChatRoom/HEAD/out/production/Server/com/wxy/ServerJframe$1.class -------------------------------------------------------------------------------- /out/production/Server/com/wxy/ServerJframe$2.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuxinyu3366/ChatRoom/HEAD/out/production/Server/com/wxy/ServerJframe$2.class -------------------------------------------------------------------------------- /out/production/Client/com/wxy/ClientJframe$Receive.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuxinyu3366/ChatRoom/HEAD/out/production/Client/com/wxy/ClientJframe$Receive.class -------------------------------------------------------------------------------- /out/production/Server/com/wxy/ServerJframe$ClientCoon.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuxinyu3366/ChatRoom/HEAD/out/production/Server/com/wxy/ServerJframe$ClientCoon.class -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /../../../../../../:\WorkingSpace\Idea_workingspace\JAVASE\ChatRoom\.idea/dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ChatRoom.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Client/Client.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Server/Server.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatRoom 2 | 多人聊天系统 2021.03.02 实现了多个聊天客户端和一个服务器端的多人聊天 3 | 4 | 项目环境: 5 | IDEA 2020.3.1 JDK1.8.0_271 6 | 7 | 项目参考: 8 | # 多人聊天系统 9 | 10 | https://www.bilibili.com/video/BV1M4411A78q 11 | 12 | # TCP用多线程实现多人聊天的项目 13 | 14 | https://www.bilibili.com/video/BV16E411F79L?from=search&seid=12745764004109122366 15 | 16 | 课程学时: 8学时 17 | 18 | 知识要点: I0 编程、Socket 编程、多线程编程、异常处理、集合类的使用 19 | 20 | 编程思路: 21 | 22 | 1、客户端聊天窗口的创建 23 | 24 | 2、添加服务器与客户端的连接 25 | 26 | 3、连接上以后考虑将客户端的信息发送到服务器上 27 | 28 | 4、利用多线程实现连接多个客户端 29 | 30 | 5、多个客户端的信息发送到服务器上以后,考虑把服务器上的信息发送到每个客户端 31 | 32 | 6、在服务器端要取到每个客户端的socket,才可以把信息发送到每个客户端上,利用集合类存储客户的多个线程的连接 33 | 34 | 7、在客户端考虑利用多线程接受服务器上的信息 35 | 36 | 8、最后实现多人聊天室的效果 37 | 38 | 9、后期修改程序中的不足和发现bug 39 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /Client/src/com/wxy/ClientChat.java: -------------------------------------------------------------------------------- 1 | package com.wxy; 2 | 3 | import javax.swing.*; 4 | import java.awt.*; 5 | import java.awt.event.ActionEvent; 6 | import java.awt.event.ActionListener; 7 | import java.io.DataInputStream; 8 | import java.io.DataOutputStream; 9 | import java.io.IOException; 10 | import java.net.Socket; 11 | import java.net.SocketException; 12 | 13 | /** 14 | * @author Administrator 15 | * @Auther: wuxy 16 | * @Date: 2021/3/2 - 03 - 02 - 20:16 17 | * @Description: com.wxy 18 | * @version: 1.0 19 | */ 20 | public class ClientChat { 21 | //这是一个main方法,是程序的入口: 22 | public static void main(String[] args) { 23 | ClientJframe clientJframe = new ClientJframe(); 24 | clientJframe.init(); 25 | } 26 | } 27 | 28 | class ClientJframe extends JFrame { 29 | //GUI布局 30 | //聊天记录显示区 31 | private JTextArea ta = new JTextArea(10, 20); 32 | //聊天记录输入区 33 | private JTextField tf = new JTextField(20); 34 | 35 | //端口 36 | // 静态常量主机端口号 37 | private static final String CONNSTR = "127.0.0.1"; 38 | // 静态常量服务器端口号 39 | private static final int CONNPORT = 8888; 40 | private Socket socket = null; 41 | 42 | //Client发送数据 43 | private DataOutputStream dataOutputStream = null; 44 | 45 | //客户端连接上服务器判断符号 46 | private boolean isConn = false; 47 | 48 | /** 49 | * 无参的构造方法 throws HeadlessException 50 | */ 51 | public ClientJframe() throws HeadlessException { 52 | super(); 53 | } 54 | 55 | public void init() { 56 | this.setTitle("客户端窗口"); 57 | this.add(ta, BorderLayout.CENTER); 58 | this.add(tf, BorderLayout.SOUTH); 59 | 60 | this.setBounds(300, 300, 400, 400); 61 | 62 | // 添加监听,使回车键可以输入数据(判断数据合法性), 63 | // 并輸入到聊天框,换行 64 | tf.addActionListener(new ActionListener() { 65 | 66 | @Override 67 | public void actionPerformed(ActionEvent e) { 68 | String strSend = tf.getText(); 69 | // 去掉空格判断长度是否为空 70 | if (strSend.trim().length() == 0) { 71 | return; 72 | } 73 | //客户端信息strSend发送到服务器上 74 | send(strSend); 75 | tf.setText(""); 76 | //ta.append(strSend + "\n"); 77 | 78 | } 79 | }); 80 | 81 | //关闭事件 82 | this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 83 | ta.setEditable(false);//聊天区域不可以输入 84 | tf.requestFocus();//光标聚焦 85 | 86 | try { 87 | socket = new Socket(CONNSTR, CONNPORT); 88 | isConn = true; 89 | } catch (Exception e) { 90 | e.printStackTrace(); 91 | } 92 | 93 | // 启动多线程 94 | new Thread(new Receive()).start(); 95 | 96 | this.setVisible(true); 97 | } 98 | 99 | /** 100 | * 客户端发送信息到服务器上的方法 101 | */ 102 | public void send(String str) { 103 | try { 104 | dataOutputStream = new DataOutputStream(socket.getOutputStream()); 105 | dataOutputStream.writeUTF(str); 106 | } catch (IOException e) { 107 | // TODO Auto-generated catch block 108 | e.printStackTrace(); 109 | } 110 | } 111 | 112 | /** 113 | * @author 武新宇 114 | * @deprecated 多线程的类,实现了Runnable接口的类 115 | */ 116 | class Receive implements Runnable { 117 | @Override 118 | public void run() { 119 | try { 120 | while (isConn) { 121 | DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); 122 | String str = dataInputStream.readUTF(); 123 | //通讯消息 124 | ta.append(str); 125 | } 126 | } catch (SocketException e) { 127 | System.out.println("服务器意外终止了!"); 128 | ta.append("服务器意外终止了!"); 129 | } catch (IOException e) { 130 | // TODO Auto-generated catch block 131 | e.printStackTrace(); 132 | } 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /Server/src/com/wxy/ServerChat.java: -------------------------------------------------------------------------------- 1 | package com.wxy; 2 | 3 | import javax.swing.*; 4 | import java.awt.*; 5 | import java.awt.event.ActionEvent; 6 | import java.awt.event.ActionListener; 7 | import java.io.DataInputStream; 8 | import java.io.DataOutputStream; 9 | import java.io.IOException; 10 | import java.net.ServerSocket; 11 | import java.net.Socket; 12 | import java.net.SocketException; 13 | import java.util.ArrayList; 14 | import java.util.Iterator; 15 | 16 | /** 17 | * @author Administrator 18 | * @Auther: wuxy 19 | * @Date: 2021/3/2 - 03 - 02 - 20:54 20 | * @Description: com.wxy 21 | * @version: 1.0 22 | */ 23 | public class ServerChat { 24 | //这是一个main方法,是程序的入口: 25 | public static void main(String[] args) throws Exception { 26 | ServerJframe serverJframe = new ServerJframe(); 27 | serverJframe.init(); 28 | } 29 | } 30 | 31 | class ServerJframe extends JFrame { 32 | //GUI相关属性 33 | JTextArea serverTa = new JTextArea(); 34 | JPanel btnTool = new JPanel(); 35 | JButton startBtn = new JButton("启动"); 36 | JButton stopBtn = new JButton("停止"); 37 | //端口 38 | private static final int PORT = 8888; 39 | //ServerSocket 40 | private ServerSocket serverSocket = null; 41 | private Socket socket = null; 42 | 43 | //Server接受数据 44 | private DataInputStream dataInputStream = null; 45 | 46 | // 多个客户端访问时,客户端对象存放入List中 47 | private ArrayList ccList = new ArrayList(); 48 | 49 | // 服务器启动的标志 (其实ServerSocket ss 初始化出来时以为者服务器的启动) 50 | private boolean isStart = false; 51 | 52 | public void init() throws Exception { 53 | this.setTitle("服务器端窗口"); 54 | this.add(serverTa, BorderLayout.CENTER);//流式布局 55 | btnTool.add(startBtn); 56 | btnTool.add(stopBtn); 57 | this.add(btnTool, BorderLayout.SOUTH); 58 | 59 | this.setBounds(0, 0, 500, 500); 60 | 61 | //判断服务器是否已经开启 62 | if (isStart) { 63 | System.out.println("服务器已经启动了\n"); 64 | } else { 65 | System.out.println("服务器还没有启动,请点击启动服务器!\n"); 66 | } 67 | //按钮监听监听服务器开启,置开始位false 68 | startBtn.addActionListener(new ActionListener() { 69 | @Override 70 | public void actionPerformed(ActionEvent e) { 71 | try { 72 | if (serverSocket == null) { 73 | serverSocket = new ServerSocket(PORT); 74 | } 75 | isStart = true; 76 | //startServer(); 77 | serverTa.append("服务器已经启动啦! \n"); 78 | } catch (Exception e1) { 79 | e1.printStackTrace(); 80 | } 81 | } 82 | }); 83 | // 终止按钮监听停止服务器,置开始位true 84 | stopBtn.addActionListener(new ActionListener() { 85 | @Override 86 | public void actionPerformed(ActionEvent e) { 87 | try { 88 | if (serverSocket != null) { 89 | serverSocket.close(); 90 | isStart = false; 91 | } 92 | System.exit(0); 93 | serverTa.append("服务器断啦!!\n"); 94 | System.out.println("服务器断啦!!\n"); 95 | 96 | } catch (Exception e1) { 97 | // TODO Auto-generated catch block 98 | e1.printStackTrace(); 99 | } 100 | } 101 | }); 102 | 103 | /** 104 | * 服务器窗口关闭应该停止服务器,需改进的代码 105 | */ 106 | // this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 107 | // serverTa.setEditable(false); 108 | this.setVisible(true); 109 | startServer(); 110 | } 111 | 112 | /** 113 | * 服务器启动代码 114 | */ 115 | public void startServer() throws Exception { 116 | try { 117 | try { 118 | serverSocket = new ServerSocket(PORT); 119 | isStart = true; 120 | } catch (Exception e) { 121 | e.printStackTrace(); 122 | } 123 | // 可以接受多个客户端的连接 124 | // 接每一個信息时,服务器不可以终断,所以将其写入while()中,判断符为服务器开关的判断符 125 | while (isStart) { 126 | socket = serverSocket.accept(); 127 | ccList.add(new ClientCoon(socket)); 128 | System.out.println("\n" + "一个客户端连接服务器" + socket.getInetAddress() + "/" + socket.getPort()); 129 | serverTa.append("\n" + "一个客户端连接服务器" + socket.getInetAddress() + "/" + socket.getPort()); 130 | } 131 | //服务器接受客户端一句话 132 | /*receiveStream();*/ 133 | } catch (SocketException e) { 134 | System.out.println("服务器终断了!!!"); 135 | } catch (Exception e) { 136 | e.printStackTrace(); 137 | } 138 | } 139 | /** 140 | * 服务器停止代码 141 | * */ 142 | 143 | /** 144 | * 服务器接受数据的方法(客户端传送一句话),不适用多个客户端进行通话 145 | * */ 146 | /*public void receiveStream(){ 147 | try { 148 | dataInputStream = new DataInputStream(socket.getInputStream()); 149 | String str = dataInputStream.readUTF(); 150 | System.out.println(str); 151 | serverTa.append(str); 152 | }catch (Exception e){ 153 | e.printStackTrace(); 154 | } 155 | }*/ 156 | 157 | /** 158 | * @author 武新宇 159 | * @deprecated 内部类声明 对象 这个对象是属于服务器端的一个连接对象 160 | */ 161 | class ClientCoon implements Runnable { 162 | Socket socket = null; 163 | 164 | public ClientCoon(Socket socket) { 165 | this.socket = socket; 166 | /** 167 | * 线程启动在这里: 168 | * 初始化方法里 初始化一个线程 ,线程中封装的是自己,做整个线程的调用 169 | */ 170 | (new Thread(this)).start(); 171 | } 172 | 173 | //接受客户端信息(多线程run()方法) 174 | @Override 175 | public void run() { 176 | try { 177 | DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); 178 | // 为了让服务器能够接受到每个客户端的多句话 179 | while (isStart) { 180 | //readUTF()是一种阻塞方法,接一句就执行完了,所以循环中 181 | String str = dataInputStream.readUTF(); 182 | System.out.println("\n" + socket.getInetAddress() + "|" + socket.getPort() + "说" + str + "\n"); 183 | serverTa.append("\n" + socket.getInetAddress() + "|" + socket.getPort() + "说" + str + "\n"); 184 | //服务器向每个客户端发送别的客户端发来的信息 185 | // 遍历ccList,调用send方法,在客户端里接受应该是多线程的接受 186 | String strSend = "\n" + socket.getInetAddress() + "|" + socket.getPort() + "说" + str + "\n"; 187 | Iterator iterator = ccList.iterator(); 188 | while (iterator.hasNext()) { 189 | ClientCoon clientCoon = iterator.next(); 190 | clientCoon.send(strSend); 191 | } 192 | } 193 | } catch (Exception e) { 194 | e.printStackTrace(); 195 | } 196 | } 197 | 198 | // 服务器向每個连接对象发送数据的方法 199 | public void send(String str) { 200 | try { 201 | DataOutputStream dataOutputStream = new DataOutputStream(this.socket.getOutputStream()); 202 | dataOutputStream.writeUTF(str); 203 | } catch (SocketException e) { 204 | e.printStackTrace(); 205 | } catch (IOException e) { 206 | e.printStackTrace(); 207 | } 208 | } 209 | } 210 | 211 | 212 | } 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /多人聊天系统 2021.03.02.md: -------------------------------------------------------------------------------- 1 | # 多人聊天系统 2 | 3 | https://www.bilibili.com/video/BV1M4411A78q 4 | 5 | # TCP用多线程实现多人聊天的项目 6 | 7 | https://www.bilibili.com/video/BV16E411F79L?from=search&seid=12745764004109122366 8 | 9 | 课程学时: 8学时 10 | 知识要点: I0 编程、Socket 编程、多线程编程、异常处理、集合类的使用 11 | 编程思路: 12 | 1、客户端聊天窗口的创建 13 | 2、添加服务器与客户端的连接 14 | 3、连接上以后考虑将客户端的信息发送到服务器上 15 | 4、利用多线程实现连接多个客户端 16 | 5、多个客户端的信息发送到服务器上以后,考虑把服务器上的信息发送到每个客户端 17 | 6、在服务器端要取到每个客户端的socket,才可以把信息发送到每个客户端上,利用集合 18 | 类存储客户的多个线程的连接 19 | 7、在客户端考虑利用多线程接受服务器上的信息 20 | 8、最后实现多人聊天室的效果 21 | 9、后期修改程序中的不足和发现bug 22 | 23 | ![image-20210302201219701](https://typora-wenjiuzhou.oss-cn-beijing.aliyuncs.com/20210302201226.png) 24 | 25 | ## 编程思路: 26 | 27 | ### 1、客户端聊天窗口的创建 28 | 29 | ![image-20210302201715624](https://typora-wenjiuzhou.oss-cn-beijing.aliyuncs.com/20210302201715.png) 30 | 31 | **ClientChat.java** 32 | 33 | ```java 34 | package com.wxy; 35 | 36 | import javax.swing.*; 37 | import java.awt.*; 38 | import java.awt.event.ActionEvent; 39 | import java.awt.event.ActionListener; 40 | 41 | /** 42 | * @author Administrator 43 | * @Auther: wuxy 44 | * @Date: 2021/3/2 - 03 - 02 - 20:16 45 | * @Description: com.wxy 46 | * @version: 1.0 47 | */ 48 | public class ClientChat { 49 | //这是一个main方法,是程序的入口: 50 | public static void main(String[] args) { 51 | ClientJframe clientJframe = new ClientJframe(); 52 | clientJframe.init(); 53 | } 54 | } 55 | 56 | class ClientJframe extends JFrame { 57 | //GUI布局 58 | //聊天记录显示区 59 | private JTextArea ta = new JTextArea(10, 20); 60 | //聊天记录输入区 61 | private JTextField tf = new JTextField(20); 62 | 63 | public void init() { 64 | this.setTitle("客户端窗口"); 65 | this.add(ta, BorderLayout.CENTER); 66 | this.add(tf, BorderLayout.SOUTH); 67 | 68 | this.setBounds(300, 300, 400, 400); 69 | 70 | // 添加监听,使回车键可以输入数据(判断数据合法性), 71 | // 并輸入到聊天框,换行 72 | tf.addActionListener(new ActionListener() { 73 | 74 | @Override 75 | public void actionPerformed(ActionEvent e) { 76 | String strSend = tf.getText(); 77 | // 去掉空格判断长度是否为空 78 | if (strSend.trim().length() == 0) { 79 | return; 80 | } 81 | tf.setText(""); 82 | ta.append(strSend + "\n"); 83 | 84 | } 85 | }); 86 | 87 | //关闭事件 88 | this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 89 | ta.setEditable(false);//聊天区域不可以输入 90 | tf.requestFocus();//光标聚焦 91 | this.setVisible(true); 92 | } 93 | } 94 | ``` 95 | 96 | ![image-20210302204523512](https://typora-wenjiuzhou.oss-cn-beijing.aliyuncs.com/20210302204523.png) 97 | 98 | #### 功能扩展 99 | 100 | 1.要求文字区域不能编辑 101 | 102 | ```java 103 | ta.setEditable(false);//聊天区域不可以输入 104 | ``` 105 | 106 | 2.输入文字按回车发送到聊天内容区ta 107 | 108 | ```java 109 | // 添加监听,使回车键可以输入数据(判断数据合法性), 110 | // 并輸入到聊天框,换行 111 | tf.addActionListener(new ActionListener() { 112 | 113 | @Override 114 | public void actionPerformed(ActionEvent e) { 115 | String strSend = tf.getText(); 116 | // 去掉空格判断长度是否为空 117 | if (strSend.trim().length() == 0) { 118 | return; 119 | } 120 | tf.setText(""); 121 | ta.append(strSend + "\n"); 122 | 123 | } 124 | }); 125 | ``` 126 | 127 | ![image-20210302205342138](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210302205342138.png) 128 | 129 | ### 2、服务器窗口的创建 130 | 131 | **ServerChat.java** 132 | 133 | ```java 134 | package com.wxy; 135 | 136 | import javax.swing.*; 137 | import java.awt.*; 138 | 139 | /** 140 | * @author Administrator 141 | * @Auther: wuxy 142 | * @Date: 2021/3/2 - 03 - 02 - 20:54 143 | * @Description: com.wxy 144 | * @version: 1.0 145 | */ 146 | public class ServerChat { 147 | //这是一个main方法,是程序的入口: 148 | public static void main(String[] args) { 149 | ServerJframe serverJframe = new ServerJframe(); 150 | serverJframe.init(); 151 | } 152 | } 153 | class ServerJframe extends JFrame{ 154 | JTextArea serverTa = new JTextArea(); 155 | JPanel btnTool = new JPanel(); 156 | JButton startBtn = new JButton("启动"); 157 | JButton stopBtn = new JButton("停止"); 158 | public void init() { 159 | this.setTitle("服务器端窗口"); 160 | this.add(serverTa, BorderLayout.CENTER);//流式布局 161 | btnTool.add(startBtn); 162 | btnTool.add(stopBtn); 163 | this.add(btnTool,BorderLayout.SOUTH); 164 | 165 | this.setBounds(0,0,500,500); 166 | this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 167 | serverTa.setEditable(false);//显示区域不可以输入文字 168 | this.setVisible(true); 169 | } 170 | } 171 | 172 | ``` 173 | 174 | 175 | 176 | ![image-20210302210314611](https://typora-wenjiuzhou.oss-cn-beijing.aliyuncs.com/20210302210410.png) 177 | 178 | ### 3、 添加服务器(分发和接受)与客户端(多线程)的连接 179 | 180 | #### 服务器 181 | 182 | ![image-20210302210503340](https://typora-wenjiuzhou.oss-cn-beijing.aliyuncs.com/20210302210503.png) 183 | 184 | ```java 185 | //端口 186 | private static final int PORT = 8888; 187 | //ServerSocket 188 | private ServerSocket serverSocket = null; 189 | private Socket socket = null; 190 | ``` 191 | 192 | 193 | 194 | ```java 195 | //按钮监听监听服务器开启 196 | startBtn.addActionListener(new ActionListener() { 197 | @Override 198 | public void actionPerformed(ActionEvent e) { 199 | try { 200 | startServer(); 201 | }catch (IOException e1){ 202 | e1.printStackTrace(); 203 | } 204 | } 205 | }); 206 | 207 | 208 | /** 209 | * 服务器启动代码 210 | * */ 211 | public void startServer() throws IOException{ 212 | serverSocket=new ServerSocket(PORT); 213 | socket = serverSocket.accept(); 214 | System.out.println("一个客户端连接服务器"+socket.getInetAddress()+"/"+socket.getPort()); 215 | serverTa.append("一个客户端连接服务器"+socket.getInetAddress()+"/"+socket.getPort()); 216 | } 217 | /** 218 | * 服务器停止代码 219 | * */ 220 | ``` 221 | 222 | **ServerChat.java** 223 | 224 | ```java 225 | package com.wxy; 226 | 227 | import javax.swing.*; 228 | import java.awt.*; 229 | import java.awt.event.ActionEvent; 230 | import java.awt.event.ActionListener; 231 | import java.io.IOException; 232 | import java.net.ServerSocket; 233 | import java.net.Socket; 234 | 235 | /** 236 | * @author Administrator 237 | * @Auther: wuxy 238 | * @Date: 2021/3/2 - 03 - 02 - 20:54 239 | * @Description: com.wxy 240 | * @version: 1.0 241 | */ 242 | public class ServerChat { 243 | //这是一个main方法,是程序的入口: 244 | public static void main(String[] args) { 245 | ServerJframe serverJframe = new ServerJframe(); 246 | serverJframe.init(); 247 | } 248 | } 249 | class ServerJframe extends JFrame{ 250 | //GUI相关属性 251 | JTextArea serverTa = new JTextArea(); 252 | JPanel btnTool = new JPanel(); 253 | JButton startBtn = new JButton("启动"); 254 | JButton stopBtn = new JButton("停止"); 255 | //端口 256 | private static final int PORT = 8888; 257 | //ServerSocket 258 | private ServerSocket serverSocket = null; 259 | private Socket socket = null; 260 | 261 | public void init() { 262 | this.setTitle("服务器端窗口"); 263 | this.add(serverTa, BorderLayout.CENTER);//流式布局 264 | btnTool.add(startBtn); 265 | btnTool.add(stopBtn); 266 | this.add(btnTool,BorderLayout.SOUTH); 267 | 268 | this.setBounds(0,0,500,500); 269 | 270 | //按钮监听监听服务器开启 271 | startBtn.addActionListener(new ActionListener() { 272 | @Override 273 | public void actionPerformed(ActionEvent e) { 274 | try { 275 | startServer(); 276 | }catch (IOException e1){ 277 | e1.printStackTrace(); 278 | } 279 | } 280 | }); 281 | //关闭客户端窗口的同时应该关闭服务器,要修改 282 | this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 283 | serverTa.setEditable(false);//显示区域不可以输入文字 284 | this.setVisible(true); 285 | } 286 | /** 287 | * 服务器启动代码 288 | * */ 289 | public void startServer() throws IOException{ 290 | serverSocket=new ServerSocket(PORT); 291 | socket = serverSocket.accept(); 292 | System.out.println("一个客户端连接服务器"+socket.getInetAddress()+"/"+socket.getPort()); 293 | serverTa.append("一个客户端连接服务器"+socket.getInetAddress()+"/"+socket.getPort()); 294 | } 295 | /** 296 | * 服务器停止代码 297 | * */ 298 | 299 | } 300 | 301 | ``` 302 | 303 | #### 客户端 304 | 305 | ![image-20210302212838914](https://typora-wenjiuzhou.oss-cn-beijing.aliyuncs.com/20210302212838.png) 306 | 307 | ```java 308 | //端口 309 | // 静态常量主机端口号 310 | private static final String CONNSTR = "127.0.0.1"; 311 | // 静态常量服务器端口号 312 | private static final int CONNPORT = 8888; 313 | private Socket socket = null; 314 | ``` 315 | 316 | 317 | 318 | ```java 319 | try { 320 | socket=new Socket(CONNSTR,CONNPORT); 321 | }catch (Exception e){ 322 | e.printStackTrace(); 323 | } 324 | ``` 325 | 326 | **ServerChat.java** 327 | 328 | ```java 329 | package com.wxy; 330 | 331 | import javax.swing.*; 332 | import java.awt.*; 333 | import java.awt.event.ActionEvent; 334 | import java.awt.event.ActionListener; 335 | import java.io.IOException; 336 | import java.net.Socket; 337 | 338 | /** 339 | * @author Administrator 340 | * @Auther: wuxy 341 | * @Date: 2021/3/2 - 03 - 02 - 20:16 342 | * @Description: com.wxy 343 | * @version: 1.0 344 | */ 345 | public class ClientChat { 346 | //这是一个main方法,是程序的入口: 347 | public static void main(String[] args) { 348 | ClientJframe clientJframe = new ClientJframe(); 349 | clientJframe.init(); 350 | } 351 | } 352 | 353 | class ClientJframe extends JFrame { 354 | //GUI布局 355 | //聊天记录显示区 356 | private JTextArea ta = new JTextArea(10, 20); 357 | //聊天记录输入区 358 | private JTextField tf = new JTextField(20); 359 | 360 | //端口 361 | // 静态常量主机端口号 362 | private static final String CONNSTR = "127.0.0.1"; 363 | // 静态常量服务器端口号 364 | private static final int CONNPORT = 8888; 365 | private Socket socket = null; 366 | 367 | public void init() { 368 | this.setTitle("客户端窗口"); 369 | this.add(ta, BorderLayout.CENTER); 370 | this.add(tf, BorderLayout.SOUTH); 371 | 372 | this.setBounds(300, 300, 400, 400); 373 | 374 | // 添加监听,使回车键可以输入数据(判断数据合法性), 375 | // 并輸入到聊天框,换行 376 | tf.addActionListener(new ActionListener() { 377 | 378 | @Override 379 | public void actionPerformed(ActionEvent e) { 380 | String strSend = tf.getText(); 381 | // 去掉空格判断长度是否为空 382 | if (strSend.trim().length() == 0) { 383 | return; 384 | } 385 | tf.setText(""); 386 | ta.append(strSend + "\n"); 387 | 388 | } 389 | }); 390 | 391 | //关闭事件 392 | this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 393 | ta.setEditable(false);//聊天区域不可以输入 394 | tf.requestFocus();//光标聚焦 395 | 396 | try { 397 | socket = new Socket(CONNSTR, CONNPORT); 398 | } catch (Exception e) { 399 | e.printStackTrace(); 400 | } 401 | 402 | this.setVisible(true); 403 | } 404 | } 405 | ``` 406 | 407 | ![image-20210302214956930](https://typora-wenjiuzhou.oss-cn-beijing.aliyuncs.com/20210302214957.png) 408 | 409 | ### 4、连接上以后考虑将客户端的信息发送到服务器上 410 | 411 | 在考虑发送信息到服务器上 412 | 413 | ![image-20210302215052078](https://typora-wenjiuzhou.oss-cn-beijing.aliyuncs.com/20210302215052.png) 414 | 415 | #### **Client**:**发送数据** 416 | 417 | ```java 418 | //Client发送数据 419 | private DataOutputStream dataOutputStream = null; 420 | ``` 421 | 422 | 423 | 424 | ```java 425 | /** 426 | * 客户端发送信息到服务器上的方法 427 | */ 428 | public void send(String str) { 429 | try { 430 | dataOutputStream = new DataOutputStream(socket.getOutputStream()); 431 | dataOutputStream.writeUTF(str); 432 | } catch (IOException e) { 433 | // TODO Auto-generated catch block 434 | e.printStackTrace(); 435 | } 436 | } 437 | ``` 438 | 439 | 440 | 441 | ```java 442 | tf.addActionListener(new ActionListener() { 443 | 444 | @Override 445 | public void actionPerformed(ActionEvent e) { 446 | String strSend = tf.getText(); 447 | //客户端信息strSend发送到服务器上 448 | send(strSend); 449 | // 去掉空格判断长度是否为空 450 | if (strSend.trim().length() == 0) { 451 | return; 452 | } 453 | tf.setText(""); 454 | ta.append(strSend + "\n"); 455 | 456 | } 457 | }); 458 | ``` 459 | 460 | 461 | 462 | **ClientChat.java** 463 | 464 | ```java 465 | package com.wxy; 466 | 467 | import javax.swing.*; 468 | import java.awt.*; 469 | import java.awt.event.ActionEvent; 470 | import java.awt.event.ActionListener; 471 | import java.io.DataOutputStream; 472 | import java.io.IOException; 473 | import java.net.Socket; 474 | 475 | /** 476 | * @author Administrator 477 | * @Auther: wuxy 478 | * @Date: 2021/3/2 - 03 - 02 - 20:16 479 | * @Description: com.wxy 480 | * @version: 1.0 481 | */ 482 | public class ClientChat { 483 | //这是一个main方法,是程序的入口: 484 | public static void main(String[] args) { 485 | ClientJframe clientJframe = new ClientJframe(); 486 | clientJframe.init(); 487 | } 488 | } 489 | 490 | class ClientJframe extends JFrame { 491 | //GUI布局 492 | //聊天记录显示区 493 | private JTextArea ta = new JTextArea(10, 20); 494 | //聊天记录输入区 495 | private JTextField tf = new JTextField(20); 496 | 497 | //端口 498 | // 静态常量主机端口号 499 | private static final String CONNSTR = "127.0.0.1"; 500 | // 静态常量服务器端口号 501 | private static final int CONNPORT = 8888; 502 | private Socket socket = null; 503 | 504 | //Client发送数据 505 | private DataOutputStream dataOutputStream = null; 506 | 507 | public void init() { 508 | this.setTitle("客户端窗口"); 509 | this.add(ta, BorderLayout.CENTER); 510 | this.add(tf, BorderLayout.SOUTH); 511 | 512 | this.setBounds(300, 300, 400, 400); 513 | 514 | // 添加监听,使回车键可以输入数据(判断数据合法性), 515 | // 并輸入到聊天框,换行 516 | tf.addActionListener(new ActionListener() { 517 | 518 | @Override 519 | public void actionPerformed(ActionEvent e) { 520 | String strSend = tf.getText(); 521 | //客户端信息strSend发送到服务器上 522 | send(strSend); 523 | // 去掉空格判断长度是否为空 524 | if (strSend.trim().length() == 0) { 525 | return; 526 | } 527 | tf.setText(""); 528 | ta.append(strSend + "\n"); 529 | 530 | } 531 | }); 532 | 533 | //关闭事件 534 | this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 535 | ta.setEditable(false);//聊天区域不可以输入 536 | tf.requestFocus();//光标聚焦 537 | 538 | try { 539 | socket = new Socket(CONNSTR, CONNPORT); 540 | } catch (Exception e) { 541 | e.printStackTrace(); 542 | } 543 | 544 | this.setVisible(true); 545 | } 546 | 547 | /** 548 | * 客户端发送信息到服务器上的方法 549 | */ 550 | public void send(String str) { 551 | try { 552 | dataOutputStream = new DataOutputStream(socket.getOutputStream()); 553 | dataOutputStream.writeUTF(str); 554 | } catch (IOException e) { 555 | // TODO Auto-generated catch block 556 | e.printStackTrace(); 557 | } 558 | } 559 | } 560 | ``` 561 | 562 | #### 服务器:接受数据 563 | 564 | ```java 565 | //Server接受数据 566 | private DataInputStream dataInputStream = null; 567 | ``` 568 | 569 | 570 | 571 | ```java 572 | /** 573 | * 服务器接受数据的方法 574 | * */ 575 | public void receiveStream(){ 576 | try { 577 | dataInputStream = new DataInputStream(socket.getInputStream()); 578 | String str = dataInputStream.readUTF(); 579 | System.out.println(str); 580 | serverTa.append(str); 581 | }catch (Exception e){ 582 | e.printStackTrace(); 583 | } 584 | } 585 | ``` 586 | 587 | 588 | 589 | ```java 590 | public void startServer() throws IOException{ 591 | serverSocket=new ServerSocket(PORT); 592 | socket = serverSocket.accept(); 593 | System.out.println("一个客户端连接服务器"+socket.getInetAddress()+"/"+socket.getPort()); 594 | serverTa.append("一个客户端连接服务器"+socket.getInetAddress()+"/"+socket.getPort()); 595 | receiveStream(); 596 | } 597 | ``` 598 | 599 | **ServerChat.java** 600 | 601 | ```java 602 | package com.wxy; 603 | 604 | import javax.swing.*; 605 | import java.awt.*; 606 | import java.awt.event.ActionEvent; 607 | import java.awt.event.ActionListener; 608 | import java.io.DataInputStream; 609 | import java.io.IOException; 610 | import java.net.ServerSocket; 611 | import java.net.Socket; 612 | 613 | /** 614 | * @author Administrator 615 | * @Auther: wuxy 616 | * @Date: 2021/3/2 - 03 - 02 - 20:54 617 | * @Description: com.wxy 618 | * @version: 1.0 619 | */ 620 | public class ServerChat { 621 | //这是一个main方法,是程序的入口: 622 | public static void main(String[] args) { 623 | ServerJframe serverJframe = new ServerJframe(); 624 | serverJframe.init(); 625 | } 626 | } 627 | class ServerJframe extends JFrame{ 628 | //GUI相关属性 629 | JTextArea serverTa = new JTextArea(); 630 | JPanel btnTool = new JPanel(); 631 | JButton startBtn = new JButton("启动"); 632 | JButton stopBtn = new JButton("停止"); 633 | //端口 634 | private static final int PORT = 8888; 635 | //ServerSocket 636 | private ServerSocket serverSocket = null; 637 | private Socket socket = null; 638 | 639 | //Server接受数据 640 | private DataInputStream dataInputStream = null; 641 | 642 | public void init() { 643 | this.setTitle("服务器端窗口"); 644 | this.add(serverTa, BorderLayout.CENTER);//流式布局 645 | btnTool.add(startBtn); 646 | btnTool.add(stopBtn); 647 | this.add(btnTool,BorderLayout.SOUTH); 648 | 649 | this.setBounds(0,0,500,500); 650 | 651 | //按钮监听监听服务器开启 652 | startBtn.addActionListener(new ActionListener() { 653 | @Override 654 | public void actionPerformed(ActionEvent e) { 655 | try { 656 | startServer(); 657 | }catch (IOException e1){ 658 | e1.printStackTrace(); 659 | } 660 | } 661 | }); 662 | //关闭客户端窗口的同时应该关闭服务器,要修改 663 | this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 664 | serverTa.setEditable(false);//显示区域不可以输入文字 665 | this.setVisible(true); 666 | } 667 | /** 668 | * 服务器启动代码 669 | * */ 670 | public void startServer() throws IOException{ 671 | serverSocket=new ServerSocket(PORT); 672 | socket = serverSocket.accept(); 673 | System.out.println("一个客户端连接服务器"+socket.getInetAddress()+"/"+socket.getPort()); 674 | serverTa.append("一个客户端连接服务器"+socket.getInetAddress()+"/"+socket.getPort()); 675 | receiveStream(); 676 | } 677 | /** 678 | * 服务器停止代码 679 | * */ 680 | 681 | /** 682 | * 服务器接受数据的方法 683 | * */ 684 | public void receiveStream(){ 685 | try { 686 | dataInputStream = new DataInputStream(socket.getInputStream()); 687 | String str = dataInputStream.readUTF(); 688 | System.out.println(str); 689 | serverTa.append(str); 690 | }catch (Exception e){ 691 | e.printStackTrace(); 692 | } 693 | } 694 | } 695 | 696 | ``` 697 | 698 | ![image-20210302221228820](https://typora-wenjiuzhou.oss-cn-beijing.aliyuncs.com/20210302221228.png) 699 | 700 | 一个客户端只能发送一句话! 701 | 702 | ### 5.利用多线程实现连接多个客户端 703 | 704 | ![image-20210302221332736](https://typora-wenjiuzhou.oss-cn-beijing.aliyuncs.com/20210302221332.png) 705 | 706 | ![image-20210302222003386](https://typora-wenjiuzhou.oss-cn-beijing.aliyuncs.com/20210302222003.png) 707 | 708 | ```java 709 | // 多个客户端访问时,客户端对象存放入List中 710 | private ArrayList ccList = new ArrayList(); 711 | 712 | // 服务器启动的标志 (其实ServerSocket ss 初始化出来时以为者服务器的启动) 713 | private boolean isStart = false; 714 | ``` 715 | 716 | 717 | 718 | ```java 719 | /** 720 | * @author 武新宇 721 | * @deprecated 内部类声明 对象 这个对象是属于服务器端的一个连接对象 722 | */ 723 | class ClientCoon implements Runnable { 724 | Socket socket = null; 725 | 726 | public ClientCoon(Socket socket) { 727 | this.socket = socket; 728 | /** 729 | * 线程启动在这里: 730 | * 初始化方法里 初始化一个线程 ,线程中封装的是自己,做整个线程的调用 731 | */ 732 | (new Thread(this)).start(); 733 | } 734 | 735 | @Override 736 | public void run() { 737 | try { 738 | DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); 739 | String str = dataInputStream.readUTF(); 740 | System.out.println(str); 741 | serverTa.append(str); 742 | } catch (Exception e) { 743 | e.printStackTrace(); 744 | } 745 | } 746 | } 747 | ``` 748 | 749 | ```java 750 | /** 751 | * 服务器启动代码 752 | */ 753 | public void startServer() throws Exception { 754 | try { 755 | try { 756 | serverSocket=new ServerSocket(PORT); 757 | isStart=true; 758 | }catch (Exception e){ 759 | e.printStackTrace(); 760 | } 761 | // 可以接受多个客户端的连接 762 | // 接每一個信息时,服务器不可以终断,所以将其写入while()中,判断符为服务器开关的判断符 763 | while (isStart) { 764 | socket = serverSocket.accept(); 765 | ccList.add(new ClientCoon(socket)); 766 | System.out.println("一个客户端连接服务器" + socket.getInetAddress() + "/" + socket.getPort()); 767 | serverTa.append("一个客户端连接服务器" + socket.getInetAddress() + "/" + socket.getPort()); 768 | } 769 | //服务器接受客户端一句话 770 | /*receiveStream();*/ 771 | }catch (Exception e){ 772 | e.printStackTrace(); 773 | } 774 | } 775 | ``` 776 | 777 | 778 | 779 | ```java 780 | //判断服务器是否已经开启 781 | if (isStart) { 782 | System.out.println("服务器已经启动了\n"); 783 | } else { 784 | System.out.println("服务器还没有启动,请点击启动服务器!\n"); 785 | } 786 | //按钮监听监听服务器开启,置开始位false 787 | startBtn.addActionListener(new ActionListener() { 788 | @Override 789 | public void actionPerformed(ActionEvent e) { 790 | try { 791 | if(serverSocket ==null){ 792 | serverSocket = new ServerSocket(PORT); 793 | } 794 | isStart = true; 795 | //startServer(); 796 | serverTa.append("服务器已经启动啦! \n"); 797 | } catch (Exception e1) { 798 | e1.printStackTrace(); 799 | } 800 | } 801 | }); 802 | // 终止按钮监听停止服务器,置开始位true 803 | stopBtn.addActionListener(new ActionListener() { 804 | @Override 805 | public void actionPerformed(ActionEvent e) { 806 | try { 807 | if (serverSocket!= null) { 808 | serverSocket.close(); 809 | isStart = false; 810 | } 811 | System.exit(0); 812 | serverTa.append("服务器断啦!!\n"); 813 | System.out.println("服务器断啦!!\n"); 814 | 815 | } catch (Exception e1) { 816 | // TODO Auto-generated catch block 817 | e1.printStackTrace(); 818 | } 819 | } 820 | }); 821 | ``` 822 | 823 | 824 | 825 | **ServerChat.java** 826 | 827 | ```java 828 | package com.wxy; 829 | 830 | import javax.swing.*; 831 | import java.awt.*; 832 | import java.awt.event.ActionEvent; 833 | import java.awt.event.ActionListener; 834 | import java.io.DataInputStream; 835 | import java.io.IOException; 836 | import java.net.ServerSocket; 837 | import java.net.Socket; 838 | import java.util.ArrayList; 839 | 840 | /** 841 | * @author Administrator 842 | * @Auther: wuxy 843 | * @Date: 2021/3/2 - 03 - 02 - 20:54 844 | * @Description: com.wxy 845 | * @version: 1.0 846 | */ 847 | public class ServerChat { 848 | //这是一个main方法,是程序的入口: 849 | public static void main(String[] args) throws Exception { 850 | ServerJframe serverJframe = new ServerJframe(); 851 | serverJframe.init(); 852 | } 853 | } 854 | 855 | class ServerJframe extends JFrame { 856 | //GUI相关属性 857 | JTextArea serverTa = new JTextArea(); 858 | JPanel btnTool = new JPanel(); 859 | JButton startBtn = new JButton("启动"); 860 | JButton stopBtn = new JButton("停止"); 861 | //端口 862 | private static final int PORT = 8888; 863 | //ServerSocket 864 | private ServerSocket serverSocket = null; 865 | private Socket socket = null; 866 | 867 | //Server接受数据 868 | private DataInputStream dataInputStream = null; 869 | 870 | // 多个客户端访问时,客户端对象存放入List中 871 | private ArrayList ccList = new ArrayList(); 872 | 873 | // 服务器启动的标志 (其实ServerSocket ss 初始化出来时以为者服务器的启动) 874 | private boolean isStart = false; 875 | 876 | public void init() throws Exception { 877 | this.setTitle("服务器端窗口"); 878 | this.add(serverTa, BorderLayout.CENTER);//流式布局 879 | btnTool.add(startBtn); 880 | btnTool.add(stopBtn); 881 | this.add(btnTool, BorderLayout.SOUTH); 882 | 883 | this.setBounds(0, 0, 500, 500); 884 | 885 | //判断服务器是否已经开启 886 | if (isStart) { 887 | System.out.println("服务器已经启动了\n"); 888 | } else { 889 | System.out.println("服务器还没有启动,请点击启动服务器!\n"); 890 | } 891 | //按钮监听监听服务器开启,置开始位false 892 | startBtn.addActionListener(new ActionListener() { 893 | @Override 894 | public void actionPerformed(ActionEvent e) { 895 | try { 896 | if(serverSocket ==null){ 897 | serverSocket = new ServerSocket(PORT); 898 | } 899 | isStart = true; 900 | //startServer(); 901 | serverTa.append("服务器已经启动啦! \n"); 902 | } catch (Exception e1) { 903 | e1.printStackTrace(); 904 | } 905 | } 906 | }); 907 | // 终止按钮监听停止服务器,置开始位true 908 | stopBtn.addActionListener(new ActionListener() { 909 | @Override 910 | public void actionPerformed(ActionEvent e) { 911 | try { 912 | if (serverSocket!= null) { 913 | serverSocket.close(); 914 | isStart = false; 915 | } 916 | System.exit(0); 917 | serverTa.append("服务器断啦!!\n"); 918 | System.out.println("服务器断啦!!\n"); 919 | 920 | } catch (Exception e1) { 921 | // TODO Auto-generated catch block 922 | e1.printStackTrace(); 923 | } 924 | } 925 | }); 926 | 927 | /** 928 | * 服务器窗口关闭应该停止服务器,需改进的代码 929 | */ 930 | // this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 931 | // serverTa.setEditable(false); 932 | this.setVisible(true); 933 | startServer(); 934 | } 935 | 936 | /** 937 | * 服务器启动代码 938 | */ 939 | public void startServer() throws Exception { 940 | try { 941 | try { 942 | serverSocket=new ServerSocket(PORT); 943 | isStart=true; 944 | }catch (Exception e){ 945 | e.printStackTrace(); 946 | } 947 | // 可以接受多个客户端的连接 948 | // 接每一個信息时,服务器不可以终断,所以将其写入while()中,判断符为服务器开关的判断符 949 | while (isStart) { 950 | socket = serverSocket.accept(); 951 | ccList.add(new ClientCoon(socket)); 952 | System.out.println("一个客户端连接服务器" + socket.getInetAddress() + "/" + socket.getPort()); 953 | serverTa.append("一个客户端连接服务器" + socket.getInetAddress() + "/" + socket.getPort()); 954 | } 955 | //服务器接受客户端一句话 956 | /*receiveStream();*/ 957 | }catch (Exception e){ 958 | e.printStackTrace(); 959 | } 960 | } 961 | /** 962 | * 服务器停止代码 963 | * */ 964 | 965 | /** 966 | * 服务器接受数据的方法(客户端传送一句话),不适用多个客户端进行通话 967 | * */ 968 | /*public void receiveStream(){ 969 | try { 970 | dataInputStream = new DataInputStream(socket.getInputStream()); 971 | String str = dataInputStream.readUTF(); 972 | System.out.println(str); 973 | serverTa.append(str); 974 | }catch (Exception e){ 975 | e.printStackTrace(); 976 | } 977 | }*/ 978 | 979 | /** 980 | * @author 武新宇 981 | * @deprecated 内部类声明 对象 这个对象是属于服务器端的一个连接对象 982 | */ 983 | class ClientCoon implements Runnable { 984 | Socket socket = null; 985 | 986 | public ClientCoon(Socket socket) { 987 | this.socket = socket; 988 | /** 989 | * 线程启动在这里: 990 | * 初始化方法里 初始化一个线程 ,线程中封装的是自己,做整个线程的调用 991 | */ 992 | (new Thread(this)).start(); 993 | } 994 | 995 | @Override 996 | public void run() { 997 | try { 998 | DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); 999 | String str = dataInputStream.readUTF(); 1000 | System.out.println(str); 1001 | serverTa.append(str); 1002 | } catch (Exception e) { 1003 | e.printStackTrace(); 1004 | } 1005 | } 1006 | } 1007 | } 1008 | ``` 1009 | 1010 | ![image-20210302231439309](https://typora-wenjiuzhou.oss-cn-beijing.aliyuncs.com/20210302231439.png) 1011 | 1012 | 现阶段:可以连多个客户端,但是每个客户端只能打一句话 1013 | 1014 | 解决问题: 1015 | 1016 | ```java 1017 | /** 1018 | * @author 武新宇 1019 | * @deprecated 内部类声明 对象 这个对象是属于服务器端的一个连接对象 1020 | */ 1021 | class ClientCoon implements Runnable { 1022 | Socket socket = null; 1023 | 1024 | public ClientCoon(Socket socket) { 1025 | this.socket = socket; 1026 | /** 1027 | * 线程启动在这里: 1028 | * 初始化方法里 初始化一个线程 ,线程中封装的是自己,做整个线程的调用 1029 | */ 1030 | (new Thread(this)).start(); 1031 | } 1032 | 1033 | @Override 1034 | public void run() { 1035 | try { 1036 | DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); 1037 | // 为了让服务器能够接受到每个客户端的多句话 1038 | while(isStart) { 1039 | //readUTF()是一种阻塞方法,接一句就执行完了,所以循环中 1040 | String str = dataInputStream.readUTF(); 1041 | System.out.println("\n"+socket.getInetAddress()+"|"+socket.getPort()+"说"+str+"\n"); 1042 | serverTa.append("\n"+socket.getInetAddress()+"|"+socket.getPort()+"说"+str+"\n"); 1043 | } 1044 | } catch (Exception e) { 1045 | e.printStackTrace(); 1046 | } 1047 | } 1048 | } 1049 | ``` 1050 | 1051 | 1052 | 1053 | ![image-20210302233030496](https://typora-wenjiuzhou.oss-cn-beijing.aliyuncs.com/20210302233030.png) 1054 | 1055 | ### 6、多个客户端的信息发送到服务器上以后,考虑把服务器上的信息发送到每个客户端 1056 | 1057 | ![image-20210302233159877](https://typora-wenjiuzhou.oss-cn-beijing.aliyuncs.com/20210302233159.png) 1058 | 1059 | #### 服務器端: 1060 | 1061 | ```java 1062 | // 服务器向每個连接对象发送数据的方法 1063 | public void send(String str) { 1064 | try { 1065 | DataOutputStream dataOutputStream = new DataOutputStream(this.s.getOutputStream()); 1066 | dataOutputStream.writeUTF(str); 1067 | } catch (SocketException e) { 1068 | e.printStackTrace(); 1069 | } catch (IOException e) { 1070 | e.printStackTrace(); 1071 | } 1072 | } 1073 | ``` 1074 | 1075 | ```java 1076 | //接受客户端信息(多线程run()方法) 1077 | @Override 1078 | public void run() { 1079 | try { 1080 | DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); 1081 | // 为了让服务器能够接受到每个客户端的多句话 1082 | while (isStart) { 1083 | //readUTF()是一种阻塞方法,接一句就执行完了,所以循环中 1084 | String str = dataInputStream.readUTF(); 1085 | System.out.println("\n" + socket.getInetAddress() + "|" + socket.getPort() + "说" + str + "\n"); 1086 | serverTa.append("\n" + socket.getInetAddress() + "|" + socket.getPort() + "说" + str + "\n"); 1087 | //服务器向每个客户端发送别的客户端发来的信息 1088 | // 遍历ccList,调用send方法,在客户端里接受应该是多线程的接受 1089 | String strSend = "\n" + socket.getInetAddress() + "|" + socket.getPort() + "说" + str + "\n"; 1090 | Iterator iterator = ccList.iterator(); 1091 | while (iterator.hasNext()){ 1092 | ClientCoon clientCoon = iterator.next(); 1093 | clientCoon.send(strSend); 1094 | } 1095 | } 1096 | } catch (Exception e) { 1097 | e.printStackTrace(); 1098 | } 1099 | } 1100 | ``` 1101 | 1102 | **ServerChat.java** 1103 | 1104 | ```java 1105 | package com.wxy; 1106 | 1107 | import javax.swing.*; 1108 | import java.awt.*; 1109 | import java.awt.event.ActionEvent; 1110 | import java.awt.event.ActionListener; 1111 | import java.io.DataInputStream; 1112 | import java.io.DataOutputStream; 1113 | import java.io.IOException; 1114 | import java.net.ServerSocket; 1115 | import java.net.Socket; 1116 | import java.net.SocketException; 1117 | import java.util.ArrayList; 1118 | import java.util.Iterator; 1119 | 1120 | /** 1121 | * @author Administrator 1122 | * @Auther: wuxy 1123 | * @Date: 2021/3/2 - 03 - 02 - 20:54 1124 | * @Description: com.wxy 1125 | * @version: 1.0 1126 | */ 1127 | public class ServerChat { 1128 | //这是一个main方法,是程序的入口: 1129 | public static void main(String[] args) throws Exception { 1130 | ServerJframe serverJframe = new ServerJframe(); 1131 | serverJframe.init(); 1132 | } 1133 | } 1134 | 1135 | class ServerJframe extends JFrame { 1136 | //GUI相关属性 1137 | JTextArea serverTa = new JTextArea(); 1138 | JPanel btnTool = new JPanel(); 1139 | JButton startBtn = new JButton("启动"); 1140 | JButton stopBtn = new JButton("停止"); 1141 | //端口 1142 | private static final int PORT = 8888; 1143 | //ServerSocket 1144 | private ServerSocket serverSocket = null; 1145 | private Socket socket = null; 1146 | 1147 | //Server接受数据 1148 | private DataInputStream dataInputStream = null; 1149 | 1150 | // 多个客户端访问时,客户端对象存放入List中 1151 | private ArrayList ccList = new ArrayList(); 1152 | 1153 | // 服务器启动的标志 (其实ServerSocket ss 初始化出来时以为者服务器的启动) 1154 | private boolean isStart = false; 1155 | 1156 | public void init() throws Exception { 1157 | this.setTitle("服务器端窗口"); 1158 | this.add(serverTa, BorderLayout.CENTER);//流式布局 1159 | btnTool.add(startBtn); 1160 | btnTool.add(stopBtn); 1161 | this.add(btnTool, BorderLayout.SOUTH); 1162 | 1163 | this.setBounds(0, 0, 500, 500); 1164 | 1165 | //判断服务器是否已经开启 1166 | if (isStart) { 1167 | System.out.println("服务器已经启动了\n"); 1168 | } else { 1169 | System.out.println("服务器还没有启动,请点击启动服务器!\n"); 1170 | } 1171 | //按钮监听监听服务器开启,置开始位false 1172 | startBtn.addActionListener(new ActionListener() { 1173 | @Override 1174 | public void actionPerformed(ActionEvent e) { 1175 | try { 1176 | if (serverSocket == null) { 1177 | serverSocket = new ServerSocket(PORT); 1178 | } 1179 | isStart = true; 1180 | //startServer(); 1181 | serverTa.append("服务器已经启动啦! \n"); 1182 | } catch (Exception e1) { 1183 | e1.printStackTrace(); 1184 | } 1185 | } 1186 | }); 1187 | // 终止按钮监听停止服务器,置开始位true 1188 | stopBtn.addActionListener(new ActionListener() { 1189 | @Override 1190 | public void actionPerformed(ActionEvent e) { 1191 | try { 1192 | if (serverSocket != null) { 1193 | serverSocket.close(); 1194 | isStart = false; 1195 | } 1196 | System.exit(0); 1197 | serverTa.append("服务器断啦!!\n"); 1198 | System.out.println("服务器断啦!!\n"); 1199 | 1200 | } catch (Exception e1) { 1201 | // TODO Auto-generated catch block 1202 | e1.printStackTrace(); 1203 | } 1204 | } 1205 | }); 1206 | 1207 | /** 1208 | * 服务器窗口关闭应该停止服务器,需改进的代码 1209 | */ 1210 | // this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 1211 | // serverTa.setEditable(false); 1212 | this.setVisible(true); 1213 | startServer(); 1214 | } 1215 | 1216 | /** 1217 | * 服务器启动代码 1218 | */ 1219 | public void startServer() throws Exception { 1220 | try { 1221 | try { 1222 | serverSocket = new ServerSocket(PORT); 1223 | isStart = true; 1224 | } catch (Exception e) { 1225 | e.printStackTrace(); 1226 | } 1227 | // 可以接受多个客户端的连接 1228 | // 接每一個信息时,服务器不可以终断,所以将其写入while()中,判断符为服务器开关的判断符 1229 | while (isStart) { 1230 | socket = serverSocket.accept(); 1231 | ccList.add(new ClientCoon(socket)); 1232 | System.out.println("\n" + "一个客户端连接服务器" + socket.getInetAddress() + "/" + socket.getPort()); 1233 | serverTa.append("\n" + "一个客户端连接服务器" + socket.getInetAddress() + "/" + socket.getPort()); 1234 | } 1235 | //服务器接受客户端一句话 1236 | /*receiveStream();*/ 1237 | } catch (SocketException e) { 1238 | System.out.println("服务器终断了!!!"); 1239 | } catch (Exception e) { 1240 | e.printStackTrace(); 1241 | } 1242 | } 1243 | /** 1244 | * 服务器停止代码 1245 | * */ 1246 | 1247 | /** 1248 | * 服务器接受数据的方法(客户端传送一句话),不适用多个客户端进行通话 1249 | * */ 1250 | /*public void receiveStream(){ 1251 | try { 1252 | dataInputStream = new DataInputStream(socket.getInputStream()); 1253 | String str = dataInputStream.readUTF(); 1254 | System.out.println(str); 1255 | serverTa.append(str); 1256 | }catch (Exception e){ 1257 | e.printStackTrace(); 1258 | } 1259 | }*/ 1260 | 1261 | /** 1262 | * @author 武新宇 1263 | * @deprecated 内部类声明 对象 这个对象是属于服务器端的一个连接对象 1264 | */ 1265 | class ClientCoon implements Runnable { 1266 | Socket socket = null; 1267 | 1268 | public ClientCoon(Socket socket) { 1269 | this.socket = socket; 1270 | /** 1271 | * 线程启动在这里: 1272 | * 初始化方法里 初始化一个线程 ,线程中封装的是自己,做整个线程的调用 1273 | */ 1274 | (new Thread(this)).start(); 1275 | } 1276 | 1277 | //接受客户端信息(多线程run()方法) 1278 | @Override 1279 | public void run() { 1280 | try { 1281 | DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); 1282 | // 为了让服务器能够接受到每个客户端的多句话 1283 | while (isStart) { 1284 | //readUTF()是一种阻塞方法,接一句就执行完了,所以循环中 1285 | String str = dataInputStream.readUTF(); 1286 | System.out.println("\n" + socket.getInetAddress() + "|" + socket.getPort() + "说" + str + "\n"); 1287 | serverTa.append("\n" + socket.getInetAddress() + "|" + socket.getPort() + "说" + str + "\n"); 1288 | //服务器向每个客户端发送别的客户端发来的信息 1289 | // 遍历ccList,调用send方法,在客户端里接受应该是多线程的接受 1290 | String strSend = "\n" + socket.getInetAddress() + "|" + socket.getPort() + "说" + str + "\n"; 1291 | Iterator iterator = ccList.iterator(); 1292 | while (iterator.hasNext()) { 1293 | ClientCoon clientCoon = iterator.next(); 1294 | clientCoon.send(strSend); 1295 | } 1296 | } 1297 | } catch (Exception e) { 1298 | e.printStackTrace(); 1299 | } 1300 | } 1301 | 1302 | // 服务器向每個连接对象发送数据的方法 1303 | public void send(String str) { 1304 | try { 1305 | DataOutputStream dataOutputStream = new DataOutputStream(this.socket.getOutputStream()); 1306 | dataOutputStream.writeUTF(str); 1307 | } catch (SocketException e) { 1308 | e.printStackTrace(); 1309 | } catch (IOException e) { 1310 | e.printStackTrace(); 1311 | } 1312 | } 1313 | } 1314 | 1315 | 1316 | } 1317 | 1318 | 1319 | 1320 | 1321 | ``` 1322 | 1323 | 1324 | 1325 | #### 客户端: 1326 | 1327 | ```java 1328 | //客户端连接上服务器判断符号 1329 | private boolean isConn =false; 1330 | ``` 1331 | 1332 | ```java 1333 | /** 1334 | * @author 武新宇 1335 | * @deprecated 多线程的类,实现了Runnable接口的类 1336 | */ 1337 | class Receive implements Runnable { 1338 | @Override 1339 | public void run() { 1340 | try { 1341 | while (isConn) { 1342 | DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); 1343 | String str = dataInputStream.readUTF(); 1344 | ta.append(str); 1345 | } 1346 | } catch (SocketException e) { 1347 | System.out.println("服务器意外终止了!"); 1348 | ta.append("服务器意外终止了!"); 1349 | } catch (IOException e) { 1350 | // TODO Auto-generated catch block 1351 | e.printStackTrace(); 1352 | } 1353 | } 1354 | } 1355 | ``` 1356 | 1357 | ```java 1358 | try { 1359 | socket = new Socket(CONNSTR, CONNPORT); 1360 | isConn = true; 1361 | } catch (Exception e) { 1362 | e.printStackTrace(); 1363 | } 1364 | 1365 | // 启动多线程 1366 | new Thread(new Receive()).start(); 1367 | ``` 1368 | 1369 | **ClientChat.java** 1370 | 1371 | ```java 1372 | package com.wxy; 1373 | 1374 | import javax.swing.*; 1375 | import java.awt.*; 1376 | import java.awt.event.ActionEvent; 1377 | import java.awt.event.ActionListener; 1378 | import java.io.DataInputStream; 1379 | import java.io.DataOutputStream; 1380 | import java.io.IOException; 1381 | import java.net.Socket; 1382 | import java.net.SocketException; 1383 | 1384 | /** 1385 | * @author Administrator 1386 | * @Auther: wuxy 1387 | * @Date: 2021/3/2 - 03 - 02 - 20:16 1388 | * @Description: com.wxy 1389 | * @version: 1.0 1390 | */ 1391 | public class ClientChat { 1392 | //这是一个main方法,是程序的入口: 1393 | public static void main(String[] args) { 1394 | ClientJframe clientJframe = new ClientJframe(); 1395 | clientJframe.init(); 1396 | } 1397 | } 1398 | 1399 | class ClientJframe extends JFrame { 1400 | //GUI布局 1401 | //聊天记录显示区 1402 | private JTextArea ta = new JTextArea(10, 20); 1403 | //聊天记录输入区 1404 | private JTextField tf = new JTextField(20); 1405 | 1406 | //端口 1407 | // 静态常量主机端口号 1408 | private static final String CONNSTR = "127.0.0.1"; 1409 | // 静态常量服务器端口号 1410 | private static final int CONNPORT = 8888; 1411 | private Socket socket = null; 1412 | 1413 | //Client发送数据 1414 | private DataOutputStream dataOutputStream = null; 1415 | 1416 | //客户端连接上服务器判断符号 1417 | private boolean isConn = false; 1418 | 1419 | /** 1420 | * 无参的构造方法 throws HeadlessException 1421 | */ 1422 | public ClientJframe() throws HeadlessException { 1423 | super(); 1424 | } 1425 | 1426 | public void init() { 1427 | this.setTitle("客户端窗口"); 1428 | this.add(ta, BorderLayout.CENTER); 1429 | this.add(tf, BorderLayout.SOUTH); 1430 | 1431 | this.setBounds(300, 300, 400, 400); 1432 | 1433 | // 添加监听,使回车键可以输入数据(判断数据合法性), 1434 | // 并輸入到聊天框,换行 1435 | tf.addActionListener(new ActionListener() { 1436 | 1437 | @Override 1438 | public void actionPerformed(ActionEvent e) { 1439 | String strSend = tf.getText(); 1440 | // 去掉空格判断长度是否为空 1441 | if (strSend.trim().length() == 0) { 1442 | return; 1443 | } 1444 | //客户端信息strSend发送到服务器上 1445 | send(strSend); 1446 | tf.setText(""); 1447 | ta.append(strSend + "\n"); 1448 | 1449 | } 1450 | }); 1451 | 1452 | //关闭事件 1453 | this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 1454 | ta.setEditable(false);//聊天区域不可以输入 1455 | tf.requestFocus();//光标聚焦 1456 | 1457 | try { 1458 | socket = new Socket(CONNSTR, CONNPORT); 1459 | isConn = true; 1460 | } catch (Exception e) { 1461 | e.printStackTrace(); 1462 | } 1463 | 1464 | // 启动多线程 1465 | new Thread(new Receive()).start(); 1466 | 1467 | this.setVisible(true); 1468 | } 1469 | 1470 | /** 1471 | * 客户端发送信息到服务器上的方法 1472 | */ 1473 | public void send(String str) { 1474 | try { 1475 | dataOutputStream = new DataOutputStream(socket.getOutputStream()); 1476 | dataOutputStream.writeUTF(str); 1477 | } catch (IOException e) { 1478 | // TODO Auto-generated catch block 1479 | e.printStackTrace(); 1480 | } 1481 | } 1482 | 1483 | /** 1484 | * @author 武新宇 1485 | * @deprecated 多线程的类,实现了Runnable接口的类 1486 | */ 1487 | class Receive implements Runnable { 1488 | @Override 1489 | public void run() { 1490 | try { 1491 | while (isConn) { 1492 | DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); 1493 | String str = dataInputStream.readUTF(); 1494 | //通讯消息 1495 | ta.append(str); 1496 | } 1497 | } catch (SocketException e) { 1498 | System.out.println("服务器意外终止了!"); 1499 | ta.append("服务器意外终止了!"); 1500 | } catch (IOException e) { 1501 | // TODO Auto-generated catch block 1502 | e.printStackTrace(); 1503 | } 1504 | } 1505 | } 1506 | } 1507 | ``` 1508 | 1509 | ![image-20210302235701653](https://typora-wenjiuzhou.oss-cn-beijing.aliyuncs.com/20210302235701.png) --------------------------------------------------------------------------------