unix网络编程第一卷学习之套接字地址结构

1.ipv4的地址结构

ipv4的地址结构通常以“sockaddr_in”命名,定义在<netinet/in.h>头文件中。套接字中肯定有地址和端口两项,还有用于区分协议族的一个字段,为了计算方便还有一个长度(unix域是变长的,必须有),所以定义如下:

struct in_addr{
      in_addr_t  s_addr;    //in_addr_t 的类型是 无符号整数
};

struct sockaddr_in {
      uint8_t        sin_len;        // 结构的长度(1+1+2+4+8 = 16字节),用无符号的8位整数表示
      sa_family_t    sin_family;     // AF_INET
      in_port_t      sin_port;       // 16位的端口号
      
      struct in_addr   sin_addr;     //32位的ipv4地址,就是上面定义的那个结构体,但要注意这跟上面结构的区别,用时须被转化位网络字节序
      
      char           sin_zero[8];    //用于填充,ipv4地址未使用该字段
};


当sockaddr_in作为一个参数传递给任一个套接字函数时,套接字结构总是通过指针来传递,但通过指针来取得此参数的套接字函数必须处理来自所支持的任何协议族的套接字地址结构。这就叫传进去容易传出来难。

ansi c可以通过void* 得到。但是套接字比ansi c还早,于是在<sys/sock.h>中定义了一个通用的套接字地址结构。如下:

struct sockaddr {
	unit8_t            sa_len;       // 8 位的长度
	sa_family_t        sa_family;    // 协议族   AF_XXX
	char               sa_data[14];  // 地址
	};

现在就明白了为什么sockaddr_in要有那填充的sin_zero数据段了吧!


2.ipv6套接字地址结构

ipv6套接字地址结构也在头文件<netinet/in.h>,定义如下:

struct in6_addr {
	uint8_t			s6_addr[16];		//128位的ipv6地址,也是需要转化为网络字节序使用的
	};
	
struct sockaddr_in6 {
	uinit8_t		sin6_len;			// 结构的长度(1+1+2+4+16 = 24)
	sa_family_t     sin6_family;        // AF_INET6
	in_port_t       sin6_port;          // 端口号
	
	uinit32_t       sin6_flowinfo;      // 流标记,低24位是流量标号,下4位是优先级,最后4位保留
	
	struct in6_addr sin6_addr;          // ipv6地址
	};

顺便说一句,这里所有的结构体定义里的成员变量的长度都是从小到大的,这样做的目的是为了字节对齐,从而提高处理器的处理效率。以后自己定义结构体时也尽量遵守这种规律。

3.ipv4、ipv6、unix域和数据链路的套接字地址结构比较



由上图可以看出,unix域和数据链路的套接字的长度是可变的,而ipv4和ipv6的长度是固定的。


4.地址转换函数

地址转换函数转换的是人们习惯的字符串和网络字节序的二进制形式。

#include<arpa/inet.h>

/**
 * 将strptr所指的字符串转换成32位的ipv4地址,并用指针addptr存储。
 * 如果地址指针为空,仍然进行串的有效性检查,只是不存储而已
 * 成功返回 1      错误返回 0
 */
int inet_aton(const char* strptr,struct in_addr* addrptr);

/**
 * 将strptr所指的字符串转换成32位的ipv4地址。不提倡使用这个函数,而用inet_aton。
 * 问题:出错时的常量INADDR_NONE是一个全一的值,这意味着255.255.255.255不能由他转换
 * 成功返回 32位的ipv4地址      错误返回 INADDR_NONE
 */
in_addr_t inet_addr(const char* strptr);

/**
 * 将inaddr所指的32位的ipv4地址转化为点分十进制的串,返回值所指的串驻留在静态内存,这意味着函数不可重入
 * 返回点分十进制的串的指针
 */
char* inet_ntoa(struct in_addr* inaddr);

为了支持ipv6,又开发出了两个新函数,

#include<arpa/inet.h>

/**
 * 转化strptr指向的串,结果存储在addrptr里,family可以是AF_INET,也可以是AF_INET6
 * 成功返回 1    不是有效的表达式返回  0     出错返回 -1 
 */
int inet_pton(int family,cosnt char * strptr,void* addrptr);

/**
 * 从数值转化为表达式,family可以是AF_INET,也可以是AF_INET6
 * strptr 参数不能为 NULL,调用前必须分配好内存
 * 如果 len 太小,返回错误并设置 errno = ENOSPC
 * 成功返回 指向结果的指针       出错返回 NULL
 */
const char* inet_ntop(int family,const void* addrptr,char * strptr,size_t len);


对以上两个函数,
1.为避免缓冲区溢出,在<netinet/in.h>中定义了地址串长的宏
#define INET_ADDRSTRLEN      16     //FOR IPV4 的点分十进制
#define INET6_ADDRSTRLEN     46     //FOR IPV6 的十六进制串

2.如果不支持协议族,都返回错误,并设置errno = EAFNOSUPPORT


5.一个小例子

一个简单的客户端的代码

#include	"unp.h"

int
main(int argc, char **argv)
{
	int					sockfd, n, counter = 0;
	char				recvline[MAXLINE + 1];
	struct sockaddr_in	servaddr;

	if (argc != 2)
		err_quit("usage: a.out <IPaddress>");

	if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		err_sys("socket error");

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port   = htons(13);	/* daytime server */
	if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
		err_quit("inet_pton error for %s", argv[1]);

	if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
		err_sys("connect error");

	while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
		counter++;
		recvline[n] = 0;	/* null terminate */
		if (fputs(recvline, stdout) == EOF)
			err_sys("fputs error");
	}
	if (n < 0)
		err_sys("read error");

	printf("counter = %d\n", counter);
	exit(0);
}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值