Open luoxn28 opened 7 years ago
文中的截图来自哪本书啊?
UNIX网络编程卷1:套接字联网API(第3版)
Thank you for replyling 2017年6月2日星期五,骆向南 notifications@github.com 写道:
UNIX网络编程卷1:套接字联网API(第3版)
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/luoxn28/ThinkInTechnology/issues/1#issuecomment-305787239, or mute the thread https://github.com/notifications/unsubscribe-auth/AYyucCoNqDMyCjYXkJYiL3G7QqM5yLt4ks5sAA5cgaJpZM4KrXEt .
Linux网络编程中,套接字编程是重要的知识,因为操作系统就是通过提供套接字来为进程提供网络通信能力的。在使用C/C++开源网络库(比如libevent/nginx/muduo)的同时,如果不去了解套接字编程,往往对这些框架或者组件没有原理上的认识。学习套接字编程,需要知道常用的套接字函数有哪些,了解这些函数在使用中的注意事项。
套接字地址结构
sockaddr_in是网络套接字地址结构,大小为16字节,定义在<netinet/in>头文件中,一般我们在程序中是使用该结构体,但是作为参数传递给套接字函数时需要强转为sockaddr类型,注意该结构体中port和addr成员是网络序的(大端结构)。
sockaddr是通过套接字地址结构,当作为参数传递给套接字函数时,套接字地址结构总是以指针方式来使用,比如bind/accept/connect函数等。
htons、ntohs、htonl和ntohl函数
Linux提供了4个函数来完成主机字节序和网络字节序之间的转换。这些函数名字中,h表示host,n表示net,s表示short,l表示long。使用这些函数时,我们并不关心主机字节序和网络字节序的真实值,也就是为大端还是小端,要做的只是调用适当的函数在主机和网络字节序之间转换为某个特定值。
ient_aton、inet_addr和inet_ntoa函数
inet_aton、inet_addr和inet_ntoa在点分十进制数串(比如"192.168.1.1")与它长度为32位的网络字节序二进制值间转换IPv4地址。在调用inet_addr时需特别注意,inet_ntoa函数的输入参数是unsigned int型的ip地址,返回的却是指向ip字符串的指针,很明显,ip字符串所占的内存是在函数内部分配的,而我们并不需要释放该内存,所以,它分配的内存是静态的,内部使用static变量存储IP点分十进制数串,也就是说第二次调用该函数时会覆盖第一次调用该函数时的内存。
inet_pton和inet_ntop函数
这两个函数对于IPv4和IPv6都适用,p代表表达式(presentation)、n表示数值(numeric)。第一个函数尝试转化由strptr指针所指的字符串,通过addptr指针存放二进制结果,成功返回1,如果对指定的family而言输入的不是有效的表达格式,那么返回0 inet_ntop进行相反的操作,如果len的值太小,不足以存放表达式结果,则返回一个空指针,并置error为ENOSPC。inet_ntop函数的strptr参数不可以是一个空指针,调用者必须为目标存储单元分配内存并制定其大小,调用成功时,这个指针就是该函数返回值。
socket函数
为了执行网络IO,一个进程必须做的第一件事就是调用socket函数,指定期望的通信协议类型(比如使用IPv4的TCP、使用IPv6的UDP、Unix域字节流协议)和套接字类型(字节流、数据报或原始套接字)。
family指定协议族,type指定套接字类型,protocol指定某个协议类型常值,或者设为0。 family的值有:
connect函数
TCP客户用connect函数来建立一个与TCP服务器连接,sockfd是由socket函数返回的套接字描述符,第二个、第三个参数分别是指向一个套接字地址结构的指针和该结构的大小,套接字结构必须含有服务器的IP地址和端口号。注意:如果connect失败后,就必须close当前的套接字描述符并重新调用socket。客户端在调用connect前不必非得调用bind函数(比如UDP客户端编程中一般就不用调用bind),内核会确定源IP地址,并选择一个临时端口作为源端口。 如果是TCP套接字,调用connect函数将激发TCP的三次握手过程,而且仅在连接建立成功或出错时才返回。注意:connect是在接收到服务端响应的SYN+ACK时的返回的,也就是三次握手的第二次动作之后。 UDP是可以调用connect函数的,但是UDP的connect函数和TCP的connect函数调用确是大相径庭的,这里没有三次握手过程。内核只是检查是否存在立即可知的错误(比如目的地址不可达),记录对端的IP和端口号,然后立即返回调用进程。使用了connect的UDP编程就可不必使用sendto函数了,直接使用write/read即可。
bind函数
bind函数把一个本地协议地址赋予一个套接字,它只是把一个协议地址赋予一个套接字,至于协议地址的含义则取决于协议本身。第二个参数指向协议地址结构的指针,第三个参数是协议地址的长度,对于TCP,调用bind函数可以指定一个端口号,或指定一个IP地址,或两者都指定,也可以两者都不指定。 bind函数绑定特定的IP地址必须属于其所在主机的网络接口之一,服务器在启动时绑定它们众所周知的端口,如果一个TCP客户端或服务端未曾调用bind绑定一个端口,当调用connect或listen时,内核就要为相应的套接字选择一个临时端口。让内核选择临时端口对于TCP客户端来说是正常的,然而对于TCP服务端来说确实罕见的,因为服务端通过他们众所周知的端口被大家认识的。
listen函数
socket创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用connect发起连接的一个客户套接字。listen函数把一个未连接的套接字转换为一个被动套接字,指示内核应接受指向该套接字的连接请求,调用listen函数将导致套接字从CLOSEE状态转换到LISTEN状态。第二个参数规定了内核应为相应套接字排队的最大连接个数。
accept函数
accept函数由TCP服务器调用,用于从已完成队列的列头返回下一个已完成连接,如果已完成队列为空,则进程被投入睡眠(如果该套接字为阻塞方式的话)。如果accept成功,那么其返回值是由内核自动生成的一个全新套接字,代表与返回客户的TCP连接,函数的第一个参数为监听套接字,返回值为已连接套接字。
close函数
close一个TCP套接字的默认行为是把该套接字标记为已关闭,然后立即返回到调用进程。注意,close实质把该套接字引用值减1,如果该引用值大于0,则对应的套接字不会被真正关掉。
服务器、客户端交互流程图
TCP状态转换图
getsockname和getpeername函数
getsockname获取sockfd对应的本端socket地址,并将其存储于address参数指定的内存地址,该socket长度存储于addrlen指向的变量中。getpeername获取远端的socket地址。 UDP客户端如果调用connect之后也是可以使用getpeername的。
recv和send函数
TCP流数据读写操作函数。flag取值如下所示:
recvfrom和sendto函数
recvfrom和snedto的前3个参数和read/write的前3个参数一样。flags表示设置的标志值,简单的UDP程序可以直接设置为0,最后两个参数表示服务端地址(对于sendto来说)或者是对端地址(对于recvfrom来说)。如果不关心对端的地址,则设置为NULL,此时addrlen也可以设置为NULL。 注意:recvfrom和sendto也可以应用于TCP编程,不过一般不这样用。UDP编程会有数据包的丢失问题,因为UDP是不可靠的,如果一个客户的数据包丢失,客户端将永远阻塞在recvfrom函数调用;类似的,如果客户数据到达了服务端,然后响应数据包丢失了,则客户永远阻塞在recvfrom调用。为了防止这样的问题出现,一般可以给recvfrom设置一个超时时间。