表示IPv4地址的结构体

1
2
3
4
5
6
struct sockaddr_in {
sa_family_t sin_family; // 地址族(Address Family)
uint16_t sin_port; // 16位TCP/UDP端口号
struct in_addr sin_addr; // 32位IP地址
char sin_zero[8]; // 不使用
};

其中的struct in_addr里面为一个32位整型

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

sockaddr_in的成员分析

sin_family

地址族 含义
AF_INET IPv4网络协议中使用的地址族
AF_INET6 IPv6网络协议中使用的地址族
AF_LOCAL 本地通信中采用的UNIX协议的地址族

sin_port

保存16位端口号,且以网络字节序保存。

sin_addr

保存32位IP地址信息,也以网络字节序保存。

sin_zero

无特殊含义。方便结构体sockaddr_in的大小与sockaddr结构体保持一致。必须填充0。

sockaddr

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

字节序与网络字节序

CPU像内存保存数据的方式有2种

  • 大端序:高位字节存放到低位地址。
  • 小端序:高位字节存放到高位地址。

对于网络字节序,统一采用大端序。

字节序转换

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

htons中的h代表主机字(host)节序,n代表网络(network)字节序。

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

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

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

可以向该函数传递类似"xxx.xxx.xxx.xxx"十进制格式的字符串。

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

// 成功时返回1,失败返回0
int inet_aton(const char* string, struct in_addr* addr);

inet_aton函数和inet_addr函数功能完全相同,但inet_aton可以自动填入in_addr的结构体。

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

// 成功返回转换的字符串地址,失败返回-1
char* inet_ntoa(struct in_addr adr);

该函数将整数型IP地址转换为字符串格式并返回。

网络地址初始化

套接字常见的初始化方式。

1
2
3
4
5
6
7
struct sockaddr_in addr;
char* serv_ip = "123.124.1.23"; // 声明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_port = htons(atoi(serv_port)); // 基于字符串的端口号初始化

INADDR_ANY

每次创建套接字都要输入IP会很繁琐,所以可以利用INADDR_ANY

1
2
3
4
5
6
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));

该方式可自动获取运行服务器端的计算机IP地址。

向套接字分配网络地址

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

// sockfd 要分配地址信息的套接字文件描述符
// myaddr 存有地址信息的结构体变量地址值
// addrlen 第二个结构体变量的长度
int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 = htonl(INADDR_ANY);
serv_addr.sin_port = htons(atoi(serv_port));

// 分配地址信息
bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr));
...

基于UDP的数据I/O函数

由于UDP套接字不会保持连接状态,因此每次传输数据都需要添加目标地址信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <sys/socket.h>

// sock 用于传输数据的UDP套接字文件描述符
// buff 保存待传输数据的缓冲地址值
// nbytes 待传输的数据长度,字节为单位
// flags 可选项参数,没有则传0
// to 存有目标地址信息的sockaddr结构体变量的地址值
// addrlen 传递给参数to的地址值结构体变量长度
ssize_t sendto(int sock, void* buff, size_t nbytes,
int flags, struct sockaddr* to, socklen_t addrken);

// from 存有发送端地址信息的sockaddr结构体变量的地址值
ssize_t recvfrom(int sock, void* buff, size_t nbytes,
struct sockaddr* from, socklen_t* addrlen);

断开连接的shutdown函数

shutdown函数用来关闭其中的一个流。

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

// sock 需要断开的套接字文件描述符
// howto 传递断开方式信息
int shutdown(int sock, int howto);

对于第二个参数有:

  • SHUT_RD:断开输入流。
  • SHUT_WR:断开输出流。
  • SHUT_RDWR:同时断开I/O流。