网络交互笔记 | 青训营

132 阅读6分钟

计算机网络相关概念

  1. MAC地址与IP地址 IP地址(Internet Protocol Address):互联网协议地址,为互联网的每一个网络和每一台主机分配一个逻辑地址,用以屏蔽物理地址的差异,属于网络层。IP地址会根据地理位置产生变化。目前的版本有IPV4(32位)和IPV6(128位)。网络层协议使数据从一个网络传递到另一个网络上。 MAC地址(Media Access Control Address):媒体访问控制地址。用于在网络中唯一标识一个网卡,长度为48位,只与硬件设备有关,属于数据链路层。数据链路层协议使数据从一个节点抽到你到相同链路上的另一个节点。
  2. 路由协议 23 张图详解路由协议:计算机网络的核心技术 - 知乎 (zhihu.com)
  3. ARP协议(Address Resolution Protocol):地址解析协议。是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到局域网络上的所有主机,并接收返回消息,以此确定目标的物理地址;收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。ARP广播请求不能跨网段发送。请求广播,应答单播。
  4. NAT(Network Address Translator):网络地址转换。用于在本地网络中使用私有地址,在连接互联网时使用全局IP,实际上是为了解决IPV4地址短缺问题。
  5. 容灾
  6. 网络传输协议:DNS,UDP,TCP,HTTP,HTTPS,HTTP2.0,QUIC

TCP的拥塞算法

常见的TCP拥塞控制算法有四种:慢启动,拥塞避免,快重传,快恢复。目的是根据网络的拥塞程度动态调整发送方的拥塞窗口,以提高带宽利用率且避免数据丢失

慢启动:用于确定网络的负载能力或拥塞程度 算法思想:由小到大逐渐增大拥塞窗口(cwnd),最多达到一个最大报文段MSS的数值1

拥塞避免 算法思想:让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样可以避免突然增加网络负载而导致拥塞的情况。

快重传 算法思想:当发送方连续收到三个重复确认时,就立即重传对方尚未收到的报文段,而不必等待重传计时器到期。这样可以减少因为超时而导致的数据传输延迟。

快恢复 算法思想:当发现有报文丢失时,并不会立刻进入慢启动状态,而是通过很巧妙的方法,即保证在数据重传过程中的拥塞避免,又能在数据确认时的快速恢复。具体来说,当发送方连续收到三个重复确认时,就把慢开始门限减半,并重传丢失的报文段,然后把拥塞窗口设置为慢开始门限加三个报文段大小。这样可以避免因为超时而导致的拥塞窗口重新从1开始增长。

数据包的封包与拆包

封包代码示例

import struct

def pack_data(num, string):
    # 根据具体数据格式进行pack操作
    # 这里假设数据格式为4字节的整数和字符串
    packed_data = struct.pack("!I4s", num, string.encode("utf-8"))
    return packed_data

# 模拟需要发送的数据
num = 1
string = "Hello"

# 封包
packed_data = pack_data(num, string)
print(packed_data)  # 输出: b'\x00\x00\x00\x01Hello'

拆包代码示例

import struct

def unpack_data(data):
    # 根据具体数据格式进行unpack操作
    # 这里假设数据格式为4字节的整数和字符串
    unpacked_data = struct.unpack("!I4s", data)
    num = unpacked_data[0]
    string = unpacked_data[1].decode("utf-8")
    return num, string

# 模拟接收到的数据包
received_data = b'\x00\x00\x00\x01Hello'

# 拆包
num, string = unpack_data(received_data)
print(num)  # 输出: 1
print(string)  # 输出: Hello

网络接入

免费ARP使用场景:局域网内加一台机器,局域网内其他机器之前未访问过它,访问它无缓存,效率较慢,这时该机器会发送免费ARP给其他机器。服务器内新增IP时也会发送免费ARP,主要为了防止IP冲突。

TCP

sequence number(序列号)是发送方给每个发送的数据包分配的唯一标识符。它用于确保接收方按正确的顺序重组数据包。序列号通常是一个递增的整数,每次发送一个新的数据包时递增。

acknowledge number(确认号)是接收方向发送方发送的数据包的响应。它表示接收方期望接收的下一个序列号。确认号用于告知发送方,接收方已经成功接收了哪些数据包。确认号通常是发送方已经成功接收并正确处理的最后一个序列号加1。

python代码模拟示例

# 发送方
sequence_number = 0
data = "Hello, World!"

# 将序列号添加到数据包中
packet = str(sequence_number) + "|" + data

# 发送数据包

# 接收方
expected_sequence_number = 0

# 接收数据包
received_packet = "0|Hello, World!"

# 解析序列号和数据
packet_parts = received_packet.split("|")
received_sequence_number = int(packet_parts[0])
received_data = packet_parts[1]

# 检查序列号是否与预期的一致
if received_sequence_number == expected_sequence_number:
    # 处理数据
    print("Received data:", received_data)
    
    # 更新确认号
    acknowledgment_number = received_sequence_number + 1
    
    # 发送确认包
    acknowledgment_packet = str(acknowledgment_number)
    
    # 更新预期的序列号
    expected_sequence_number += 1

UDP socket实现ack

发送方(client)

import socket

# 创建UDP Socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 设置超时时间(单位:秒)
client_socket.settimeout(1)

# 目标服务器地址和端口
server_address = ('127.0.0.1', 12345)

# 待发送的数据
data = b'Hello, Server!'

while True:
    try:
        # 发送数据
        client_socket.sendto(data, server_address)

        # 等待ACK
        ack, address = client_socket.recvfrom(1024)

        # 收到ACK,继续发送下一个数据
        print('Received ACK:', ack.decode())
        break
    except socket.timeout:
        # 超时未收到ACK,重新发送数据
        print('Timeout! Retransmitting data...')
        continue

# 关闭Socket
client_socket.close()

接收方(server)

import socket

# 创建UDP Socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 绑定服务器地址和端口
server_address = ('127.0.0.1', 12345)
server_socket.bind(server_address)

while True:
    # 接收数据
    data, address = server_socket.recvfrom(1024)

    # 处理数据
    print('Received data:', data.decode())

    # 发送ACK
    server_socket.sendto(b'ACK', address)

# 关闭Socket
server_socket.close()

c++代码

#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <sys/socket.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 12345

struct Packet {
    int seqNum;
    char data[1024];
};

int main() {
    int clientSocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (clientSocket == -1) {
        std::cerr << "Failed to create socket" << std::endl;
        return -1;
    }

    struct sockaddr_in serverAddress{};
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, SERVER_IP, &(serverAddress.sin_addr)) <= 0) {
        std::cerr << "Invalid address/Address not supported" << std::endl;
        return -1;
    }

    Packet packet{};
    packet.seqNum = 0;
    std::strcpy(packet.data, "Hello, server!");

    if (sendto(clientSocket, &packet, sizeof(Packet), 0, (struct sockaddr *) &serverAddress, sizeof(serverAddress)) == -1) {
        std::cerr << "Failed to send data" << std::endl;
        return -1;
    }

    Packet ackPacket{};
    if (recvfrom(clientSocket, &ackPacket, sizeof(Packet), 0, nullptr, nullptr) == -1) {
        std::cerr << "Failed to receive ACK" << std::endl;
        return -1;
    }

    std::cout << "Received ACK with sequence number: " << ackPacket.seqNum << std::endl;

    close(clientSocket);
    return 0;
}