在上一篇,利用线程使服务端实现了能够接收多客户端请求的功能,这里便需要客户端接收多客户端消息的同时还能把消息转发到每个连接的客户端,并且客户端要能在内容显示区域显示出来,从而实现简单的在线群聊。
在实现客户端转发,无非就是增加输出流;而之前客户端都只发不收,这里也需要更改客户端达到循环接收服务端消息的目的,因此也需要实现多线程。
在实现这个功能的时候,偶然想起随机生成验证码的功能,于是也灵机一动随机给每个客户端生成一个名字,从而在输出的时候看起来更加像是群聊,不仅有消息输出,还能看到是谁。
实现这些功能之后,基本上就可以几个人同时在线群聊了,因为代码中有main方法,因此可以把服务端和客户端都打成可执行jar包,可参考我的另一篇博文:
使用eclipse创建java程序可执行jar包
之后在桌面双击相应的jar文件启动服务端和客户端即可,不需要再依赖eclipse运行。
修改后的客户端代码如下:
package chat.chat; import java.awt.BorderLayout; import java.awt.Frame; import java.awt.TextArea; import java.awt.TextField; 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.net.UnknownHostException; import java.util.Random; /** * 在线聊天客户端 步骤: 1、生成图形窗口界面轮廓 2、为轮廓添加关闭事件 3、在轮廓中加入输入区域和内容展示区域 4、为输入区域添加回车事件 * 5、建立服务端连接并发送数据 * * @author tuzongxun123 * */ public class ChatClient extends Frame { /** * */ private static final long serialVersionUID = 1L; // 用户输入区域 private TextField tfTxt = new TextField(); // 内容展示区域 private TextArea tarea = new TextArea(); private Socket socket = null; // 数据输出流 private DataOutputStream dataOutputStream = null; // 数据输入流 private DataInputStream dataInputStream = null; private boolean isConnect = false; Thread tReceive = new Thread(new ReceiveThread()); String name = ""; public static void main(String[] args) { ChatClient chatClient = new ChatClient(); chatClient.createName(); chatClient.launcFrame(); } /** * 建立一个简单的图形化窗口 * * @author:tuzongxun * @Title: launcFrame * @param * @return void * @date May 18, 2016 9:57:00 AM * @throws */ public void launcFrame() { setLocation(300, 200); this.setSize(200, 400); add(tfTxt, BorderLayout.SOUTH); add(tarea, BorderLayout.NORTH); // 根据窗口里面的布局及组件的preferedSize来确定frame的最佳大小 pack(); // 监听图形界面窗口的关闭事件 this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { System.exit(0); disConnect(); } }); tfTxt.addActionListener(new TFLister()); // 设置窗口可见 setVisible(true); connect(); // 启动接受消息的线程 tReceive.start(); } /** * 连接服务器 * * @author:tuzongxun * @Title: connect * @param * @return void * @date May 18, 2016 9:56:49 AM * @throws */ public void connect() { try { // 新建服务端连接 socket = new Socket("127.0.0.1", 8888); // 获取客户端输出流 dataOutputStream = new DataOutputStream(socket.getOutputStream()); dataInputStream = new DataInputStream(socket.getInputStream()); System.out.println("连上服务端"); isConnect = true; } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } // 生成随机的客户端名字 public void createName() { String[] str1 = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" }; Random ran = new Random(); for (int i = 0; i < 6; i++) { // long num = Math.round(Math.random() * (str1.length - 0) + 0); // int n = (int) num; int n = ran.nextInt(str1.length); if (n < str1.length) { String str = str1[n]; name = name + str; System.out.println(name); } else { i--; continue; } } this.setTitle(name); } /** * 关闭客户端资源 * * @author:tuzongxun * @Title: disConnect * @param * @return void * @date May 18, 2016 9:57:46 AM * @throws */ public void disConnect() { try { isConnect = false; // 停止线程 tReceive.join(); } catch (InterruptedException e) { e.printStackTrace(); } finally { try { if (dataOutputStream != null) { dataOutputStream.close(); } if (socket != null) { socket.close(); socket = null; } } catch (IOException e) { e.printStackTrace(); } } } /** * 向服务端发送消息 * * @author:tuzongxun * @Title: sendMessage * @param @param text * @return void * @date May 18, 2016 9:57:56 AM * @throws */ private void sendMessage(String text) { try { dataOutputStream.writeUTF(name + ":" + text); dataOutputStream.flush(); } catch (IOException e1) { e1.printStackTrace(); } } /** * 图形窗口输入区域监听回车事件 * * @author tuzongxun123 * */ private class TFLister implements ActionListener { @Override public void actionPerformed(ActionEvent e) { String text = tfTxt.getText().trim(); // 清空输入区域信息 tfTxt.setText(""); // 回车后发送数据到服务器 sendMessage(text); } } private class ReceiveThread implements Runnable { @Override public void run() { try { while (isConnect) { String message = dataInputStream.readUTF(); System.out.println(message); String txt = tarea.getText(); if (txt != null && !"".equals(txt.trim())) { message = tarea.getText() + "\n" + message; } tarea.setText(message); } } catch (IOException e) { e.printStackTrace(); } } } }
修改后的服务端代码如下:
package chat.chat; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.net.BindException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.ArrayList; import java.util.List; /** * java使用socket和awt组件以及多线程简单实现在线聊天功能服务端 : * 实现服务端把接收到的客户端信息转发到所有连接的客户端,并且让客户端读取到这些信息并显示在内容显示区域中。 * * @author tuzongxun123 * */ public class ChatServer { public static void main(String[] args) { new ChatServer().start(); } // 是否成功启动服务端 private boolean isStart = false; // 服务端socket private ServerSocket ss = null; // 客户端socket private Socket socket = null; // 保存客户端集合 List<Client> clients = new ArrayList<Client>(); public void start() { try { // 启动服务器 ss = new ServerSocket(8888); } catch (BindException e) { System.out.println("端口已在使用中"); // 关闭程序 System.exit(0); } catch (Exception e) { e.printStackTrace(); } try { isStart = true; while (isStart) { // 启动监听 socket = ss.accept(); System.out.println("one client connect"); // 启动客户端线程 Client client = new Client(socket); new Thread(client).start(); clients.add(client); } } catch (Exception e) { e.printStackTrace(); } finally { // 关闭服务 try { ss.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 客户端线程 * * @author tuzongxun123 * */ private class Client implements Runnable { // 客户端socket private Socket socket = null; // 客户端输入流 private DataInputStream dataInputStream = null; // 客户端输出流 private DataOutputStream dataOutputStream = null; private boolean isConnect = false; public Client(Socket socket) { this.socket = socket; try { isConnect = true; // 获取客户端输入流 dataInputStream = new DataInputStream(socket.getInputStream()); // 获取客户端输出流 dataOutputStream = new DataOutputStream( socket.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } } /** * 向客户端群发(转发)数据 * * @author:tuzongxun * @Title: sendMessageToClients * @param @param message * @return void * @date May 18, 2016 11:28:10 AM * @throws */ public void sendMessageToClients(String message) { try { dataOutputStream.writeUTF(message); } catch (SocketException e) { } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { isConnect = true; Client c = null; try { while (isConnect) { // 读取客户端传递的数据 String message = dataInputStream.readUTF(); System.out.println("客户端说:" + message); for (int i = 0; i < clients.size(); i++) { c = clients.get(i); c.sendMessageToClients(message); } } } catch (EOFException e) { System.out.println("client closed!"); } catch (SocketException e) { if (c != null) { clients.remove(c); } System.out.println("Client is Closed!!!!"); } catch (Exception e) { e.printStackTrace(); } finally { // 关闭相关资源 try { if (dataInputStream != null) { dataInputStream.close(); } if (socket != null) { socket.close(); socket = null; } } catch (IOException e) { e.printStackTrace(); } } } } }