JAVA|基于TCP的网络编程——简单聊天程序

使用TCP网络编程可以实现简单的群聊程序【多线程】
通过《基于TCP的网络编程——基本使用》中的案例演示会发现,如果互相通信,客户端和服务器端的【处理数据】部分完全一致,所以完全可以封装一个线程类,将该过程抽出,服务器可以循环监听,处理接入的多个客户端线程。

//线程类(主要用来处理数据) public class ChatThread extends Thread{ private Socket socket; //这里是构建客户端socket,在服务器类中可以循环接收,启动线程 public ChatThread() { } public ChatThread(Socket socket) { this.socket = socket; }//处理数据 @Override public void run() { BufferedReader br = null; //这里方便在最后的finally统一关闭管道 if (null != socket) { try { //try-catch-finally必须统一处理,尽量不要try中嵌套try-catch //接收部分不能放到循环里面,因为这里是服务器与客户端建立连接的地方,一次连接好就可以 //循环体里面仅仅处理流数据 br = new BufferedReader(new InputStreamReader(socket.getInputStream())); while (true) { String str = br.readLine(); if (str.equals("88")) { //打出88,说明客户端退出了 System.out.println(socket.getInetAddress().getHostAddress() + "退出了"); break; } System.out.println(socket.getInetAddress().getHostAddress() + "说:" + str); } } catch (IOException e) { e.printStackTrace(); System.out.println(socket.getInetAddress().getHostAddress() + "异常退出"); } finally { try { br.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }//服务器端 public class ChatServer { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(9990); System.out.println("服务器启动"); try { while (true) { Socket socket = ss.accept(); //循环监听 new ChatThread(socket).start(); //这里的socket不用最后关闭,因为线程中已经关闭了 } } catch (IOException e) { e.printStackTrace(); System.out.println("聊天结束。。"); } finally { ss.close(); } } }//客户端 public class ChatClient { public static void main(String[] args) throws IOException { Socket socket = new Socket(InetAddress.getLocalHost().getHostName(), 9990); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); Scanner input = new Scanner(System.in); while (true) { String str = input.next(); bw.write(str); bw.newLine(); //必须换行,否则无法让服务器读取一行 bw.flush(); if (str.equals("88")) { break; } } input.close(); bw.close(); socket.close(); } }

【JAVA|基于TCP的网络编程——简单聊天程序】这种聊天程序,只适合群聊,如果还要保证私聊,就必须让客户端以某一格式发送信息,服务器接收,判断到有该字符串,就发生转发到目标客户端,并提供发送者的信息。
让服务器作为中继站,实现客户端和客户端之间的通信。
//线程类,处理数据,包括转发 public class ChatThread extends Thread { private Socket socket; //创建线程安全的并发集合 private ConcurrentHashMap, Socket> maps; public ChatThread() { } public ChatThread(Socket socket, ConcurrentHashMap, Socket> maps) { this.socket = socket; this.maps = maps; }@Override public void run() { BufferedReader br = null; if (socket != null) { try { br = new BufferedReader(new InputStreamReader(socket.getInputStream())); while (true) { String data = https://www.it610.com/article/br.readLine(); if (data == null) { System.out.println(socket.getInetAddress().getHostAddress() +"退出了聊天"); break; } System.out.println(socket.getInetAddress().getHostAddress() + "说:" + data); if (data.equals("88")) { System.out.println(socket.getInetAddress().getHostAddress() + "退出了聊天"); }//处理转发 //要判断是否有需要转发的情况 if (data.indexOf(":") < 0) { continue; } //获取ip地址,如果转发,客户的必须按照 “ip地址+ :” 的形式 String ip = data.substring(0, data.indexOf(":")); //判断ip地址合法性的格式 String pattern = "((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(\\.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}"; if (ip != null) { //ip不是空,并且ip地址合法 if (ip.matches(pattern)) { //从集合中找出对应的socket,属于接受者的socket,建立连接 Socket socket = maps.get(ip); if (socket != null) { synchronized (socket) { //客户端和客户端之间的连接必须要保证同步,否则发生多个客户端同时抢占一个客户端临界资源,发生问题 //可以进行转发,建立输出流 //流不可以关闭,否则return退出,报错 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); //内容必须有发送者的信息,这里使用this.socket调取类中sokcet也就是发送者的socket //内容必须是冒号后面的话 bw.write(this.socket.getInetAddress().getHostAddress() + "说:" + data.substring(data.indexOf(":") + 1)); bw.newLine(); bw.flush(); System.out.println(this.socket.getInetAddress().getHostAddress() + "向" + ip + "发送了信息"); } } } } } } catch (IOException e) { e.printStackTrace(); System.out.println(socket.getInetAddress().getHostAddress() + "退出了聊天"); //退出聊天后,将其从集合中移除 maps.remove(socket.getInetAddress().getHostAddress()); } finally { try { br.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }//服务器 public class Server { public static void main(String[] args) throws IOException { ServerSocket listener = new ServerSocket(8880); //创建集合,必须是线程安全的集合,保证客户端和客户端之间的访问的安全,后面put存储管理多个客户端 ConcurrentHashMap, Socket> maps = new ConcurrentHashMap<>(); System.out.println("服务器已启动"); try { while (true) { Socket socket = listener.accept(); System.out.println("连接成功:" + socket.getInetAddress().getHostAddress()); maps.put(socket.getInetAddress().getHostAddress(), socket); new ChatThread(socket, maps).start(); } } catch (IOException e) { System.out.println("聊天室结束了"); } finally { listener.close(); } } }//客户端 public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket(InetAddress.getLocalHost().getHostName(), 8880); //由于客户端会发送也必须能接收,所以必须建立两个线程,发送线程和接收线程 //发送线程 new Thread(new Runnable() { @Override public void run() { BufferedWriter bw = null; Scanner input = new Scanner(System.in); try { //仅仅是建立连接,一次就好 bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); while (true) { String data = https://www.it610.com/article/input.nextLine(); bw.write(data); bw.newLine(); bw.flush(); if (data.equals("88")) { break; } } } catch (IOException e) { e.printStackTrace(); } finally { try { bw.close(); input.close(); // if (!socket.isClosed()) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } } } }).start(); //接收线程 new Thread(new Runnable() { @Override public void run() { BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(socket.getInputStream())); //读取线程一直开启,不关闭 while (true) { String data = https://www.it610.com/article/br.readLine(); System.out.println(data); } } catch (IOException e) { System.out.println("已经退出"); } finally { try { br.close(); //避免socket重复关闭,进行判定 if (!socket.isClosed()) { socket.close(); } } catch (IOException e) { System.out.println("已经退出"); //e.printStackTrace(); } } } }).start(); } }

    推荐阅读