目录
1.前言
哈喽大家好,今天继续进行计算机网络的初阶学习,今天学习的是tcp回显服务器的实现,正文开始
2.正文
在正式讲解Tcp回显服务器,还要介绍两个包,一个是ServerSocket包,这个包是专门给服务器用的,Socket包是服务器和客户端都会用,下文详解。
2.1ServerSocket类
Java中的ServerSocket
类是用于在服务器端监听客户端连接请求的核心类,属于java.net
包。它允许服务器应用程序在指定端口上等待客户端的连接,并为每个连接创建一个Socket
对象进行通信。
核心作用
监听端口:绑定到特定端口,等待客户端连接。
接受连接:通过
accept()
方法阻塞等待客户端连接,返回代表客户端的Socket
对象。管理连接队列:通过
backlog
参数设置等待连接队列的最大长度。关键方法
构造方法
ServerSocket(int port)
:绑定到指定端口。
ServerSocket(int port, int backlog)
:指定端口和连接队列长度。
ServerSocket(int port, int backlog, InetAddress bindAddr)
:绑定到特定IP地址的端口。核心方法
Socket accept()
:阻塞等待客户端连接,返回连接的Socket
对象。
void bind(SocketAddress endpoint)
:绑定到指定地址和端口。
void close()
:关闭服务器套接字。
int getLocalPort()
:获取监听的端口号。
void setSoTimeout(int timeout)
:设置accept()
的超时时间(毫秒)。使用流程
创建
ServerSocket
并绑定端口。循环调用
accept()
接受客户端连接。为每个连接的
Socket
启动新线程处理请求。处理完成后关闭资源。
2.2Socket类
Java中的Socket
类是用于实现网络通信的核心类,属于java.net
包。它代表客户端与服务器之间的一个连接,允许通过输入流和输出流进行双向数据传输。Socket
类通常与ServerSocket
类配合使用,实现客户端-服务器模型的通信。
核心作用
建立连接:连接到服务器端的指定IP地址和端口。
数据传输:通过输入流(
InputStream
)和输出流(OutputStream
)进行数据交换。关闭连接:释放资源并终止通信。
关键方法
构造方法
Socket(String host, int port)
:连接到指定主机和端口。
Socket(InetAddress address, int port)
:使用InetAddress
对象连接到指定主机和端口。
Socket(String host, int port, InetAddress localAddr, int localPort)
:绑定到本地地址和端口,同时连接到远程主机。核心方法
InputStream getInputStream()
:获取输入流,用于读取服务器发送的数据。
OutputStream getOutputStream()
:获取输出流,用于向服务器发送数据。
void close()
:关闭套接字,释放资源。
void shutdownInput()
:关闭输入流。
void shutdownOutput()
:关闭输出流。
boolean isConnected()
:检查是否已连接。
boolean isClosed()
:检查是否已关闭。使用流程
创建
Socket
对象并连接到服务器。获取输入流和输出流进行数据交换。
处理数据并完成通信。
关闭
Socket
和相关资源。
2.3Tcp回显服务器
2.3.1TcpEchoServer
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* TCP回显服务器实现类
* 功能:接收客户端消息并原样返回(回显)
*/
public class TcpEchoServer {
private ServerSocket serverSocket = null; // 服务器套接字对象
// 构造方法:初始化服务器并绑定端口
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port); // 创建ServerSocket并绑定指定端口
}
/**
* 启动服务器核心逻辑
*/
public void start() throws IOException {
System.out.println("启动服务器");
// 创建线程池(动态调整线程数量,适合短任务)
ExecutorService executorService = Executors.newCachedThreadPool();
// 持续监听客户端连接
while (true) {
// 阻塞等待客户端连接
Socket clientSocket = serverSocket.accept();
// 将客户端连接提交给线程池处理
executorService.submit(() -> {
try {
processConnection(clientSocket); // 处理单个客户端连接
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
/**
* 处理单个客户端连接的完整生命周期
* @param clientSocket 客户端套接字对象
*/
private void processConnection(Socket clientSocket) throws IOException {
// 打印客户端连接信息
System.out.printf("[%s:%d] 客户端上线!\n",
clientSocket.getInetAddress(),
clientSocket.getPort());
// 使用try-with-resources自动关闭流
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
// 使用Scanner和PrintWriter包装流对象
Scanner scanner = new Scanner(inputStream); // 输入流扫描器
PrintWriter printWriter = new PrintWriter(outputStream); // 输出流写入器
// 持续处理客户端请求
while (true) {
// 1. 检测连接状态(若输入流中没有数据,说明客户端断开)
if (!scanner.hasNext()) {
System.out.printf("[%s:%d] 客户端下线!\n",
clientSocket.getInetAddress(),
clientSocket.getPort());
break;
}
// 2. 读取请求并处理
String request = scanner.next(); // 读取客户端请求(空格分隔)
String response = process(request); // 处理请求生成响应
// 3. 返回响应给客户端
printWriter.println(response); // 写入响应
printWriter.flush(); // 强制刷新缓冲区
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
clientSocket.close(); // 确保关闭客户端套接字
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* 处理请求的核心逻辑(示例为简单回显)
* @param request 客户端请求内容
* @return 返回与请求相同的字符串
*/
private String process(String request) {
// 此处可添加业务逻辑(示例直接返回原内容)
return request;
}
/**
* 主方法:启动服务器实例
*/
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090); // 创建服务器实例(端口9090)
tcpEchoServer.start(); // 启动服务器
}
}
核心思路讲解:
整体架构设计:
该代码实现了一个多线程TCP回显服务器,核心功能是接收客户端发送的文本消息,并将消息原样返回(回显)。其架构设计遵循经典客户端-服务器模型,核心特点包括:
多线程处理:通过线程池动态分配线程,避免单线程阻塞导致的性能瓶颈。
资源自动管理:利用
try-with-resources
确保流和套接字的自动释放。松耦合设计:将连接处理(
processConnection
)与业务逻辑(process
)分离,便于扩展。
核心组件解析:
(1) ServerSocket 的职责
端口监听:绑定到指定端口(如9090),通过
accept()
阻塞等待客户端连接。连接队列管理:默认使用操作系统提供的连接队列(通过
backlog
参数可调整队列长度)。生命周期控制:服务器运行时持续监听,直到进程终止(代码中未实现优雅关闭逻辑)。
(2) Socket 连接处理
双向通信:每个客户端连接对应一个
Socket
对象,通过其输入流(InputStream
)和输出流(OutputStream
)实现数据交换。连接状态检测:通过
scanner.hasNext()
判断客户端是否断开(输入流关闭时返回false
)。(3) 线程池的作用
动态资源分配:
Executors.newCachedThreadPool()
创建的线程池会根据任务量自动扩展/收缩:
空闲线程默认存活60秒后被回收。
适合短生命周期任务(如HTTP请求)。
避免线程爆炸:相比为每个连接直接创建
new Thread()
,线程池能有效控制系统资源占用。
3. 关键流程详解:
(1) 启动阶段
初始化
ServerSocket
并绑定端口。创建线程池,进入无限循环等待连接。
(2) 连接处理阶段
接受连接:
accept()
返回客户端Socket
对象。提交任务:将
processConnection
方法包装为任务提交到线程池。处理请求:
通过
Scanner
逐词读取客户端请求(空格分隔)。调用
process()
生成响应(此处简单回显)。通过
PrintWriter
写回响应并强制刷新缓冲区。(3) 连接终止
客户端主动断开:
scanner.hasNext()
检测到输入流结束,跳出循环关闭连接。异常处理:捕获
IOException
并关闭套接字,防止资源泄漏。
2.3.2TcpEchoClient
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
// 定义一个Socket对象,用于与服务器建立连接
private Socket socket = null;
// 构造函数,用于初始化客户端并连接到指定的服务器IP和端口
private TcpEchoClient(String serverIp, int serverPort) throws IOException {
// 创建一个Socket对象,连接到指定的服务器IP和端口
socket = new Socket(serverIp, serverPort);
}
// 启动客户端,开始与服务器进行通信
public void start() {
// 创建一个Scanner对象,用于从控制台读取用户输入
Scanner scanner = new Scanner(System.in);
try (
// 获取Socket的输入流,用于接收服务器发送的数据
InputStream inputStream = socket.getInputStream();
// 获取Socket的输出流,用于向服务器发送数据
OutputStream outputStream = socket.getOutputStream()
) {
// 创建一个Scanner对象,用于从输入流中读取服务器发送的数据
Scanner scannerNet = new Scanner(inputStream);
// 创建一个PrintWriter对象,用于向输出流中写入数据
PrintWriter writer = new PrintWriter(outputStream);
// 进入一个无限循环,持续与服务器进行通信
while (true) {
// 从控制台读取用户输入的数据
String request = scanner.next();
// 将用户输入的数据发送到服务器
writer.println(request);
// 刷新输出流,确保数据被发送
writer.flush();
// 从服务器读取响应数据
String response = scannerNet.next();
// 将服务器返回的响应数据打印到控制台
System.out.println(response);
}
} catch (IOException e) {
// 如果发生IO异常,抛出运行时异常
throw new RuntimeException(e);
}
}
// 主函数,程序的入口
public static void main(String[] args) throws IOException {
// 创建一个TcpEchoClient对象,连接到本地服务器的9090端口
TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
// 启动客户端,开始与服务器通信
client.start();
}
}
核心思路讲解:
Socket连接:
在
TcpEchoClient
的构造函数中,通过new Socket(serverIp, serverPort)
创建一个Socket对象,连接到指定的服务器IP和端口。这个Socket对象将用于后续的通信。输入输出流:
在
start
方法中,通过socket.getInputStream()
和socket.getOutputStream()
分别获取Socket的输入流和输出流。输入流用于接收服务器发送的数据,输出流用于向服务器发送数据。用户输入与服务器通信:
使用
Scanner
从控制台读取用户输入的数据,并通过PrintWriter
将数据发送到服务器。使用
Scanner
从输入流中读取服务器返回的响应数据,并将其打印到控制台。循环通信:
通过一个无限循环
while (true)
,客户端可以持续与服务器进行通信。每次循环中,客户端都会读取用户输入,发送到服务器,并等待服务器的响应。异常处理:
如果在通信过程中发生IO异常,代码会捕获该异常并抛出运行时异常。
主函数:
在
main
函数中,创建一个TcpEchoClient
对象,并连接到本地服务器的9090端口。然后调用start
方法,启动客户端与服务器的通信。
3.小结
今天的分享到这里就结束了,喜欢的小伙伴不要忘记点点赞点个关注,你的鼓励就是对我最大的支持,加油!