TCPIP网络编程 -- (三)地址族与数据序列
TCP/IP网络编程 -- (三)地址族与数据序列
3.2地址信息的表示
创建好一个 socket 并准备监听传入连接时,要调用 bind 函数来指定 socket 监听的ip地址与端口
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
因此需要知道三个信息:
- 采用哪一种地址族:IPv4等
- IP地址是多少:211.204.214.76
- 端口号是多少: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向内存保存数据/解析数据的方法有大端序,小端序两种
因此在网络运输中,要约定统一的格式为网络字节序 = 大端序,因此要先把数据转换为大端序再进行网络传输
需要主机字节序与网络字节序转换的函数
#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;
}