文章目录
1.HTTP协议简介
超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。
1.1HTTP协议概述
HTTP是一个客户端终端(用户)和服务器端(网站)请求和应答的标准(TCP)。通过使用网页浏览器或者其它的工具,客户端发起一个HTTP请求到服务器上指定端口(默认端口为80)。我们称这个客户端为用户代理程序(user agent)。应答的服务器上存储着一些资源,比如HTML文件和图像。我们称这个应答服务器为源服务器(originserver)。通常,由HTTP客户端发起一个请求,创建一个到服务器指定端口(默认是80端口)的连接。HTTP服务
器则在那个端口监听客户端的请求。一旦收到请求,服务器会向客户端返回一个状态,比如"HTTP/1.1200 OK",以及返回的内容,如请求的文件、错误消息、或者其它信息。
1.2HTTP工作原理
1:首先是客户端连接到Web服务器:
浏览器对url进行域名解析拿到IP地址,然后就封装HTTP请求报文。
2:发送HTTP请求:
操作系统协议栈将应用层的HTTP报文,基于TCP协议再次封装,然后和服务器建立一个TCP套接字连接并且向Web服务器发送一个请求报文,报文由请求行、请求头、空行和请求数据组成。
3:服务器接受请求并且返回HTTP响应:
Web服务器解析请求,定位资源,最后将服务资源输出到TCP套接字中,由客户端浏览器读取。一个响应报文由响应行、响应头、空行、响应数据组成。
4:释放连接的TCP连接
若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求
5:客户端浏览器解析响应数据
浏览器会根据响应头中的数据,对应的就知道该怎样处理数据,若是HTML就渲染成网页,如果是文本就直接呈现在网页上。
图解其过程:
1.3HTTP报文格式
1.3.1请求报文
1.3.2响应报文
2.套接字(socket)
2.1Socket概览
2.2流socket概述
在网络通讯中,两个程序通过一个双端的通信连接管道实现数据交换,我们称这个管道叫Socket;一般是客户端创建socket连接服务端,与之相对应的服务端会建立一个服务端的socket服务进程等待客户端的socket连接!
图解过程:
2.3流socket原理
通过互联网进行通信,至少需要一对套接字,其中服务端的称之为ServerSocket,客户端的称为Socket,本质上讲服务端的套接字就是为了处理客户端的套接字的,每一个向服务端发起socket连接的都能称之为客户端。
通信过程图:
2.4流socket(网络编程)
了解流Socket以后,来简单的基于TCP来实现服务端与客户端的通信:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* 服务端
*/
public class Test0721_1 {
private static String name="服务端";
public static void main(String[] args) throws IOException {
//向系统注册一个套接字服务
ServerSocket server=new ServerSocket(8888);
Scanner scanner=new Scanner(System.in);
//套接字阻塞监听在8888端口,等待处理来连接的套接字
Socket accept = server.accept();
//拿到输入流,就可以读取到套接字里面的内容
InputStream in = accept.getInputStream();
//拿到输出流,就可以往套接字里面写入内容
OutputStream os = accept.getOutputStream();
while (true){
int len;
byte[] data=new byte[1024];
//服务端读取套接字里面的内容
while ((len=in.read(data))!=-1){
System.out.print("客户端:");
System.out.println(new String(data,0,len));
if(len<1024){ //读取的长度如果少于自定义的长度,说明一次读取完毕,跳出本次读取
break;
}
}
System.out.print(name+":");
//服务端往套接字里面写入内容
os.write(scanner.next().getBytes());
}
}
}
===================================================================
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Scanner;
/**
* 客户端
*/
public class Test0721_2 {
private static String name="客户端";
public static void main(String[] args) throws IOException {
//创建一个套接字
Socket socket=new Socket();
Scanner scanner=new Scanner(System.in);
//绑定连接的主机和端口
socket.connect(new InetSocketAddress(8888));
//拿到输出流,就可以往套接字里面写入内容
OutputStream os = socket.getOutputStream();
//拿到输入流,就可以读取套接字里面的内容
InputStream in = socket.getInputStream();
while (true){
byte[] data=new byte[1024];
int len;
System.out.print(name+":");
//客户端往套接字里面写入内容
os.write(scanner.next().getBytes());
while ((len=in.read(data))!=-1){ //客户端从套接字里面读取内容
String s=new String(data,0,len);
System.out.println("服务端:"+s);
if(s.equals("bye")){ //模拟收到服务端关闭连接的信号
//关闭所有的资源
socket.shutdownInput();
socket.shutdownOutput();
socket.close();
os.close();
in.close();
break;
// System.exit(0);
}
if(len<1024){
break;
}
}
if(socket.isClosed()){
break;
}
}
}
}
运行结果:
2.5数据报套接字(DatagramSocket)
在网络通讯中,数据包套接字,只要知道目的主机的IP地址与端口,即可进行通信,就是通过发送数据包给对方,接受数据包即可!
2.6数据报套接字(DatagramSocket)原理
2.7数据报套接字(网络编程)
了解数据报套接字以后,来简单的基于UDP来实现两台主角的通信:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UdpSeverA {
public static void main(String[] args) throws IOException {
//创建一个数据报套接字
DatagramSocket socket=new DatagramSocket(6666);
//获得从键盘输入的流对象
BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
while (true){
byte[] data=new byte[1024];
//准备一个数据包,也就是发送数据的载体
DatagramPacket packet=new DatagramPacket(data,0,data.length);
//阻塞的接收数据包
socket.receive(packet);
String msg=new String(packet.getData(),0,packet.getData().length);
System.out.println(msg);
if(msg.trim().equals("bye")){
socket.close();
break;
}
String msgsend=reader.readLine();
byte[] sendData=msgsend.getBytes();
//目的IP地址
InetAddress localhost = InetAddress.getByName("localhost");
//目的端口
int port=5555;
//准备数据包(发送数据包要绑定对方主机IP和端口)
DatagramPacket packet1=new DatagramPacket(sendData,0,sendData.length,localhost,port);
socket.send(packet1);
}
reader.close();
}
}
===================================================================
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UdpServerB {
private static String flag=null;
public static void main(String[] args) throws Exception {
//建立socket连接
DatagramSocket socket=new DatagramSocket(5555);
//准备从控制台读取数据
BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
while (true) {
String data = reader.readLine();
byte[] bytes = data.getBytes();
//要发送的端口好
int port = 6666;
//要发送的主机IP地址
InetAddress localhost = InetAddress.getByName("localhost");
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length, localhost, port);
//发送数据
socket.send(packet);
if (data.equals("bye")) {
break;
}
//为了也能接收到数据包和发送数据包同时进行,需要另外开辟一个线程来接收数据包
Thread t = new Thread(new Runnable() {
@Override
public void run() {
byte[] res = new byte[1024];
DatagramPacket packet1 = new DatagramPacket(res, 0, res.length);
try {
socket.receive(packet1);
} catch (IOException e) {
e.printStackTrace();
}
String msg = new String(packet1.getData(), 0, packet1.getData().length);
System.out.println(msg);
if ("bye".equals(msg)) {
flag=msg;
socket.close();
}
}
});
if("bye".equals(flag)){
//结束收数据线程
t.interrupt();
}
t.start();
}
reader.close();
}
}
运行结果:
2.8补充URL网络下载
通过网络连接到网络上的资源,根据URL可以定位某个资源再根据流,就可以把资源输出到我们想要的地方。
简单编写一个程序,下载网络上的资源:
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class URLTest {
public static void main(String[] args) throws Exception {
//下载地址
URL url = new URL("https://9uu33.com/20210311/bf6f2ac51d8f895372a48ed527d5d59a.mp4");
//连接到这个资源
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
//通过流来连接对象
InputStream inputStream= urlConnection.getInputStream();
FileOutputStream fos = new FileOutputStream("m.mp4");
byte[] buffer = new byte[1024];
int len;
while ((len=inputStream.read(buffer))!=-1){
fos.write(buffer,0, len);//写出数据
}
fos.close();
inputStream.close();
urlConnection.disconnect();
}
}
3.手动实现一个简单的HTTP服务器
3.1 总体思路与过程如下:
3.2代码实现
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 服务器启动类
*/
public class Http {
public static void main(String[] args) throws IOException {
// 创建服务器
ServerSocket server = new ServerSocket();
// 绑定端口
server.bind(new InetSocketAddress(6888));
System.out.println("server is running! listening on port 6888!");
// 开始监听,阻塞的方法
while (true){
Socket accept = server.accept();
System.out.println("A person"+accept.getRemoteSocketAddress().toString()+" is coming!");
// 使用线程池,提升性能
ExecutorService executorService = Executors.newFixedThreadPool(20);
//来一个请求,丢到线程池里面去
executorService.submit(new MyTask(accept));
}
}
}
===================================================================
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* 处理请求和响应的任务类
*/
public class MyTask implements Runnable {
private Socket accept;
public MyTask(Socket accept){
this.accept = accept;
}
@Override
public void run() {
InputStream in = null;
OutputStream out = null;
try {
StringBuilder sb = new StringBuilder();
//拿到输入流,就可以读取套接字里面请求的内容
in = accept.getInputStream();
int len;
byte[] buf = new byte[512];
while ((len = in.read(buf)) != -1) {
sb.append(new String(buf, 0, len));
if (len < buf.length) {
accept.shutdownInput();
}
}
// 构建一个请求对象
Request request = Request.buildRequest(sb.toString());
// 拿到输出流
out = accept.getOutputStream();
// 构建一个响应
Response response = new Response();
//根据请求的URL路径定位到资源后,把资源放到响应数据里面
response.setData(HttpUtils.getPage(request.getUrl()));
//把输出流放到响应对象里面去
response.write(out);
}catch (Exception e){
e.printStackTrace();
}finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (accept != null){
try {
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
===================================================================
/**
* 帮助定位资源的工具类
*/
public class HttpUtils {
// 使用流获得页面的字符串
public static String getPage(String url){
//资源存放的文件
// String rootPath="/javaDailyProctice/Test0720/MyTomcat";
StringBuilder sb = new StringBuilder();
String result = null;
try {
if ("".equals(url) || "/".equals(url) || url == null){
url = "index.html";
}
// F:\桌面文件\Tomcat
// 寻找觉对的父路劲 ==>"/D:/JavaPrectice/out/production/JavaPrectice/" ==>其实就是编译后的根路径的根文件而已
String path = HttpUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath();
path = path.substring(0,path.lastIndexOf("/")) + "/pages/";
url = path + url;
boolean exists = new File(url).exists();
if (!exists){
url = path +"404.html";
}
InputStream resource = new FileInputStream(url);
byte[] buf = new byte[1024];
int len;
while ((len = resource.read(buf)) != -1){
sb.append(new String(buf,0,len));
}
result = sb.toString().replace("{{name}}", "涉外—攀枝花");
} catch (Exception e){
e.printStackTrace();
}
return result ;
}
}
===================================================================
import java.util.HashMap;
import java.util.Map;
/**
* 封装请求类
*/
public class Request {
//请求类型(get/post)
private String type;
//请求的URL资源路径
private String url;
//请求的协议版本
private String protocol;
//文本类型
private String contentType;
//存放请求头中的键值对数据
private Map<String,String> headers = new HashMap<>(8);
//请求数据
private Map<String,String> attributes = new HashMap<>(8);
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public Map<String, String> getHeaders() {
return headers;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
public Map<String, String> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}
}
// 通过请求的报文字符串构建一个请求对象
public static Request buildRequest(String requestStr){
Request request = new Request();
// 从请求中切分出请求行和请求头
String[] split = requestStr.split("\r\n\r\n");
//继续从中切分出请求头 lineAndHeader ==>请求行、[请求头的键值对]
String[] lineAndHeader = split[0].split("\r\n");
//继续切分出请求行,此时的lines==> 请求方法、URL、协议版本、
String[] lines = lineAndHeader[0].split(" ");
request.setType(lines[0]);
request.setUrl(lines[1]);
request.setProtocol(lines[2]);
//必须从下标为1开始遍历,因为下标为0对应的是整个请求行的数据
for (int i = 1; i < lineAndHeader.length; i++) { //遍历请求头
String[] header = lineAndHeader[i].split(": ");
request.getHeaders().put(header[0].trim().toLowerCase(),header[1].trim());
}
request.setContentType(request.getHeaders().get("content-type"));
// // 处理请求体
// if (split.length == 2){
// 暂且不做处理,因为简单发起HTTP请求,我们只需要截取到URL即可,
// 并不需要和后端进行数据交互,所以不处理
// }
return request;
}
}
===================================================================
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
/**
* 构造响应的类
*/
public class Response {
//版本协议
private String protocol = "HTTP/1.1";
//状态码
private Integer code = 200;
//状态码描述
private String msg = "OK";
//响应的格式
private String ContentType = "text/html;charset=utf-8";
//响应报文的长度
private String ContentLength;
private Map<String,String > headers = new HashMap(){{
put("content-type",ContentType);
}};
private String data;
public Response(){}
public Response(String protocol, Integer code, String msg) {
this.protocol = protocol;
this.code = code;
this.msg = msg;
}
/**
* 构建响应
* @return
*/
public String buildResponse(){
StringBuilder sb = new StringBuilder();
//根据响应的数据格式,构造响应
sb.append(this.getProtocol()).append(" ")
.append(this.getCode()).append(" ")
.append(this.getMsg()).append("\r\n");
for (Map.Entry<String,String> entry : this.getHeaders().entrySet()){
sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
}
sb.append("\r\n").append(this.getData());
return sb.toString();
}
/**
* 输出响应
* @param os
*/
public void write(OutputStream os){
try {
//把响应数据输出到流套接字里面去
os.write(buildResponse().getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if(os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 加一个响应头
* @param key
* @param value
*/
public void addHeader(String key,String value){
this.getHeaders().put(key,value);
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Map<String, String> getHeaders() {
return headers;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
this.setContentLength(buildResponse().getBytes().length+"");
}
public String getContentType() {
return this.getHeaders().get("content-type");
}
public void setContentType(String contentType) {
this.getHeaders().put("content-type",contentType);
}
public String getContentLength() {
return this.getHeaders().get("content-length");
}
public void setContentLength(String contentLength) {
this.getHeaders().put("content-length",this.data.getBytes().length + "");
}
}
3.3打包验证
代码写好后,打成一个Jar包,然后去浏览器进行验证,过程如下:
将Jar包复制到非中文路径下,然后在编写启动脚本start.bat,内容如下:
最后,启动后,使用浏览器进行验证: