• 网络编程——套接字


    端口号

    进行网络传输时,可以通过IP地址找到指定的机器,但是需要将数据传递给那个程序就需要端口号来决定了。

    端口号(port)是传输层协议的内容,来告诉操作系统当前数据要交给哪个进程来处理

    • 端口号是一个2字节16位的整数;
    • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
    • 一个端口号只能被一个进程占用.

    端口号与进程ID的关系:

    一个进程可以绑定多个端口号,但是一个端口号只能绑定一个进程

    源端口号和目的端口号:

    传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 “数据是谁发的, 要发给谁”;

    网络字节序

    在计算机存储中有着大端和小端之分,网络数据流中也有着大小端之分。

    在这里插入图片描述

    网络数据流传输流程:

    1. 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
    2. 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
    3. 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.

    TCP/IP 协议中规定:网络数据流应采用大端字节序,即低地址高字节。

    如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;

    网络字节序和主机字节序转换函数:

    在这里插入图片描述

    在这里插入图片描述

    关于inet_ntoa

    inet_ntoa这个函数返回了一个char*,man手册上说, inet_ntoa函数, 是把这个返回结果放到了静态存储区. 这个时候不需要我们手动进行释放。但是多次调用该函数可能导致最后一次调用的结果覆盖之前的结果。

    网络套接字

    套接字 = I P 地址 + 端口号 套接字 = IP地址 +端口号 套接字=IP地址+端口号

    网络套接字允许应用程序通过网络与其他计算机或设备进行通信。它们在客户端和服务器之间建立起连接,并负责处理数据的发送和接收。网络套接字通常包含一个IP地址和一个端口号,它们用于标识网络上的特定应用程序或服务。

    有了套接字就能准确无误地将数据从远端进程传递给指定的进程。

    通过网络套接字,应用程序可以使用不同的协议进行通信,如TCP和UDP

    TCP和UDP:

    T C P ( T r a n s m i s s i o n C o n t r o l P r o t o c o l 传输控制协议 ) : TCP(Transmission Control Protocol 传输控制协议) : TCP(TransmissionControlProtocol传输控制协议)

    • 传输层协议
    • 有连接
    • 可靠传输
    • 面向字节流

    U D P ( U s e r D a t a g r a m P r o t o c o l 用户数据报协议 ) : UDP(User Datagram Protocol 用户数据报协议): UDP(UserDatagramProtocol用户数据报协议)

    • 传输层协议
    • 无连接
    • 不可靠传输
    • 面向数据报

    socketaddr结构

    对于不同的协议套接字的结构地址不同,但是底层都是使用同一个结构 sockaddr,不同的类型使用socket API时需要转换为sockaddr类型的指针。

    在这里插入图片描述

    一般使用IPV4进行通信,sockaddr_in结构参数的初始化:

    在这里插入图片描述

    socket常见API及参数

    // 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
    int socket(int domain, int type, int protocol);
    
    // 绑定端口号 (TCP/UDP, 服务器)
    int bind(int socket, const struct sockaddr *address, socklen_t address_len);
    		 
    // 开始监听socket (TCP, 服务器)
    int listen(int socket, int backlog);
    
    // 接收请求 (TCP, 服务器)
    int accept(int socket, struct sockaddr* address, socklen_t* address_len);
    		   
    // 建立连接 (TCP, 客户端)
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    			
    // 接受数据(UDP,客户端/服务端)
    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, 
    				 struct sockaddr *src_addr, socklen_t *addrlen);
    
    // 接受数据(TCP,客户端/服务端)
    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    
    // 发送数据(UDP,客户端/服务端)
    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                   const struct sockaddr *dest_addr, socklen_t addrlen);
    
    // 发送数据(UDP,客户端/服务端)
    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                   const struct sockaddr *dest_addr, socklen_t addrlen);
    
    // 发送数据(TCP,客户端/服务端)
    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    参数解释:

    1. domain: 套接字的协议族,如 AF_INET 表示 Internet 协议族。

    2. type: 套接字的类型,可以是 SOCK_STREAM(流式套接字)或 SOCK_DGRAM(数据报套接字)。

    3. protocol: 指定要使用的协议。如果给定大于 0 的协议号,则使用指定的协议,否则使用默认协议。

    4. sockaddr_in: 存储套接字地址的结构体,包含 IP 地址和端口号等信息。其中 sin_family字段表示协议族,sin_port 字段表示端口号,sin_addr 字段表示 IP 地址。

    5. backlog: 可以连接的客户端数量,即在调用 listen() 函数后,队列中等待连接的最大数量。

    6. sockfd: 套接字描述符,表示对套接字的引用。可以通过 socket() 函数创建,并在之后的操作中使用。

    7. addr: 一个指向存有目标套接字地址的指针,通常为 sockaddr_in 结构体类型的指针。

    8. addrlen: 目标套接字地址的长度,通常为 sizeof(struct sockaddr_in)。

    9. buf: 存储消息内容的缓冲区。

    10. len: 要发送或接收的消息的长度。

    11. flags: 一些特殊的标志位,如 MSG_DONTWAIT、MSG_ERRQUEUE 等。一般设为0即可

    UDP编程流程

    服务端流程

    1. 创建套接字(socket):使用系统调用 socket() 创建一个数据报套接字,用于发送和接收 UDP 数据报。
    #include <sys/socket.h>
    
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    
    • 1
    • 2
    • 3
    1. 绑定地址(bind):使用系统调用 bind() 绑定本地 IP 地址和端口号,使得该套接字能够监听指定的地址。
      IP地址一般设为任意INADDR_ANY,表示接受所有发送到指定端口的数据。
    #include <arpa/inet.h>
    
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port); // 端口号
    addr.sin_addr.s_addr = INADDR_ANY; // 本机 IP 地址
    bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 发送数据(sendto):使用系统调用 sendto() 发送 UDP 数据报到指定的目标地址和端口号。
    #include <unistd.h>
    
    const char* message = "Hello, World!";
    ssize_t sent_bytes = sendto(sockfd, message, strlen(message), 0, 
                               (struct sockaddr*)&dest_addr, sizeof(dest_addr));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 接收数据(recvfrom):使用系统调用 recvfrom() 接收来自指定源地址的 UDP 数据报。该函数是一个阻塞函数,如果没有数据可读会一直等待。
    char buffer[1024];
    struct sockaddr_in src_addr;
    socklen_t addrlen = sizeof(src_addr);
    ssize_t recv_bytes = recvfrom(sockfd, buffer, sizeof(buffer), 0,
                                  (struct sockaddr*)&src_addr, &addrlen);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    客户端流程

    1. 创建套接字(socket):使用系统调用 socket() 创建一个 UDP 套接字,用于发送和接收数据包。
    #include <sys/socket.h>
    
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    
    • 1
    • 2
    • 3
    1. 设置服务器地址和端口号:创建一个 sockaddr_in 结构体,并指定服务器的 IP 地址和端口号。
    #include <arpa/inet.h>
    
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port); // 服务器端口号
    inet_pton(AF_INET, server_ip, &(server_addr.sin_addr));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 发送数据(sendto):使用系统调用 sendto() 向服务器发送数据包。
    const char* message = "Hello, Server!";
    ssize_t sent_bytes = sendto(sockfd, message, strlen(message), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
    
    • 1
    • 2
    1. 接收数据(recvfrom):使用系统调用 recvfrom() 接收来自服务器的数据包。
    char buffer[1024];
    socklen_t addrlen = sizeof(server_addr);
    ssize_t recv_bytes = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&server_addr, &addrlen);
    
    • 1
    • 2
    • 3
    1. 关闭套接字(close):使用系统调用 close() 关闭套接字,释放资源。
    close(sockfd);
    
    • 1

    TCP编程流程

    服务端流程

    1. 创建套接字(socket):使用系统调用 socket() 创建一个 TCP 套接字,用于监听客户端的连接请求。
    #include <sys/socket.h>
    
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    • 1
    • 2
    • 3
    1. 绑定地址(bind):使用系统调用 bind() 绑定服务器的 IP 地址和端口号,使得该套接字能够监听指定的地址。
      IP地址一般设为任意INADDR_ANY,表示接受所有发送到指定端口的数据。
    #include <arpa/inet.h>
    
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port); // 端口号
    addr.sin_addr.s_addr = INADDR_ANY; // 本机 IP 地址
    bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 监听连接(listen):使用系统调用 listen() 开始监听连接请求,指定最大同时连接数。
    #include <sys/types.h>
    #include <sys/socket.h>
    
    int backlog = 5; // 最大同时连接数
    listen(sockfd, backlog);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 接受连接(accept):使用系统调用 accept() 接受客户端的连接请求,返回一个新的套接字用于与客户端进行通信。
    #include <sys/types.h>
    #include <sys/socket.h>
    
    struct sockaddr_in cli_addr;
    socklen_t addrlen = sizeof(cli_addr);
    int newsockfd = accept(sockfd, (struct sockaddr*)&cli_addr, &addrlen);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 接收和发送数据(recv、send):使用系统调用 recv() 和 send() 在新的套接字上进行数据的收发。
    char buffer[1024];
    ssize_t recv_bytes = recv(newsockfd, buffer, sizeof(buffer), 0);
    
    const char* message = "Hello, Client!";
    ssize_t sent_bytes = send(newsockfd, message, strlen(message), 0);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 关闭连接和套接字:使用系统调用 close() 关闭新的套接字和监听套接字。
    close(newsockfd);
    close(sockfd);
    
    • 1
    • 2

    客户端流程

    在 TCP 客户端编程中,需要先创建套接字并设置服务器的地址和端口号,然后通过 connect() 函数与服务器建立连接。在连接建立之后,可以使用 send() 发送数据到服务器,使用 recv() 接收服务器返回的数据。最后,记得关闭连接和套接字,释放资源。

    1. 创建套接字(socket):使用系统调用 socket() 创建一个 TCP 套接字。
    #include <sys/socket.h>
    
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    • 1
    • 2
    • 3
    1. 设置服务器地址和端口号:创建一个 sockaddr_in 结构体,并指定服务器的 IP 地址和端口号。
    #include <arpa/inet.h>
    
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port); // 服务器端口号
    inet_pton(AF_INET, server_ip, &(server_addr.sin_addr));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 连接服务器(connect):使用系统调用 connect() 与服务器建立连接。
    connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    
    • 1
    1. 发送和接收数据(send、recv):使用系统调用 send() 和 recv() 在套接字上进行数据的发送和接收。
    const char* message = "Hello, Server!";
    ssize_t sent_bytes = send(sockfd, message, strlen(message), 0);
    
    char buffer[1024];
    ssize_t recv_bytes = recv(sockfd, buffer, sizeof(buffer), 0);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 关闭连接和套接字:使用系统调用 close() 关闭连接和套接字,释放资源。
    close(sockfd);
    
    • 1
  • 相关阅读:
    【全志T113-S3_100ask】2-编写第一个驱动
    Pytorch分布式训练,其他GPU进程占用GPU0的原因
    使用Python进行机器学习:从基础到实战
    typescript44-对象之间的类兼容器
    《优化接口设计的思路》系列:第三篇—留下用户调用接口的痕迹
    oracle sql语言模糊查询
    超声波清洗机靠谱吗?实用性比较高的超声波清洗机推荐
    Queue & Deque 介绍
    node.js+uniapp(vue),阿里云短信验证码
    牛客网论坛最具争议的Java面试成神笔记,GitHub已下载量已过百万
  • 原文地址:https://blog.csdn.net/weixin_54202947/article/details/132946043