Java Socket 编程实战:3个核心类(InetAddress/URL/Socket)构建简易HTTP服务器

发布时间:2026/7/11 8:38:37

Java Socket 编程实战:3个核心类(InetAddress/URL/Socket)构建简易HTTP服务器 Java Socket 编程实战构建简易HTTP服务器的3个核心类解析在当今互联网时代HTTP服务器作为Web应用的基础设施理解其底层实现原理对开发者至关重要。本文将带您深入Java网络编程核心通过InetAddress、URL和Socket三个关键类从零构建一个功能完整的简易HTTP服务器。这个服务器不仅能解析HTTP请求、响应静态文件还支持多线程处理完美适用于教学演示和小型项目实践。1. 网络编程基础与核心类介绍Java网络编程建立在TCP/IP协议栈之上提供了丰富的API来简化开发。在开始构建HTTP服务器前我们需要先掌握三个核心类的使用方法。1.1 InetAddress网络地址处理InetAddress类是Java中用于表示IP地址的核心类它封装了与IP地址相关的所有操作。这个类没有公共构造函数而是通过静态工厂方法获取实例// 获取本地主机地址 InetAddress localHost InetAddress.getLocalHost(); System.out.println(本地主机: localHost); // 通过主机名获取地址 InetAddress googleAddress InetAddress.getByName(www.google.com); System.out.println(Google IP: googleAddress.getHostAddress()); // 获取所有关联地址 InetAddress[] allAddresses InetAddress.getAllByName(www.github.com); for(InetAddress addr : allAddresses) { System.out.println(GitHub IP: addr.getHostAddress()); }关键方法解析getHostName()获取主机名getHostAddress()获取IP地址字符串isReachable(int timeout)测试地址可达性提示InetAddress类还支持IPv6地址可以通过getByName()方法自动识别IPv4和IPv6地址格式。1.2 URL统一资源定位URL类提供了对Web资源的抽象表示可以方便地进行资源定位和内容获取URL url new URL(http://example.com/index.html); System.out.println(协议: url.getProtocol()); System.out.println(主机: url.getHost()); System.out.println(端口: url.getPort()); // 未指定时返回-1 System.out.println(路径: url.getPath()); // 打开连接并读取内容 try (BufferedReader reader new BufferedReader( new InputStreamReader(url.openStream()))) { String line; while ((line reader.readLine()) ! null) { System.out.println(line); } }URL组成部分protocol://host:port/path?query#fragment1.3 Socket网络通信基石Socket类是TCP网络通信的核心它代表了一个通信端点。与ServerSocket配合使用可以实现客户端-服务器模型// 客户端Socket示例 try (Socket clientSocket new Socket(example.com, 80); PrintWriter out new PrintWriter(clientSocket.getOutputStream(), true); BufferedReader in new BufferedReader( new InputStreamReader(clientSocket.getInputStream()))) { out.println(GET / HTTP/1.1); out.println(Host: example.com); out.println(); String responseLine; while ((responseLine in.readLine()) ! null) { System.out.println(responseLine); } }Socket通信基本流程创建Socket/ServerSocket实例打开连接到Socket的输入/输出流按照协议读写数据关闭流和Socket2. HTTP服务器核心实现现在我们将利用上述三个类构建一个简易HTTP服务器。这个服务器需要处理以下核心功能监听指定端口解析HTTP请求响应静态文件支持多线程处理2.1 服务器启动与监听首先创建ServerSocket来监听指定端口public class SimpleHttpServer { private static final int DEFAULT_PORT 8080; private static final String WEB_ROOT System.getProperty(user.dir) File.separator webroot; private final ServerSocket serverSocket; private final ExecutorService threadPool; public SimpleHttpServer(int port) throws IOException { this.serverSocket new ServerSocket(port); this.threadPool Executors.newFixedThreadPool(10); System.out.println(服务器启动监听端口: port); System.out.println(Web根目录: WEB_ROOT); } public void start() { while (true) { try { Socket clientSocket serverSocket.accept(); threadPool.execute(new RequestHandler(clientSocket)); } catch (IOException e) { System.err.println(接受连接错误: e.getMessage()); } } } public static void main(String[] args) throws IOException { int port args.length 0 ? Integer.parseInt(args[0]) : DEFAULT_PORT; new SimpleHttpServer(port).start(); } }关键点说明WEB_ROOT定义了服务器静态文件的根目录使用线程池(ExecutorService)处理并发请求RequestHandler是实际处理HTTP请求的线程类2.2 HTTP请求解析HTTP请求由请求行、请求头和请求体组成。我们需要解析这些内容来确定客户端请求的资源class RequestHandler implements Runnable { private static final Pattern REQUEST_LINE_PATTERN Pattern.compile(^(\\w)\\s(\\S)\\sHTTP/(\\d\\.\\d)$); private final Socket clientSocket; public RequestHandler(Socket socket) { this.clientSocket socket; } Override public void run() { try (BufferedReader in new BufferedReader( new InputStreamReader(clientSocket.getInputStream())); OutputStream out clientSocket.getOutputStream()) { // 解析请求行 String requestLine in.readLine(); if (requestLine null) return; Matcher matcher REQUEST_LINE_PATTERN.matcher(requestLine); if (!matcher.find()) { sendErrorResponse(out, 400, Bad Request); return; } String method matcher.group(1); String path matcher.group(2); String version matcher.group(3); // 解析请求头 MapString, String headers new HashMap(); String headerLine; while (!(headerLine in.readLine()).isEmpty()) { int separator headerLine.indexOf(:); if (separator 0) { String key headerLine.substring(0, separator).trim(); String value headerLine.substring(separator 1).trim(); headers.put(key, value); } } // 处理请求 handleRequest(out, method, path); } catch (IOException e) { System.err.println(处理请求错误: e.getMessage()); } finally { try { clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } // 其他方法... }HTTP请求示例GET /index.html HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 Accept: text/html2.3 静态文件响应根据解析出的请求路径我们需要从文件系统读取相应文件并构造HTTP响应private void handleRequest(OutputStream out, String method, String path) throws IOException { if (!GET.equalsIgnoreCase(method)) { sendErrorResponse(out, 501, Not Implemented); return; } // 安全处理路径防止目录遍历攻击 String safePath path.replaceAll(/\\.\\./, /).replaceAll(^\\.\\./, ); File file new File(SimpleHttpServer.WEB_ROOT, safePath); if (!file.exists() || file.isDirectory()) { sendErrorResponse(out, 404, Not Found); return; } // 确定内容类型 String contentType text/plain; if (file.getName().endsWith(.html)) { contentType text/html; } else if (file.getName().endsWith(.jpg) || file.getName().endsWith(.jpeg)) { contentType image/jpeg; } else if (file.getName().endsWith(.png)) { contentType image/png; } // 发送响应头 String header HTTP/1.1 200 OK\r\n Content-Type: contentType \r\n Content-Length: file.length() \r\n Connection: close\r\n\r\n; out.write(header.getBytes()); // 发送文件内容 try (FileInputStream fis new FileInputStream(file)) { byte[] buffer new byte[4096]; int bytesRead; while ((bytesRead fis.read(buffer)) ! -1) { out.write(buffer, 0, bytesRead); } } } private void sendErrorResponse(OutputStream out, int statusCode, String statusText) throws IOException { String response HTTP/1.1 statusCode statusText \r\n Content-Type: text/html\r\n Connection: close\r\n\r\n htmlbodyh1 statusCode statusText /h1/body/html; out.write(response.getBytes()); }安全注意事项路径规范化处理防止../目录遍历攻击检查文件是否存在及是否为普通文件根据文件扩展名设置正确的Content-Type正确计算并发送Content-Length3. 高级功能与性能优化基础HTTP服务器实现后我们可以进一步扩展其功能和性能。3.1 多线程优化虽然我们已经使用了线程池但还可以进一步优化// 在SimpleHttpServer类中改进线程池初始化 this.threadPool new ThreadPoolExecutor( 4, // 核心线程数 16, // 最大线程数 60, TimeUnit.SECONDS, // 空闲线程存活时间 new LinkedBlockingQueue(100), // 任务队列 new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 );线程池参数选择建议参数建议值说明核心线程数CPU核心数充分利用CPU资源最大线程数核心数*2~4处理突发请求空闲时间30-60秒平衡资源占用与响应速度队列容量50-200根据内存和预期负载调整3.2 连接管理与Keep-AliveHTTP/1.1默认支持持久连接(Keep-Alive)我们可以改进实现// 在RequestHandler中处理Connection头 boolean keepAlive keep-alive.equalsIgnoreCase(headers.get(Connection)) || (HTTP/1.1.equals(version) !close.equalsIgnoreCase(headers.get(Connection))); String connectionHeader keepAlive ? Connection: keep-alive\r\n : Connection: close\r\n; // 在响应头中加入 String header HTTP/1.1 200 OK\r\n Content-Type: contentType \r\n Content-Length: file.length() \r\n connectionHeader \r\n;3.3 支持HTTP方法扩展目前只支持GET方法我们可以扩展支持更多HTTP方法private void handleRequest(OutputStream out, String method, String path, MapString, String headers) throws IOException { switch (method.toUpperCase()) { case GET: handleGetRequest(out, path); break; case HEAD: handleHeadRequest(out, path); break; case POST: handlePostRequest(out, path, in, headers); break; default: sendErrorResponse(out, 501, Not Implemented); } } private void handleHeadRequest(OutputStream out, String path) throws IOException { // 类似GET但不发送实体主体 File file resolveFile(path); if (file null) { sendErrorResponse(out, 404, Not Found); return; } String contentType getContentType(file); String header HTTP/1.1 200 OK\r\n Content-Type: contentType \r\n Content-Length: file.length() \r\n Connection: close\r\n\r\n; out.write(header.getBytes()); }3.4 性能测试与调优使用Apache Benchmark工具测试服务器性能ab -n 1000 -c 50 http://localhost:8080/index.html常见性能瓶颈及解决方案文件IO瓶颈使用NIO的FileChannel替代传统IO对小文件使用内存缓存线程竞争使用ConcurrentHashMap替代HashMap存储常用资源考虑使用异步IO模型内存使用合理设置缓冲区大小(通常8KB-32KB)及时关闭资源避免内存泄漏4. 完整代码示例与部署将所有部分整合以下是完整的HTTP服务器实现import java.io.*; import java.net.*; import java.util.*; import java.util.concurrent.*; import java.util.regex.*; public class SimpleHttpServer { private static final int DEFAULT_PORT 8080; private static final String WEB_ROOT System.getProperty(user.dir) File.separator webroot; private final ServerSocket serverSocket; private final ExecutorService threadPool; public SimpleHttpServer(int port) throws IOException { this.serverSocket new ServerSocket(port); this.threadPool Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); createWebRootIfNotExists(); } private void createWebRootIfNotExists() { File webRoot new File(WEB_ROOT); if (!webRoot.exists()) { webRoot.mkdir(); // 创建示例文件 try (PrintWriter writer new PrintWriter(new File(webRoot, index.html))) { writer.println(htmlbodyh1Welcome to Simple HTTP Server/h1/body/html); } catch (FileNotFoundException e) { e.printStackTrace(); } } } public void start() { System.out.println(Server started on port serverSocket.getLocalPort()); System.out.println(Web root: WEB_ROOT); while (!Thread.interrupted()) { try { Socket clientSocket serverSocket.accept(); threadPool.execute(new RequestHandler(clientSocket)); } catch (IOException e) { System.err.println(Error accepting connection: e.getMessage()); } } } private static class RequestHandler implements Runnable { private static final Pattern REQUEST_LINE_PATTERN Pattern.compile(^(\\w)\\s(\\S)\\sHTTP/(\\d\\.\\d)$); private final Socket clientSocket; RequestHandler(Socket socket) { this.clientSocket socket; } Override public void run() { long startTime System.currentTimeMillis(); String clientAddress clientSocket.getInetAddress().getHostAddress(); try (BufferedReader in new BufferedReader( new InputStreamReader(clientSocket.getInputStream())); OutputStream out clientSocket.getOutputStream()) { // 解析请求行 String requestLine in.readLine(); if (requestLine null) return; Matcher matcher REQUEST_LINE_PATTERN.matcher(requestLine); if (!matcher.find()) { sendErrorResponse(out, 400, Bad Request); return; } String method matcher.group(1); String path matcher.group(2); String version matcher.group(3); // 记录请求日志 System.out.printf([%s] %s %s%n, clientAddress, method, path); // 解析请求头 MapString, String headers new HashMap(); String headerLine; while ((headerLine in.readLine()) ! null !headerLine.isEmpty()) { int separator headerLine.indexOf(:); if (separator 0) { String key headerLine.substring(0, separator).trim(); String value headerLine.substring(separator 1).trim(); headers.put(key, value); } } // 处理请求 handleRequest(out, method, path, headers, version); } catch (IOException e) { System.err.println(Error handling request from clientAddress : e.getMessage()); } finally { try { clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } long duration System.currentTimeMillis() - startTime; System.out.printf([%s] Request processed in %d ms%n, clientAddress, duration); } } private void handleRequest(OutputStream out, String method, String path, MapString, String headers, String version) throws IOException { if (!GET.equalsIgnoreCase(method)) { sendErrorResponse(out, 501, Not Implemented); return; } // 安全处理路径 String safePath path.replaceAll(/\\.\\./, /).replaceAll(^\\.\\./, ); if (safePath.endsWith(/)) { safePath index.html; } File file new File(SimpleHttpServer.WEB_ROOT, safePath); if (!file.exists() || file.isDirectory()) { sendErrorResponse(out, 404, Not Found); return; } // 确定内容类型 String contentType getContentType(file); // 检查If-Modified-Since头 if (headers.containsKey(If-Modified-Since)) { long ifModifiedSince parseHttpDate(headers.get(If-Modified-Since)); if (file.lastModified() / 1000 ifModifiedSince / 1000) { sendNotModifiedResponse(out, version); return; } } // 发送响应头 String header buildResponseHeader(version, contentType, file.length(), file.lastModified(), headers); out.write(header.getBytes()); // 发送文件内容 try (FileInputStream fis new FileInputStream(file)) { byte[] buffer new byte[8192]; int bytesRead; while ((bytesRead fis.read(buffer)) ! -1) { out.write(buffer, 0, bytesRead); } } } private String getContentType(File file) { String fileName file.getName().toLowerCase(); if (fileName.endsWith(.html) || fileName.endsWith(.htm)) { return text/html; } else if (fileName.endsWith(.jpg) || fileName.endsWith(.jpeg)) { return image/jpeg; } else if (fileName.endsWith(.png)) { return image/png; } else if (fileName.endsWith(.css)) { return text/css; } else if (fileName.endsWith(.js)) { return application/javascript; } else { return text/plain; } } private String buildResponseHeader(String version, String contentType, long contentLength, long lastModified, MapString, String headers) { boolean keepAlive HTTP/1.1.equals(version) !close.equalsIgnoreCase(headers.get(Connection)); return HTTP/1.1 200 OK\r\n Content-Type: contentType \r\n Content-Length: contentLength \r\n Last-Modified: formatHttpDate(lastModified) \r\n Connection: (keepAlive ? keep-alive : close) \r\n\r\n; } private void sendErrorResponse(OutputStream out, int statusCode, String statusText) throws IOException { String html htmlbodyh1 statusCode statusText /h1/body/html; String response HTTP/1.1 statusCode statusText \r\n Content-Type: text/html\r\n Content-Length: html.length() \r\n Connection: close\r\n\r\n html; out.write(response.getBytes()); } private void sendNotModifiedResponse(OutputStream out, String version) throws IOException { String response version 304 Not Modified\r\n Connection: close\r\n\r\n; out.write(response.getBytes()); } private long parseHttpDate(String dateStr) { // 简化实现实际应按照HTTP日期格式解析 try { return new Date(dateStr).getTime(); } catch (Exception e) { return 0; } } private String formatHttpDate(long timestamp) { // 简化实现实际应按照HTTP日期格式格式化 return new Date(timestamp).toString(); } } public static void main(String[] args) throws IOException { int port args.length 0 ? Integer.parseInt(args[0]) : DEFAULT_PORT; SimpleHttpServer server new SimpleHttpServer(port); server.start(); } }部署步骤编译代码javac SimpleHttpServer.java创建webroot目录并添加测试文件运行服务器java SimpleHttpServer 8080浏览器访问http://localhost:8080/index.html5. 扩展思路与实际应用基础HTTP服务器实现后可以考虑以下扩展方向5.1 支持HTTPS安全连接通过Java的SSL/TLS支持添加HTTPS功能// 创建SSLServerSocket SSLServerSocketFactory sslServerSocketFactory (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); SSLServerSocket sslServerSocket (SSLServerSocket) sslServerSocketFactory.createServerSocket(8443); // 配置支持的加密套件 sslServerSocket.setEnabledCipherSuites( sslServerSocket.getSupportedCipherSuites());5.2 实现动态内容处理通过Servlet-like接口支持动态内容public interface SimpleServlet { void service(InputStream in, OutputStream out, MapString, String headers); } // 在RequestHandler中添加 if (path.startsWith(/servlet/)) { String servletName path.substring(/servlet/.length()); SimpleServlet servlet servletRegistry.get(servletName); if (servlet ! null) { servlet.service(clientSocket.getInputStream(), out, headers); return; } }5.3 添加管理接口实现简单的管理接口查看服务器状态// 添加特殊路径处理 if (/server-status.equals(path)) { String status getServerStatus(); String response HTTP/1.1 200 OK\r\n Content-Type: text/plain\r\n Content-Length: status.length() \r\n\r\n status; out.write(response.getBytes()); return; } private String getServerStatus() { return Server Status:\n Uptime: (System.currentTimeMillis() - startTime) ms\n Active Threads: threadPool.getActiveCount() \n Total Requests: totalRequests; }5.4 性能监控与日志添加详细的性能监控和请求日志// 使用JMX暴露监控指标 public class ServerStats implements ServerStatsMBean { private long requestCount; private long errorCount; private long totalBytesSent; public void incrementRequestCount() { requestCount; } public void incrementErrorCount() { errorCount; } public void addBytesSent(long bytes) { totalBytesSent bytes; } // Getter方法... } // 在RequestHandler中记录指标 stats.incrementRequestCount(); stats.addBytesSent(file.length());通过本文的实现您已经掌握了使用Java Socket API构建HTTP服务器的核心技能。这个简易服务器虽然功能有限但涵盖了网络编程的关键概念为进一步开发更复杂的网络应用奠定了坚实基础。

相关新闻