《Linux高性能服务器编程》笔记5--Linux网络编程基础API

Linux网络编程基础API

socket地址api

主机字节序和网络字节序

字节序分为大端字节序和小端字节序。
大端字节序:一个整数的高位字节(2331bit)存储在内存的低地址处,低位字节(07bit)存储在内存的高地址处。这里一个整数是4字节。
小端字节序:整数的高位字节存放在内存的高地址处,低位字节存储在内存的低地址处。

数值0x2211使用两个字节储存:高位字节是0x22,低位字节是0x11
大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法。
小端字节序:低位字节在前,高位字节在后,即以0x1122形式储存。

0x1234567的大端字节序和小端字节序的写法如下图。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。
人类还是习惯读写大端字节序。所以,除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存。

“只有读取的时候,才必须区分字节序,其他情况都不用考虑。”
处理器读取外部数据的时候,必须知道数据的字节序,将其转成正确的值。然后,就正常使用这个值,完全不用再考虑字节序。
即使是向外部设备写入数据,也不用考虑字节序,正常写入一个值即可。外部设备会自己处理字节序的问题。

现代PC大多采用小端字节序,因此小端字节序也被称为主机字节序。

发送端总是要把要发送的数据转换成大端字节序数据后再发送,而接收端根据自己的字节序决定是否对接收到的数据进行转换。因此大端字节序也称为网络字节序,给所有接收数据的主机提供了一个正确解释收到的格式化数据的保证。
同一台机器上的两个进程也要考虑字节序的问题。

4个函数完成主机字节序和网络字节序的转换。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

长整型函数通常用于转换IP地址,短整型用于转换端口号。任何格式化数据通过网络传输时,都要调用这些函数完成转换字节序。

通用socket地址

socket网络编程接口中表示socket地址的是结构体sockaddr
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

sa_family_t成员是地址族类型的变量。地址族类型常与协议族类型对应。
常见协议族(protocol family,也称domain)和对应的地址族如下所示。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

PF_*AF_*都定义在bits/socket.h中,后者与前者有完全相同的值,所以二者通常混用。

sa_data成员用于存放socket地址值。不同协议族的地址值具有不同的含义和长度。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

由此可见,14字节的sa_data根本无法完全容纳多数协议族的地址值。因此,Linux定义了下面这个新的通用socket地址结构体。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

不仅提高了足够大的空间存放地址值,而且是内存对齐的(__ss_align成员的作用)。

专用socket地址

Linux给各个协议族提供了专门的socket地址结构体。
UNIX本地域协议族使用如下专用socket地址结构体。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

TCP/IP协议族有sockaddr_in和sockaddr_in6两个专门的socket地址结构体,分别用于IPv4,IPv6。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

所有专用socket地址(包括sockaddr_storage)类型的变量在实际使用时都需要转化成通用socket地址类型sockaddr。所有socket编程接口使用的地址参数的类型都是sockaddr。

IP地址转换函数

《Linux高性能服务器编程》笔记5--Linux网络编程基础API

用于点分十进制字符串表示的IPv4地址和用网络字节序整数表示的IPv4地址之间的转换。

inet_addr:点分十进制字符串转化成网络字节序整数。失败返回INADDR_NONE.
inet_aton:点分十进制字符串转换成网络字节数整数,结果存储在参数inp指向的地址结构中。成功返回1,失败返回0.
inet_ntoa:网络字节序转换成点分十进制字符串。函数内部用一个静态遍布存储转换结果,函数的返回值指向该静态内存,因此inet_ntoa是不可重入的。

同时适用于IPv4和IPv6的转换函数。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

inet_pton:用字符串表示的IP地址src转换成网络字节序整数表示的IP地址,并将结果存储在dst指向的内存中。af指协议族,可以是AF_INET或者AN_INET6。成功返回1,失败返回0并设置errno。
inet_ntop:网络字节序转换成字符串。cnt指定目标存储单元的大小。inet_ntop成功时返回目标存储单元的地址,失败返回NULL并设置errno。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

创建socket

Unix/Linux中:所有东西都是文件。socket就是可读、可写、可控制、可关闭的文件描述符。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

domain参数告诉系统选择哪个底层协议族。对TCP/IP协议族而言,该参数设置为PF_INET(IPv4)或PE_INET6(IPv6);对于UNIX本地域协议族,设置为PE_UNIX
type指定服务类型。主要有SOCK_STREAM服务(流服务)和SOCK_DGRAM服务(数据报服务)。对于TCP/IP协议族来说,取SOCK_STREAM表示传输层使用TCP协议,取SOCK_DGRAM表示传输层使用UDP协议。还有SOCK_NONBLOCKSOCK_CLOEXEC,分别表示新创建的socket设为非阻塞的,以及用fork调用创建子进程时在子进程中关闭该socket。
protocol指在前两个参数构成的协议集合下,再选择一个具体的协议。这个值一般是唯一的,设置为0,表示使用默认协议。

socket系统调用成功返回一个socket文件描述符,失败返回-1且设置errno。

命名socket

创建socket时,指定了地址族,并未指定使用该地址族中的具体socket地址。将一个socket和socket地址绑定称为给socket命名。在服务器程序中,需要命名socket,只有命名后客户端才知道怎么连接它。客户端则通常不需要命名,采取匿名方式,操作系统主动分配的socket地址。
命名socket的系统调用是bind。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

bind将my_addr所指的socket地址分配给未命名的sockfd文件描述符,addrlen指出该socket地址的长度。
成功时返回0,失败返回-1并设置errno。
常见errno:
EACCES:被绑定的地址是受保护的地址,仅超级用户能够访问。
EADDRINUSE:被绑定的地址正在使用中。

监听socket

socket命名之后,不能马上接受客户端连接。需要创建一个监听队列以存放待处理的客户连接:
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

sockfd指被监听的socket。backlog指内核监听队列的最大长度。如果监听队列超过此值,服务器将不受理新的客户连接,客户端收到ECONNREFUSED错误信息。
listen成功返回0,失败返回-1并设置errno。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xkJGU2X4-1590112854532)(_v_images/20200511141610898_19743.png =531x)]

接受连接

从listen监听队列中接受一个连接。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

sockfd是执行过listen系统调用的监听socket,addr是用来获取被接收连接的远程socket地址,该socket地址长度由addrlen自己获取。
accept成功时返回一个新的连接socket,该socket唯一标识了被接受的这个连接,服务器可以读写这个socket来与被接受连接的客户端通信。失败时返回-1并设置errno。

执行过listen调用,处于LISTEN状态的socket称为监听socket,所有处于ESTABLISHED状态的socket称为连接socket。

发起连接

服务器通过listen调用来被动接受连接,那么客户端需要通过如下系统调用来主动与服务器建立连接:
《Linux高性能服务器编程》笔记5--Linux网络编程基础API
sockfd是由socket系统调用返回一个socket。serv_addr是服务器监听的socket地址,addrlen是这个地址的长度。
connect成功返回0.一旦成功建立连接,sockdf就唯一标识了这个连接,客户端可以读写sockfd来与服务器通信。connect失败则返回01并设置errno。
常见errno:
ECONNREFUSED:目标端口不存在,连接被拒绝。
ETIMEDOUT:连接超时。

关闭连接

关闭连接实际上就是关闭该连接对应的socket。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

fd是待关闭的socket。close系统调用并不总是立即关闭一个连接,而是将fd的引用计数减1,当fd的引用计数为0时,才真正关闭连接。多进程程序中,一次fork调用默认将使父进程中打开的socket引用计数加1.因此,必须在父进程和子进程中都对该socket进行close调用才能将连接关闭。

如果无论如何要立即终止连接,可使用shutdown调用。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

sockfd参数是待关闭的socket。howto决定了shutdown 的行为。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

close在关闭连接时同时关闭读写。

数据读写

TCP数据读写

对于文件读写操作的readwrite同样使用于socket。但是socket提供了专用的数据读写操作,增加了对数据读写的控制。
TCP流数据的读写系统调用:
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

  • recv读取sockfd上的数据,buf和len分别指定读缓冲区的位置和大小,flags通常设置为0.recv成功时返回实际读取的数据长度。返回0表示通信对方已经关闭连接。出错时返回-1并设置errno。
  • send往sockfd上写数据,buf和len指定写缓冲区的位置和大小。send成功时返回实际写入的数据产长度,失败返回-1且设置errno。

《Linux高性能服务器编程》笔记5--Linux网络编程基础API
服务器运行1:
《Linux高性能服务器编程》笔记5--Linux网络编程基础API
服务器运行2:
《Linux高性能服务器编程》笔记5--Linux网络编程基础API
客户端运行:
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

UDP数据读写

《Linux高性能服务器编程》笔记5--Linux网络编程基础API

recvfrom读取sockfd上的数据,buf和len指定缓冲区位置和大小。src_addr发送端的socket地址,addrlen指地址长度。
sendto往sockfd上写数据,buf,len;dest_addr指定接收端socket地址,addrlen地址长度。

通用数据读写

既可用于TCP,也可以用于UDP。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

msg_name指向一个socket地址结构,指定通信对方的socket地址,对于TCP来说,必须设定为NULL。mag_namelen是指定socket地址长度。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

iovec封装了一块内存的起始地址和长度。msg_iovlen指定这样的iovec结构对象有多少个。
对于recvmsg,数据被读取并存放在msg_iovlen块分散的内存中,内存位置和长度由msg_iov指向的数组决定,这叫分散读。
对于sendmsg,msg_iovlen块分散内存中的数据被一并发送,这叫集中写。

带外标记

《Linux高性能服务器编程》笔记5--Linux网络编程基础API

地址信息函数

获取一个连接socket的本端socket地址和远端socket地址。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

getsockname:获取本端socket地址,地址存放address,地址的长度存放address_len;
getpeername:获取远端socket地址。

socket选项

读取和设置socket文件描述符属性。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

sockfd:目标socket。 level:操作哪个协议的属性。option_name:选项的名字。option_value:选项的值。option_len:选项长度。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

成功返回0,失败返回-1并设置errno。

SO_REUSEADDR

设置socket选项SO_REUSEADDR来强制使用处于TIME_WAIT状态的连接占用的socket地址。

SO_RCVBUF 和SO_SNDBUF

TCP接收缓冲区和发送缓冲区大小。使用setsockopt来设置TCP的接收缓冲区和发送缓冲区的大小时,系统都会将其值加倍,并且不小于某个最小值。确保TCP连接拥有足够的空闲缓冲区来处理拥塞。
接收窗口:
《Linux高性能服务器编程》笔记5--Linux网络编程基础API
发送缓冲区:
《Linux高性能服务器编程》笔记5--Linux网络编程基础API
抓包:
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

SO_RCVLOWAT 和 SO_SNDLOWAT

TCP接收缓冲区和发送缓冲区的低水位标记。用来判断socket是否可读或可写。

SO_LINGER

控制close调用在关闭TCP连接时的行为。默认情况下,用close调用关闭一个socket时,close立即返回,TCP模块负责把该socket对应的TCP发送缓冲区中残留的数据发送给对方。

网络信息API

socket地址的两个要素,IP地址和端口号。主机名可访问机器,服务名称代替端口号。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

gethostbyname 和 gethostbyaddr

gethostbyname根据主机名称获取主机的完整信息,gethostbyaddr根据IP地址获取主机的完整信息。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

name:主机名。addr:主机地址。len:地址长度。type:地址类型(IPv4,IPv6)。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

getservbyname 和 getservbyport

getservbyname根据名称获取某个服务发完整信息。getservbyport根据端口号获取某个服务的完整信息。读取/etc/services文件获取服务信息。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

name: 目标服务;port:目标服务对应的端口号;proto指定服务类型,传递tcp表示获取流服务,传递udp获取数据报服务,给它传递NULL表示获取所有类型服务。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

在获取daytime时,主机需要开启daytime服务。不然访问会被拒绝。开启daytime服务:开启daytime
《Linux高性能服务器编程》笔记5--Linux网络编程基础API
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

getaddrinfo

既能通过主机名获得IP地址(内部使用gethostbyname),也能通过服务名获取端口号(内部使用getservbyname)。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

hostname可以接收主机名,也可以接收字符串表示的IP地址。service即可接收服务名,也可以接收字符串表示的十进制端口号。hints是一个提示,对输出进行更精确的控制。hints可以设置为NULL,表示允许函数反馈任何可用的结果。result指向一个链表,用于存储反馈的结果。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

ai_protocol指具体的网络协议,通常设置为0.
ai_flags取下表的标志的按位或。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API

getnameinfo

通过socket地址同时获取字符串表示的主机名(内部使用gethostbyaddr函数)和服务名(内部使用getservbyport)。
《Linux高性能服务器编程》笔记5--Linux网络编程基础API
《Linux高性能服务器编程》笔记5--Linux网络编程基础API
《Linux高性能服务器编程》笔记5--Linux网络编程基础API
《Linux高性能服务器编程》笔记5--Linux网络编程基础API