基础知识
socket是一种IPC方法,它允许位于同一主机(计算机)或使用网络(IPV4或IPV6)连接起来的不同主机上的应用程序之间交换数据。P945
socket存在于一个通信domain中,现代操作系统至少支持下列domain:
UNIXdomain(即AF_UNIX):允许同一主机上的应用程序间通信;
Ipv4domain (即AF_INET):允许通过使用IPV4连接起来的主机上的应用程序通信;
Ipv6domain (即AF_INET6):允许通过使用IPV6连接起来的主机上的应用程序通信;P946
它确定以下信息:
Domain | 执行的通信 | 应用程序间的通信 | 地址格式 | 地址结构 |
AF_UNIX (即UNIX domain) | 通过内核
| 同一主机
| 路径名
| Struct sockaddr_un |
AF_INET | 通过IPv4
| 通过Ipv4网络连接起来的主机 | 32位IPV4地址+16位端口号 | Struct sockaddr_in |
AF_INET6 | 通过IPV6 | 通过IPV6连接起来的主机 | 128位IPV6地址+16位端口号 | Struct sockaddr_in6 |
socket有两种类型:流socket和数据报socket,这两种类型在以上三种domain中都得到了支持。P946
TCP/IP
联网协议是指定义如何在网络上传输信息的一组规则。(P969)
TCP/IP协议套件是使用最广泛的一种联网协议套件。P968
之所以成为套件,是因为TCP/IP协议套件分为很多层。
分为多层,优点是每一个协议层都对上一层隐藏下层的复杂性。
如果应用程序使用流socket类型,应用层通过TCP协议和网络层通信(即将数据打包成TCP报头+TCP数据的形式,发送给网络层);如果使用了数据报socket类型,应用层通过UDP协议与网络层通信(即将数据打包成UDP报头+UDP数据的形式,发送给网络层);如果使用了裸SOCKET类型,直接与网络层通信。P970
裸SOCKET 即SOCK_RAW,P972,p970
传输层协议(TCP或者UDP)的任务是向位于不同主机(或者同一主机)上的应用程序提供端到端的通信服务。
同一台主机上,一个IP可以绑定多个应用程序(比如一台主机可能就一个IP),因此仅靠IP无法确定是目的地是哪个应用程序,因此需要一个16位的端口号对这些程序区分。P975
SOCKET在IPV4、IPV6连接起来的主机上的应用程序间通信时,给connect函数的地址参数为IP地址和端口号,此处的IP地址为远端主机IP,端口号为远端主机上应用程序的端口号。
服务:有些众所周知的端口号已经被永久的分配给特定的应用程序了,这些应用程序叫做服务。windows中计算机管理的中的服务应该就是这里所谓的服务。P976
绑定IP和端口号
两台通过网络(ipv4或ipv6)连接起来的主机通信时,服务器端的程序需要将socket绑定一个地址,32位IP地址+16位端口号(IPV4domain),或者128位IP地址+16位端口号(IPV6domain),客户端也需要这个IP地址和端口号(与主机相同)。客户端上该地址信息需要填写在structsockaddr_in或structsockaddr_in6中。如下结构,sin_port填写16位端口号,sin_addr填写32位IP地址。
struct sockaddr_in{ /*Ipv4 socket address*/
sa_family_t sin_family; /*address family (AF_INET)*/
in_port_t sin_port; /*portnumber*/
structin_addr sin_addr; /*ipv4 address*/
unsignedchar _pad[X]; /*pad*(填满) tosize of struct sockaddr,sockaddr=16bytes,因此X=16-2-1-4=9*/
}; (P986)
struct in_addr{
in_addr_t s_addr; /*unisgned 32-bit integer,32位的整型数*/
};
定义一个变量struct sockaddr_in addr,那么接下来就要给struct sockaddr_in赋值了,很明显sin_family=AF_INET,那么addr.sin_addr.s_addr应该赋予什么值呢?
addr.sin_addr.s_addr=192.168.0.1?肯定不行,因为192.168.0.1称为点分十进制,只是ip地址的一种表示方法,不属于任何数据类型。addr.sin_addr.s_addr=”192.168.0.1”?也不行,因为定义中,s_addr是一个32位整型。
只能把一个32位整型IP地址赋给s_addr。。如,想绑定IP:192.168.0.1,其对应二进制1100000010101000 00000000 00000001,对应十六进制0XC0A80001,那么这样赋值:
addr.sin_addr.s_addr=0XC0A80001;
可以吗?接近了,但还是不行。
根据数据存储的顺序——最小地址处先存最高有效位还是最低有效位,可以分为大端存储和小端存储,先存储最高有效的是大端,先存储最低有效位的是小端,。由于该结构体中数据都是网络字节序(大端),而主机是小端的,因此必须把数据转化为网络字节序,再赋值给结构体。把该IP地址0XC0A80001用htonl()(P983)转换成网络字节序,赋给sin_addr,把该端口号5000用htons()(P983)转换为网络字节序,赋给sin_port。
将sockaddr_in结构体赋值完后,发现一个问题,bind()函数中第二个参数类型为struct sockaddr *,而非struct sockaddr_in *,那该怎么办呢?
对比struct sockaddr和struct sockaddr_in的定义,发现两者是并列结构,即字节数相同(自己的理解,structsockaddr_in中pad[x]的作用就是使两个结构体字节数相等)。因此,可以使用强制转换:
structsockaddr_in *addr;
bind(sockfd,(struct sockaddr *)addr, sizeof(struct sockaddr))
struct sockaddr{ /*通用SOCKET地址结构*/
sa_family_t sa_family; /* address family :AF_INET or AF_INET6 or AF_UNIX*/
char sa_data[14];
}; ( P949 )
上文中,将IP:192.168.0.1赋给struct sockaddr_in中的sin_addr,要先由点分十进制表示得到其16进制数据,再将该数据用htonl()函数转化为网络字节序,赋给sin_addr。这个过程未免繁琐。其实Linux提供了一个函数可以一步到位,直接将192.168.0.1转化为网络字节序十六进制,并且赋给sin_addr,这个函数就是inet_pton()(P989)。如下,
struct sockaddr_in addr;
inet_pton(AF_INET,”192.168.0.1”,&(addr.sin_addr),INET_ADDRSTRLEN);
//INET_ADDRSTRLEN=16,在<netinet/in.h>中
该函数名中,p表示展现presentation,n表示network。展现即为点分十进制表示192.168.0.1。
由主机名获取IP地址,由服务名获取端口号
主机名是连接在网络上的一个系统(可能拥有多个IP,即有多个网口)的符号标识符,服务名是端口的符号表示。在Linux系统中,主机名与IP的对应关系在/etc/hosts文件中,服务名与端口号的对应关系在/etc/services文件中。(992,994)
上文中,我们直接指定想要绑定的IP地址,还有一中获取IP地址的方法,由主机名获取。这就要用到getaddrinfo()函数(P996)。该函数示例如下:
structaddrinfo *hints,*result;
getaddrinfo(“localhost.locaodomain”,”smtp”,&hints,&result);
其中,localhost.locaodomain是在/etc/hosts文件中查到的主机名,smtp是在/etc/services中查的主机名。