TCPIP网络编程 -- (三)地址族与数据序列

TCP/IP网络编程 -- (三)地址族与数据序列

3.2地址信息的表示

创建好一个 socket 并准备监听传入连接时,要调用 bind 函数来指定 socket 监听的ip地址与端口

#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);

因此需要知道三个信息:

  1. 采用哪一种地址族:IPv4等
  2. IP地址是多少:211.204.214.76
  3. 端口号是多少:8888

所以需要用一个结构体来回答上述三个问题,并将该结构体作为地址信息传递给 bind 函数

struct in_addr {
    In_addr_t 		s_addr; //32位IPv4地址
};

struct sockaddr_in {
  	sa_family_t 	sin_family; //地址族
    uint16_t 		sin_port;//16位TCP/UDP端口号
    struct 			in_addr sin_addr;//32位IP地址
    char 			sin_zero[8];//不使用,为了使该结构体长度与 sockaddr 长度相等
};

**再传入 bind 函数时将 sockaddr_in 类型转换为 sockaddr 类型,一开始用 sockaddr_in 结构体只是为了编程方便
bind 函数的第二个参数是一个指向 sockaddr 类型的指针,用于指定要绑定的地址信息。但是,sockaddr 类型并不方便直接使用,因为它只包含了地址族和地址信息两个字段,而没有提供用于设置 IP 地址和端口号的字段。

因此,通常我们会使用 sockaddr_in 类型来设置地址信息。sockaddr_in 类型包含了用于设置 IP 地址和端口号的字段,使用起来更加方便。在调用 bind 函数时,我们需要将 sockaddr_in 类型强制转换为 sockaddr 类型,以满足 bind 函数的参数要求。

struct sockaddr_in serv_addr;
int serv_sock;
bind(serv, (struct sockaddr*)&serv_addr, sizeof serv_addr);

3.3网络字节序与地址变换

CPU向内存保存数据/解析数据的方法有大端序,小端序两种

image

因此在网络运输中,要约定统一的格式为网络字节序 = 大端序,因此要先把数据转换为大端序再进行网络传输

需要主机字节序与网络字节序转换的函数

#include <arpa/inet.h>
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohs(unsigned long);

unsigned short host_addr = 0x12345678;
net_addr = htonl(host_addr);

其中 h 代表主机字节序(host),n 为网络字节序(net)

不用 int 类型的原因是在 linux 中,long 始终为 4 个字节,而 int 的大小不一致

注意只有在向 sockaddr_in 结构体填充数据时需要考虑字节序的问题,其他情况下已经自动转换好了,程序员无需考虑

3.4网络地址的初始化与分配

注意到 sockaddr_in 结构体中是用 32 为整形来存储 IP 地址的,因此需要将点分十进制法表示的 IP 转换成 32 位整型大端序

#include <arpa/inet.h>
in_addr_t inet_addr(const char* string) //成功返回32位大端序整数,失败返回 INADDR_NONE

char *addr1 = 1.2.3.4;
unsigned long conv_addr = inet_addr(addr1);
if (conv_addr == INADDR_NONE)
    printf("ERROR!");

该函数还可检查 IP 地址是否合法,如果点分十进制表示中某位超过了 255,则会返回 INADDR_NONE

除了使用 inet_addr 函数外,也可以使用 inet_aton 函数,直接将字符串形式的 IP 地址转换 32 位网络序

#include <arpa/inet.h>
int inet_aton(const char* string, struct in_addr* addr);//成功返回 1,失败返回 0
//string 为待转换的 ip 地址,addr 为 sockaddr_in 结构体中储存地址的结构体 in_addr 指针
char *addr = "127.232.124.79";
struct sockaddr_in addr_inet;
if (!inet_aton(addr, &addr_inet.sin_addr))
    printf("ERROR!");

此外,该函数会自动将 addr 转换为网络序后的值写入 addr_inet.sin_addr 中

网络地址初始化

#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>

int main() {
    struct sockaddr_in addr;
    char *serv_ip = "211.217.168.13";
    char *serv_port = "9190";
    memset(&addr, 0, sizeof addr);
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(serv_ip);
    addr.sin_port = htons(atoi(serv_port));
    return 0;
}

每次创建服务器端 socket 都要输入 IP 地址,可以用 INADDR_ANY 来代替

struct sockaddr_in addr;
char *serv_port = "9190";
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(atoi(serv_port));

常数 INADDR_ANY 会自动获取服务器端的 IP 地址。如果同一个计算机有多个IP地址(多宿主计算机,路由器属于这一类,IP地址的数量与 NIC 相等),只要端口号一致,每个 IP 地址都可以接受数据。因此服务器优先考虑这种模式

将 socket 分配 IP 的过程

//第三章末尾,向套接字分配网络地址的过程
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

int main() {
    int serv_sock;
    struct sockaddr_in serv_addr;
    char *serv_port = "9190";

    //创建服务器端 socket
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);

    //地址信息初始化
    memset(&serv_addr, 0, sizeof serv_addr);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(serv_port));

    //分配地址信息
    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof serv_addr);
    
    //...
    return 0;
}
posted @ 2023-03-13 22:01  hzy0227  阅读(28)  评论(0编辑  收藏  举报