目录
1、概念了解
2、Socket通信的基本流程
3、TCP报文协议格式
4、TCP的三次握手
5、TCP的四次挥手
6、TCP的数据重传
7、滑动窗口
8、其他
9、小结
10、代码实现
1、概念了解 HTTP (hypertest transfer protocol) http协议是建议在tcp协议 之上,工作在应用层 ;是web互联的基础,也是手机联网常用的协议之一。 UDP: 建立在IP协议之上,工作在传输层;效率高,不可靠,允许丢失和重发;常用于视屏聊天,丢帧可以接受; TCP: 建立在IP协议之上,工作在传输层;可靠传输协议,三次握手;常用于文件可靠传输; IP协议: IP协议是TCP/IP 的核心协议,工作在网络层,所有的TCP.UDP.ICMP.IGMP都是按照这种格式发送数据的,IP提供了一种“ 尽力而为”的服务; SOCKET套接字:是支持 TCP/IP协议的网络通信的基本单元, scoket是通信的基石。 网络通信的五元素:他是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:
- 连接使用的协议;
- 本地主机的ip地址;
- 本地进程的协议端口;
- 远地主机的ip地址;
- 远地进程的协议端口;
文章图片
2、Socket通信的基本流程
文章图片
3、TCP报文协议格式
文章图片
TCP常见flag:
- URG 当为1时,用来保证TCP连接不被中断, 并且将该次TCP内容数据的紧急程度提升(就是告诉电脑,你丫赶快把这个给resolve了)
- ACK 用来应答的,通常是服务器端返回的。 用来表示应答是否有效。 1为有效,0为无效
- PSH 表示,当数据包得到后,立马给应用程序使用(PUSH到最顶端)
- RST 用来确保TCP连接的安全。 该flag用来表示 一个连接复位的请求。 如果发生错误连接,则reset一次,重新连。当然也可以用来拒绝非法数据包。
- SYN 用来同步的, 通常是由客户端发送,用来建立连接的。第一次握手时: SYN:1 , ACK:0. 第二次握手时: SYN:1 ACK:1
- FIN 用来表示是否结束该次TCP连接。 通常当你的数据发送完后,会自动带上FIN 然后断开连接
- Seq:包头的序列号;
- (1)只有三次握手才能保证,客户端和服务端的相互确认,从而进行可靠的数据传输;
- (2)防止实效的连接被server接受,产生较多的无效的半连接;
- (3)三次是保证客户端和服务端进行可靠传输的最低值,大于三次就多余了。
- 第一步:客户端发起关闭,只能证明客户端没有数据要发送了;
- 第二步:server端收到fin信号,回复一个ack表示收到关闭通知了,但是server端可能还有数据要发送;
- 第三步:server端发送数据完毕,发送fin信号告诉客户端可以关闭了;
- 第四步:client收到fin信号后等待2ms后关闭;
- (1)序列号突然下降,一般是TCP重传,因为重传是基于积累确认机制,当发现某个数据包丢失后,所有其后面的数据包都不会被确认接收,所以必须重传该数据包,那么它的序列号一定比某些数据包的序列号小。
- (2)如果发现某一时刻出现了两个相同的数据包,包括数据的长度,序列号甚至应用数据一样,则说明,该数据包是重传数据包。
- “滑动”则是指这段“允许发送的范围”是可以随着发送的过程而变化的,变化的方式就是按顺序“滑动”;
- “窗口”对应的是一段可以被发送者发送的字节序列,其连续的范围称之为“窗口”;
文章图片
文章图片
滑动窗口动态效果演示: https://media.pearsoncmg.com/aw/ecs_kurose_compnetwork_7/cw/content/interactiveanimations/selective-repeat-protocol/index.html 对ACK的认识 ACK通常被理解为收到数据或某种请求后发送一个ACK确认信息,其实ACK包含两个重要的信息: 一、期望接收到的下一个字节的序号n,该n代表接收方已经接收到了前n-1个字节,此时如果接收到的是n+1而不是n个字节,接收方式不会发送n+2的ACK的。如上图所示,如果收到发送端发来的5,6,7两个字节,而没有收到4这个字节接收端是不会发送ACK=8的,而依旧发送ACK=4,因为这样就会激活超时消息重传功能,确保4能正常的接收。 二、当前的窗口大小m,如此发送方在接收到ACK包含的这两个数据后就可以计算出还可以发送多少个字节给对方,假定当前发送方已经发送到第n个字节,期待下一个要发送的字节的序号为X,则发送端可以发送的字节数为:send=m-(n-X); 窗口的大小属性 TCP的window是一个16bit位字段,它代表的是窗口的字节容量,也就是TCP标准窗口最大为2^16-1=65535个字节;另外在TCP的选项字段中还包含了一个TCP窗口的扩展因子,option-kind为:3,option-length为3个字节,option-data取值范围0-14。窗口扩大因子用来扩大TCP窗口,可把原来的16bit的窗口,扩大为31bit。 发送窗口和接收窗口: TCP是双工的协议,会话的双方都可以同时接收、发送数据。TCP会话的双方都各自维护一个“发送窗口”和一个“接收窗口”。其中各自的“接收窗口”大小取决于应用、系统、硬件的限制,但是tcp的传输速率不能大于应用的数据处理能力,否则就会缓冲区淹没。各自的“发送窗口”则取决于对端通告的“接收窗口”,也就是取决于接收的缓冲区的大小。 发送窗口只有在收到对端对于本段发送窗口内字节的ACK确认,才会移动发送窗口的左边界。 接收窗口只有在前面的所有数据确认收到的情况下,才会移动左边的窗口。当在前面还有未接受的字节,而后面的字节已接收的情况下,窗口也不会移动,并不会对后续的字节进行确认。一次确保对端会对这些未收到的数据进行重传。主要涉及到接收端的累积确认接收机制。 8、其他 (1)Dos攻击 或者 SYN攻击? DenialOfService 拒绝服务攻击: 利用大量无效的ip请求,第二次握手后,服务端处于SYN_RECV状态,server不断的超时重发确认,造成第三次握手失败创造大量的半连接请求,导致半连接队列被无效请求占满,致使正常的syn请求被拒绝,目标系统运行变慢,引起网络拥塞及系统瘫痪。 监测方法:
- 当服务器上有大量的半连接状态的连接,就说明遭遇了syn攻击了;
- http的header头数据的压缩:content-encoding:gzip;
- node端很简单,只要加上compress模块即可;
- nginx:gzip on 打开即可
#include
#include
#include
#include
#include
#include
#include
#define BACKLOG 5//完成三次握手但没有accept的队列的长度
#define CONCURRENT_MAX 8//应用层同时可以处理的连接
#define SERVER_PORT 11332
#define BUFFER_SIZE 1024
#define QUIT_CMD ".quit"
int client_fds[CONCURRENT_MAX];
int main(int argc, const char * argv[])
{
int i;
char input_msg[BUFFER_SIZE];
char recv_msg[BUFFER_SIZE];
//本地地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero), 8);
//创建-socket
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if(server_sock_fd == -1)
{
perror("socket error");
return 1;
}//绑定-socket
int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(bind_result == -1)
{
perror("bind error");
return 1;
}//侦听-listen
if(listen(server_sock_fd, BACKLOG) == -1)
{
perror("listen error");
return 1;
}//fd_set
fd_set server_fd_set;
int max_fd = -1;
struct timeval tv;
//超时时间设置
while(1)
{
tv.tv_sec = 20;
tv.tv_usec = 0;
FD_ZERO(&server_fd_set);
FD_SET(STDIN_FILENO, &server_fd_set);
if(max_fd0)
{
int index = -1;
for( i = 0;
i < CONCURRENT_MAX;
i++)
{
if(client_fds[i] == 0)
{
index = i;
client_fds[i] = client_sock_fd;
break;
}
}
if(index >= 0)
{
printf("新客户端(%d)加入成功 %s:%d\n", index, inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
}
else
{
bzero(input_msg, BUFFER_SIZE);
strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");
send(client_sock_fd, input_msg, BUFFER_SIZE, 0);
printf("客户端连接数达到最大值,新客户端加入失败 %s:%d\n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
}
}
}
for( i =0;
i < CONCURRENT_MAX;
i++)
{
if(client_fds[i] !=0)
{
if(FD_ISSET(client_fds[i], &server_fd_set))
{
bzero(recv_msg, BUFFER_SIZE);
//处理某个客户端过来的消息
long byte_num = recv(client_fds[i], recv_msg, BUFFER_SIZE, 0);
if (byte_num > 0)
{
if(byte_num > BUFFER_SIZE)
{
byte_num = BUFFER_SIZE;
}
recv_msg[byte_num] = '\0';
printf("客户端(%d):%s\n", i, recv_msg);
}
else if(byte_num < 0)
{
printf("从客户端(%d)接受消息出错.\n", i);
}
else
{
FD_CLR(client_fds[i], &server_fd_set);
client_fds[i] = 0;
printf("客户端(%d)退出了\n", i);
}
}
}
}
}
}
return 0;
}
客户端实现:
#include
#include
#include
#include
#include
#include
#include
#define BUFFER_SIZE 1024int main(int argc, const char * argv[])
{
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(11332);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero), 8);
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if(server_sock_fd == -1)
{
perror("socket error");
return 1;
}
char recv_msg[BUFFER_SIZE];
char input_msg[BUFFER_SIZE];
if(connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) == 0)
{
fd_set client_fd_set;
struct timeval tv;
while(1)
{
tv.tv_sec = 20;
tv.tv_usec = 0;
FD_ZERO(&client_fd_set);
FD_SET(STDIN_FILENO, &client_fd_set);
FD_SET(server_sock_fd, &client_fd_set);
select(server_sock_fd + 1, &client_fd_set, NULL, NULL, &tv);
if(FD_ISSET(STDIN_FILENO, &client_fd_set))
{
bzero(input_msg, BUFFER_SIZE);
fgets(input_msg, BUFFER_SIZE, stdin);
if(send(server_sock_fd, input_msg, BUFFER_SIZE, 0) == -1)
{
perror("发送消息出错!\n");
}
}
if(FD_ISSET(server_sock_fd, &client_fd_set))
{
bzero(recv_msg, BUFFER_SIZE);
long byte_num = recv(server_sock_fd, recv_msg, BUFFER_SIZE, 0);
if(byte_num > 0)
{
if(byte_num > BUFFER_SIZE)
{
byte_num = BUFFER_SIZE;
}
recv_msg[byte_num] = '\0';
printf("服务器:%s\n", recv_msg);
}
else if(byte_num < 0)
{
printf("接受消息出错!\n");
}
else
{
printf("服务器端退出!\n");
exit(0);
}
}
}
}
return 0;
}
资料参考:
【网络编程|网络基本功(TCP报文及可靠性保证)】《TCP/IP详解》
推荐阅读
- 网络编程|TCP/IP网络编程(8) 基于Linux的多进程服务器
- C++|基于协程io_uring 异步网络库系列 III( Proactor、异步与协程 | C++20 coroutine 教程 | io_uring 异步IO 网络框架 系列笔记)
- C++后台开发|7 张图,轻松掌握零拷贝原理
- 网络编程|【网络编程】TCP的五层协议栈之TCP协议
- 网络编程|【UNIX网络编程】|【06】基本UDP套接字编程【数据报丢失、性能、流量控制....】
- 网络编程|走进boost
- 学习教程|php搭建网站入口搭建细节
- 网络编程|网络编程——socket套接字、黏包
- 网络编程|【网络编程】计算机网络重点知识详解(IP协议+MAC地址+路由+DNS+NAT+以太网帧+MTU+ARP)