网络基础

字节序和大小端

字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,也就是说对于单字符来说是没有字节序问题的,字符串是单字符的集合,因此字符串也没有字节序问题。

字符串也没有字节序问题

  • Little-Endian -> 主机字节序 (小端)

数据的低位字节存储到内存的低地址位, 数据的高位字节存储到内存的高地址位
我们使用的PC机,数据的存储默认使用的是小端

  • Big-Endian -> 网络字节序 (大端)

数据的低位字节存储到内存的高地址位, 数据的高位字节存储到内存的低地址位
套接字通信过程中操作的数据都是大端存储的,包括:接收/发送的数据、IP地址、端口。

// 有一个16进制的数, 有32位 (int): 0xab5c01ff
// 字节序, 最小的单位: char 字节, int 有4个字节, 需要将其拆分为4份
// 一个字节 unsigned char, 最大值是 255(十进制) ==> ff(16进制) 
                 内存低地址位                内存的高地址位
--------------------------------------------------------------------------->
小端:         0xff        0x01        0x5c        0xab
大端:         0xab        0x5c        0x01        0xff

大小端转换函数

整形数字的转换

#include <arpa/inet.h>
// u:unsigned
// 16: 16位, 32:32位
// h: host, 主机字节序
// n: net, 网络字节序
// s: short
// l: int

// 这套api主要用于 网络通信过程中 IP 和 端口 的 转换
// 将一个短整形从主机字节序 -> 网络字节序
uint16_t htons(uint16_t hostshort);	
// 将一个整形从主机字节序 -> 网络字节序
uint32_t htonl(uint32_t hostlong);	

// 将一个短整形从网络字节序 -> 主机字节序
uint16_t ntohs(uint16_t netshort)
// 将一个整形从网络字节序 -> 主机字节序
uint32_t ntohl(uint32_t netlong);

IP地址转换

字符串到整形 小段->大端

// 主机字节序的IP地址转换为网络字节序
// 主机字节序的IP地址是字符串, 网络字节序IP地址是整形
int inet_pton(int af, const char *src, void *dst); 

参数:

  • af: 地址族(IP地址的家族包括ipv4和ipv6)协议
    • AF_INET: ipv4格式的ip地址
    • AF_INET6: ipv6格式的ip地址
  • src: 传入参数, 对应要转换的点分十进制的ip地址: 192.168.1.100
  • dst: 传出参数, 函数调用完成, 转换得到的大端整形IP被写入到这块内存中

返回值:成功返回1,失败返回0或者-1

大端->小端 字符串->字符串

#include <arpa/inet.h>
// 将大端的整形数, 转换为小端的点分十进制的IP地址        
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
  • 参数:

    • af: 地址族协议
      AF_INET: ipv4格式的ip地址
      AF_INET6: ipv6格式的ip地址
    • src: 传入参数, 这个指针指向的内存中存储了大端的整形IP地址
    • dst: 传出参数, 存储转换得到的小端的点分十进制的IP地址
    • size: 修饰dst参数的, 标记dst指向的内存中最多可以存储多少个字节
  • 返回值:

成功: 指针指向第三个参数对应的内存地址, 通过返回值也可以直接取出转换得到的IP字符串
失败: NULL

sockaddr结构

// 在写数据的时候不好用,写不方便
struct sockaddr {
	sa_family_t sa_family;       // 地址族协议, ipv4
	char        sa_data[14];     // 端口(2字节) + IP地址(4字节) + 填充(8字节)
}

typedef unsigned short  uint16_t;
typedef unsigned int    uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
typedef unsigned short int sa_family_t;
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))

struct in_addr
{
    in_addr_t s_addr;
};  

// sizeof(struct sockaddr) == sizeof(struct sockaddr_in)
// 所有数据先转换成大端再输入
// 使用sockaddr_in初始化,然后转换成sockaddr进行输入

struct sockaddr_in
{
    sa_family_t sin_family;		/* 地址族协议: AF_INET */
    in_port_t sin_port;         /* 端口, 2字节-> 大端  */
    struct in_addr sin_addr;    /* IP地址, 4字节 -> 大端  */
    /* 填充 8字节 */
    unsigned char sin_zero[sizeof (struct sockaddr) - sizeof(sin_family) -
               sizeof (in_port_t) - sizeof (struct in_addr)];
};  

通信流程

服务器端流程

监听的文件描述符listen_fd只需要有一个,它只用于监听连接,而不参与收发数据。

用于通信的文件描述符correspond_fd负责和客户端进行通信,有多少个客户端建立了连接,就有多少个correspond_fd,与之对应的,客户端也有一个correspond_fd负责和服务端通信。

  1. 创建一个用于监听的套接字,是一个文件描述符
int lfd = socket();
  1. 将文件描述符和本地的一个端口绑定
bind();
  1. 设置(监听成功之后开始监听, 监听的是客户端的连接)
listen();
  1. 等待并接受客户端的连接请求, 建立新的连接, 会得到一个新的文件描述符(通信的),没有新连接请求就阻塞
int cfd = accept();
  1. 读写操作 默认是阻塞的
// 接收数据
read(); / recv();
// 发送数据
write(); / send();
  1. 断开
close();

服务器端流程

  1. 创建一个通信的套接字
int cfd = socket();
  1. 连接服务器,需要知道IP和端口
connect();
  1. 读写操作 默认是阻塞的
// 接收数据
read(); / recv();
// 发送数据
write(); / send();
  1. 断开
close();
posted @ 2023-10-18 21:35  LiviaYu  阅读(19)  评论(0)    收藏  举报