Java Socket 是一种用于在网络上进行通信的机制,可以实现客户端和服务器之间的数据传输。在简易多人聊天室中,使用 Java Socket 实现聊天内容或文件的传输的原理如下:
-
服务器端启动:聊天室的服务器端在指定的端口上监听客户端的连接。它创建一个 ServerSocket 对象,并通过调用
accept()
方法等待客户端的连接请求。 -
客户端连接:聊天室的客户端通过创建一个 Socket 对象并指定服务器的地址和端口,与服务器建立连接。客户端和服务器之间的连接建立后,可以进行数据传输。
-
服务器端接收消息:当客户端连接到服务器后,服务器会为每个客户端创建一个独立的线程,用于处理与该客户端的通信。在服务器端的线程中,通过调用输入流的
readLine()
方法读取客户端发送的消息。服务器可以根据接收到的消息类型(例如普通消息或文件传输请求),采取相应的处理逻辑。 -
服务器端广播消息:服务器在接收到客户端发送的消息后,可以将该消息广播给其他连接到服务器的客户端。通过遍历保存所有客户端的列表,并调用相应客户端的输出流发送消息给客户端。
-
客户端发送消息:客户端可以通过输出流向服务器发送消息。客户端将消息写入输出流,然后通过网络发送给服务器。
-
客户端接收消息:客户端在一个独立的线程中通过输入流不断监听服务器端的消息。通过调用输入流的
readLine()
方法读取服务器端发送的消息,并进行相应的处理,如将消息显示在用户界面上。 -
文件传输:客户端可以通过输入流读取文件内容,并通过输出流将文件数据发送给服务器。服务器在接收到文件数据后,可以将文件广播给其他客户端。
以上就是使用 Java Socket 实现简易多人聊天室传输聊天内容或文件的基本原理。通过建立客户端和服务器之间的连接,以及使用输入流和输出流进行数据的读写,可以实现实时的聊天和文件传输功能。
---------------------------------------------------------------------------------------------------------------------------------
项目结构如下
Server类
import java.io.*;
import java.net.*;
import java.util.*;
public class ChatServer {
private static final int PORT = 12345;
private List<ChatClientHandler> clients;
public ChatServer() {
clients = new ArrayList<>();
}
public void start() {
try {
ServerSocket serverSocket = new ServerSocket(PORT);
System.out.println("Server started on port " + PORT);
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("New client connected");
ChatClientHandler handler = new ChatClientHandler(clientSocket, this);
clients.add(handler);
new Thread(handler).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void broadcastMessage(String message, ChatClientHandler sender) {
for (ChatClientHandler client : clients) {
if (client != sender) {
client.sendMessage(message);
}
}
}
public void broadcastFile(String fileName, byte[] fileData, ChatClientHandler sender) {
for (ChatClientHandler client : clients) {
if (client != sender) {
client.sendFile(fileName, fileData);
}
}
}
public void removeClient(ChatClientHandler client) {
clients.remove(client);
System.out.println("Client disconnected");
}
public static void main(String[] args) {
ChatServer server = new ChatServer();
server.start();
}
}
import java.io.*;
import java.net.*;
import java.util.*;
public class ChatServer {
private static final int PORT = 12345;
private List<ChatClientHandler> clients;
public ChatServer() {
clients = new ArrayList<>();
}
public void start() {
try {
ServerSocket serverSocket = new ServerSocket(PORT);
System.out.println("Server started on port " + PORT);
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("New client connected");
ChatClientHandler handler = new ChatClientHandler(clientSocket, this);
clients.add(handler);
new Thread(handler).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void broadcastMessage(String message, ChatClientHandler sender) {
for (ChatClientHandler client : clients) {
if (client != sender) {
client.sendMessage(message);
}
}
}
public void broadcastFile(String fileName, byte[] fileData, ChatClientHandler sender) {
for (ChatClientHandler client : clients) {
if (client != sender) {
client.sendFile(fileName, fileData);
}
}
}
public void removeClient(ChatClientHandler client) {
clients.remove(client);
System.out.println("Client disconnected");
}
public static void main(String[] args) {
ChatServer server = new ChatServer();
server.start();
}
}
import
语句导入了所需的Java类和接口。ChatServer
类是服务器端的主类。PORT
常量指定了服务器将要监听的端口号。clients
是一个存储ChatClientHandler
对象的列表,用于跟踪连接到服务器的客户端。start()
方法启动服务器的主要逻辑。- 创建一个
ServerSocket
对象,绑定到指定的端口,并开始监听客户端连接请求。 serverSocket.accept()
方法等待客户端连接并返回一个Socket
对象表示与客户端的通信。- 为每个客户端连接创建一个
ChatClientHandler
对象,并将其添加到clients
列表中。 - 启动一个新的线程来处理每个客户端连接的消息传输。
broadcastMessage()
方法用于向所有连接的客户端广播消息,但不发送给消息的发送者。- 遍历
clients
列表中的每个ChatClientHandler
对象,并调用其sendMessage()
方法发送消息。 broadcastFile()
方法用于向所有连接的客户端广播文件内容,但不发送给文件的发送者。- 遍历
clients
列表中的每个ChatClientHandler
对象,并调用其sendFile()
方法发送文件内容。 removeClient()
方法从clients
列表中移除指定的ChatClientHandler
对象,表示该客户端已断开连接。
这个类是 ChatServer 中的一个线程类,用于处理与客户端的通信。它实现了 Runnable 接口,通过在独立的线程中运行来处理客户端的消息。
ChatClientHandler 类的作用是与客户端建立连接并处理与客户端的通信,包括接收和发送消息以及接收和发送文件。通过该类,服务器可以与多个客户端同时进行通信并实现聊天室的功能。
ChatClientHandler 类
package com.qqcc.server;
/**
* @author Scyth1
* @create 2023/7/17 16:28
*/
import java.io.*;
import java.net.*;
import java.util.*;
public class ChatClientHandler implements Runnable {
private Socket clientSocket;
private BufferedReader reader;
private PrintWriter writer;
private ChatServer server;
public ChatClientHandler(Socket clientSocket, ChatServer server) {
this.clientSocket = clientSocket;
this.server = server;
try {
reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
writer = new PrintWriter(clientSocket.getOutputStream(), true);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
String message;
while ((message = reader.readLine()) != null) {
if (message.startsWith("/file ")) {
String fileName = message.substring(6);
int fileSize = Integer.parseInt(reader.readLine());
byte[] fileData = new byte[fileSize];
// 使用 DataInputStream 读取字节数据
DataInputStream dis = new DataInputStream(clientSocket.getInputStream());
dis.readFully(fileData);
server.broadcastFile(fileName, fileData, this);
} else {
server.broadcastMessage(message, this);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
server.removeClient(this);
close();
}
}
public void sendMessage(String message) {
writer.println(message);
}
public void sendFile(String fileName, byte[] fileData) {
writer.println("/file " + fileName);
writer.println(fileData.length);
try {
// 使用 DataOutputStream 发送字节数据
DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());
dos.write(fileData, 0, fileData.length);
dos.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public void close() {
try {
if (reader != null) {
reader.close();
}
if (writer != null) {
writer.close();
}
if (clientSocket != null) {
clientSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
ChatClient类(客户端)
import java.io.*;
import java.net.*;
import java.util.*;
public class ChatClient {
private static final String SERVER_HOST = "localhost";
private static final int SERVER_PORT = 12345;
public static void main(String[] args) {
try {
Socket socket = new Socket(SERVER_HOST, SERVER_PORT);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
// 创建并启动接收消息的线程
Thread receiverThread = new Thread(new MessageReceiver(reader));
receiverThread.start();
// 读取用户输入并发送消息或文件给服务器
BufferedReader userInputReader = new BufferedReader(new InputStreamReader(System.in));
String userInput;
while ((userInput = userInputReader.readLine()) != null) {
if (userInput.startsWith("/sendfile ")) {
String filePath = userInput.substring(10);
sendFile(filePath, writer);
} else {
writer.println(userInput);
}
}
// 关闭资源
writer.close();
reader.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void sendFile(String filePath, PrintWriter writer) {
try {
File file = new File(filePath);
String fileName = file.getName();
FileInputStream fileInputStream = new FileInputStream(file);
byte[] fileData = new byte[(int) file.length()];
fileInputStream.read(fileData);
writer.println("/file " + fileName);
writer.flush();
writer.println(fileData.length);
writer.flush();
writer.println(Base64.getEncoder().encodeToString(fileData));
writer.flush();
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static class MessageReceiver implements Runnable {
private BufferedReader reader;
public MessageReceiver(BufferedReader reader) {
this.reader = reader;
}
@Override
public void run() {
try {
String message;
while ((message = reader.readLine()) != null) {
if (message.startsWith("/file ")) {
String fileName = message.substring(6);
receiveFile(fileName, reader);
} else {
System.out.println(message);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void receiveFile(String fileName, BufferedReader reader) {
try {
int fileSize = Integer.parseInt(reader.readLine());
String fileData = reader.readLine();
byte[] fileBytes = Base64.getDecoder().decode(fileData);
FileOutputStream fileOutputStream = new FileOutputStream(fileName);
fileOutputStream.write(fileBytes);
fileOutputStream.close();
System.out.println("Received file: " + fileName);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
import
语句导入了所需的Java类和接口。ChatClient
类是客户端的主类。SERVER_HOST
和SERVER_PORT
常量分别指定服务器的主机名和端口号。main()
方法是程序的入口点。- 创建一个接收消息的线程,并通过
Thread
类进行管理和启动。 MessageReceiver
类是一个实现Runnable
接口的内部类,用于在单独的线程中接收服务器发送的消息。- 从标准输入读取用户输入的消息。
- 使用
BufferedReader
和InputStreamReader
从标准输入流(System.in)读取用户输入。 - 循环读取用户输入,直到用户输入为
null
(按下Ctrl + D或输入结束标记)。 - 如果用户输入以"/sendfile "开头,则解析文件路径,并调用
sendFile()
方法发送文件给服务器。 - 否则,将用户输入的消息通过
PrintWriter
的println()
方法发送给服务器。 sendFile()
方法用于发送文件给服务器。- 解析文件路径并获取文件名。
- 使用
FileInputStream
读取文件的字节数据。 - 将文件名、文件大小和文件内容依次发送给服务器,通过
PrintWriter
的println()
方法发送。 - 使用
Base64
编码将文件内容转换为字符串形式发送。 MessageReceiver
类实现了Runnable
接口,用于在单独的线程中接收服务器发送的消息。run()
方法是线程的执行逻辑。- 通过
BufferedReader
的readLine()
方法从服务器接收消息。 - 如果接收到的消息以"/file "开头,则解析文件名,并调用
receiveFile()
方法接收文件内容。 - 否则,将接收到的消息打印到控制台。
receiveFile()
方法用于接收文件内容。- 解析文件大小和文件内容,并进行
Base64
解码。 - 使用
FileOutputStream
将文件内容写入到指定文件中。 - 打印接收到的文件名到控制台。
客户端2
package com.qqcc.Client;
/**
* @author Scyth1
* @create 2023/7/17 16:34
*/
import java.io.*;
import java.net.*;
import java.util.*;
public class ChatClient1 {
private static final String SERVER_HOST = "localhost";
private static final int SERVER_PORT = 12345;
public static void main(String[] args) {
try {
Socket socket = new Socket(SERVER_HOST, SERVER_PORT);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
// 创建并启动接收消息的线程
Thread receiverThread = new Thread(new MessageReceiver(reader));
receiverThread.start();
// 读取用户输入并发送消息或文件给服务器
BufferedReader userInputReader = new BufferedReader(new InputStreamReader(System.in));
String userInput;
while ((userInput = userInputReader.readLine()) != null) {
if (userInput.startsWith("/sendfile ")) {
String filePath = userInput.substring(10);
sendFile(filePath, writer);
} else {
writer.println(userInput);
}
}
// 关闭资源
writer.close();
reader.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void sendFile(String filePath, PrintWriter writer) {
try {
File file = new File(filePath);
String fileName = file.getName();
FileInputStream fileInputStream = new FileInputStream(file);
byte[] fileData = new byte[(int) file.length()];
fileInputStream.read(fileData);
writer.println("/file " + fileName);
writer.flush();
writer.println(fileData.length);
writer.flush();
writer.println(Base64.getEncoder().encodeToString(fileData));
writer.flush();
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static class MessageReceiver implements Runnable {
private BufferedReader reader;
public MessageReceiver(BufferedReader reader) {
this.reader = reader;
}
@Override
public void run() {
try {
String message;
while ((message = reader.readLine()) != null) {
System.out.println(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端3
package com.qqcc.Client;
/**
* @author Scyth1
* @create 2023/7/17 16:34
*/
import java.io.*;
import java.net.*;
import java.util.*;
public class ChatClient1 {
private static final String SERVER_HOST = "localhost";
private static final int SERVER_PORT = 12345;
public static void main(String[] args) {
try {
Socket socket = new Socket(SERVER_HOST, SERVER_PORT);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
// 创建并启动接收消息的线程
Thread receiverThread = new Thread(new MessageReceiver(reader));
receiverThread.start();
// 读取用户输入并发送消息或文件给服务器
BufferedReader userInputReader = new BufferedReader(new InputStreamReader(System.in));
String userInput;
while ((userInput = userInputReader.readLine()) != null) {
if (userInput.startsWith("/sendfile ")) {
String filePath = userInput.substring(10);
sendFile(filePath, writer);
} else {
writer.println(userInput);
}
}
// 关闭资源
writer.close();
reader.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void sendFile(String filePath, PrintWriter writer) {
try {
File file = new File(filePath);
String fileName = file.getName();
FileInputStream fileInputStream = new FileInputStream(file);
byte[] fileData = new byte[(int) file.length()];
fileInputStream.read(fileData);
writer.println("/file " + fileName);
writer.flush();
writer.println(fileData.length);
writer.flush();
writer.println(Base64.getEncoder().encodeToString(fileData));
writer.flush();
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static class MessageReceiver implements Runnable {
private BufferedReader reader;
public MessageReceiver(BufferedReader reader) {
this.reader = reader;
}
@Override
public void run() {
try {
String message;
while ((message = reader.readLine()) != null) {
System.out.println(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行结果图