typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned short int sa_family_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
通用套接字地址结构sockaddr,定义在<sys/socket.h>
sockaddr结构体的缺陷:sa_data把目标地址和端口信息混在一起了
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
}
sizeof(structsockaddr) ==16
sockaddr_type:type指定了所需的地址类型。
IPv4套接字地址结构通常也称为“网际套接字地址结构”,它以sockaddr_in命名,定义在<netinet/in.h>中,sockaddr_in结构体:
struct sockaddr_in
{
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* 16-bit TCP or UDP port number */
/* networkbyte ordered */
struct in_addr sin_addr; /* 32-bit IPv4 address */
/* networkbyte ordered */
unsigned char sin_zero[sizeof(structsockad dr) –
(sizeof(unsigned short int)) –
sizeof(in_port_t) -
sizeof(struct in_addr)];
}
strcut in_addr{
in_addr_t s_addr;
}
in_addr_t 一般为 32位的unsigned int,其字节顺序为网络顺序(network byte ordered),即该无符号整数采用大端字节序
sizeof(struct sockaddr_in) ==16== sizeof(struct sockaddr)
其实从sockaddr_in的定义中就看的出来两者大小相同,只不过sockaddr_in只是为了方便使用而已,本来struct就是用来改变一块内存的外在样子,以另一种形式访问。
sin_port和sin_addr必须是NBO(Network Byte Order)。
然而,大部分函数必须处理来自所支持的任何协议族的套接字地址结构,不仅仅是IPv4的sockaddr_in,在如何声明所传递指针的数据类型上村在一个问题。有了ANSI C后解决办法很简单:void *通用指针类型。然而套接字函数实在ANSI C之前定义的,在1982年采取的办法是定义并使用通用套接字sockaddr。这就要求对这些函数的调用都必须对结构体进行强制类型转换(casting),变成指向通用套接字地址结构的指针,例如:
struct sockaddr_in serv; /* IPv4 socket address structure */
/* fill in serv{} */
bind(sockfd, (struct sockaddr *)&serv, sizeof(serv));
其实,如果使用了void *的版本,程序猿会更加方便,让内核可以自己根据传入的sa_family确定真实类型,程序猿就不用手动强制类型转换了。
sin_addr字段是一个结构,而不仅仅是一个in_addr_t的类型的无符号长整形,这是有历史原因的。早期的版本(4.2BSD)把in_addr结构定义为多种结构的联合(union),允许访问一个32位的IPv4地址中的所有4个字节,或者访问它的2个16位值。这用在地址被划分成A、B和C三类的时期,便于获取地址中的适当字节。然而随着子网划分技术的来临和无类型地址编排的出现,各种地址类正在消失,那个联合已不再需要了。如今大多数系统已经废除了该联合,转而把in_addr定义为仅有一个in_addr_t字段的结构体。
结构体赋值
NBO(Network ByteOrder)
HBO(Host ByteOrder)
赋值时,常用的一些转化函数:
int inet_aton(const char *cp, structin_addr *inp);
将IPv4字符串点数格式地址cp转化成二进制,存储在inp所指结构体中(NBO),地址有效,返回非0,否则返回0. strcut in_addr{ in_addr_ts_addr; }
char *inet_ntoa(struct in_addr in);
将NBO地址转化成字符串点数格式,返回的string存储在静态区域,随后的每次调用将覆盖之前的string。这意味着该函数是不可重入的。另外,需要主要,参数是结构体而不是一个指向结构体的指针。函数以结构体为参数是罕见的,更常见的是以指向结构的指针参数。
in_addr_t inet_addr(const char *cp);
将IPv4字符串点数格式(IPv4numbers-and-dots notation)的地址cp转化成in_addr_t(unsigned int) (NBO)
如果输入无效,返回INADDR_NONE(通常是-1),使用这个函数是存在一定问题的,因为-1的二进制对目前大部分路由器上是个有效的地址(255.255.255.255),历史遗留问题,尽量用其它能指示错误的函数替代,像inet_aton,inet_pton,getaddrinfo()
in_addr_t inet_network(const char*cp);
将IPv4字符串点数格式(IPv4numbers-and-dots notation)的地址cp转化成in_addr_t(unsigned int) (HBO),返回的字节序不同之外,与in_addr_tinet_addr(const char *cp);相同,返回值也是有问题。
下面的两个函数是随IPv6出现的新函数,对于IPv4和IPv6都适用。函数名中p和n分别代表表达(presentation)和数值(numeric)。地址的表达格式通常是ASCII字符串,数值格式则是存放到套接字地址结构中的二进制。
int inet_pton(int af, const char*src, void *dst);
将IPv4或IPv6的src指定的string地址,按af指定的类型转化成二进制位,然后拷贝到dst指定的内存中。af只能是AF_INET、AF_INET6,分别指明IPv4和IPv6。使用不支持的af时返回-1,并将errno置为EAFNOSUPPORT,src地址在af类型中无效的,则返回0,成功返回1。比如inet_pton(AF_INET, “192.168.0.50”, &sockaddr_in.sin_addr) <= 0
const char *inet_ntop(int af, constvoid *src, char *dst,socklen_t size);
功能与上面的inet_pton相反,转换后存到dst指定的buff,size为buff长度。socklen_t为unsigned int类型,如果size太小,不足以容纳表达式格式结果(包括结尾的空字符),那么返回一个空指针,并置errno为ENOSPC。
#include <netinet/in.h>
uint16_t htons(uint16_t) “Host to Network Short”
uint32_t htonl(uint32_t) “Host to Network Long”
uint16_t ntohs(uint16_t) “Network to Host Short”
uint32_t ntohl(uint32_t ) “Network to Host Long”
s代表short,l代表long。short和long这个称谓是出自4.2BSD的Digital VAX实现的历史产物。如今我们应该把s视为16位的值(例如TCP或UDP端口号),把l视为一个32位的值(例如IPv4地址)。事实上即使在64位的Digital Alpha中,尽管长整数占用64位,htonl和ntohl函数操作的仍然是32位值。
端口号类型简单,为内置类型,用htons()、ntohs()刚好可以用来转化端口号。
一个赋值例子
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family= AF_INET; //IPv4
server_addr.sin_port= htons(13); //13端口号
if(inet_pton(AF_INET, “192.168.0.50”, &server_addr.sin_addr) <= 0)
{ fprintf(stderr, “%s\n”, strerror(errno)); exit(1); }//“192.168.0.50”
另外需要注意bzero使用strings.h,strings.h曾近是POSIX的标准的一部分,但是在POSIX.1-2001标准里面,这些函数被标记为遗留函数而不推荐使用。在POSIX.1-2008标准中,已经没有这些函数了。推荐使用memset代替bzero。
最简单基本的socket编程模型:
TCP server伪代码:
ss = socket() //create server socket
ss.bind() //bind socket to address
ss.listen() //listen for connection
inf_loop: //server infinite loop
cs = ss.accept() //accept client connection
comm_loop: //communication loop
cs.recv()/cs.send() //dialog(receive/send)
cs.close() //close client socket
ss.close //close server socket
TCP client伪代码:
cs = socket() //create client socket
cs.connect() //attempt server connection
comm_loop: //communication loop
cs.send()/cs.recv() //dialog(send/receive)
cs.close() //close client socket