什么是Socket
Socket代表通过互联网进行双向通信的两个程序的其中一端,Socket需要绑定一个端口,这样TCP层才能知道应该把数据发送给哪个进程。JAVA中的Socket是对TCP协议的封装。底层是使用TCP协议实现的
以C/S架构为例
在Server端,在创建Server时需要绑定一个端口号,例如8080。之后Server端监听这个端口收到的数据
在Client端,Client需要使用Server的主机名和Server绑定的端口号来来创建一个Socket来代表Server端,并来发起连接请求。Client端不需要主动绑定指定的端口号,操作系统会自动分配一个端口号给Client端。
Server端收到Client的连接请求之后,会使用Client端的主机名和端口号创建一个Socket来代表Client端。
代码示例
Server端
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept();
System.out.println("" + socket.getLocalPort() + ", " + socket.getPort());
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Client端
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 8080);
System.out.println("" + socket.getLocalPort() + ", " + socket.getPort());
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
运行结果
先运行Server端,再运行Client端
Client端运行结果如下
61420, 8080
Process finished with exit code 0
Server端运行如下
8080, 61420
Process finished with exit code 0
使用Socket实现Client端和Server端的通信
实现Client向Server发送Hello Server!, Server端回复Hello Client给Client。
Client端如下
- 创建Socket
- 打开Socket的输入/出流
- 向输出流中写入"Hello Server!"
- 读取输入流,读到数据后跳出循环。
- 关闭资源
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 8080);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
){
writer.println("Hello Server!");
String str;
while ((str = reader.readLine()) != null) { // 当Socket没有被关闭时,返回的值为非null,如果Socket被关闭,返回的时null
System.out.println(str);
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
Server端如下
- 创建Server
- 获取代表Client的Socket
- 打开Client Socket的输入/出流
- 读取输入流内容,读到内容后,写"Hello Client!"到输出流
- Client关闭资源后,Server也会关闭
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true)) {
String str;
while ((str = reader.readLine()) != null) {
System.out.println(str);
writer.println("Hello Client!");
}
} catch (IOException e) {
e.printStackTrace();
}
}
运行结果如下
先运行Server,再运行Client
Client端
Hello Client!
Process finished with exit code 0
Server端
Hello Server!
Process finished with exit code 0
模拟WebServer
创建一个Server返回用户通过浏览器访问的页面。
例如用户访问http://localhost:8080/index.html时,我们应该返回index.html给浏览器
首先看看在浏览器中访问http://localhost:8080/index.html,Server端收到的是什么
创建一个Server,监听8080, 并打印出收到的数据
package Sample_4;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));) {
String str;
while ((str = reader.readLine()) != null) {
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
先运行Server, 然后在在浏览器中访问http://localhost:8080/index.html
运行结果如下
GET /index.html HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
我们只需要关注并解析第一行
GET /index.html HTTP/1.1
实现Server
Server的执行流程是:
- 从第一行中解析出请求的资源,如index.html
- 如果能找到这个资源,向Socket的输出流中写请求结果给浏览器
- 写响应行:HTTP/1.1 200 OK\r\n
- 写响应头:content‐Type:text/html\r\n
- 写空行:\r\n
- 把资源写到Socket的输出流
- 如果没有找到这个资源,向Socket的输出流中写请求结果给浏览器
- 写响应行:HTTP/1.1 404 OK\r\n
代码实现如下
package Sample_4;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Objects;
public class Server {
public static void transferOut(File file, OutputStream out) throws IOException {
FileInputStream in = null;
try {
in = new FileInputStream(file);
in.transferTo(out);
} finally {
Objects.requireNonNull(in).close();
}
}
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
OutputStream out = socket.getOutputStream()
) {
String str;
while ((str = reader.readLine()) != null) {
String[] params = str.split(" ");
String path = params[1].substring(1);
File file = new File(path);
if (file.exists()) {
out.write("HTTP/1.1 200 OK\r\n".getBytes());
}
else {
file = new File("404.html");
out.write("HTTP/1.1 404 Not Found\r\n".getBytes());
}
out.write("content‐Type:text/html\r\n".getBytes());
out.write("\r\n".getBytes());
transferOut(file, out);
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
准备一个index.html和404.html
然后运运行Server, 然后从浏览器中访问
改进Server
现在我们创建的Server只能处理一次请求,处理完就关闭Server了
我们需要把他改成可以处理多个请求。思路就是每次收到Client的连接请求就创建一个线程来处理
package Sample_5;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Objects;
public class Server {
public static void transferOut(File file, OutputStream out) throws IOException {
FileInputStream in = null;
try {
in = new FileInputStream(file);
in.transferTo(out);
} finally {
Objects.requireNonNull(in).close();
}
}
public static void handler(Socket socket) {
new Thread(() -> {
try(BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
OutputStream out = socket.getOutputStream()){
String str;
while ((str = reader.readLine()) != null) {
String[] params = str.split(" ");
String path = params[1].substring(1);
File file = new File(path);
if (file.exists()) {
out.write("HTTP/1.1 200 OK\r\n".getBytes());
} else {
file = new File("404.html");
out.write("HTTP/1.1 404 Not Found\r\n".getBytes());
}
out.write("content‐Type:text/html\r\n".getBytes());
out.write("\r\n".getBytes());
transferOut(file, out);
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8080);) {
while (true) {
Socket socket = serverSocket.accept();
if (socket != null)
handler(socket);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
try (ServerSocket serverSocket = new ServerSocket(8080);) {
while (true) {
Socket socket = serverSocket.accept();
if (socket != null)
handler(socket);
}
} catch (IOException e) {
e.printStackTrace();
}
}