基础知识 域名解析服务器 DNS 负责把域名翻译成对应的 IP,客户端再根据 IP 地址访问服务器。可以使用nslookup
查看域名对应的IP地址。
应用层,提供应用程序之间的通信;
表示层:处理数据格式,加解密等等;
会话层:负责建立和维护会话;
传输层:负责提供端到端的可靠传输;
网络层:负责根据目标地址选择路由来传输数据;
链路层和物理层负责把数据进行分片并且真正通过物理网络传输,例如,无线网、光纤等。
互联网实际使用的 TCP/IP 模型并不是对应到 OSI 的 7 层模型,而是大致对应 OSI 的 5 层模型:
常见协议 IP 协议是一个分组交换,它不保证可靠传输。而 TCP 协议是传输控制协议,它是面向连接的协议,支持可靠传输和双向通信。TCP 协议是建立在 IP 协议之上的,简单地说,IP 协议只负责发数据包,不保证顺序和正确性,而 TCP 协议负责控制数据包传输,它在传输数据之前需要先建立连接,建立连接后才能传输数据,传输完后还需要断开连接。TCP 协议之所以能保证数据的可靠传输,是通过接收确认、超时重传这些机制实现的。并且,TCP 协议允许双向通信,即通信双方可以同时发送和接收数据。
TCP 协议也是应用最广泛的协议,许多高级协议都是建立在 TCP 协议之上的,例如 HTTP、SMTP 等。 TCP(传输控制协议)和 IP(互联网协议)通常一起使用,被称为 TCP/IP 协议。
TCP/IP 协议栈分为四层:应用层、传输层、网络层和链路层。IP 协议位于网络层,负责将数据包从源主机路由到目标主机;TCP 协议位于传输层,负责在源主机和目标主机之间建立可靠的连接并确保数据的有序传输。
UDP 协议(User Datagram Protocol)是一种数据报文协议,它是无连接协议,不保证可靠传输。因为 UDP 协议在通信前不需要建立连接,因此它的传输效率比 TCP 高,而且 UDP 协议比 TCP 协议要简单得多。
Java Socket 套接字(Socket)是一个抽象层,应用程序可以通过它发送或接收数据;就像操作文件那样可以打开、读写和关闭。套接字允许应用程序将 I/O 应用于网络中,并与其他应用程序进行通信。网络套接字是 IP 地址与端口的组合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 try (Socket socket = new Socket ("bbs.newsmth.net" , 23 );) { InputStream is = socket.getInputStream(); Scanner scanner = new Scanner (is, "gbk" ); while (scanner.hasNextLine()) { String line = scanner.nextLine(); System.out.println(line); } } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
1)建立套接字连接非常简单,只需要一行代码:
1 Socket socket = new Socket (host, port)
host 为主机名,port 为端口号(23 为默认的 telnet 端口号)。如果无法确定主机的 IP 地址,则抛出 UnknownHostException
异常;如果在创建套接字时发生 IO 错误,则抛出 IOException
异常。
需要注意的是,套接字在建立的时候,如果远程主机不可访问,这段代码就会阻塞很长时间,直到底层操作系统的限制而抛出异常。所以一般会在套接字建立后设置一个超时时间。
1 2 Socket socket = new Socket (...);socket.setSoTimeout(10000 );
2)套接字连接成功后,可以通过 java.net.Socket
类的 getInputStream()
方法获取输入流。有了 InputStream
对象后,可以借助文本扫描器类(Scanner)将其中的内容打印出来。
1 2 3 4 5 6 7 InputStream is = socket.getInputStream();Scanner scanner = new Scanner (is, "gbk" );while (scanner.hasNextLine()) { String line = scanner.nextLine(); System.out.println(line); }
ServerSocket 实例 接下来,我们模拟一个远程服务,通过 java.net.ServerSocket
实现。代码示例如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 try { ServerSocket server = new ServerSocket (8888 ); Socket socket = server.accept(); InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); Scanner scanner = new Scanner (is)) PrintWriter pw = new PrintWriter (new OutputStreamWriter (os, "gbk" ), true ); pw.println("你好啊,欢迎关注「沉默王二」 公众号,回复关键字「2048」 领取程序员进阶必读资料包" ); boolean done = false ; while (!done && scanner.hasNextLine()) { String line = scanner.nextLine(); System.out.println(line); if ("2048" .equals(line)) { done = true ; } } } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
1)建立服务器端的套接字也比较简单,只需要指定一个能够独占的端口号就可以了(0~1023 这些端口都已经被系统预留了)。
1 ServerSocket server = new ServerSocket (8888 );
2)调用 ServerSocket 对象的 accept()
等待客户端套接字的连接请求。一旦监听到客户端的套接字请求,就会返回一个表示连接已建立的 Socket 对象,可以从中获取到输入流和输出流。
1 2 3 Socket socket = server.accept();InputStream is = socket.getInputStream();OutputStream os = socket.getOutputStream();
客户端套接字发送的所有信息都会包裹在服务器端套接字的输入流中;而服务器端套接字发送的所有信息都会包裹在客户端套接字的输出流中。
3)服务器端可以通过以下代码向客户端发送消息。
1 2 PrintWriter pw = new PrintWriter (new OutputStreamWriter (os, "gbk" ), true );pw.println("你好啊,欢迎关注「沉默王二」 公众号,回复关键字「2048」 领取程序员进阶必读资料包" );
4)服务器端可以通过以下代码读取客户端发送过来的消息。
1 2 3 4 5 6 7 8 9 10 Scanner scanner = new Scanner (is);boolean done = false ;while (!done && scanner.hasNextLine()) { String line = scanner.nextLine(); System.out.println(line); if ("2048" .equals(line)) { done = true ; } }
运行该服务后,可以通过 telnet localhost 8888
命令连接该远程服务,不出所料,你将会看到以下信息。
04、为多个客户端服务 非常遗憾的是,上面的例子中,服务器端只能为一个客户端服务——这不符合服务器端一对多的要求。 优化方案也非常简单(你应该也能想得到):服务器端接收到客户端的套接字请求时,可以启动一个线程来处理,而主程序继续等待下一个连接。代码示例如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 try (ServerSocket server = new ServerSocket (8888 )) { while (true ) { Socket socket = server.accept(); Thread thread = new Thread (new Runnable () { @Override public void run () { } }); thread.start(); } } catch (IOException e) { e.printStackTrace(); }
线程内部(run(){}
方法里)用来处理套接字,代码示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 try { InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); Scanner scanner = new Scanner (is); } catch (IOException e) { e.printStackTrace(); } finally { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } }
服务器端代码优化后重新运行,你就可以通过 telnet 命令测试了。打开一个命令行窗口输入 telnet localhost 8888
,再打开一个新的命令行窗口输入 telnet localhost 8888
,多个窗口都可以和服务器端进行通信,除非服务器端代码中断运行。
05、加入多线程 多线程 我们后面会详细讲,这里就主要是写个例子,好让大家感觉更有趣一些,其实也非常简单。
来看服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import java.io.*;import java.net.ServerSocket;import java.net.Socket;public class MultiThreadedServer { public static void main (String[] args) throws IOException { int port = 12345 ; ServerSocket serverSocket = new ServerSocket (port); System.out.println("Server is listening on port " + port); while (true ) { Socket socket = serverSocket.accept(); System.out.println("Client connected" ); new ClientHandler (socket).start(); } } } class ClientHandler extends Thread { private Socket socket; public ClientHandler (Socket socket) { this .socket = socket; } public void run () { try { InputStream input = socket.getInputStream(); BufferedReader reader = new BufferedReader (new InputStreamReader (input)); OutputStream output = socket.getOutputStream(); PrintWriter writer = new PrintWriter (output, true ); String line; while ((line = reader.readLine()) != null ) { System.out.println("Received: " + line); writer.println("Server: " + line); } socket.close(); } catch (IOException e) { System.out.println("Client disconnected" ); } } }
在这个示例中,我们使用了一个 ClientHandler 类,该类继承自 Thread 类。这使得每个客户端连接都可以在单独的线程中处理,从而允许服务器同时处理多个客户端连接。当一个新客户端连接到服务器时,服务器会创建一个新的 ClientHandler 对象,并使用 start()
方法启动线程。ClientHandler 类的 run()
方法包含处理客户端请求的逻辑。
来看客户端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import java.io.*;import java.net.Socket;public class Client { public static void main (String[] args) throws IOException { String hostname = "localhost" ; int port = 12345 ; Socket socket = new Socket (hostname, port); System.out.println("Connected to the server" ); InputStream input = socket.getInputStream(); BufferedReader reader = new BufferedReader (new InputStreamReader (input)); OutputStream output = socket.getOutputStream(); PrintWriter writer = new PrintWriter (output, true ); writer.println("Hello, server!" ); String response = reader.readLine(); System.out.println("Server response: " + response); socket.close(); } }
08、DatagramSocket 实例 DatagramSocket 类是 Java 中实现 UDP 协议的核心类。与基于 TCP 的 Socket 和 ServerSocket 类不同,DatagramSocket 类提供了无连接的通信服务,发送和接收数据包。由于无需建立连接,UDP 通常比 TCP 更快,但可能不如 TCP 可靠。 以下是一个简单的 DatagramSocket 示例,展示了如何使用 UDP 协议在客户端和服务器之间发送和接收消息。
服务器端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;public class UDPServer { public static void main (String[] args) throws IOException { int port = 12345 ; DatagramSocket serverSocket = new DatagramSocket (port); System.out.println("Server is listening on port " + port); byte [] buffer = new byte [1024 ]; DatagramPacket packet = new DatagramPacket (buffer, buffer.length); serverSocket.receive(packet); String message = new String (packet.getData(), 0 , packet.getLength()); System.out.println("Received: " + message); serverSocket.close(); } }
客户端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import java.io.IOException;import java.net.*;public class UDPClient { public static void main (String[] args) throws IOException { String hostname = "localhost" ; int port = 12345 ; InetAddress address = InetAddress.getByName(hostname); DatagramSocket clientSocket = new DatagramSocket (); String message = "Hello, server!" ; byte [] buffer = message.getBytes(); DatagramPacket packet = new DatagramPacket (buffer, buffer.length, address, port); clientSocket.send(packet); System.out.println("Message sent" ); clientSocket.close(); } }
在这个示例中,服务器端创建一个 DatagramSocket 对象并监听端口 12345。然后,它创建一个 DatagramPacket 对象,用于存储接收到的数据包。serverSocket.receive(packet)
方法阻塞,直到收到一个数据包。收到数据包后,服务器从数据包中提取并打印消息。 客户端首先解析服务器的 IP 地址,然后创建一个 DatagramSocket 对象。接着,客户端创建一个包含要发送消息的 DatagramPacket 对象,并指定目标地址和端口。最后,客户端通过调用 clientSocket.send(packet)
方法发送数据包。
Java Socket实现HTTP服务器 HTTP 协议 ServerSocket 走的是 TCP 协议,HTTP 协议本身是在 TCP 协议之上的一层。
TCP 是一种面向连接的、可靠的、基于字节流的传输层协议。TCP 在两个网络节点之间提供了一条可靠的通信信道,确保数据在传输过程中不会丢失、重复或乱序。TCP 使用握手过程建立连接,通过确认和重传机制确保数据可靠传输,并使用流量控制和拥塞控制算法来优化网络性能。
HTTP 是一个用于在 Web 浏览器和 Web 服务器之间传输超文本、图像、视频和其他媒体资源的应用层协议。HTTP 使用请求-响应模型,即客户端(通常是 Web 浏览器)发送请求给服务器,服务器处理请求并返回响应。HTTP 协议定义了一组方法(如 GET、POST、PUT、DELETE 等),用于指定请求的类型和目的。此外,HTTP 协议还定义了一组状态代码(如 200、404、500 等),用于表示响应的结果。
HTTP 协议依赖于 TCP 协议来传输数据。当 Web 浏览器向 Web 服务器发送 HTTP 请求时,它首先使用 TCP 协议与服务器建立连接。一旦连接建立,HTTP 请求消息会被封装在 TCP 数据包中,然后通过 TCP 信道发送给服务器。服务器收到 TCP 数据包后,解包提取 HTTP 请求消息,处理请求并生成 HTTP 响应消息。最后,HTTP 响应消息被封装在 TCP 数据包中,并通过相同的 TCP 信道发送回客户端。客户端收到 TCP 数据包后,解包提取 HTTP 响应消息并显示给用户。
请求消息 HTTP 请求消息由请求行(Request Line)、请求头(Request Headers)、空行(Empty Line)、请求体(Request Body,可选)几个部分组成。
①、请求行又包含三个部分,HTTP 方法(例如 GET, POST, PUT, DELETE 等)、请求的目标 URL(通常是相对 URL,但也可以是绝对 URL)、HTTP 版本(例如 HTTP/1.1 或 HTTP/2),这些部分用空格分隔,例如:
1 GET /index.html HTTP/1.1
②、请求头是一系列以键值对表示的元数据,用于描述请求的附加信息。每个请求头占一行,键和值之间用冒号(:)分隔。请求头包含诸如 Host、User-Agent、Content-Type、Content-Length、Accept 等信息。例如:
1 2 3 Host: www.tobebetterjavaer.com User-Agent: Mozilla/5.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
③、请求头和请求体之间有一个空行,表示请求头的结束。
④、对于某些 HTTP 方法(例如 POST、PUT 等),还可以在请求消息中包含请求体。请求体用于传输要发送给服务器的数据。请求体的格式和内容取决于 Content-Type 请求头的值。
例如,当提交 HTML 表单时,请求体可能如下所示:
1 username=沉默王二&password=123456
将这些部分放在一起,就构成了一个完整的 HTTP 请求消息:
1 2 3 4 5 6 7 8 POST /login HTTP/1.1 Host: Host: www.tobebetterjavaer.com User-Agent: Mozilla/5.0 Content-Type: application/x-www-form-urlencoded Content-Length: 29 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 username=沉默王二&password=123456
我用一张思维导图来表示下:
响应消息 一个典型的 HTTP 响应消息由三部分组成:状态行(Status Line)、响应头(Response Headers)、响应体(Response Body)。
不管是请求消息还是响应消息,都可以划分为三部分
第一行:状态行
第二行到第一个空行:header(请求头/相应头)
剩下所有:正文
HTTP 服务器设计 接下来进入正题,基于 Socket 创建一个 HTTP 服务器,使用 Socket 基本没啥太大的问题,我们需要额外关注以下两点:
a. 请求数据解析 我们从 Socket 中拿到所有的数据,然后解析为对应的 HTTP 请求,我们先定义个 Request 对象,内部保存一些基本的 HTTP 信息,接下来重点就是将 Socket 中的所有数据都捞出来,封装为 request 对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Data public static class Request { private String method; private String uri; private String version; private Map<String, String> headers; private String message; }
根据前面的 HTTP 协议介绍,解析过程如下,我们先看请求行的解析过程。
请求行 ,包含三个基本要素:请求方法 + URI + HTTP 版本,用空格进行分割,所以解析代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 private static void decodeRequestLine (BufferedReader reader, Request request) throws IOException { String[] strs = StringUtils.split(reader.readLine(), " " ); assert strs.length == 3 ; request.setMethod(strs[0 ]); request.setUri(strs[1 ]); request.setVersion(strs[2 ]); }
请求头的解析 ,从第二行,到第一个空白行之间的所有数据,都是请求头;请求头的格式也比较清晰,形如 key:value
, 具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private static void decodeRequestHeader (BufferedReader reader, Request request) throws IOException { Map<String, String> headers = new HashMap <>(16 ); String line = reader.readLine(); String[] kv; while (!"" .equals(line)) { kv = StringUtils.split(line, ":" ); assert kv.length == 2 ; headers.put(kv[0 ].trim(), kv[1 ].trim()); line = reader.readLine(); } request.setHeaders(headers); }
最后就是正文的解析了 ,这一块需要注意一点,正文可能为空,也可能有数据;有数据时,我们要如何把所有的数据都取出来呢?
先看具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 private static void decodeRequestMessage (BufferedReader reader, Request request) throws IOException { int contentLen = Integer.parseInt(request.getHeaders().getOrDefault("Content-Length" , "0" )); if (contentLen == 0 ) { return ; } char [] message = new char [contentLen]; reader.read(message); request.setMessage(new String (message)); }
注意上面我的使用姿势,首先是根据请求头中的Content-Type
的值,来获得正文的数据大小,因此我们获取的方式是创建一个这么大的char[]
数组来读取流中所有数据,如果我们的数组比实际的小,则读不完;如果大,则数组中会有一些空的数据;
最后将上面的几个解析封装一下 ,完成 request 解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public static Request parse2request (InputStream reqStream) throws IOException { BufferedReader httpReader = new BufferedReader (new InputStreamReader (reqStream, "UTF-8" )); Request httpRequest = new Request (); decodeRequestLine(httpReader, httpRequest); decodeRequestHeader(httpReader, httpRequest); decodeRequestMessage(httpReader, httpRequest); return httpRequest; }
接下来,是请求结果的封装,给一个简单的进行演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 @Data public static class Response { private String version; private int code; private String status; private Map<String, String> headers; private String message; } public static String buildResponse (Request request, String response) { Response httpResponse = new Response (); httpResponse.setCode(200 ); httpResponse.setStatus("ok" ); httpResponse.setVersion(request.getVersion()); Map<String, String> headers = new HashMap <>(); headers.put("Content-Type" , "application/json" ); headers.put("Content-Length" , String.valueOf(response.getBytes().length)); httpResponse.setHeaders(headers); httpResponse.setMessage(response); StringBuilder builder = new StringBuilder (); buildResponseLine(httpResponse, builder); buildResponseHeaders(httpResponse, builder); buildResponseMessage(httpResponse, builder); return builder.toString(); } private static void buildResponseLine (Response response, StringBuilder stringBuilder) { stringBuilder.append(response.getVersion()).append(" " ).append(response.getCode()).append(" " ) .append(response.getStatus()).append("\n" ); } private static void buildResponseHeaders (Response response, StringBuilder stringBuilder) { for (Map.Entry<String, String> entry : response.getHeaders().entrySet()) { stringBuilder.append(entry.getKey()).append(":" ).append(entry.getValue()).append("\n" ); } stringBuilder.append("\n" ); } private static void buildResponseMessage (Response response, StringBuilder stringBuilder) { stringBuilder.append(response.getMessage()); }
b. 请求任务 HttpTask 每个请求,单独分配一个任务来干这个事情,就是为了支持并发,对于 ServerSocket 而言,接收到了一个请求,那就创建一个 HttpTask 任务来实现 HTTP 通信。 那么这个 httptask 干啥呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public class HttpTask implements Runnable { private Socket socket; public HttpTask (Socket socket) { this .socket = socket; } @Override public void run () { if (socket == null ) { throw new IllegalArgumentException ("socket can't be null." ); } try { OutputStream outputStream = socket.getOutputStream(); PrintWriter out = new PrintWriter (outputStream); HttpMessageParser.Request httpRequest = HttpMessageParser.parse2request(socket.getInputStream()); try { String result = null ; String httpRes = HttpMessageParser.buildResponse(httpRequest, result); out.print(httpRes); } catch (Exception e) { String httpRes = HttpMessageParser.buildResponse(httpRequest, e.toString()); out.print(httpRes); } out.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
c. HTTP 服务搭建 前面的基本上把该干的事情都干了,剩下的就简单了,创建ServerSocket
,绑定端口接收请求,我们在线程池中跑这个 HTTP 服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 public class BasicHttpServer { private static ExecutorService bootstrapExecutor = Executors.newSingleThreadExecutor(); private static ExecutorService taskExecutor; private static int PORT = 8999 ; static void startHttpServer () { int nThreads = Runtime.getRuntime().availableProcessors(); taskExecutor = new ThreadPoolExecutor (nThreads, nThreads, 0L , TimeUnit.MILLISECONDS, new LinkedBlockingQueue <>(100 ), new ThreadPoolExecutor .DiscardPolicy()); while (true ) { try { ServerSocket serverSocket = new ServerSocket (PORT); bootstrapExecutor.submit(new ServerThread (serverSocket)); break ; } catch (Exception e) { try { TimeUnit.SECONDS.sleep(10 ); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } } bootstrapExecutor.shutdown(); } private static class ServerThread implements Runnable { private ServerSocket serverSocket; public ServerThread (ServerSocket s) throws IOException { this .serverSocket = s; } @Override public void run () { while (true ) { try { Socket socket = this .serverSocket.accept(); HttpTask eventTask = new HttpTask (socket); taskExecutor.submit(eventTask); } catch (Exception e) { e.printStackTrace(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } } } } }
这段代码是一个简单的 HTTP 服务器实现。以下是关于这个 HTTP 服务器的主要组件和功能的详细解释: 1、bootstrapExecutor:一个单线程的 ExecutorService,用于执行 HTTP 服务器的启动任务。 2、taskExecutor:一个线程池,用于处理来自客户端的 HTTP 请求。线程池的大小等于处理器可用核心数,队列大小为100,使用 DiscardPolicy 丢弃策略。 3、PORT:服务器侦听的端口号,默认为 8999。 4、startHttpServer() 方法: - a.创建一个线程池 taskExecutor 用于处理 HTTP 请求。 - b.在一个循环中,尝试创建一个 ServerSocket 实例并绑定到指定端口。如果失败,则等待 10 秒后重试。 - c.当成功创建 ServerSocket 实例后,将其作为参数提交给 bootstrapExecutor 执行 ServerThread 任务。 - d.关闭 bootstrapExecutor。 5、ServerThread 类实现了 Runnable 接口,它是 HTTP 服务器的主要任务: - a.serverSocket 成员变量:保存传递给构造函数的 ServerSocket 实例。 - b.run() 方法: - 在一个无限循环中,调用 serverSocket.accept() 方法等待客户端的连接。 - 当接受到一个新的客户端连接时,创建一个 HttpTask 实例,将 Socket 实例作为参数传递。 - 将 HttpTask 提交给 taskExecutor 执行。
这个 HTTP 服务器的主要逻辑是:使用一个线程来监听客户端连接,当有新的客户端连接时,创建一个 HttpTask 来处理客户端的 HTTP 请求,并将这个任务提交给线程池 taskExecutor 执行。这样可以实现多个客户端请求的并发处理。 到这里,一个基于 Socket 实现的 HTTP 服务器基本上就搭建完了,接下来就可以进行测试了