问题解答
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);
}
代码运行结果
结束
代码重在练习!
代码重在练习!
代码重在练习!
今天的分享就到此结束了,希望对你有所帮助,如果你喜欢我的分享,请点赞收藏夹关注,谢谢大家!!!