Java聊天室
使用了多线程处理发送消息与接受消息,发送消息与接受消息互不干扰
接受消息类
AcceptMsg: 用于接收消息,扩展了Thread类。它在构造函数中接受一个Socket对象,并重写Thread类的run()方法
- 在run()方法中,它创建了一个InputStream和一个BufferedReader对象,用于从Socket的输入流中读取消息。
- 然后,它进入一个无限循环,从输入流中连续读取消息,直到收到消息“exit”。如果消息是“退出”,它将打印一条消息,指示用户已离开聊天室并脱离循环。否则,它只是打印收到的消息。
- 如果在读取输入流时抛出IOException,它会检查异常消息是否为“Socket closed”。如果不是,它将打印异常的堆栈跟踪。
- 最后,它使用一个名为closeAll()的实用程序方法关闭所有输入/输出流和Socket对象。
- 总的来说,这个类用于从Socket的输入流中读取消息,并将它们打印到控制台。
代码:
/**
*此类表示一个线程,该线程接受来自套接字连接的消息。
*它扩展了Thread类。
*/
public class AcceptMsg extends Thread {
private final Socket accept;
/**
*构造一个具有给定名称和套接字连接的新AcceptMsg对象。
*@param name线程的名称
*@param accept从中接受消息的套接字连接
*/
public AcceptMsg(String name, Socket accept) {
super(name);
this.accept = accept;
}
/**
*线程的主要逻辑。
*它从套接字连接读取消息并将其打印到控制台。
*如果消息是“退出”,它将打印一条离开消息并中断循环。
*如果发生IOException,它将检查套接字是否已关闭,如果未关闭,则打印堆栈跟踪。
*最后,它关闭输入流、读取器和套接字连接。
*/
@Override
public void run() {
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = accept.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream));
while (true) {
String msg = reader.readLine();
if ("exit".equals(msg)){
System.out.println(getName() + "已经离开聊天室");
break;
}
System.out.println(msg);
}
} catch (IOException e) {
String errMsg = "Socket closed";
if (!errMsg.equals(e.getMessage())){
e.printStackTrace();
}
} finally {
Utils.closeAll(inputStream, reader,accept);
}
}
}
发送消息类
SendMsg: 用于发送消息,通过套接字连接发送消息的线程。
- 构造函数接受一个名称和一个套接字作为参数。名称用于标识线程,套接字用于建立连接。
- run()方法被重写以定义线程的行为。在方法内部,创建了一个输出流和一个缓冲写入器,用于将消息写入套接字。还创建了一个扫描仪来读取用户输入。
- 线程进入一个while循环,该循环无限期地继续。在循环中,系统会提示用户输入要发送的消息。如果消息是“exit”,
- 线程将检查其名称是否为“李四(中文中“Li-Si”的意思)。如果是,则会打印一条消息,指示线程不能首先退出。
- 否则,“退出”消息将写入套接字,循环将终止。
- 如果消息不是“exit”,线程会将消息及其名称作为对套接字的响应写入。
- 如果发生IOException,线程将检查错误消息是否为“Socket closed”。如果不是,则打印异常。最后,使用名
- 为“closeAll()”的实用程序方法关闭输出流、写入程序和套接字
代码:
/**
*SendMsg类负责通过套接字向服务器发送消息。
*它扩展了Thread类以允许并发执行。
*/
public class SendMsg extends Thread {
private final Socket socket;
/**
*构造一个具有给定名称和套接字的新SendMsg对象。
*@param name线程的名称
*@param socket用于与服务器通信的套接字
*/
public SendMsg(String name, Socket socket) {
super(name);
this.socket = socket;
}
/**
*run方法在线程启动时执行。
*它读取来自用户的消息并将其发送到服务器。
*/
@Override
public void run() {
OutputStream outputStream = null;
BufferedWriter writer = null;
try {
outputStream = socket.getOutputStream();
writer = new BufferedWriter(new OutputStreamWriter(outputStream));
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println(getName() + "发送:");
String msg = scanner.next();
if ("exit".equals(msg)) {
if ("李四".equals(getName())){
System.out.println("你不能先退出");
continue;
}
writer.write(msg);
writer.newLine();
writer.flush();
System.out.println("退出成功!");
break;
}
writer.write(getName() + "回复:" + msg);
writer.newLine();
writer.flush();
}
} catch (IOException e) {
String errMsg = "Socket closed";
if (!errMsg.equals(e.getMessage())){
e.printStackTrace();
}
} finally {
Utils.closeAll(outputStream, writer,socket);
}
}
}
服务器端类
ServerSocketTest: 作为服务器端类
这个类代表ServerSocket的一个测试类,它创建一个ServerSocket并等待连接,一旦建立了连接,它就会发送和接收来自客户端的消息。
public class ServerSocketTest {
/**
*ServerSocketTest类的主要方法。
*它创建一个ServerSocket并等待连接。
*一旦建立了连接,它就会发送和接收来自客户端的消息。
*
*@param args命令行参数。
*@throws IOException如果在等待连接或发送/接收消息时发生I/O错误。
*/
public static void main(String[] args) throws IOException {
System.out.println("==============ServerSocket端=============");
//在端口8095上创建ServerSocke
ServerSocket serverSocket = new ServerSocket(8095);
System.out.println("等待链接...");
//等待连接
Socket accept = serverSocket.accept();
System.out.println("链接成功");
//获取套接字的输入和输出流
InputStream inputStream = accept.getInputStream();
OutputStream outputStream = accept.getOutputStream();
//定义发送方和接收方的名称
String sendName = "李四";
String acceptName = "张三";
byte[] bytes = new byte[1024];
//从客户端读取消息
int len = inputStream.read(bytes);
System.out.println(new String(bytes, 0, len));
//向客户端发送消息
outputStream.write(("你好,Socket端,我是" + sendName).getBytes());
//启动用于接收和发送消息的线程
new AcceptMsg(acceptName,accept).start();
new SendMsg(sendName, accept).start();
}
}
客户端类
SocketTest: 作为客户端类
- 该程序在端口8095建立到本地机器上运行的服务器的套接字连接。然后,它创建一个向服务器发送数据的输出流和
- 一个从服务器接收数据的输入流。
- 程序通过将消息写入输出流来向服务器发送消息。它还通过读取输入流从服务器读取响应。
- 此外,该程序还创建了两个线程——一个用于发送消息,另一个用于接收消息。这些线程与主线程同时运行,允许
- 客户端和服务器之间同时进行通信
/**
*这个类表示一个客户端套接字测试程序。
*它与服务器建立套接字连接,并发送和接收消息。
*/
public class SocketTest {
public static void main(String[] args) throws IOException {
System.out.println("==============Socket端=============");
//与服务器建立套接字连接
Socket socket = new Socket("127.0.0.1",8095);
//获取套接字的输出流
OutputStream outputStream = socket.getOutputStream();
//获取套接字的输入流
InputStream inputStream = socket.getInputStream();
//定义发送方和接收方的名称
String sendName = "张三";
String acceptName = "李四";
//向服务器发送消息
outputStream.write(("你好,ServerSocket端,我是" + sendName).getBytes());
byte[] bytes = new byte[1024];
//从服务器读取响应
int len = inputStream.read(bytes);
System.out.println(new String(bytes, 0, len));
//启动用于发送和接收消息的线程
new SendMsg(sendName,socket).start();
new AcceptMsg(acceptName,socket).start();
}
}
工具类
Utils: 关闭资源类,用于关闭所有开通的资源,如:输入与输出流;此类提供用于处理实现Closeable接口的资源的实用程序方法。
closeAll方法可用于同时关闭多个资源:
- 如果资源不为null,则将使用close方法关闭该资源。
- 如果在关闭资源时发生IOException,则异常将打印到标准错误流中。
/**
*此类提供了用于关闭Closeable对象的实用方法。
*/
public class Utils {
/**
*关闭所有提供的Closeable对象。
*@param关闭要关闭的Closeable对象数组
*/
public static void closeAll(Closeable... close){
for (Closeable c : close) {
if (c != null){
try {
c.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
运行效果
客户端:
服务器端: