首先我们需要了解套接字地址结构到底长啥样,我们可以在<netinet/in.h>中找到下面的结构
/*
* Internet address (a structure for historical reasons)
*/
struct in_addr {
in_addr_t s_addr; // 32位的IPV4地址,一般实现方式是uint32_t
};
/*
* Socket address, internet style.
*/
struct sockaddr_in {
__uint8_t sin_len; //非POSIX规范要求,表示的是套接字地址结构的长度
sa_family_t sin_family; //表示套接字地址结构的地址族,如果有sin_len,一般为8位无符号整数,否则就是16位无符号整数
in_port_t sin_port; //TCP或者UDP接口,一般是uint16_t
struct in_addr sin_addr; //地址,具体看上面
char sin_zero[8];//非强制要求,但是几乎所有的实现都增加了这个
};
我们需要注意的是,POSIX中规定必须包含的仅仅是sin_family,sin_port和sin_addr。 sin_len是BSD为了增加对OSI协议的支持增加的,而且一般来说不需要我们设置;sin_zero则一般默认是0
我们需要记住的是,在结构中,端口号(sin_port)和sin_addr都是按照网络字节序存储的(一般就是大端法)
当然可能我们也会奇怪为什么sin_addr是一个只包含in_addr_t的in_addr结构(而不是直接保存in_addr_t),这是因为历史沿革,最早的时候in_addr包括了若干个union来实现ABC地址,但是后来ABC地址不那么重要之后,就被去掉只剩下s_addr
当我们定义了结构之后,当然要考虑怎么向套接字函数如bind之类传递,一般而言,我们会传入套接字结构的引用(指针),考虑到我们面对的套接字结构的多样性,现代的话我们也许会考虑使用void *,遗憾的是,在当时设立标准的时候,却还没有void *这个特性,他们的解决方法是采用一个通用的套接字地址结构,准确来说,是在<sys/socket.h>上定义了如下的套接字结构
/*
* [XSI] Structure used by kernel to store most addresses.
*/
struct sockaddr {
__uint8_t sa_len; /* total length */
sa_family_t sa_family; /* [XSI] address family */
char sa_data[14]; /* [XSI] addr value (actually larger) */
};
于是当我们需要转换的时候,我们可以用类似下面的代码
struct sockaddr_in serv;
//中间包括对地址的填充
bind(sockfd,(struct sockaddr*)&serv,sizeof(serv));
我们可以认为sockaddr结构体的作用就是让我们完成这么个强制类型转换
值-结果参数
如果说我们观察bind函数定义和accept函数定义的话(int accept(int, struct sockaddr * __restrict, socklen_t * __restrict)),我们会发现有一个些微的区别,就是bind第三个参数size是通过传值实现的,而accept则需要我们传入一个指针,这是为什么呢?
其实道理很简单,当我们使用bind函数的时候,我们是要将进程数据向内和空间发送(我们请求bind),于是我们需要提供值给内核空间让它知道复制在什么时候结束,同样的函数还包括connect和sendto
但是当使用到accept等的时候,事情就会复杂点,一方面,当调用函数的时候,进程需要告知内核空间你该读多少长度(就像是传值一样),而当函数结束的时候,内核空间又会写入长度来告知进程该读取多少(这个时候反映的是结果),这种参数我们可以称之为“值-结果参数”,因为一个参数同时负责调用时传入值和返回时传入结果的责任,除了accept之外,有这种“值-结果参数”的还有recvfrom(),getsockname()和getpeername()
字节排序函数
我们需要注意的是,整数的存储到底是最高有效位在前还是在后其实是没有固定的,如果最高有效位在前,我们可以称之为大端法,反之则是小端法,但是两种字节排序的方法都有系统再用,显然如果一个小端法的主机向大端法按照顺序发送数据,会得到不一致的结果
庆幸的是,网络协议规定了特定的网络字节序(一般是大端法),考虑到规定了套接字地址结构中端口号和地址都必须按照网络字节序存储,因此我们有必要去知道一些实现函数
具体来说,我们可以利用htonl,htons,ntohl,ntohs四个函数实现
这四个函数其实挺好记忆的,htonl->host(h) -to - network(n)-long(l),也就是说从主机到网络就是htonl或者htons,l和s分别是long(32位,四字节,适用于地址)和short(16位,两字节,适用于端口号)
我们可以直接使用而不用担心我们原先的主机字节序是大端还是小端,一般来说,在大端法的主机上,这四个函数是空宏(因为不需要转换)
字节操纵函数
所谓的字节操纵函数,是对某一个地址起头的数据进行设置、复制和比较工作,我们可以用两组函数
第一组函数来自BSD所以是以b开头的,分别是
int bcmp(const void *ptr1, const void *ptr2, size_t nbytes); //比较,如果相同返回0,否则返回非0
void bcopy(const void *src, void *dest, size_t nbytes); // 将src复制到dest中,注意src在前
void bzero(