APUE 学习笔记 - Chapter 16 . Network IPC: Sockets
1.字节序
由于历史的原因,不同的系统架构使用不同的字节序,主要分为大端字节序(Big-Endian)与小端字节序(Little-Endian)。
大端字节序将最高有效位落在最低地址,而小端字节序则将最低有效位落在最低地址上:
Big Endian :
低地址 高地址
----------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 12 | 34 | 56 | 78 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Little Endian :
低地址 高地址
----------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 78 | 56 | 34 | 12 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
对于人类而言,Big-Endian的字节序会更符合思维习惯。使用Power PC架构的机器一般使用大端字节序,而使用X86架构的机器一般使用小端字节序。为了在不同的机器中传输数据时,不会因为字节序的不同而造成传输结果的差异,将大端字节序定义为网络字节序,并提供了若干函数进行字节序的转换。要判断当前机器的字节序,可以使用以下函数:
static bool IsLittleEndian() { int i = 1; char * b = (char*)&i; return b[0] == 1; }
2.地址:
在网络中,每一个结点都有一个地址作为对应,使用sockaddr以网络字节序进行存储,可以使用inet_ntop与inet_pton两个函数进行网络字节序与字符串之间的转换,只支持AF_INET与AF_INET6。在网络中,通常需要知道对方的hostname或者servicename两者其中,查找出对应的地址,无论是从本机的配置(/etc/services,/etc/hosts, etc)中,或者从DNS中进行查找,都可以使用getaddrinfo这个函数进行查找,具体应用的代码如下:
#include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <string.h> #include <arpa/inet.h> int main(int argc, char **argv) { if (argc != 3) { fprintf(stderr, "Usage: %s hostname servicename\n", argv[0]); exit(1); } struct addrinfo *answer, hint, *curr; char ipstr[16]; bzero(&hint, sizeof(hint)); hint.ai_family = AF_INET; hint.ai_socktype = SOCK_STREAM; int ret = getaddrinfo(argv[1], argv[2], &hint, &answer); if (ret != 0) { fprintf(stderr,"getaddrinfo: %s\n", gai_strerror(ret)); exit(1); } for (curr = answer; curr != NULL; curr = curr->ai_next) { inet_ntop(AF_INET, &(((struct sockaddr_in *)(curr->ai_addr))->sin_addr), ipstr, 16); printf("%s\n", ipstr); } freeaddrinfo(answer); exit(0); }3.网络通信
每个网络通信都必须基于一个socket,采用与文件通信类似的描述符可以方便文件操作中的一些函数仍然能够在socket中使用。
首先,必须采用socket(3)函数来创建一个socket,其中,socket共支持四种类型,分别如下:
1.SOCK_STREAM,有保障,可信赖的面向对象连接
2.SOCK_DGRAM,固定长度,无连接,不可靠的信息传递
3.SOCK_RAW,接收到本机网卡上的数据帧或者数据包
4.SOCK_SEQPACKET,固定长度,有序的可靠面向对象连接
创建后的socket,对于面向连接的网络通信,如TCP,需要进行与地址端口的绑定,可以使用函数bind(3)。
对于服务器端,还需要进行监听,可以使用listen(3)函数,对该socket进行监听,使用accept(3)函数接收客户端的请求,这种情况是阻塞的,如果需要使用非阻塞的连接,需要使用1.指定该socket的拥有进程,以接收相应信号;2.通知socket,如果该套接字不再阻塞时,通知进程。这时候需要响应并接收SIGIO信号。对于客户端,可以直接进行connect(3)操作并不需要事先进行绑定。客户端与服务器端connect成功之后,可以使用read,write进行通信,也可以使用send和recv进行通信。
对于无连接的网络通信, 直接使用sendto与recvfrom对相应地址与端口进行通信,但是并不能保证包的送达,在程序中需要进行一些处理,以防程序在recvfrom中长期阻塞。
4.Socket选项
在UNIX中,会禁止程序在超时前再次绑定同一个地址,所以如果需要在短时间内重新绑定同一个地址的话,需要使用setsockopt(3)函数来指定SO_REUSEADDR这个选项来支持这个目的。