【网络编程套接字】IP地址 & 端口 & TCP 及 UDP 协议实现

IP地址

作用:在网络当中唯一标识一台主机

本质:IPV4:uint32_t 类型的值,最大的范围是 42 亿多,采用点分十进制来表示 IP 地址(例:172.16.99.129),每一个字节能表示的最大数据为 255

目的 IP 地址:标识数据去向
源 IP 地址:标识数据来向

IPV6:16个字节的整数,128 位的无符号整型数据,IPV6 天然不向下兼容 IPV4

port

作用:在一台主机当中标识一个进程

本质:uint16_t 端口的范围 0 - 65535,其中 0 - 1023 是知名端口,例:mysql – 3306,oracle – 1521

使用:网络当中的程序,通信的时候,都是需要使用端口进行通信
客户端:主动发起请求的一方,被称为客户端
服务端:被动的在固定位置上接受请求的一方是服务器

问题:为什么不使用 PID 作为请求与在主机当中查找进程的标准?
答案:由于进程的关闭和重启会导致每次的 PID 不一样,对用户访问造成了困扰

在网络程序当中存在两个端口
源端口:src_port,目的端口:dest_port

在网络程序当中的一条数据当中存在一个 5 元组
src_ip,src_port,dest_ip,dest_port,protocol(协议)

字节序

大端字节序:低地址存高位
小端字节序:低地址存低位
网络字节序:CPU 对内存当中的数据进行存取的顺序,网络的通信标准是大端字节序
主机字节序:当前计算机的字节序,一般情况下,x86_64 机器都是小端机器
大小端机器需要进行通信的时候,都是需要遵循网络字节序(大端字节序),小端字节序在网络通信时则需要进行大小端转换

uint32_t htonl(uint32_t hostlong); 将32位的主机字节序的数据转换成为网络字节序
h:host 主机
to:转换
n:network
l:32位的整型数据
uint32_t ntohl(uint32_t netlong); 将32位的网络字节序的数据转换成为主机字节序
uint16_t htonl(uint16_t hostlong); 将32位的主机字节序的数据转换成为网络字节序
uint16_t ntohl(uint16_t netlong); 将32位的网络字节序的数据转换成为主机字节序

传输层的两个协议

TCP(Transmission Control Protocol)传输控制协议
面向连接、可靠传输、字节流服务
UDP(User Datagram Protocol) 用户数据报协议
无连接、不可靠、面向数据报

UDP 套接字编程的流程

在这里插入图片描述
理解数据的缓冲区
在这里插入图片描述

UDP 编程的接口

  1. 创建套接字
int socket(int domain, int type, int protocol)
    domain:地址域,传入的是协议的版本
        网络层:AF_INEF --> ipv4 版本的 ip 协议
                      AF_INEF6 --> ipv6 版本的 ip 协议
    type:套接字的类型
        传输层:tcp / udp
            SOCK_STREAM:流式套接字 --> 默认对应的协议:tcp,不支持 udp
            SOCK_DGRAM:数据报套接字 --> 默认对应的协议:udp,不支持 tcp
    protocol:协议类型
        0:采用套接字对应的默认类型
        IPPROTO_TCP --> 6
        IPPROTO_UDP --> 17
        返回值:返回套接字的操作句柄,其实就是一个文件描述符,一般称之为套接字描述符
  1. 绑定地址信息(客户端不推荐绑定地址信息)
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen)
    sockfd:套接字操作句柄,socket 函数的返回值
    addr:
        struct sockaddr
        {
            sa_family_t sa_family;//填充地址域的,当前协议版本,占两个字节
            char sa_data[14];//填充地址信息,14个字节
            //ipv4:port(2) + ip(4) + 填充信息(8)
        }

ipv4 结构体在这里插入图片描述
bind 函数可以兼容不同协议的地址信息,在使用 bind 函数的时候,进行强转就可以,sa_family_t 表示哪一个协议填充了地址类型,方便内核去解析后面的 14 个字节

问题:为什么要传入地址信息的长度?
答案:为了兼容地址信息的结构体长度已经超过了 sockaddr 定义的 16 个字节,例如:ipv6 版本的 ip 协议,单单是 ip 地址的长度就占 16 字节,把当前地址信息的长度传递给内核,内核在解析传入的地址信息的时候,就会按照传入的长度进行解析

  1. 发送数据
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);
    sockfd:套接字的操作句柄
    buf:要发送什么数据
    len:数据的长度
    flags:0:阻塞发送
    dest_addr:目标主机的地址信息(目的 ip + 目的 port)
    addrlen:地址信息长度
  1. 接收接口
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);
      sockfd:套接字的操作句柄
      buf:从接收缓冲区拿到的数据存放位置
     len:接收 buf 定义的最大长度,预留 \0 的位置
     flags:0:阻塞接收
     src_addr:源主机的地址信息(ip + port)
     addrlen:地址信息长度,输入输出型参数,作为入参,指定传入源主机地址信息结构体的长度,作为出参,将实际的地址信息长度返回
  1. 关闭套接字
close(int sockfd);

TCP 套接字编程的流程

在这里插入图片描述
连接流程
在这里插入图片描述

TCP 编程的接口

  1. 监听接口
int listen(int sockfd, int backlog)
    sockfd:指定套接字操作句柄
    backlog:已完成连接队列的大小
         同一时刻,服务端最大的并发连接数

监听的时候,一旦有新的连接到来,OS 会对新的连接分配一个 socket,进行 1 对 1 服务

  1. 接收连接
int accept(int sockfd, struct sockaddr* addr, socklen_t addrlen);
    sockfd:侦听 socket
    addr:客户端地址信息
    addrlen:客户端地址信息的长度,输入输出型参数
    返回值:返回的是操作系统内核创建的新的 socket 文件描述符
  1. 客户端的发起连接的接口
int connect(int sockfd, const sockaddr* addr, socklen_t addrlen)
    sockfd:套接字描述符
    addr:服务器的地址信息(当前客户端需要连接哪一个服务端的地址信息)
    addlen:地址信息长度
  1. 发送数据
ssize_t send(int sockfd, void* buf, size_t len, int flags);
    sockfd:accept函数返回的操作系统内核新创建的 socket 句柄
    buf:要给对端发送什么数据
    len:发送数据的长度
    flags:0:阻塞发送
    dest_addr:目标主机的地址信息(目的 ip + 目的 port)
    addrlen:地址信息长度
  1. 接收接口
ssize_t recv(int sockfd, void* buf, size_t len, int flags);
      sockfd:accept函数的返回值
      buf:从接收缓冲区拿到的数据存放位置
     len:接收 buf 定义的最大长度,预留 \0 的位置
     flags:0:阻塞接收
                MSG_PEEK:探测接收(不会将接收缓冲区当中的数据擦除,而是拷贝接收缓冲区的数据,接收缓冲区当中还是原有的数据)
  1. 关闭套接字
close(int sockfd);
发布了60 篇原创文章 · 获赞 122 · 访问量 8759

猜你喜欢

转载自blog.csdn.net/qq_44759710/article/details/104783513