- 计算机网络:是由两台或多台计算机组成的网络,在同一个网络中的,任意两台计算机可以想相互通信,所有计算机都需要遵循同一种网络协议
- 互联网:就是将多个计算机网络连接起来的网络,并为他们制定相同的通信协议:TPC/IP协议
- TPC/IP协议:泛指互联网协议,只有遵循TPC/IP协议,才能被接入互联网,
-
- 主流TCP/IP协议包含:Http、Https、SSH、FTP等
- IP地址:一个IP地址对应一个网络接口,一台互联网计算机肯定有一个IP地址,但也可能有多个
- 域名:因为IP不好记忆,所以通常使用域名来访问某个特定的服务,域名解析器DNS将域名解析为对应的IP,客户端在根据IP访问服务器
- OSI网络模型:
-
- 物理层:二进制位流。集线器(信号放大),中继器(信号放大)
- 数据链路层:帧,MAC地址。交换机(数据转发)、网桥(连接两个局域网)、差错检测和修正、流量控制
- 网络层:包、IP地址、路由选择。路由器(路由转发、存储转发、流量管理)
- 传输层:报文(段)。TCP、UDP
- 会话层:访问验证、会话管理
- 表示层:格式化表示、数据转换、解密解密、压缩解压缩
- 应用层:网关。用户服务、接口服务
- TCP/IP的网络模型为4层:应用层-》传输层-》网络层-》链路层,其中TCP是传输层协议,负责在源主机和目标主机之间建立可靠的连接并确保数据的有序传输,IP为网络层协议,负责将数据包从源主机路由到目标主机
网络编程的核心套接字Socket
- 套接字是一个抽象层,是不同主机之间交换数据的接口
- 套接字允许应用程序将I/O应用在网络中,并与其它应用程序进行通信。通过套接字应用程序可以发送和接受数据,就像操作文件那样可以打开,读写然后关闭
- ping:一种计算机网络工具,用于测试数据包是否能透过IP协议到达目标主机
- 基于TCP的Socket和ServerSocket、基于UDP的DatagramSocket
基于TCP的Socket和ServerSocket
- Socket和ServerSocket是Java实现TCP协议的核心类,Socket用于客户端程序、ServerScoket用于服务端程序
- TCP:连接控制协议,特点:
-
- 面向链接
- 可靠传输
- 效率低
- 保证数据顺序
- 阻塞控制
@Test
public void test() throws Exception {
try ( Socket socket = new Socket("bbs.newsmth.net", 23);){
socket.setSoTimeout(5000);
InputStream inputStream = socket.getInputStream();
Scanner scanner = new Scanner(inputStream, "gbk");
while (scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
scanner.close();
}catch (UnknownHostException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
}
@Test
public void test1() throws Exception {
CountDownLatch serverStarted = new CountDownLatch(1);
CountDownLatch clientFinishLatch = new CountDownLatch(1);
Thread serverThread = new Thread(() -> {
try (ServerSocket serverSocket = new ServerSocket(8888);) {
serverSocket.setSoTimeout(5000);//设置超时时间
serverStarted.countDown();//通知客户端,服务端已启动
try (Socket accept = serverSocket.accept();
InputStream in = accept.getInputStream();
OutputStream out = accept.getOutputStream();
Scanner scanner = new Scanner(in, StandardCharsets.UTF_8);
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));) {
//服务端输出
printWriter.println("HTTP/1.1 200 OK");
printWriter.println("Content-Type: text/html; charset=utf-8");
printWriter.println();
printWriter.println("这是serverSocket服务端输入的内容");
printWriter.println("end");
printWriter.flush();
//接受客户端输入 scanner.hasNextLine() 会无限阻塞,需要做结束判断
clientFinishLatch.await();
boolean flag = true;
while (flag && scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
if("end".equalsIgnoreCase(line)){
flag = false;
}
}
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});
serverThread.start();
Thread clientThread = new Thread(() -> {
try {
serverStarted.await();
try (Socket socket = new Socket("127.0.0.1", 8888);
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
Scanner scanner = new Scanner(in, StandardCharsets.UTF_8);
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));) {
//接受服务端的输入 scanner.hasNextLine() 会无限阻塞,需要做结束判断
boolean flag = true;
while (flag && scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
if ("end".equals(line)){
flag = false;
}
}
//向服务端发送内容
printWriter.println("POST / HTTP/1.1");
printWriter.println("Content-Type: text/html; charset=utf-8");
printWriter.println("Content-Length: 27");
printWriter.println();
printWriter.println("这是clientSocket客户端输入的内容");
printWriter.println("end");
printWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
System.out.println("客户端已结束");
clientFinishLatch.countDown(); //通知服务端,客户端已结束
}
});
clientThread.start();
clientThread.join();
serverThread.join();
}
@Test
public void test2() throws Exception {
try (ServerSocket serverSocket = new ServerSocket(8888);) {
while (true) {
try (Socket accept = serverSocket.accept();) {
System.out.println("连接成功");
new Thread(() -> {
try (InputStream in = accept.getInputStream();
OutputStream out = accept.getOutputStream();
Scanner scanner = new Scanner(in, StandardCharsets.UTF_8);
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));){
//向客户端输出
//.....
//接受客户端输入
//.....
} catch (IOException e) {
throw new RuntimeException(e);
}
}).start();
}
}
}
}
基于UDP的DatagramSocket
- DatagramScoket是java中实现UDP协议的核心类,提供了无连接的通信服务,发送和数据包
- UDP:用户数据协议,特点:
-
- 无链接
- 不可靠传输
- 效率高
- 不保证数据顺序
- 不阻塞
- 由于无需建立连接,所以比TCP更快,但可能不如TCP可靠
@Test
public void test3() throws Exception {
Thread serverThread = new Thread(() -> {
try (DatagramSocket datagramSocket = new DatagramSocket(8888);){
byte[] buffer = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length);
//阻塞等待接收客户端发送的数据包
datagramSocket.receive(datagramPacket);
String message = new String(datagramPacket.getData(), 0, datagramPacket.getLength(), StandardCharsets.UTF_8);
System.out.println("接收到客户端发送的数据:" + message);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
serverThread.start();
Thread clientThread = new Thread(() -> {
try (DatagramSocket datagramSocket = new DatagramSocket();){
byte[] bytes = "这是来自客户端的一条消息".getBytes(StandardCharsets.UTF_8);
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length, InetAddress.getLocalHost(), 8888);
datagramSocket.send(datagramPacket);
System.out.println("发送成功");
} catch (IOException e) {
throw new RuntimeException(e);
}
});
clientThread.start();
clientThread.join();
serverThread.join();
}
用Socket实现一个HTTP服务器
- ServerSocket是走的TCP协议,而HTTP是一个应用层协议
- TCP是一种面向链接的、可靠、基于字节流的传输层协议,作用在两个网络节点之间建立一条通信信道,确保数据在传输的过程中有序、不丢失、不重复。TCP使用三次握手四次挥手,来建立和释放链接,通过确认和重传机制保证可靠性传输,并使用流量控制和阻塞算法来优化网络性能
- HTTP是应用层用于Web浏览器和Web服务器之间的超文本传输协议,使用请求-响应模型,即客户端发送请求给服务端,服务端处理请求并返回响应
- HTTP协议定义数据内容以及请求格式,并且依赖于TCP协议进行数据传输
HTTP的请求格式
-
- 请求行包含3部分:请求类型 目标URL HTTP版本,用空格分隔,例如:
GET /index.html HTTP/1.1
- 请求头是一系列键值对表示的元数据,用于描述请求的附加信息
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表单:
username=张三&password=123456
- 完成的HTTP请求消息:
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版本 状态代码 状态
- 响应头:key-value形式,响应头和响应体之间存在空行,表示响应头的结束
- 响应体:响应内容,
实现
package org.example.http服务器;
import lombok.Data;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Data
public class Request {
/**
* 请求行
*/
private String reqType;
private String url;
private String httpVer;
/**
* 请求头
*/
private Map<String, String> headers;
/**
* 请求体
*/
private String body;
/**
* 构建request对象
*/
public Request(BufferedReader reader) throws IOException {
editReqLine(reader);
editHeader(reader);
editBody(reader);
}
/**
* 设置请求行
*
* @param reader
* @throws IOException
*/
private void editReqLine(BufferedReader reader) throws IOException {
String[] split = reader.readLine().split(" ");
assert split.length == 3;
reqType = split[0];
url = split[1];
httpVer = split[2];
}
/**
* 设置请求头
* 注意:请求头和请求体之间存在一个空行,用于区分请求头是否结束
*
* @param reader
* @throws IOException
*/
private void editHeader(BufferedReader reader) throws IOException {
String line = reader.readLine();
Map<String, String> handlerMap = new HashMap<>();
while (!" ".equals(line)) {
String[] split = line.split(": ");
System.out.println(Arrays.toString( split));
assert split.length == 2;
handlerMap.put(split[0], split[1]);
line = reader.readLine();
}
headers = handlerMap;
}
/**
* 设置请求体
*/
private void editBody(BufferedReader reader) throws IOException {
int bodyLength = Integer.parseInt(headers.getOrDefault("Content-Length", "0"));
if(bodyLength == 0){
//无请求体,直接返回
return;
}
char[] chars = new char[bodyLength];
int read = reader.read(chars);
body = new String(chars, 0, read);
}
}
package org.example.http服务器;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
public class Response {
/**
* 响应行
*/
private String httpVer;
private String code;
private String status;
/**
* 响应头
*/
private Map<String, String> headers;
/**
* 响应体
*/
private String body;
/**
* 构建response对象
*
* @param request
* @param respMsg
*/
public Response(Request request, String respMsg) {
this.httpVer = request.getHttpVer();
this.code = "200";
this.status = "OK";
this.headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put("Content-Length", String.valueOf(respMsg.getBytes().length));
this.body = respMsg;
}
public String editResp() {
StringBuilder stringBuilder = new StringBuilder();
//状态行
editRespLine(stringBuilder);
//响应头
editRespHead(stringBuilder);
//响应体
editRespBody(stringBuilder);
return stringBuilder.toString();
}
/**
* 设置响应体
*
* @param stringBuilder
*/
private void editRespBody(StringBuilder stringBuilder) {
stringBuilder.append(body);
}
/**
* 设置响应头
*
* @param stringBuilder
*/
private void editRespHead(StringBuilder stringBuilder) {
for (String key : headers.keySet()) {
stringBuilder.append(key)
.append(":")
.append(headers.get(key))
.append("\r\n");
}
stringBuilder.append("\r\n");
}
/**
* 设置响应行
*
* @param stringBuilder
*/
private void editRespLine(StringBuilder stringBuilder) {
stringBuilder.append(httpVer)
.append(" ")
.append(code)
.append(" ")
.append(status)
.append("\r\n");
}
}
package org.example.http服务器;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
public class CustomServer {
public static final int PORT = 8888;
private static ExecutorService starteServer = Executors.newSingleThreadExecutor();
private static final ThreadPoolExecutor threadPoolExecutor;
static {
int threadNum = Runtime.getRuntime().availableProcessors();
threadPoolExecutor = new ThreadPoolExecutor(threadNum,
threadNum,
0,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(20),
new ThreadPoolExecutor.DiscardOldestPolicy());
}
static class ServerThread implements Runnable {
private ServerSocket serverSocket;
public ServerThread(ServerSocket serverSocket) {
this.serverSocket = serverSocket;
}
@Override
public void run() {
Thread.currentThread().setName("server-thread");
while (true) {
try {
//等待客户端连接
Socket accept = this.serverSocket.accept();
System.out.println("客户端连接成功");
//创建执行线程
threadPoolExecutor.execute(new HttpTask(accept));
} catch (IOException e) {
e.printStackTrace();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
}
}
}
static class HttpTask implements Runnable {
private Socket socket;
public HttpTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
Thread.currentThread().setName("http-task");
if (socket == null) {
throw new IllegalArgumentException("socket can't be null.");
}
try (InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(out);) {
//获取客户端的请求
Request request = new Request(new BufferedReader(new InputStreamReader(in)));
//响应客户端
try {
String httpRes = new Response(request, "请求成功").editResp();
printWriter.println(httpRes);
} catch (Exception e) {
String httpRes = new Response(request, e.getMessage()).editResp();
printWriter.println(httpRes);
}
printWriter.flush();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
package org.example.http服务器;
import org.junit.jupiter.api.Test;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class Demo {
@Test
public void test() throws Exception {
ServerSocket serverSocket = new ServerSocket(CustomServer.PORT);
Thread startServer = new Thread(new CustomServer.ServerThread(serverSocket));
startServer.start();
startServer.join();
}
@Test
public void test1() throws Exception {
try (Socket socket = new Socket("127.0.0.1", 8888);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
Scanner scanner = new Scanner(in);
PrintWriter printWriter = new PrintWriter(out);) {
//向服务器发送请求
String reqBody = "username=张三&password=123456";
printWriter.println("GET /test HTTP/1.1");
printWriter.println("Host: 127.0.0.1:8888");
printWriter.println("Connection: application/json");
printWriter.println("Content-Type: " + reqBody.getBytes().length);
printWriter.println(" ");
printWriter.println(reqBody);
printWriter.flush();
//接收服务端的响应
while (scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
}
}
}
- 使用ServerSocket绑定服务端口,accept方法阻塞等待客户端连接,连接成功新建线程执行请求和响应,然后循环等下一个客户端连接
- Socket指定ip和端口,封装http请求格式,发送给服务端,并接收服务端响应