1. 端口号
在同一台主机或设备上,可能有多个进程同时在使用TCP或UDP协议,端口号的作用就是区分这些不同的进程,即每个进程使用各自不同的端口号。
对于TCP协议和UDP协议,端口号都是用unsigned short类型表示,即端口号的范围为0-65535,这65536个端口号被分为3段:
- 众所周知端口(well-known port):范围为0-1023,这些端口被赋予了专用功能,如FTP的21端口、Web的80端口等,应用程序不能将它们用作其他功能
- 已登记端口(registered port):范围为1024-49151
- 临时端口:范围为49152-65535
well-known端口在Unix系统中称为保留端口,使用保留端口的服务器程序,必须以root用户启动运行。
2. 套接字对
TCP套接字对是定义TCP连接两个端点的四元组,包括:本地IP地址、本地端口号、对端IP地址、对端端口号,每个端点的IP地址和端口号通常称为一个套接字。
TCP套接字对可用于唯一标识一个网络中的TCP连接。
3. 套接字地址结构
大多数套接字API都需要一个指向套接字地址结构的指针作为函数参数,每个协议族都定义它自己的套接字地址结构,这些结构的名字均以sockaddr_开头,并以对应每个协议族的唯一后缀结尾。
IPv4套接字地址结构
IPv4套接字地址结构通常也称为“网际套接字地址结构”,它以sockaddr_in
命名,定义在<netinet/in.h>
头文件中。
#include <netinet/in.h>
struct in_addr
{
in_addr_t s_addr; //32-bit IPv4 address, network byte ordered
};
struct sockaddr_in
{
uint8_t sin_len; //length of structure
sa_family_t sin_family; //AF_INET
in_port_t sin_port; //16-bit TCP Or UDP port number, network byte ordered
struct in_addr sin_addr; //IPv4 address structure
char sin_zero[8]; //unused
};
- IPv4地址和端口号在套接字地址结构中必须以网络字节序存储
- IPv4地址可以按sockaddr_in.sin_addr和sockaddr_in.sin_addr.s_addr两种方式引用
- 按照使用惯例,总是先把整个结构请0,再填写相应字段
- 网络通讯时,传递的是套接字地址结构中的某些字段,结构本身仅在当前主机上使用
通用套接字地址结构
通用套接字地址结构struct sockaddr
定义在<sys/socket.h>
头文件中。
#include <sys/socket.h>
struct sockaddr
{
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[4];
};
对于应用程序开发人员来说,通用套接字地址结构的唯一用途就是:将特定协议的套接字地址结构指针强制类型转换为通用套接字地址结构指针,并传递给套接字API。
bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
4. 字节排序函数
- IPv4地址和端口号必须以网络字节序存储,TCP/IP协议中的网络字节序采用大端字节序
- 某个给定系统使用的字节序叫做主机字节序,主机字节序可能为大端字节序或小端字节序
- 在填写套接字地址结构时,必须将IPv4地址和端口号从主机字节序转换为网络字节序
- 字节排序函数用于在主机字节序和网络字节序之间进行转换
#include <arpa/inet.h>
/* 主机字节序——>网络字节序 */
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
/* 网络字节序——>主机字节序 */
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
上面4个字节排序函数只适用于整数类型,因此在套接字编程中:
- 字节排序函数常用来转换16位端口号和IPv4通配地址INADDR_ANY
- 其余IP地址则一般使用地址转换函数进行转换
5. 地址转换函数
地址转换函数用于在IP地址的字符串格式和网络字节序格式之间进行转换。
- inet_addr()、inet_aton()、inet_ntoa()仅适用于IPv4地址转换
- inet_pton()和inet_ntop()适用于IPv4和IPv6地址
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/*
* 字符串——>网络字节序;
* 成功返回网络字节序IPv4地址,失败返回INADDR_NONE;
*/
in_addr_t inet_addr(const char *str);
/*
* 字符串——>网络字节序,并通过addr指针保存;
* 若字符串有效则成功返回1,否则失败返回0.
*/
int inet_aton(const char *str, struct in_addr *addr);
/*
* 网络字节序——>字符串;
* 返回字符串格式的IPv4地址.
*/
char *inet_ntoa(struct in_addr addr);
上面三个函数有两点需要注意:
- INADDR_NONE是一个32位都为1的整数,因此inet_addr()不能转换地址255.255.255.255
- inet_ntoa()的参数是结构体变量,而非结构体指针