通常大多数套接字函数都需要一个指向套接字地址结构的指针作为参数。每个协议族都定义了他们各自的套接字地址结构。这些地址结构以sockaddr_开头,并对应各自唯一的后缀。
一、IPv4套接字地址结构
IPv4套接字地址结构通常称为“网际套接字地址结构”,它以sockadd_in命名。头文件及声明如下:
#include <netinet/in.h>
struct sockaddr_in{
uint8_t sin_len;
sa_family_t sin_family; /*AF_INET*/
in_port_t sin_port; /*16位TCP或者UDP端口号,网络字节序*/
struct in_addr sin_addr; /*32位IPv4结构,网络字节序*/
char sin_zero[8]; /*未使用*/
};
struct in_addr{
in_addr_t s_addr; /*32位IPv4地址,网络字节序*/
};
其中,数据类型如下图所示:
二、IPv6套接字地址结构
IPv6套接字地址结构头文件及结构体内容如下:
#include <sys/socket.h>
#define SIN6_LEN // required for compile-time tests
struct sockaddr_in6
{
uint8_t int sin6_len; //IPv6结构长度,是一个无符号的8为整数,表示128为IPv6地址长度
sa_family_t sin6_family; //地址类型AF_INET6
int_port_t sin6_port; //存储端口号,按网络字节顺序
uint32_t sin6_flowinfo; //低24位是流量标号,然后是4位的优先级标志,剩下四位保留
struct in6_addr sin6_addr; //IPv6地址,网络字节顺序
};
struct in6_addr
{
uint8_t s6_addr; //128位的IPv6地址,网络字节顺序
};
三、通用套接字结构
为什么需要通用套接字地址结构?
当作为一个参数传递给任何套接字函数时,套接字地址结构总是以引用形式(也就是以指向该结构的指针)来传递。然而以这样的指针作为参数之一的任何套接字函数必须处理来自所支持的任何协议族的套接字地址结构。因此,我们需要强制转换特定协议的套接字地址结构为通用的套接字地址结构,然后再传递给套接字函数。通用套接字地址结构体及头文件如下:
#include <sys/socket.h>
struct sockaddr{
uint8_t sa_len;
sa_family_t sa_family; /*地址族:AF_xxx*/
char sa_data[4]; /*protocol-specific address*/
};
请记住:设计通用套接字地址结构的目的是把它作为参数传递给套接字函数。唯一用途是对指向特定协议(IPv4/ IPv6<支持吗?>)的套接字地址结构的指针进行强制类型转换,转换为通用套接字地址结构的指针,再传递给套接字函数。
比如:套接字函数bind原型为: int bind(int sockfd, const struct sockaddr *addr, socklen_taddrlen); 我们不能为第二个参数直接传递指向特定协议的套接字地址结构的指针,需要先转换为通用套接字地址结构的指针。正确的使用方法如下:
struct sockaddr_in serv; // IPv4套接字地址结构
bind(sockfd, (struct sockaddr*) &serv, sizeof(serv));
四、新的通用套接字地址结构
不像struct sockaddr,在IPv6里提出的新的struct sockaddr_storage足以容纳系统所支持的任何套接字地址结构(包括IPv4, IPv6),sockaddr_storage结构在<netinet/in.h>头文件中定义。
struct sockaddr_storage {
uint8_t ss_len; /* length of this struct (implementation dependent) */
sa_family_t ss_family; /* address family: AF_xxx value */
/* implementation-dependent elements to provide:
* a) alignment sufficient to fulfill the alignment requirements of
* all socket address types that the system supports.
* b) enough storage to hold any type of socket address that the
* system supports.
*/
};
sockaddr_storage和sockaddr的主要差别
(1) sockaddr_storage通用套接字地址结构满足对齐要求。
(2) sockaddr_storage通用套接字地址结构足够大,能够容纳系统支持的任何套接字地址结构。
五、用tcp_connect重写的时间获取客户程序(参见11.12节)
下面的程序使用了tcp_connect重写时间获取客户程序,稍微修改了11.12节代码以考查了如何从sockaddr_storage结构中获取IP地址。因为sockaddr_storage结构比较复杂,而且兼容了IPv4和IPv6地址结构,无法直接从中取得IP地址或端口号,那么如何取得IP地址呢?请细读第42行代码。
#include <unistd.h>
#include <sys/types.h> /* basic system data types */
#include <sys/socket.h> /* basic socket definitions */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <arpa/inet.h> /* inet(3) functions */
#include <sys/select.h> /* select function*/
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#define MAXLINE 4098
// use getaddrinfo
int tcp_connect(const char *host, const char *serv);
int main(int argc, char **argv)
{
int sockfd, nread;
struct sockaddr_storage ss;
in_addr_t sinaddr;
socklen_t addrlen;
char recvBuf[MAXLINE + 1];
char dst[MAXLINE];
if(argc != 3)
{
perror("usage: daytimetcpcli <hostname/IPaddress> <service/port#>");
}
// tcp_connect calling
sockfd = tcp_connect(argv[1], argv[2]);
addrlen = sizeof(ss);
getpeername(sockfd, (struct sockaddr*)&ss, &addrlen);
//printf("Connected to %s\n", sock_ntop_host((struct sockaddr*)&ss, addrlen) );
sinaddr = ((struct sockaddr_in*)((struct sockaddr*)&ss))->sin_addr.s_addr;
printf("Connected to:%s \n", inet_ntop(AF_INET, &sinaddr, dst ,addrlen ));
while((nread = read(sockfd, recvBuf, MAXLINE)) > 0){
recvBuf[nread] = 0;// null terminate
fputs(recvBuf, stdout);
}
}
int tcp_connect(const char *host, const char *serv)
{
int sockfd, n;
struct addrinfo hints, *res, *ressave;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ( (n = getaddrinfo(host, serv, &hints, &res)) != 0){
printf("tcp_connect error for %s, %s: %s",
host, serv, gai_strerror(n));
}
ressave = res;
do {
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd < 0)
continue; /* ignore this one */
if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
break; /* success */
close(sockfd); /* ignore this one */
} while ( (res = res->ai_next) != NULL);
if (res == NULL){ /* errno set from final connect() */
printf("tcp_connect error for %s, %s", host, serv);
}
freeaddrinfo(ressave);
return(sockfd);
}
调试及结果如下图: