其实在写这篇文章开始之前,原本想打算先介绍一下TCP/IP协议的内容,但是在网上看了一些博客,大概都讲的差不多,随便找几篇博客来看(https://developer.51cto.com/art/201906/597961.htm),你就会对这个协议有一个大概的了解(有些地方或许读者和我一样可能也看的不是很明白,但是这对编程阻碍不大),所以我也不打算写这个了(理由是,自己也比较菜,只要大概了解一下这部分内容就行,在日后学习或者工作当中遇到什么不理解的地方再去深入学,比较有针对性;所以侧重点还是在编程上,最终实现理论转到实践当中去,才是王道)。不过经典的TCP三次握手和四次挥手告别,这个基本你必须要明白,这里简单介绍一下,那么就开始今天的内容了。
一、TCP三次握手和四次挥手告别:
(1)三次握手过程(要明白一点TCP传输数据是可靠的,所以才要这套机制):
(2)四次挥手告别:
a、第一次次挥手:客户端发送一个FIN=M,用来关闭客户端到服务器端的数据传送,客户端进入FIN_WAIT_1状态。意思是说"我客户端没有数据要发给你了",但是如果你服务器端还有数据没有发送完成,则不必急着关闭连接,可以继续发送数据。
b 、第二次挥手:服务器端收到FIN后,先发送ack=M+1,告诉客户端,你的请求我收到了,但是我还没准备好,请继续你等我的消息。这个时候客户端就进入FIN_WAIT_2 状态,继续等待服务器端的FIN报文。
c、第三次挥手:当服务器端确定数据已发送完成,则向客户端发送FIN=N报文,告诉客户端,好了,我这边数据发完了,准备好关闭连接了。服务器端进入LAST_ACK状态。
d、第四次挥手:客户端收到FIN=N报文后,就知道可以关闭连接了,但是他还是不相信网络,怕服务器端不知道要关闭,所以发送ack=N+1后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。服务器端收到ACK后,就知道可以断开连接了。客户端等待了2MSL后依然没有收到回复,则证明服务器端已正常关闭,那好,我客户端也可以关闭连接了。最终完成了四次挥手告别。
(3)注意:上面是一方主动关闭,另一方被动关闭的情况,实际中还会出现同时发起主动关闭的情况。
二、什么是socket?:
在讲解这个之前,你得必须要明白在Linux系统下,一切皆是文件,(举个简单的例子,一般我们在Windows系统下,你是可以清楚的看到硬盘盘符,并标有大小的,可以直接用鼠标拖拉文件到里面去;而在Linux系统下是看不到的,它是以文件的形式存在的,对它操作可就没那么简单了)。为啥这里要说一下这个呢?因为后面接口函数里面的参数就有这个socket描述符(如果你第一次看到这个可能就有点懵逼了,不知道为啥要有这个东西),其实它和我们之前一系列的文章里讲的文件描述符是一样的(文件描述符这里就不介绍了,前面对文件操作的文章里面已经很详细的介绍了这个,读者可以去看我之前写的文章,如有不理解的地方,欢迎来交流)
其实这个socket就是套接字(套接字是网络数据传输用的软件设备;这里有一个比较形象的比喻,因为socket这个英文单词的中文意思是插座的意思,所以我们把插头插到插座上就能从电网获得电力供给),同样,为了与远程计算机进行传输数据,需要连接到因特网,而编程套接字就是用来连接该网络的工具。
三、使用打电话的形式来介绍socket接口函数:
1、调用socket函数(安装电话机)时进行的通话:
问:"接电话需要准备什么?"
答:"当然是电话机!"
说明:
int domain(这里domain的中文意思是领域,域名):它是创建套接字所使用的协议栈,通常为AF_INET(也就是IPv4网络协议),下面试各种协议栈的汇总:
DESCRIPTION
socket() creates an endpoint for communication and returns a file descriptor that refers to that endpoint. The file descriptor returned by a
successful call will be the lowest-numbered file descriptor not currently open for the process.
The domain argument specifies a communication domain; this selects the protocol family which will be used for communication. These families
are defined in <sys/socket.h>. The currently understood formats include:
Name Purpose Man page
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device netlink(7)
AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK AppleTalk ddp(7)
AF_PACKET Low level packet interface packet(7)
AF_ALG Interface to kernel crypto API
type:它是用来指定套接字的类型,即指定数据流套接字(SOCK_STREAM)还是数据报套接字(SOCK_DGRAM):
The socket has the indicated type, which specifies the communication semantics. Currently defined types are:
SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be sup‐
ported.
SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length).
SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a con‐
sumer is required to read an entire packet with each input system call.
SOCK_RAW Provides raw network protocol access.
SOCK_RDM Provides a reliable datagram layer that does not guarantee ordering.
SOCK_PACKET Obsolete and should not be used in new programs; see packet(7).
Some socket types may not be implemented by all protocol families.
Since Linux 2.6.27, the type argument serves a second purpose: in addition to specifying a socket type, it may include the bitwise OR of any
of the following values, to modify the behavior of socket():
SOCK_NONBLOCK Set the O_NONBLOCK file status flag on the new open file description. Using this flag saves extra calls to fcntl(2) to
achieve the same result.
SOCK_CLOEXEC Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor. See the description of the O_CLOEXEC flag in open(2) for
reasons why this may be useful.
protocol:它通常设置为0,函数返回一个socket描述符(上面已有提醒这个哦)。
2、调用bind函数(分配电话号码)时进行的对话:
套接字也是这样。就像电话机分配电话号码一样(虽然不是真的把电话号码分配给电话机),这里类似的用bind函数给创建好的套接字分配地址信息(地址信息包括:IP地址和端口号,这个如果不知道的话,也可以去查看百度或者博客,这里就不介绍了。)。
下面我们来看一下在Linux系统下它的原型:
BIND(2)
Linux Programmer's Manual
BIND(2)
NAME
bind - bind a name to a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
说明:
int sockfd:这个就是socket文件描述符了。
const struct sockaddr *addr:这个参数可以看它的源注解,下它里面讲解的那个地址查询,还是用man手册来查看(例如man ipv6):
When a socket is created with socket(2), it exists in a name space (address family) but has no address assigned to it. bind() assigns the
address specified by addr to the socket referred to by the file descriptor sockfd. addrlen specifies the size, in bytes, of the address
structure pointed to by addr. Traditionally, this operation is called “assigning a name to a socket”.
It is normally necessary to assign a local address using bind() before a SOCK_STREAM socket may receive connections (see accept(2)).
The rules used in name binding vary between address families. Consult the manual entries in Section 7 for detailed information. For AF_INET,
see ip(7); for AF_INET6, see ipv6(7); for AF_UNIX, see unix(7); for AF_APPLETALK, see ddp(7); for AF_PACKET, see packet(7); for AF_X25, see
x25(7); and for AF_NETLINK, see netlink(7).
The actual structure passed for the addr argument will depend on the address family. The sockaddr structure is defined as something like:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
The only purpose of this structure is to cast the structure pointer passed in addr in order to avoid compiler warnings. See EXAMPLE below.
socklen_t addrlen:这个参数就是指分配地址空间的大小
3、调用listen函数(连接电话)时进行的对话:
一连接电话线,电话机可以转为可接听的状态,这时其他人可以拨打电话请求连接到该机。同样需要把套接字转化成可接收连接的状态。下面是他函数原型:
LISTEN(2)
Linux Programmer's Manual
LISTEN(2)
NAME
listen - listen for connections on a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
说明:
这里主要讲第二参数 int backlog,它 是用来设置请求队列中允许的最大请求数。注意:TCP传输中为被动套接字设置了两个队列:完全建立连接的队列和未完全建立连接的队列。
The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a connection request arrives when the queue is full, the
client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt
at connection succeeds.
4、调用accept函数(拿起话筒)时进行的对话:
拿起话筒意味着接收了对方的连接请求。套接字也是这样,如果有人为了完成数据传输而请求连接,就需要调用accept函数来进行处理。我们来看一下它的原型:
ACCEPT(2)
Linux Programmer's Manual
ACCEPT(2)
NAME
accept, accept4 - accept a connection on a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sys/socket.h>
int accept4(int sockfd, struct sockaddr *addr,
socklen_t *addrlen, int flags);
说明:
这里主要说一下第四个参数int flags(这里我没有翻译,看原注释更好一点,自己翻译翻着意思就变了。哈哈,还是自己的英文水平比较菜):
If flags is 0, then accept4() is the same as accept(). The following values can be bitwise ORed in flags to obtain different behavior:
SOCK_NONBLOCK Set the O_NONBLOCK file status flag on the new open file description. Using this flag saves extra calls to fcntl(2) to achieve the same result.
SOCK_CLOEXEC Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor. See the description of the O_CLOEXEC flag in open(2) for reasons why this may be use‐
ful.
四、总结:
socket编程中接收连接请求的套接字创建过程如下: