文章

[TCP/IP网络编程] 地址族与数据序列

[TCP/IP网络编程] 地址族与数据序列

分配给套接字的IP地址与端口号

IP是网络协议的简写,IP协议规定了数据包的格式,地址怎么编码、怎么寻址、怎么路由转发。为了收发网络数据我们需要按照IP协议来给计算机分配一个值(IP地址)。端口号是为了区分程序中创建的套接字而分配给套接字的编号(从操作系统的视角来看,端口号是分配给进程的,端口最终被某个进程占用,进程通过套接字持有)。

IP地址

IP地址分为两类:

  • IPv4 : 4字节地址族
  • IPv6: 16字节地址族

IPv4标准的4字节IP地址分为网络地址和主机地址。分为A、B、C、D、E等类型。当我们向一个IP地址传数据时,首先向相应的网络地址传输数据(构成网络的路由器或交换机),之后由路由器或交换机根据主机地址向目标主机传数据。

地址信息的表示

应用程序中使用的IP地址和端口号以结构体的形式给出定义。

表示IPv4地址的结构体

此结构体将作为地址信息传递给bind函数

1
2
3
4
5
6
7
8
9
10
struct sockaddr_in {
    sa_family_t			sin_family;	//地址族
    uint16_t			sin_port;	//16位端口号
    struct in_addr		sin_addr;	//32位IP地址
    char 				sin_zero[0]	//结构体填充
}

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

bind函数的使用:

1
2
3
4
struct sockaddr_in serv_addr;

if(bind(serv_sock, (strcut sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
    	error_handling("bind() error");

这里的第二个参数期望得到sockaddr结构体变量的地址值,包括地址族、端口号、IP地址等。

1
2
3
4
struct sockaddr {
	sa_family_t 		sin_family;		// 地址族
	char				sa_data[14];	// 地址信息
}

其中sa_data保存的地址信息中需要包含IP地址和端口号,剩余应该填充0,但是直接填写非常麻烦,所以我们需要一个新的结构体sockaddr_in,我们填写sockaddr_in后将其转换位sockaddr型的结构体变量,再传递给bind函数使用。

网络字节序与地址变换

不同CPU中数据在内存中的保存方式是不同的,分为大端序和小端序两种。因此,在通过网络传输数据时需要按照约定统一方式,这种约定称为网络字节序,统一为大端序。

也就说,我们要把数据数组转换成大端序格式后再进行网络传输。因此所有的计算机接收到的数据应该识别该数据是网络字节序格式,小端序系统传输数据时应该转换为大端序排列方式。

字节序转换

1
2
3
4
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);

其中h代表主机(host)字节序,n代表网络字节序,s和l分别表示数据类型

网络地址的初始化与分配

将字符串信息转换为网络字节序的整数型

sockaddr_in中保存的地址信息为32位整型,因此,为了分配IP地址,需要将其转换位32位整数型数据。

1
2
3
4
5
6
7
#include <arpa/inet.h>

in_addr_t inet_addr(const char* string);	//成功返回32位大端序整数型,失败返回INADDR_NONE

int inet_aton(const chr* string, strcut in_addr* addr);	//成功返回1,失败返回-1

char* inet_ntoa(strcut in_addr adr); //整数型IP转为字符串,成功返回字符串地址值,失败返回-1

网络地址初始化

1
2
3
4
5
6
7
8
strcut sockaddr_in addr;
char* serv_ip = "211.217.168.13";	//声明IP地址为字符串
char* serv_port = "9190";			//声明端口号字符串
memset(&addr, 0, sizeof(addr));		//初始化为0
addr.sin_family = AF_INET;			//指定地址族
addr.sin_addr.s_addr = inet_addr(serv_ip);	//基于字符串的IP地址初始化
// addr.sin_addr.s_addr = htonl(INADDR_ANY); //自动获取服务器端的IP地址
addr.sin_port = htons(atoi(serv_port));		//基于字符串端口号初始化

向套接字分配网络地址

1
2
3
4
5
6
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen);
// sockfd	套接字文件描述符
// myaddr	存有地址信息的结构体变量地址值
// addrlen	第二个结构体变量的长度

下面展示了服务器端常见的套接字初始化过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int serv_sock;
struct sockaddr_in serv_addr;
char *serv_port = "9190";

//创建服务器套接字
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 = htol(INADDR_ANY);
serv_addr.sin_port = htos(atoi(serv_port));

//分配地址信息
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
本文由作者按照 CC BY 4.0 进行授权