Linux网络编程 多线程Web服务器:HTTP协议与TCP并发实战

news2025/4/24 22:20:27

问题解答

TCP是如何防止SYN洪流攻击的?

方式有很多种,我仅举例部分:

1、调整内核参数

我们知道SYN洪流攻击的原理就是发送一系列无法完成三次握手的特殊信号,导致正常的能够完成三次握手的信号因为 连接队列空间不足,无法正常传输。

(1)增加 未连接队列 的容量

(2)缩短 未连接信号 的超时时间

(3)减少SYN-ACK重传次数

这里主要介绍一下(3):在三次握手过程中,当服务器收到客户端的SYN请求时,会回应SYN-ACK请求。当客户端不回应,则会重传。因此我们这里可以减少SYN-ACK重传次数,剪短一个异常信号占用内存空间的时间,SYN-ACK重传次数不易太少,太少会降低TCP传输数据的可靠度。((2)(3)原理都是差不多的,目的都是减少异常信号占用内存资源的时间)

具体操作需要用到bash操作,大家自行AI查询即可。

2、防火墙

防火墙会限制单个IP的并发SYN请求速率。

知识点1【Web服务器的概述】

web服务器使用的传送协议是:HTTP(超文本传送协议),基于TCP

HTML语言,超文本编辑语言,用于显示,可以脱离系统

HTTP协议 超文本传送协议 用于数据的传输

URL同一地址定位符 网址

访问的时候不能阻塞别人访问这个服务器,因此需要 实现的是并发服务器

TCP并发服务器又分为 线程版和进程版,我们这里选择线程版。

客户端是浏览器 我们需要实现的是服务器

因为TCP是传输层协议,HTTP是应用层,我们类似于在UDP中,TFTP协议理解,我们在TCP并发服务器的基础上,在核心代码(子进程的任务体代码(对应TCP并发服务器文章))中,以HTTP的专属报文格式进行解包组包即可。

web服务器的好处,无论是安卓,还是windows,Linux,都可以访问

特点

1、支持C/S架构

2、简单快速,客户端服务器申请服务时,只需要传送方法和路径,方法有GET和POST

3、无连接:限制每次连接只处理一个请求,且每一个请求是独立的 浏览器连接服务器只会有一个请求,即每次请求只要一个文件,不会要多个

文本算一个客户端,图片算一个客户端,图片文本分开存储,浏览器想要图片,需要向服务器发出请求,申请图片,即打开一个网页时,可能有几十个,甚至几百个客户端连接服务器。每个客户端一个请求

浏览器只需要传输请求的方式(GET/POST),GET是明文,POST是密文。

举例说明,当我们使用GET的方式,会把输入的内容放在红框部分,做成URL,发给服务器,如果是密码,那只要别人一抓包,密码就泄露了。

POST是密文传输,不过多介绍了。

4、无状态:如果后续处理需要前面的信息,必须重传,这样

HTTP传送文件没有固定大小限制

固定大小和大小限制的区别:

固定大小限制:不会限制为固定的字节大小发送

固定大小限制:最大发送多少字节

补充

HTML 超文本标记语言:显示文本

HTTP 超文本传输协议:传输协议

URL 同一地址定位符

浏览器的请求方式:

IP:端口号/请求的文件

浏览器会发出,向IP的端口,申请文件的请求(由浏览器完成)

解析文件名,打开本地文件(成功/失败)

以下都是报文格式,请仔细查看

注意

着重关注文件名,GET后有一个空格,文件名后有一个空格

服务器应答的格式:请求失败

服务器应答的格式:请求成功

现在我们通过代码实现一下

知识点2【多进程并发服务器代码复习】

这里我们先复习一下 多线程并发服务器的创建

1、创建套接字

2、绑定端口

3、监听(将套接字改为监听套接字,产生连接列表)

4、循环

提取已连接套接字

创建子进程

(1)子进程中

关闭监听套接字

任务体

关闭已连接套接字

关闭子进程

(2)父进程

关闭已连接套接字

回收子进程空间

5、关闭监听套接字

下面先实现多线程并发服务器

代码演示

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <strings.h> //bzero
#include <unistd.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <stdlib.h> //atoi

//释放子进程空间函数
void release_space(int signal)
{
    while(1)
    {
        int ret_wait = waitpid(-1,NULL,WNOHANG);
        if(ret_wait == 0 || ret_wait < 0)
        {
            break;
        }
    }
}
//子进程任务体(核心函数)
void fun_subprocess(int fd)
{
    while(1)
    {
        char buf[256] = "";
        int len = recv(fd,buf,sizeof(buf),0);
        if(len < 0)
        {
            perror("recv");
            _exit(-1);
        }
        send(fd,buf,sizeof(buf),0);
        if(len == 0)
        {
            break;
        }
    }
}

int main(int argc, char const *argv[])
{
    //参数个数判断
    if(argc != 2)
    {
        printf("demo:./a.out 8000\\n");
        _exit(-1);
    }

    //创建套接字
    int fd_sock = socket(AF_INET,SOCK_STREAM,0);
    if(fd_sock < 0)
    {
        perror("socket");
        _exit(-1);
    }

    //实现端口复用
    int opt;//这个opt,作用是二次确认
    setsockopt(fd_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

    //绑定套接字
    struct sockaddr_in addr_bind;
    bzero(&addr_bind,sizeof(addr_bind));
    addr_bind.sin_family = AF_INET;
    addr_bind.sin_port = htons(atoi(argv[1]));
    addr_bind.sin_addr.s_addr = htonl(INADDR_ANY);
    int ret_bind = bind(fd_sock,(struct sockaddr *)&addr_bind,sizeof(addr_bind));
    if(ret_bind < 0)
    {
        perror("bind");
        _exit(-1);
    }

    //监听套接字
    int ret_listen = listen(fd_sock,10);
    if(ret_listen < 0)
    {
        perror("listen");
        _exit(-1);
    }

    //循环
    while (1)
    {
        //提取已连接套接字
        struct sockaddr_in addr_accept;
        bzero(&addr_accept,sizeof(addr_accept));
        int len_accept = sizeof(addr_accept);
        int fd_accept = accept(fd_sock,(struct sockaddr *)&addr_accept,&len_accept);
        if(fd_accept < 0)
        {
            perror("accept");
            _exit(-1);
        }

        //创建子进程
        int pid = fork();
        if(pid < 0)
        {
            perror("fork");
            _exit(-1);
        }
        
        //子进程
        if(pid == 0)
        {
            //遍历一下 已连接客户端的IP,端口号,以及为其分配的进程号
            char buf_IP[16] = "";
            inet_ntop(AF_INET,&addr_accept.sin_port,buf_IP,sizeof(buf_IP));
            int int_port = ntohs(addr_accept.sin_port);
            printf("[%s:%d] pid:%d\\n",buf_IP,int_port,getpid());

            //关闭监听套接字
            close(fd_sock);

            //任务体
            fun_subprocess(fd_accept);

            //关闭接受套接字
            close(fd_accept);

            //关闭子进程
            _exit(0);
        }

        //父进程
        else
        {
            //关闭接受套接字
            close(fd_accept);

            //释放子进程
            signal(SIGCHLD,release_space);
        }
    }
    //关闭监听套接字
    close(fd_sock);
    return 0;
}

代码运行结果

非常流畅,大家可以自己动手写一下

知识点3【代码演示】

知识点补充

下面我们来写一下Web服务器的代码

首先我们先了解一下web服务器的收到的数据格式,这里我们只介绍明文方式

GET /文件名

前面我们着重标记了空格的位置,希望大家在这里能够理解,这是我们下面解包的重要方式

在打开文件时,我们需要对打开文件(.html)是否成功进行判断,下面分别是 HTTP协议,服务器应答成功与失败的报文格式

//打开失败,服务器应答
char err[] = "HTTP/1.1 404 Not Found\\r\\n"
"Content‐Type: text/html\\r\\n"
"\\r\\n"
"<HTML><BODY>File not found</BODY></HTML>";
//打开成功,服务器应答
char head[] = "HTTP/1.1 200 OK\\r\\n"
"Content‐Type: text/html\\r\\n"
"\\r\\n";

一定要严格遵守这个报文格式,哪怕是多一个 \0都不可以,因此发送长度的时候,要用strlen,这里大家用的时候直接复制粘贴即可,手敲反而容易出错。

在读取内容的时候,大家用循环读取,按照recv()函数的返回值作为判断条件即可,我在这里建议大家使用do{}while()类型,这样代码会更加简洁。

最后,请大家不要忘记相关文件描述符的关闭问题。

下面我们 来实践一下

代码演示

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <strings.h> //bzero
#include <unistd.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <stdlib.h> //atoi
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

char err[] = "HTTP/1.1 404 Not Found\\r\\n"
             "Content‐Type: text/html\\r\\n"
             "\\r\\n"
             "<HTML><BODY>File not found</BODY></HTML>";
char head[] = "HTTP/1.1 200 OK\\r\\n"
              "Content‐Type: text/html\\r\\n"
              "\\r\\n";
// 函数声明
// 释放子进程空间函数
void release_space(int signal);

// 子进程任务体(核心函数)
void fun_subprocess(int arg);

int main(int argc, char const *argv[])
{
    // 参数个数判断
    if (argc != 2)
    {
        printf("demo:./a.out 8000\\n");
        _exit(-1);
    }

    // 创建套接字
    int fd_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (fd_sock < 0)
    {
        perror("socket");
        _exit(-1);
    }

    // 实现端口复用
    int opt; // 这个opt,作用是二次确认
    setsockopt(fd_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 绑定套接字
    struct sockaddr_in addr_bind;
    bzero(&addr_bind, sizeof(addr_bind));
    addr_bind.sin_family = AF_INET;
    addr_bind.sin_port = htons(atoi(argv[1]));
    addr_bind.sin_addr.s_addr = htonl(INADDR_ANY);
    int ret_bind = bind(fd_sock, (struct sockaddr *)&addr_bind, sizeof(addr_bind));
    if (ret_bind < 0)
    {
        perror("bind");
        _exit(-1);
    }

    // 监听套接字
    int ret_listen = listen(fd_sock, 10);
    if (ret_listen < 0)
    {
        perror("listen");
        _exit(-1);
    }

    // 循环
    while (1)
    {
        // 提取已连接套接字
        struct sockaddr_in addr_accept;
        bzero(&addr_accept, sizeof(addr_accept));
        int len_accept = sizeof(addr_accept);
        int fd_accept = accept(fd_sock, (struct sockaddr *)&addr_accept, &len_accept);
        if (fd_accept < 0)
        {
            perror("accept");
            _exit(-1);
        }

        // 创建子进程
        int pid = fork();
        if (pid < 0)
        {
            perror("fork");
            _exit(-1);
        }

        // 子进程
        if (pid == 0)
        {
            // 遍历一下 已连接客户端的IP,端口号,以及为其分配的进程号
            char buf_IP[16] = "";
            inet_ntop(AF_INET, &addr_accept.sin_port, buf_IP, sizeof(buf_IP));
            int int_port = ntohs(addr_accept.sin_port);
            printf("[%s:%d] pid:%d\\n", buf_IP, int_port, getpid());

            // 关闭监听套接字
            close(fd_sock);

            // 任务体
            fun_subprocess(fd_accept);

            // 关闭接受套接字
            close(fd_accept);

            // 关闭子进程
            _exit(0);
        }

        // 父进程
        else
        {
            // 关闭接受套接字
            close(fd_accept);

            // 释放子进程
            signal(SIGCHLD, release_space);
        }
    }
    // 关闭监听套接字
    close(fd_sock);
    return 0;
}

// 释放子进程空间函数
void release_space(int signal)
{
    while (1)
    {
        int ret_wait = waitpid(-1, NULL, WNOHANG);
        if (ret_wait == 0 || ret_wait < 0)
        {
            break;
        }
        else
        {
            printf("进程%d已退出\\n", ret_wait);
        }
    }
}

// 客户端核心任务线程函数
void fun_subprocess(int arg)
{
    unsigned char cmd_buf[1024] = "";
    int len = recv(arg, cmd_buf, sizeof(cmd_buf), 0);
    if (len <= 0)
    {
        close(arg);
        _exit(-1);
    }

    char file_name[128] = "./html/";
    sscanf(cmd_buf, "GET /%[^ ]", file_name + 7);
    if (file_name[7] == '\\0')
    {
        strcat(file_name, "index.html");
    }
    printf("file_name=##%s##\\n", file_name);
    // open打开本地文件
    int fd = open(file_name, O_RDONLY);
    if (fd < 0)
    {
        // 告诉浏览器404
        send(arg, err, strlen(err), 0);
        close(arg);
        perror("open");
        _exit(-1);
    }
    // 告诉浏览器 200 打开成功准备接受
    send(arg, head, strlen(head), 0);
    //循环读取本地文件数据发送给浏览器
    while (1)
    {
            unsigned char buf[512] = "";
            // 读取本地文件数据
            int len = read(fd, buf, sizeof(buf));
            send(arg, buf, len, 0);
            if (len < 512)
                break;
    }
    close(fd);
    _exit(-1);
}

代码运行结果

结束

代码重在练习!

代码重在练习!

代码重在练习!

今天的分享就到此结束了,希望对你有所帮助,如果你喜欢我的分享,请点赞收藏夹关注,谢谢大家!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2341985.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt 下载的地址集合

Qt 下载离线安装包 download.qt.io/archive/qt/5.14/5.14.2/ Qt 6 安装下载在线安装包 Index of /qt/official_releases/online_installers/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror

ubuntu下gcc/g++安装及不同版本切换

1. 查看当前gcc版本 $ gcc --version# 查看当前系统中已安装版本 $ ls /usr/bin/gcc*2. 安装新版本gcc $ sudo apt-get update# 这里以版本12为依据&#xff08;也可以通过源码方式安装&#xff0c;请自行Google&#xff01;&#xff09; $ sudo apt-get install -y gcc-12 g…

FPGA入门学习Day1——设计一个DDS信号发生器

目录 一、DDS简介 &#xff08;一&#xff09;基本原理 &#xff08;二&#xff09;主要优势 &#xff08;三&#xff09;与传统技术的对比 二、FPGA存储器 &#xff08;一&#xff09;ROM波形存储器 &#xff08;二&#xff09;RAM随机存取存储器 &#xff08;三&…

微信小程序拖拽排序有效果图

效果图 .wxml <view class"container" style"--w:{{w}}px;" wx:if"{{location.length}}"><view class"container-item" wx:for"{{list}}" wx:key"index" data-index"{{index}}"style"--…

WT2000T专业录音芯片:破解普通录音设备信息留存、合规安全与远程协作三大难题

在快节奏的现代商业环境中&#xff0c;会议是企业决策、创意碰撞和战略部署的核心场景。然而&#xff0c;传统会议记录方式常面临效率低、信息遗漏、回溯困难等痛点。如何确保会议内容被精准记录并高效利用&#xff1f;会议室专用录音芯片应运而生&#xff0c;以智能化、高保真…

【Python 学习笔记】 pip指令使用

系列文章目录 pip指令使用 文章目录 系列文章目录前言安装配置使用pip 管理Python包修改pip下载源 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 当前文章记录的是我在学习过程的一些笔记和思考&#xff0c;可能存在有误解的地方&#xff0c;仅供大家…

C# 文件读取

文件读取是指使用 C# 程序从计算机文件系统中获取文件内容的过程。将存储在磁盘上的文件内容加载到内存中&#xff0c;供程序处理。主要类型有&#xff1a;文本文件读取&#xff08;如 .txt, .csv, .json, .xml&#xff09;&#xff1b;二进制文件读取&#xff08;如 .jpg, .pn…

leetcode125.验证回文串

class Solution {public boolean isPalindrome(String s) {s s.replaceAll("[^a-zA-Z0-9]", "").toLowerCase();for(int i0,js.length()-1;i<j;i,j--){if(s.charAt(i)!s.charAt(j))return false;}return true;} }

【Android面试八股文】Android系统架构【一】

Android系统架构图 1.1 安卓系统启动 1.设备加电后执行第一段代码&#xff1a;Bootloader 系统引导分三种模式&#xff1a;fastboot&#xff0c;recovery&#xff0c;normal&#xff1a; fastboot模式&#xff1a;用于工厂模式的刷机。在关机状态下&#xff0c;按返回开机 键进…

【数据可视化-21】水质安全数据可视化:探索化学物质与水质安全的关联

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

【prometheus+Grafana篇】从零开始:Linux 7.6 上二进制安装 Prometheus、Grafana 和 Node Exporter

&#x1f4ab;《博主主页》&#xff1a;奈斯DB-CSDN博客 &#x1f525;《擅长领域》&#xff1a;擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控&#xff1b;并对SQLserver、NoSQL(MongoDB)有了解 &#x1f496;如果觉得文章对你有所帮…

STM32(M4)入门:GPIO与位带操作(价值 3w + 的嵌入式开发指南)

一&#xff1a;GPIO 1.1 了解时钟树&#xff08;必懂的硬件基础&#xff09; 在 STM32 开发中&#xff0c;时钟系统是一切外设工作的 “心脏”。理解时钟树的工作原理&#xff0c;是正确配置 GPIO、UART 等外设的核心前提。 1.1.1 为什么必须开启外设时钟&#xff1f; 1. 计…

Linux419 三次握手四次挥手抓包 wireshark

还是Notfound 没连接 可能我在/home 准备配置静态IP vim ctrlr 撤销 u撤销 配置成功 准备关闭防火墙 准备配置 YUM源 df -h 未看到sr0文件 准备排查 准备挂载 还是没连接 计划重启 有了 不重启了 挂载准备 修改配置文件准备 准备清理缓存 ok 重新修改配…

CSS-跟随图片变化的背景色

CSS-跟随图片变化的背景色 获取图片的主要颜色并用于背景渐变需要安装依赖 colorthief获取图片的主要颜色. 并丢给背景注意 getPalette并不是个异步方法 import styles from ./styles.less; import React, { useState } from react; import Colortheif from colorthief;cons…

解决Docker 配置 daemon.json文件后无法生效

vim /etc/docker/daemon.json 在daemon中配置一下dns {"registry-mirrors": ["https://docker.m.daocloud.io","https://hub-mirror.c.163.com","https://dockerproxy.com","https://docker.mirrors.ustc.edu.cn","ht…

虚幻基础:ue碰撞

文章目录 碰撞&#xff1a;碰撞体 运动后 产生碰撞的行为——碰撞响应由引擎负责&#xff0c;并向各自发送事件忽略重叠阻挡 碰撞响应关系有忽略必是忽略有重叠必是重叠有阻挡不一定阻挡&#xff08;双方都为阻挡&#xff09; 碰撞启用&#xff1a;纯查询&#xff1a;开启移动检…

数据治理体系的“三驾马车”:质量、安全与价值挖掘

1. 执行摘要 数据治理已从合规驱动的后台职能&#xff0c;演变为驱动业务成果的战略核心。本文将深入探讨现代数据治理体系的三大核心驱动力——数据质量、数据安全与价值挖掘——它们共同构成了企业在数字时代取得成功的基石。数据质量是信任的基石&#xff0c;确保决策所依据…

leetcode 二分查找应用

34. Find First and Last Position of Element in Sorted Array 代码&#xff1a; class Solution { public:vector<int> searchRange(vector<int>& nums, int target) {int low lowwer_bound(nums,target);int high upper_bound(nums,target);if(low high…

Ngrok 内网穿透实现Django+Vue部署

目录 Ngrok 配置 注册/登录 Ngrok账号 官网ngrok | API Gateway, Kubernetes Networking Secure Tunnels 直接cmd运行 使用随机生成网址&#xff1a;ngrok http 端口号 使用固定域名生成网址&#xff1a;ngrok http --domain你的固定域名 端口号 Django 配置 1.Youre a…

利用OLED打印调试信息: 控制PC13指示灯点灯的实验

Do口暗的时候才是高电平,因为光敏电阻传感器的高电平是依靠LM393电压比较器上引脚进入高阻态再加上上拉电阻上拉产生的高电平DO口什么时候会输出高阻态?电压比较器的正极输入电压大于负极输入电压,而正极输入电压是光敏电阻分得的电压,光敏电阻的阻值越大,已分得的电压就越大,…