您的位置:首页 > 理论基础 > 计算机网络

【网络编程基础笔记】struct sockaddr和struct sockaddr_in的区别和用法

2013-04-26 19:36 627 查看
编写网络应用程序时,经常会用到sockaddr和sockaddr_in这两个结构体,对于新手来说,比较容易搞混它们的区别和使用方法。本文的笔记试图讲清楚它们之间的关系和正确的用法。

1. struct sockaddr_in(针对IPv4使用)

struct sockaddr_in是linux kernel针对IPv4用到的socket address structure(针对IPv6则定义了struct sockaddr_in6来实现类似的结构),写网络应用程序时,我们需要引用其头文件<netinet/in.h>文件。

以sockaddr_in为例,其定义代码如下(严格地说,应该是redhat linux 2.6.9中的定义,vendor或kernel version不同,相关结构的定义形式可能会有差别,但底层的数据表示其实是一致的):
/* Type to represent a port.  */
typedef uint16_t in_port_t;

/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr; /* 由于TCP/IP发展的历史原因,这里只有1个字段还用struct包装起来 */
/*《UNIX Network Programming Volume 1》3.1节对此有一段解释,这里不再赘述 */
};

/* Structure describing an Internet socket address.  */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);      /* 本行等价为: sa_family_t sin_family */
/* gcc -E查看preprocess阶段宏展开后的源码可以确认这一点 */
in_port_t sin_port;            /* Port number.  */
struct in_addr sin_addr;       /* Internet address.  */

/* Pad to size of `struct sockaddr'.  */
unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)];
};


由struct sockaddr_in定义的最后1行可知,sockaddr_in的size其实是依赖struct sockaddr的size的,通过sin_zero字段的padding,可以使这两个structs的size始终保持相等,以方便在调用socket api时两种类型的指针互转

如果感兴趣,可以在下面的源文件中查看sockaddr_in在linux kernel source tree中的定义:

a. 若是linux 2.6.x版本,可在include/linux/in.h中查看

b. 若是linux 3.x版本,可在include/uapi/linux/in.h中查看

另外需要说明的是,在某些版本提供的linux kernel(如4.3 BSD-Reno)中,struct sockaddr_in的定义会多一个uint8_t sin_len字段(这种情况下,与此对应的struct sockaddr也会有uint8_t
sin_len字段)。根据《UNIX Network Programming Volume 1》3.1节中的说法,我们可以不关注这个细节(即可以认为这个sin_len字段存在与否对我们的应用程序是透明的)。这个字段不是每种Linux版本都提供,且POSIX标准中对struct sockaddr_in的定义是否需包含该字段不做要求
2. struct sockaddr

与struct sockaddr_in的定义相比,struct sockaddr的定义相对简单些。其被称为"Generic Socket Address Structure",在<sys/socket.h>中的定义如下:

struct sockaddr {
sa_family_t	sa_family;	/* address family, AF_xxx	*/
char    	sa_data[14];	/* 14 bytes of protocol address	*/
};


常用的库函数如bind,accept,connect,sendto,recvfrom,getpeername及getsockname等,其函数原型中均以Generic Socket Address Structrues的指针(struct sockaddr *)作为地址参数。

如果感兴趣,可以在下面的源文件中查看sockaddr在linux kernel source tree中的定义:

a. 若是linux 2.6.x版本,可在include/linux/socket.h中查看

b. 若是linux 3.x版本,可在include/linux/socket.h中查看
3. 两种structs的使用方法

关于如何正确使用这两个结构体,UNP一书中有所说明:

From an application programmer's point of view, the only use of these generic socket address structures is to cast pointers to protocol-specific structures.

总之,对于编写网络应用程序的程序员来说,通常的做法是定义struct sockaddr_in类型的变量,对其family、port、addr等字段赋值后,在调用socket相关api时,将其指针强制转换为struct sockaddr *类型即可(由它们的定义代码可知,二者的size始终保持相等,均为16 bytes,因此,对其指针做强制类型转换后不会引起问题),内核会根据sockaddr.sin_family字段指定的协议类型做进一步的底层操作。

此外,UNP中还提到的API不用void *作为通用参数(这是屏蔽细节的惯用做法,如memset/memcpy系列函数的参数类型),而选择struct sockaddr *作为参数的原因:

From the kernel's perspective, another reason for using pointers to generic socket address structures as arguments is that the kernel must take the caller's pointer,
cast it to a struct sockaddr *, and then look at the value of sa_family to determine the type of the structure. But from an application programmer's perspective, it would be simpler if the pointer type was void *, omitting the need for the explicit cast.
【参考资料】

UNIX Network Programming Volume 1. 第3章

================== EOF ====================
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐