自学内容网 自学内容网

详解TCP和UDP通信协议

目录

OSI的七层模型的主要功能

tcp是什么

TCP三次握手

为什么需要三次握手,两次握手不行吗

TCP四次挥手

挥手会什么需要四次

什么是TCP粘包问题?发生的原因

原因

解决方案

UDP是什么

TCP和UDP的区别

网络层常见协议

利用socket进行tcp传输代码示例

客户端

服务端

利用socket进行udp传输代码示例

客户端

服务端

注意tcp的send/recv和udp的sendto/recvfrom有一点区别

OSI的七层模型的主要功能

  • **物理层:**利用传输介质为数据链路层提供物理连接,实现比特流的透明传输。
  • **数据链路层:**接收来自物理层的位流形式的数据,并封装成帧,传送到上一层
  • **网络层:**将网络地址翻译成对应的物理地址,并通过路由选择算法为分组通过通信子网选择最适当的路径。
  • **传输层:**在源端与目的端之间提供可靠的透明数据传输
  • **会话层:**负责在网络中的两节点之间建立、维持和终止通信
  • **表示层:**处理用户信息的表示问题,数据的编码,压缩和解压缩,数据的加密和解密
  • **应用层:**为用户的应用进程提供网络通信服务

tcp是什么

TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,即位于传输层。

TCP三次握手

三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小 信息。

为什么需要三次握手,两次握手不行吗

TCP四次挥手

建立一个连接需要三次握手,而终止一个连接要经过四次挥手(也有将四次挥手叫做四次握手的)。这由TCP的半关闭(half-close)造成的。所谓的半关闭,其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。
TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),客户端或服务器均可主动发起挥手动作。

挥手会什么需要四次

什么是TCP粘包问题?发生的原因

一个完整的业务可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,即是无边界的流传输,这个就是TCP的粘包问题。

  • 封包:封包就是在发送数据报的时候为每个TCP数据包加上一个包头,将数据报分为包头和包体两个部分。包头是一个固定长度的结构体,里面包含该数据包的总长度。
  • 拆包:接收方在接收到报文后提取包头中的长度信息进行截取。

原因

1、应用程序写入数据的字节大小大于套接字发送缓冲区的大小.

2、进行MSS大小的TCP分段。( MSS=TCP报文段长度-TCP首部长度)

3、以太网的payload大于MTU进行IP分片。(MTU指:一种通信协议的某一层上面所能通过的最大数据包大小。)

解决方案

1、消息定长。
2、在包尾部增加回车或者空格符等特殊字符进行分割
3、将消息分为消息头和消息尾
4、使用其它复杂的协议,如RTMP协议等。

UDP是什么

是一种提供无连接的,尽最大努力的数据传输服务(不保证数据传输的可靠性)的通信协议,即位于传输层。

TCP和UDP的区别

1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的前不需要建立连接

2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付

3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流:UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)

4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

5、TCP首部开销20字节;UDP的首部开销小,只有8个字节

6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

7、UDP是面向报文的,发送方的UDP对应用层交下来的报文,不合并,不拆分,只是在其上面加上首部后就交给了下面的网络层,论应用层交给UDP多长的报文,它统统发送,一次发送一个。而对接收方,接到后直接去除首部,交给上面的应用层就完成任务了。因此,它需要应用层控制报文的大小

TCP是面向字节流的,它把上面应用层交下来的数据看成无结构的字节流会发送,可以想象成流水形式的,发送方TCP会将数据放入“蓄水池”(缓存区),等到可以发送的时候就发送,不能发送就等着TCP会根据当前网络的拥塞状态来确定每个报文段的大小。

网络层常见协议

利用socket进行tcp传输代码示例

客户端

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[1024] = {0};

    // 创建 socket 文件描述符
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        std::cout << "Socket creation error" << std::endl;
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 将服务端地址转换为二进制
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        std::cout << "Invalid address / Address not supported" << std::endl;
        return -1;
    }

    // 连接到服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cout << "Connection Failed" << std::endl;
        return -1;
    }

    // 发送数据到服务器
    const char *hello = "Hello from client";
    send(sock, hello, strlen(hello), 0);
    std::cout << "Hello message sent" << std::endl;

    // 读取服务器发送的数据
    int valread = read(sock, buffer, 1024);
    std::cout << "Received: " << buffer << std::endl;

    close(sock);
    return 0;
}

服务端

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[1024] = {0};

    // 创建 socket 文件描述符
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 将 socket 绑定到端口 8080
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 接受客户端连接
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 读取客户端发送的数据
    int valread = read(new_socket, buffer, 1024);
    std::cout << "Received: " << buffer << std::endl;

    // 发送数据到客户端
    const char *hello = "Hello from server";
    send(new_socket, hello, strlen(hello), 0);
    std::cout << "Hello message sent" << std::endl;

    close(new_socket);
    close(server_fd);
    return 0;
}

利用socket进行udp传输代码示例

客户端

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080

int main() {
    int sockfd;
    char buffer[1024];
    struct sockaddr_in servaddr;

    // 创建 socket 文件描述符
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    memset(&servaddr, 0, sizeof(servaddr));

    // 填写服务器信息
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    servaddr.sin_addr.s_addr = INADDR_ANY;

    // 发送消息到服务器
    const char *hello = "Hello from client";
    sendto(sockfd, hello, strlen(hello), MSG_CONFIRM, (const struct sockaddr *)&servaddr, sizeof(servaddr));
    std::cout << "Hello message sent." << std::endl;

    // 接收服务器消息
    int n;
    socklen_t len;
    n = recvfrom(sockfd, (char *)buffer, 1024, MSG_WAITALL, (struct sockaddr *)&servaddr, &len);
    buffer[n] = '\0';
    std::cout << "Server : " << buffer << std::endl;

    close(sockfd);
    return 0;
}

服务端

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080

int main() {
    int sockfd;
    char buffer[1024];
    struct sockaddr_in servaddr, cliaddr;

    // 创建 socket 文件描述符
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    memset(&cliaddr, 0, sizeof(cliaddr));

    // 填写服务器信息
    servaddr.sin_family = AF_INET; // IPv4
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(PORT);

    // 绑定 socket
    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    int n;
    socklen_t len;
    len = sizeof(cliaddr); // 客户端地址长度

    // 接收客户端消息
    n = recvfrom(sockfd, (char *)buffer, 1024, MSG_WAITALL, (struct sockaddr *)&cliaddr, &len);
    buffer[n] = '\0';
    std::cout << "Client : " << buffer << std::endl;

    // 发送消息到客户端
    const char *hello = "Hello from server";
    sendto(sockfd, hello, strlen(hello), MSG_CONFIRM, (const struct sockaddr *)&cliaddr, len);
    std::cout << "Hello message sent." << std::endl;

    close(sockfd);
    return 0;
}

注意tcp的send/recv和udp的sendto/recvfrom有一点区别


原文地址:https://blog.csdn.net/m0_71124168/article/details/140262007

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!