使用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();
}
}
推荐阅读
- java|java socket 服务端_Java通过 Socket 实现 TCP服务端
- spring|SpringBoot 2整合SpringSecurity权限管理(一)SpringSecurity介绍及简单搭建
- 使用Minio遇见的一些坑
- #|性能测试_Day_05(jmeter函数助手、json断言、beanshell、参数化)
- java|SpringBoot从入门到入门学习笔记
- spring|SpringBoot 集成minio MinioClient无法依赖问题-已解决
- MyBatis|Spring Boot(十)(Druid的监控统计和多数据源配置)
- java|EasyExcel 在java中实现从数据库导入导出
- Java基础与算法|Linux学习(二)---实操篇1远程登录