当前位置: 首页 > news >正文

【Linux】38.网络基础(2.1)

文章目录

  • 1. 应用层
    • 1.1 再谈 "协议"
      • 1.1.1 网络版计算器
    • 1.2 HTTP协议
      • 1.2.1 认识URL
      • 1.2.2 urlencode和urldecode
      • 1.2.3 HTTP协议格式
      • 1.2.4 HTTP的方法
      • 1.2.5 HTTP的状态码
      • 1.2.6 HTTP常见Header
      • 1.2.7 最简单的HTTP服务器


1. 应用层

我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层。


1.1 再谈 “协议”

协议是一种 “约定”。socket api的接口,在读写数据时,都是按 “字符串” 的方式来发送接收的。如果我们要传输一些"结构化的数据" 怎么办呢?

1.1.1 网络版计算器

例如, 我们需要实现一个服务器版的加法器。我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端。

约定方案一:

客户端发送一个形如"1+1"的字符串;

这个字符串中有两个操作数, 都是整形;

两个数字之间会有一个字符是运算符, 运算符只能是 + ;

数字和运算符之间没有空格;

约定方案二:

定义结构体来表示我们需要交互的信息;

发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;

这个过程叫做 “序列化” 和"反序列化"

序列化和反序列化是将数据结构或对象转换的过程:

序列化(Serialization):

  • 将内存中的数据结构/对象转换为可存储或传输的格式
  • 常见格式: JSON、XML、二进制等
  • 目的: 便于存储和网络传输

反序列化(Deserialization):

  • 将存储/传输格式的数据恢复为内存中的数据结构/对象
  • 是序列化的逆过程
  • 使数据可被程序再次使用

使用场景:

  1. 网络传输数据
  2. 数据持久化存储
  3. 缓存
  4. 跨语言/平台数据交换
// proto.h - 协议头文件
// 定义客户端和服务器之间通信用的数据结构

// 请求结构体 - 客户端发送给服务器的数据格式
typedef struct Request {
    int a;    // 第一个操作数
    int b;    // 第二个操作数
} Request;

// 响应结构体 - 服务器返回给客户端的数据格式  
typedef struct Response {
    int sum;  // 计算结果
} Response;

// client.c - 客户端程序
Request request;    // 创建请求结构体实例
Response response;  // 创建响应结构体实例

// 从用户输入获取两个整数,格式为"a,b" 
scanf("%d,%d", &request.a, &request.b);

// 将请求结构体写入管道发送给服务器
write(fd, request, sizeof(Request));

// 从管道读取服务器的响应
read(fd, response, sizeof(Response));

// server.c - 服务器程序 
Request request;    // 创建请求结构体实例

// 从客户端读取请求数据
read(client_fd, &request, sizeof(request));

Response response;  // 创建响应结构体实例

// 计算两数之和
response.sum = request.a + request.b;

// 将响应结果写回客户端
write(client_fd, &response, sizeof(response));

无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是ok的。这种约定, 就是应用层协议


1.2 HTTP协议

虽然我们说, 应用层协议是我们程序猿自己定的。

但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)就是其中之一。


1.2.1 认识URL

平时我们俗称的 “网址” 其实就是说的 URL

2419ae549b8b9226c88b7ab4b296a204


1.2.2 urlencode和urldecode

urlencode和urldecode是用于URL编码和解码的方法:

urlencode:

  • 将普通文本转换为URL编码格式
  • 把特殊字符(如空格、中文等)转换为%加上两位16进制数
  • 例如:
    • 空格转换为%20
    • 中文"你好"转换为%E4%BD%A0%E5%A5%BD

urldecode:

  • 将URL编码格式转换回普通文本
  • 把%加两位16进制数转换为对应的字符
  • 是urlencode的反向操作

使用场景:

  1. URL传参时对参数值进行编码
  2. 处理包含特殊字符的URL
  3. 表单提交时对数据进行编码

像 / ? : 等这样的字符, 已经被url当做特殊意义理解了。因此这些字符不能随意出现。

比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义。

转义的规则如下:

将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式

例如:

150634525b1ed1ad37041b3d821e1e99

这里的"+" 被转义成了 “%2B”


1.2.3 HTTP协议格式

协议格式

311c04fa709b3dc6f3a2207cc8d29466

  • 首行: [方法] + [url] + [版本]
  • Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
  • Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度

HTTP请求

17e19d6f625ef130f55d9801409edffb

HTTP响应

570520a73017135374383331bbf272ed

  • 首行: [版本号] + [状态码] + [状态码解释]
  • Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
  • Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中

1.2.4 HTTP的方法

其中最常用的就是GET方法和POST方法

882c3c5094eb22c885a00a5afc49380b


1.2.5 HTTP的状态码

最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

a08b14431feafbe91841b83c031d860e


1.2.6 HTTP常见Header

  • Content-Type: 数据类型(text/html等)
  • Content-Length: 正文Body的长度
  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上
  • User-Agent: 声明用户的操作系统和浏览器版本信息
  • referer: 当前页面是从哪个页面跳转过来的
  • location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问
  • Cookie: 用于在客户端存储少量信息,通常用于实现会话(session)的功能

1.2.7 最简单的HTTP服务器

实现一个最简单的HTTP服务器, 只在网页上输出 “hello world”; 只要我们按照HTTP协议的要求构造数据, 就很容易能做到

// 包含必要的系统头文件
#include <sys/socket.h>    // Socket接口
#include <netinet/in.h>    // 网络地址结构
#include <arpa/inet.h>     // IP地址转换函数
#include <unistd.h>        // UNIX标准函数
#include <stdio.h>         // 标准输入输出
#include <string.h>        // 字符串处理
#include <stdlib.h>        // 标准库函数

// 打印使用说明
void Usage() {
    printf("usage: ./server [ip] [port]\n");
}

int main(int argc, char* argv[]) {
    // 检查命令行参数
    if (argc != 3) {  // 需要IP地址和端口两个参数
        Usage();
        return 1;
    }

    // 创建TCP Socket
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        perror("socket");  // 打印错误信息
        return 1;
    }

    // 配置服务器地址结构
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;                    // 使用IPv4
    addr.sin_addr.s_addr = inet_addr(argv[1]);    // 设置IP地址
    addr.sin_port = htons(atoi(argv[2]));         // 设置端口号

    // 绑定Socket到指定地址和端口
    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if (ret < 0) {
        perror("bind");
        return 1;
    }

    // 开始监听连接请求
    ret = listen(fd, 10);  // 最多排队10个连接
    if (ret < 0) {
        perror("listen");
        return 1;
    } 

    // 主循环:处理客户端连接
    for (;;) {
        // 接受新的客户端连接
        struct sockaddr_in client_addr;  // 客户端地址信息
        socklen_t len;
        int client_fd = accept(fd, (struct sockaddr*)&client_addr, &len);
        if (client_fd < 0) {
            perror("accept");
            continue;  // 继续等待下一个连接
        }

        // 读取客户端请求
        char input_buf[1024 * 10] = {0};  // 10KB的缓冲区
        ssize_t read_size = read(client_fd, input_buf, sizeof(input_buf) - 1);
        if (read_size < 0) {
            return 1;  // 读取失败
        }

        // 打印收到的HTTP请求
        printf("[Request] %s", input_buf);

        // 构造HTTP响应
        char buf[1024] = {0};
        const char* hello = "<h1>hello world</h1>";  // 响应体内容
        // 构造HTTP响应头和响应体
        sprintf(buf, "HTTP/1.0 200 OK\nContent-Length:%lu\n\n%s", 
                strlen(hello), hello);

        // 发送响应给客户端
        write(client_fd, buf, strlen(buf));
    }

    return 0;
}

编译, 启动服务。在浏览器中输入 http://[ip]:[port], 就能看到显示的结果 “Hello World”

http://[云服务器公网IP]:[端口号]

  1. 进入轻量应用服务器,点击这里的管理规则

d450fd8a633ca10f1c9f3ebaf649b60c

  1. 点击添加规则,自定义规则,协议TCP,端口号9090,添加备注,点击确定

70df2967d65c1bd3c050202ddb416f8c

  1. 编译,运行程序mian.cc,生成main文件,执行main文件:./main 0.0.0.0 9090

  2. 浏览器输入网址,http://[ip]:[port], 就能看到显示的结果 “Hello World”

538ebdea50e666ff072407f5a0fc0efe

备注:

此处我们使用 9090 端口号启动了HTTP服务器。虽然HTTP服务器一般使用80端口, 但这只是一个通用的习惯,并不是说HTTP服务器就不能使用其他的端口号。

使用Edge测试我们的服务器时, 可以看到服务器打出的请求中还有一个 GET / HTTP/1.1 这样的请求

4e62f40d35913018fb49a5590f8eb3a7

http://www.dtcms.com/a/61542.html

相关文章:

  • K8S学习之基础二十一:k8s的持久化存储之emptyDir
  • 【AI深度学习网络】Transformer时代,RNN(循环神经网络)为何仍是时序建模的“秘密武器”?
  • 【开源】OpenAL、OpenCL、OpenCV 和 OpenGL
  • [machine learning] DP(Data Parallel) vs DDP(Distributed Data Parallel)
  • 25、C++中的多线程同步机制【中高频】
  • Redis 面试篇
  • Nuxt3 ssr build/dev时区分不同的环境
  • Unity 基础知识总结(持续更新中...)
  • golang从入门到做牛马:第七篇-Go语言常量-不可变的“守护者”
  • 数据清洗级可视化中,Pandasnumyp的主要作用
  • 02C#基本结构篇(D1_基本语法)
  • 使用hutool封装http请求
  • 工厂模式加策略模式 -- 具体实现
  • 若依RuoYi-Cloud-Plus微服务版(完整版)前后端部署
  • 种子填充(Floodfill、泛滥填充、洪水填充) 算法c++模板
  • 固定表头、首列 —— uniapp、vue 项目
  • C#主流日志库深度对比:NLog、log4net与Serilog如何选择?
  • Qt 元对象系统
  • PyCharm 接入 DeepSeek、OpenAI、Gemini、Mistral等大模型完整版教程(通用)!
  • 《Mycat核心技术》第19章:基于MySQL实现读写分离
  • [数据结构]并查集--C++版本的实现代码
  • 【AI】神经网络|机器学习——图解Transformer(完整版)
  • Python数据分析之数据分析工具
  • 【C语言】--- 动态内存管理详解
  • 转自南京日报:天洑软件创新AI+仿真技术变制造为“智造
  • 网络安全反渗透 网络安全攻防渗透
  • 【性能测试】Jmeter详细操作-小白使用手册(2)
  • 常见排序算法深度评测:从原理到10万级数据实战
  • 【产品小白】Axure的简单操作
  • 【NexLM 开源系列】如何封装多个大模型 API 调用