在第二篇博文中我们学习了套接字编程所需要的关于传输层协议的一些理论知识,从而对像“TCP建立连接的三次握手”等知识有了了解。在掌握了这些知识之后,我们开始学习套接字编程的API。因为API太多而且使用不同传输协议的套接字有些API不同,所以给我们的学习带来了很大的困难。为了系统的有层次的学习这些API,我们将遵循《Unix网络编程套接字联网API》这本书中的顺序来学习。所以我们首先需要学习的是所有套接字都会用的一些辅助的基础的API,包括:“套接字地址结构”,“字节排序函数”,“字节操作函数”,“地址转换函数”等。下面我们依次来学习这些API。
总结一些知识是快乐的也是痛苦的。
------------------------------------------------------------------------------------------------------------------------
在最开始的第一篇博文的daytimetcpcli.c的line 8中有过这么一个结构体的定义:
struct sockaddr_in servaddr;
当时候的解释是这样的:“line8 定义了一个sockaddr_in类型的结构体 servaddr用来标识将要访问的服务器的ip地址以及端口号等,sockaddr_in是ipv4的结构体类型”。在daytiemimetcpsrv.c中也有同样的一个地址结构的定义。在这一部分,我们就开始讲解sockaddr_in等这一类套接字地址结构。
学习套接字地址结构的意义在于好多的套接字函数都需要一个指向这个套接字地址结构的指针作为参数,而且有些时候需要对这个地址结构的一些成员进行赋值操作,所以我们应该熟知这些地址结构的内容。主要有如下几类套接字地址结构:
IPV4套接字地址结构也称为“网际套接字地址结构”,以sockaddr_in来命名,可以查看你机器的或者是linux源码中的<netinet/in.h>中的内容。因为Unix毕竟和linux有些不同,而且不同发行版本之间也有不同,所以这个结构体中的有些成员函数的定义有少许差别,但是总体上没有太大差别,这里列出书上给的定义:
struct in_addr { in_addr_t s_addr; /*32位的网络字节流ip地址 */ } struct sockaddr_in { unit8_t sin_len; /* length of structure (16) */ sa_family_t sin_family; /* AF_INET */ in_port_t sin_port; /* 16-bit TCP or UDP port number in network byte ordered*/ struct in_addr sin_addr; /* 32-bit IPv4 adddress in network byte ordered*/ char size_zero[8]; /* unused */ };
在这里有两个字段一般不用分别是sin_len以及size_zero[8], 下面三个是POXIS标准需要的字段:
sin_family:他的数据类型是 sa_family_t ,它可以是任何无符号的整数类型。一般是8位(unsinged char)型或者是16位(unsigned short int)型。
sin_port: 用来标识端口号,一般是32位的无符号整数(unsigned int)型;
sin_addr: 是一个结构体类型。现在之所以in_addr只有一个成员s_addr且是32位无符号整数,是因为子网掩码的发展导致我们不需要去刻意区别主机号和网络号所以,在以前的版本中in_addr的许多成员变量现在就不再存在了。
仍然是回想到博客(一)中的daytimetcpcli.c,在其中中有一行代码,line22,就是我们现在要讲解的应用
if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
当时的解释是:这里的SA是struct sockaddr的缩写形式,在"unp.h"中定义,这里是将某个特殊的套接字地址向通用型进行转换。
这个struct sockaddr就是现在要讲的通用套接字地址,因为那时候还没有void*这种类型,所以为了在套接字的一些函数中处理来自任何协议族的套接字地质结构就需要使用这个通用的类型。下面是这个通用地址结构的组成:
struct sockaddr { unit8_t sa_len; sa_family_t sa_family; /* address family: AF_xxx value */ char sa_data[14]; /* protocol-specific address */ }
所以如需强制转换,只需要使用 (struct sockaddr *) &servaddr
除了IPv4地址结构以外,还有IPv6,,Unix域,数据链路和存储等其它几种不同的地址结构。下面截取一幅图给出这些对比:
以上就是5种地址结构的组成,以最左边的IPv4为例,它的结构体的名字是sockaddr_in,长度占8bit, sin_family占8bit取值为AF_INET。sin_port(端口号)占16bit,sin_addr(Ip地址占)32bit,另外还有未使用的32bit。同样可分析其它的几个地址结构,这里就不一一陈述。下面进入字节排序的学习。
仍然是回想到博客(一)中的daytimetcpcli.c,在其中中有一行代码,line18,
servaddr.sin_port = htons(13);
当时的解释是:htons是(host to network short)的简称,他将主机字节序转换为网络中使用的网络字节序。
现在我们讲解的就是这个问题:
字节排序函数解决的是数据在机器上的存储(成为主机字节序)和在网络上的传输时候(网络字节序)的大端(big-endian)小端(little-endian)问题。
主机字节序中符合我们习惯的应该是小端模式,即低位数据存储在内存的地地址,比如0x4321这个数据,先存储1再存储2。。。
而网络字节序中符合我们习惯的可能是大端模式:对于0x4321,先发送4再发送3等。。。
所以我们需要在主机字节序和网络字节序之间进行统一。在我们下载的源码中有 intro/byteorder.c这个函数可以用来检测我们的机器是大端还是小端。
下面我们给出几个转换函数的API:
#include <netinet/in.h> uint16_t htons (uint16_t host16bitvalue); uint32_t htonl (uint32_t host32bitvalue); /*这两个函数实现主机字节序向网络字节序进行转换,一个转换的是16位的一个转换的是32位的*/ uint16_t ntohs (uint16_t net16bitvalue); uint32_t ntohl (uint32_t net32bitvalue); /*这两个函数实现网络字节序向主机字节序进行转换,一个转换的是16位的一个转换的是32位的*/
在使用这些函数的时候我们并不需要关心主机字节序和网络字节序的真实值,只需要进行转换就可以了。下面讲解字节操纵函数。
在C语言中,处理以空字符结尾的C字符串只要使用<string.h>头文件中的以str开头的一些函数就可以了,比如:strlen();strcmp()等;但是,在网络编程中有时候需要处理
套接字的地址结构,而这些数据是一些不以空字符结束的,所以需要一些特殊的字节操作函数,包括,之前例子中使用的这个置0函数:
bzero(&servaddr, sizeof(servaddr));
但是这个函数并不是标准C库中的函数,以下的几个函数是标准的C库的函数:
#include<string.h> void *memset(void *dst, int c, size_t len); /*将dst指向的字符串从头开始 len的长度个字符用 c 来替换,这里的c是ASIC字符对应的十进制数*/ void *memcpy(void *dst, const void *src, size_t nbytes) /*拷贝src中的nbytes个字符到dst指向的字符数组中*/ int memcmp (const void *ptr1, const void *ptr2, size_t nbytes ); /*比较ptr1和ptr2指向的字符串的前nbytes个字符的大小*/
上面的一些字节操作函数灵活使用可以帮助我们解决有关字节操作的很多问题。下面我们介绍几个地址转换函数:
在博客(一)中的daytimetcpcli.c的line 19有一判断语句:
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
当时给出的解释是:“使用inet_pton函数将输入的ip地址从我们机器上表示的ASCII字符串编码转换为套接字地址结构中的二进制值”。
使得地址可以在ASCII字符串和网络字节序的二进制地址之间进行转换。关于这些函数存在两个版本,一个是较早出现的,只能处理IPv4版本,还有一种是比较新的,可以处理既处理Ipv4又处理Ipv6:
#include <arpa/inet.h> int inet_aton(const char *strptr, struct in_addr *addrptr); /*ASCII string to numeric */ in_addr_t inet_addr(const char *strptr); /*功能类似于inet_aton*/ char *inet_ntoa(struct in_addr inaddr); /* numeric to ASCII string */
以上三个函数中,第二个函数inet_addr因为返回值的问题,不能处理225.225.255.255这个地址,所以现在已经不再使用了。
inet_aton和inet_ntoa并不能处理ipv6的地址,所以也渐渐被下面的两个函数取代:
#include <arpa/inet.h> int inet_pton(int family, const char *strptr, void *addrptr); /* presentation to numeric*/ const char *inet_ntop( int family, const void *addrptr, char *strptr, size_t len);
这两个函数中的family取AF_INET, AF_INET6等分别代表ipv4和ipv6.
inet_ntop的返回值是:如成功则指向结果的指针,若出错则为NULL。分析inet_ntop的参数可知,第二个参数需要知道一个套接字地址结构中的sin_addr这个变量,然后再做相应的操作,为了简化这些步骤作者自己定义了一个函数 sock_ntop:
#include "unp.h" char *sock_ntop(const struct sockaddr *sockaddr, socklen_t addrlen)
在这个函数里只需要提供指向套接字地址结构的指针就可以,返回值是一个指针,这个指针指向在函数体内部创建的一个用于保存ASCII字符串形式IP地址的区域。
具体的代码这里就不给出了,可以去我们下载的源码的 lib/sock_ntop.c中进行查看;
-------------------------------------------------------------------------------------------------------------------------------------
总结:
到此为止,我们分别介绍了“套接字地址结构”,“字节排序函数”,“字节操作函数”,“地址转换函数”等,并在学习每一块内容时都和我们博文(一)中的客户端程序进行对照,从而加深了印象。套接字地址结构是每个网络应用程序中都很重要的组成部分,要熟练的掌握它。字节排序函数解决了主机字节序和网络字节序的转换问题。字节操作函数提供了对非C串的操作。地址转换函数提供了从网络的二进制地址数据类型和ASCII字符串之间的转换。
2015/02/02 于南京 CSDN 如需转载请注明http://blog.csdn.net/michael_kong_nju/article/details/43410451