TCP多线程服务器

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));
// 读取HTTP请求
boolean requestOk = false;
String first = reader.readLine();
System.out.println(first);
// 这里我们只处理GET /的请求
if (first.startsWith("GET / HTTP/1.")) {
requestOk = true;
}
for (;;) {
// 注意BufferedReader会省略一行末尾的\r\n
String header = reader.readLine();
// 读取到空行时, HTTP Header读取完毕
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");
// 空行标识Header和Body的分隔
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。