TCP多线程服务器
在Web应用中,浏览器请求一个URL,服务器就把生成的HTML网页发送给浏览器,而浏览器和服务器之间的传输协议是HTTP,所以:
- HTML是一种用来定义网页的文本,会HTML,就可以编写网页;
- HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。
HTTP协议是一个基于TCP协议之上的请求-响应协议,它非常简单,通常浏览器获取的第一个资源是HTML网页,在网页中,如果嵌入了JavaScript、CSS、图片、视频等其他资源,浏览器会根据资源的URL再次向服务器请求对应的资源。一个HTTP Server本质上是一个TCP服务器,我们先用TCP编程的多线程实现服务器端框架。
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 77 78 79 80
| public class ServerDemo { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(8080); System.out.println("server is running..."); for (;;) { Socket sock = ss.accept(); System.out.println("connected from " + sock.getRemoteSocketAddress()); Thread t = new Handler(sock); t.start(); } } }
class Handler extends Thread { Socket sock; public Handler(Socket sock) { this.sock = sock; } public void run() { try (InputStream input = sock.getInputStream()) { try (OutputStream output = sock.getOutputStream()) { handler(input, output); } } catch (IOException e) { System.out.println("socket input or output get failure"); } finally { try { sock.close(); } catch (IOException e) { System.out.println("socket close failure"); throw new RuntimeException(e); } System.out.println("client disconnected"); } }
private void handler(InputStream input, OutputStream output) throws IOException { System.out.println("process new http request..."); BufferedReader reader = new BufferedReader(new InputStreamReader(input)); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output)); boolean requestOk = false; String first = reader.readLine(); System.out.println(first); if (first.startsWith("GET / HTTP/1.")) { requestOk = true; } for (;;) { String header = reader.readLine(); if (header.isEmpty()) { break; } System.out.println(header); } System.out.println(requestOk ? "Response OK" : "Response Error"); if (!requestOk) { writer.write("HTTP/1.0 404 Not Found\r\n"); writer.write("Content-Length: 0\r\n"); writer.write("\r\n"); writer.flush(); } else { String data = "<html><body><h1>Hello, world!</h1></body></html>"; int length = data.getBytes(StandardCharsets.UTF_8).length; writer.write("HTTP/1.0 200 OK\r\n"); writer.write("Connection: close\r\n"); writer.write("Content-Type: text/html\r\n"); writer.write("Content-Length: " + length + "\r\n"); writer.write("\r\n"); writer.write(data); writer.flush(); } } }
|
上面代码中有两处需要注意的地方,知识点分别对应HTTP的格式和BufferedWriter的字符缓冲区:
- 判断请求头的Header字段是否全部读取完成,是根据是否读到空行,因为HTTP的格式如下,每一行以\r\n结尾,请求头与请求体之间多一个\r\n
1 2 3 4 5 6 7
| GET / HTTP/1.1 Host: www.sina.com.cn User-Agent: Mozilla/5.0 xxx Accept: */* Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8
data...
|
- BufferedWriter写入数据会判断写入的数据大小是否超出内部缓冲区容量限制(默认是8192大小的字符数组),如果超出了则先刷新内部缓冲区数据到基本字符流,然后把要写入的字符串转换为字符数组直接写入内部的基本字符流,否则循环写入内部缓冲区的随后位置并等待写满刷新。因此,我们写入数据后通常需要调用flush方法强制BufferedWriter刷新缓冲区,保证数据完整正确发送。
这里的核心代码是,先读取HTTP请求,这里我们只处理GET /
的请求。当读取到空行时,表示已读到连续两个\r\n
,说明请求结束,可以发送响应。发送响应的时候,首先发送响应代码HTTP/1.0 200 OK
表示一个成功的200响应,使用HTTP/1.0
协议,然后,依次发送Header,发送完Header后,再发送一个空行标识Header结束,紧接着发送HTTP Body。