TCP多线程服务器
在Web应用中,浏览器请求一个URL,服务器就把生成的HTML网页发送给浏览器,而浏览器和服务器之间的传输协议是HTTP,所以:
- HTML是一种用来定义网页的文本,会HTML,就可以编写网页;
- HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。
HTTP协议是一个基于TCP协议之上的请求-响应协议,它非常简单,通常浏览器获取的第一个资源是HTML网页,在网页中,如果嵌入了JavaScript、CSS、图片、视频等其他资源,浏览器会根据资源的URL再次向服务器请求对应的资源。一个HTTP Server本质上是一个TCP服务器,我们先用TCP编程的多线程实现服务器端框架。
| 12
 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
| 12
 3
 4
 5
 6
 7
 
 | GET / HTTP/1.1Host: 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。