UNIX网络编程2:套接字编程简介
程序员文章站
2022-07-14 20:33:33
...
1. 套接字地址结构
大多数套接字函数都需要一个指向套接字地址结构的指针作为参数,每个协议族都定义它自己的套接字地址结构,这些结构的名字均以sockaddr_开头。
1.1 IPv4套接字地址结构(网际套接字地址结构):名称sockaddr_in,定义在<netinet/in.h>头文件
- 长度字段sin_len:可简化对长度可变套接字地址结构的操作,POSIX规范并不要求有这个成员
- 套接字地址结构的地址族sin_family:可以是任何的无符号整数类型,在支持长度字段的实现中通常是1个字节,在不支持长度字段的实现中通常是2个字节。IPv4的地址族是AF_INET。
- 端口号sin_port:TCP或UDP端口号,类型一般为uint16_t,以网络字节序存储
- Ipv4地址sin_addr:长度类型一般为uint32_t,以网络字节序存储
- sin_zero:几乎所有实现都增加了这个字段,未曾使用,总是置为0
- 套接字地址结构仅在给定主机上使用,虽然结构中的端口和IP字段用在不同主机之间的通信中,但是结构本身并不在主机之间通信
struct in_addr
{
in_addr_t s_addr;
};
struct sockaddr_in
{
uint8_t sin_len; //length of structure
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8]; //unused
};
1.2 通用套接字地址结构
前面说过大多数套接字函数都需要一个指向套接字地址结构的指针作为参数,这个指针需要能够接受来自任何协议族的套接字地址结构,那如何定义这个指针的类型呢?有以下两种解决方法:
- void *是通用的指针类型(ANSI C后出现)
- 定义一个通用的套接字地址结构(在<sys/socket.h>头文件中定义),并对指向特定协议的套接字地址结构的指针进行强制类型转换
struct sockaddr
{
uint8_t sa_len;
sa_family_t sa_family; //address family:AF_xxx value
char sa_data[14]; //protocal specific address
};
struct sockaddr_in serv;
bind(sockfd, (struct sockaddr *) &serv, sizeof(serv));
1.3 IPv6套接字地址结构:名称sockaddr_in6,定义在<netinet/in.h>头文件中
- 如果系统支持套接字地址结构中的长度字段,则SIN6_LEN常值必须定义
- sin6_flowinfo分为两个字段:低序20位是流标,高序12位保留
- 对于具备范围的地址,sin6_scope_id标识其范围
struct in6_addr
{
uint8_t s6_addr[16]; //address of IPv6,128 bits
};
#define SIN6_LEN
struct sockaddr_in6
{
uint8_t sin6_len; //length of this structure(28)
sa_family_t sin6_family; //AF_INET6
in_port_t sin6_port; //uint16_t
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
1.4 新的通用套接字地址结构:名称sockaddr_storage,在<netinet/in.h>头文件中定义
struct sockaddr_storage {
uint8_t ss_len;
sa_family_t ss_family;
};
1.5 套接字地址结构的比较
2. 值-结果(value-result)参数
套接字函数的参数除了指向套接字地址结构的指针之外,还有该结构的长度,该结构的长度这个参数的传递方式取决于传递方向:从进程到内核、还是从内核到进程。
2.1 从进程到内核传递套接字地址结构的函数: bind、connect、sendto
struct sockaddr_in serv;
connect(sockfd, (SA *)&serv, sizeof(serv));
2.2 从内核到进程传递套接字地址结构的函数:accept、recvfrom、getsockname、getpeername
这里的长度参数传递的也是指针,是一个值-结果参数。
struct sockaddr_un cli;
socklen_t len;
len = sizeof(cli);
getpeername(unixfd, (SA *)&cli, &len); //len may have changed
2.3 其它的一些值-结果参数
- select函数中间的三个参数
- getsockopt函数的长度参数
- 使用recvmsg函数时,msghdr结构中的msg_namelen和msg_controllen字段
- ifconf结构中的ifc_len字段
- sysctl函数两个长度参数中的第一个
3. 字节排序函数
- 字节序:小端(little-endian)字节序、大端(big-endian)字节序
- 主机字节序:两种字节序主机都可以使用,把某个给定系统使用的字节序称为主机字节序
- 网络字节序:多字节字段各个字节的传送顺序,网络字节序为大端字节序
- 套接字地址结构中的某些字段必须按照网络字节序进行维护
- 两种字节序之间的转换函数有如下4个
- 名字以b(字节)开头的第一组函数起源于4.2BSD
- 名字以mem(内存)开头的第二组函数起源于ANSI C标准
5. 地址转换函数
5.1 在点分十进制数串(如206.168.112.96)与它长度为32位的网络字节序二进制值间转换IPv4地址
5.2 在点分十进制数串与它长度为32位的网络字节序二进制值间转换IPv4和IPv6地址——inet_pton和inet_ntop函数
p和n分别代表表达(presentation)和数值(numerical),地址的表达格式通常是ASCII字符串、地址的数值形式通常是存放在套接字地址结构中的二进制值。
#include <arpa/inet.h>
/*如果转换IPv4地址则family为AF_INET,如果转换IPv6地址则为AF_INET6
成功返回1,输入不是有效的表达格式返回0,出错返回-1*/
int inet_pton(int family, const char * strptr, void *addrptr);
/*返回:若成功则为指向结果的指针,若出错则为NULL
len为目标存储单元的大小,以免该函数溢出其调用者的缓冲区,为了有助于指定这个大小,在<nteinet/in.h>头文件中有如下定义
#define INET_ADDRSTRLEN 16 //IPv4点分10进制
#define INET6_ADDRSTRLEN 46 //IPv6十六进制字符串
strptr不能是空指针,应该为其分配内存,返回的就是strptr*/
const char * inet_ntop(int family, const void * addrptr, char * strptr, size_t len);
6. sock_ntop和相关函数inet_ntop函数的一个问题是:需要传入一个指向二进制地址的指针,而这个指针是包含在一个套接字地址结构中的,这就要求调用者必须知道这个结构的格式和地址族,因此为IPv4和IPv6编写的代码是不一样的。为了使得编写的代码和协议无关,我们自行编写了名为sock_ntop的函数以及其它一些函数
#include "unp.h"
//成功则返回非空指针,否则返回NULL
char * sock_ntop(const struct sockaddr * sockaddr, socklen_t socklen);
6. readn、writen和readline函数
字节流套接字上调用read和write函数输入和输出的字节数可能少于请求的数量,这是因为内核中用于套接字的缓冲区可能达到了极限。为了防止这种情况产生,我们自己编写了以下三个函数。