Linux高级--2.4.2 linux TCP 系列操作函数 -- 深层明白
一、操作函数简介在 Linux 中,TCP(传输控制协议)操作涉及多种系统调用和函数,通常用来创建套接字、毗连、发送/接收数据、关闭毗连等。以下是一些常用的 TCP 操作函数和它们的简要说明:
1. socket()
[*]函数原型: int socket(int domain, int type, int protocol);
[*]功能: 创建一个新的套接字(socket),它是与网络通信相干的根本对象。
[*]参数:
[*]domain: 协议族(如 AF_INET 用于 IPv4,AF_INET6 用于 IPv6)。
[*]type: 套接字范例(如 SOCK_STREAM 表示 TCP,SOCK_DGRAM 表示 UDP)。
[*]protocol: 使用的协议,通常设为 0,由系统自动选择符合的协议。
[*]返回值: 返回一个套接字形貌符(文件形貌符),失败时返回 -1。
2. bind()
[*]函数原型: int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
[*]功能: 将套接字与本地地址(IP 地址和端口)绑定。
[*]参数:
[*]sockfd: 要绑定的套接字。
[*]addr: 地址结构,通常是 struct sockaddr_in,指定 IP 和端口。
[*]addrlen: 地址结构的长度。
[*]返回值: 成功返回 0,失败返回 -1。
3. listen()
[*]函数原型: int listen(int sockfd, int backlog);
[*]功能: 将套接字设置为被动模式,等待客户端毗连。
[*]参数:
[*]sockfd: 套接字形貌符。
[*]backlog: 最多可毗连的等待队列的大小。
[*]返回值: 成功返回 0,失败返回 -1。
4. accept()
[*]函数原型: int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
[*]功能: 担当来自客户端的毗连哀求,并返回一个新的套接字形貌符用于与客户端通信。
[*]参数:
[*]sockfd: 已经调用 listen() 的套接字。
[*]addr: 客户端的地址信息。
[*]addrlen: 地址结构的大小。
[*]返回值: 返回新的套接字形貌符,用于与客户端的通信,失败时返回 -1。
5. connect()
[*]函数原型: int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
[*]功能: 客户端发起与服务器的毗连哀求。
[*]参数:
[*]sockfd: 客户端套接字形貌符。
[*]addr: 目的服务器的地址信息。
[*]addrlen: 地址结构的长度。
[*]返回值: 成功返回 0,失败返回 -1。
6. send()
[*]函数原型: ssize_t send(int sockfd, const void *buf, size_t len, int flags);
[*]功能: 通过套接字发送数据。
[*]参数:
[*]sockfd: 套接字形貌符。
[*]buf: 数据缓冲区。
[*]len: 发送数据的长度。
[*]flags: 发送标志(一般设为 0)。
[*]返回值: 返回实际发送的字节数,失败时返回 -1。
7. recv()
[*]函数原型: ssize_t recv(int sockfd, void *buf, size_t len, int flags);
[*]功能: 从套接字接收数据。
[*]参数:
[*]sockfd: 套接字形貌符。
[*]buf: 存储接收到数据的缓冲区。
[*]len: 接收数据的最大长度。
[*]flags: 接收标志(一般设为 0)。
[*]返回值: 返回实际接收的字节数,失败时返回 -1。
8. close()
[*]函数原型: int close(int fd);
[*]功能: 关闭套接字,释放相干资源。
[*]参数:
[*]fd: 套接字形貌符。
[*]返回值: 成功返回 0,失败返回 -1。
9. shutdown()
[*]函数原型: int shutdown(int sockfd, int how);
[*]功能: 用于关闭套接字的读、写大概双向通信。
[*]参数:
[*]sockfd: 套接字形貌符。
[*]how: 控制关闭的方式,常用值为:
[*]SHUT_RD: 关闭读取(不能再读取数据)。
[*]SHUT_WR: 关闭写入(不能再发送数据)。
[*]SHUT_RDWR: 同时关闭读写。
[*]返回值: 成功返回 0,失败返回 -1。
10. getsockopt() 和 setsockopt()
[*]函数原型:
[*]int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
[*]int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
[*]功能: 用于获取或设置套接字的选项(如 TCP 的各种参数,如缓冲区大小、超时时间等)。
[*]参数:
[*]sockfd: 套接字形貌符。
[*]level: 设置选项的协议层级,通常为 SOL_SOCKET(套接字层)或 IPPROTO_TCP(TCP 层)。
[*]optname: 选项名称(如 SO_RCVBUF,SO_RCVBUF 等)。
[*]optval: 选项的值。
[*]optlen: 选项值的长度。
[*]返回值: 成功返回 0,失败返回 -1。
11. select() 和 poll()
[*]函数原型:
[*]int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
[*]int poll(struct pollfd *fds, nfds_t nfds, int timeout);
[*]功能: 允许步伐监听多个套接字,并在某些事件(如可读、可写等)发生时举行处理。
12. accept4()(Linux 特有)
[*]函数原型: int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
[*]功能: 与 accept() 类似,但支持额外的标志(如 SOCK_NONBLOCK 等),在非阻塞模式下返回。
[*]返回值: 返回一个新的套接字形貌符,失败时返回 -1。
小结:
这些是常见的用于 TCP 通信的 Linux 系统调用和函数。它们允许应用步伐通过网络举行根本的毗连管理、数据发送/接收等操作。通常情况下,服务器会使用 socket()、bind()、listen() 和 accept() 来创建并处理客户端毗连,而客户端则使用 socket() 和 connect() 发起毗连。数据的发送和接收使用 send() 和 recv()。
二、socket/listen/accept与TCB的关系
下面将具体解释在socket()、listen()、accept()等函数调用过程中,TCP控制块(TCB,struct tcp_sock)的创建和队列的使用,以及它们与文件形貌符(socket_fd 和 client_fd)的关系。
1. socket() 函数调用后的TCB关联
[*]当你调用socket()函数时,操作系统会为这个套接字创建一个struct sock结构体(具体来说,假如是TCP套接字,将创建一个struct tcp_sock,它是struct sock的子类)。这个结构体就是TCP控制块(TCB),负责管理该套接字的所有TCP毗连状态。
[*]创建的sock结构体会与socket_fd绑定,socket_fd是应用层与内核层举行通信的文件形貌符。通过socket_fd,内核可以找到与之关联的sock结构体。
2. listen() 函数调用后的队列创建
[*]当调用listen()函数时,TCP进入监听状态,这时在与该监听套接字对应的TCB上会创建两个队列:
[*]半毗连队列(Syn Queue):存放正在举行三次握手的毗连。
[*]全毗连队列(Accept Queue):存放已经完成三次握手的毗连。
这些队列用于管理TCP毗连的不同状态,但队列中的成员并不是直接的TCB(struct tcp_sock)范例:
[*] 半毗连队列中的成员:是struct request_sock范例。request_sock是一个轻量级的数据结构,用于在三次握手未完成时存储毗连哀求的状态信息。在接收到客户端的SYN之后,服务端在半毗连队列中分配一个request_sock,并等待三次握手完成。
[*] 全毗连队列中的成员:在三次握手完成后,内核会从半毗连队列移除request_sock并创建一个完整的struct tcp_sock(也称作TCB),然后将其移入全毗连队列中,表示该毗连已经建立。
3. accept() 函数调用后
[*] 当应用步伐调用accept()函数时,内核会从全毗连队列中取出一个已经完成三次握手的TCP毗连。
[*] 在全毗连队列中的成员是一个完整的struct tcp_sock(即TCB),它记载了该毗连的所有TCP状态。
[*] 内核会为这个新的TCP毗连创建一个新的文件形貌符,称为client_fd,并将该文件形貌符与这个TCP毗连的TCB(struct tcp_sock)举行绑定。
换句话说,client_fd与新毗连的struct tcp_sock关联起来,使得通过client_fd可以操作该TCP毗连(如发送或接收数据)。
总结流程
[*] socket(): 创建一个struct sock(具体为struct tcp_sock),并与socket_fd关联。
[*] listen(): 在tcp_sock上创建半毗连队列和全毗连队列:
[*]半毗连队列存放struct request_sock,用于管理三次握手中的毗连。
[*]全毗连队列存放已建立毗连的struct tcp_sock。
[*] accept(): 从全毗连队列中取出一个struct tcp_sock,为它分配一个新的文件形貌符client_fd,并将client_fd与这个TCP毗连的TCB(struct tcp_sock)绑定。
因此,调用accept()后,全毗连队列中的TCP毗连会与新的client_fd关联,应用步伐通过client_fd来处理这个TCP毗连。
三、listen函数backlog的作用
listen()函数的backlog参数在TCP服务器中用于指定全毗连队列(Accept Queue)的最大长度,即允许在服务器上排队等待accept()的已建立毗连的最大数目。
1. listen() 函数及 backlog 参数的作用
当你调用listen()函数时,服务器的套接字进入监听状态,开始等待客户端的毗连哀求。backlog参数界说了以下内容:
[*]最大已完成毗连数:backlog参数指定全毗连队列的最大长度,即已经完成三次握手但尚未被应用步伐accept()取走的毗连数。
[*]当客户端发起毗连哀求并完成了三次握手,毗连会被放入全毗连队列。假如队列已满,新完成的毗连将被拒绝,客户端会收到TCP RST(复位)信号,表示毗连无法建立。
2. backlog 参数的工作机制
在listen(sockfd, backlog)中:
[*]全毗连队列(Accept Queue) 存放的是已经完成三次握手、处于ESTABLISHED状态的毗连,这些毗连等待应用步伐调用accept()来处理。
[*]半毗连队列(Syn Queue) 管理尚未完全建立的毗连(正在三次握手中的毗连),它与backlog关系较小,主要受tcp_max_syn_backlog内核参数的影响。
具体举动:
[*]当全毗连队列中的毗连数达到backlog限制时,新完成的毗连将无法进入队列,导致客户端收到RST包,毗连被拒绝。
[*]假如设置的backlog值太小,服务器可能无法处理高并发毗连,导致毗连哀求频仍被拒绝。
[*]假如设置的backlog值过大,可能会增加系统负担,尤其是在没有足够的资源或处理本领时。
3. backlog 参数的实际值
[*] 虽然应用步伐可以指定backlog的大小,但内核实际上会对该值举行限制。
[*] Linux内核中有一个参数somaxconn,它界说了允许的最大backlog值。假如你在listen()中传入的backlog值大于/proc/sys/net/core/somaxconn中设定的值,系统会将backlog限制为somaxconn的值。
[*]查看和调解 somaxconn 参数: cat /proc/sys/net/core/somaxconn
echo 1024 > /proc/sys/net/core/somaxconn
4. 实际例子
假设你调用了如下的listen()函数:
listen(sockfd, 10);
[*]这意味着全毗连队列的长度最大为10,即最多允许10个已经完成三次握手的毗连排队等待accept()。
[*]假如第11个毗连尝试建立,服务器将返回TCP RST包,拒绝该毗连。
5. 总结
[*]backlog参数用于指定服务器上全毗连队列的最大长度,即等待应用层accept()调用的已建立毗连数的最大值。
[*]过小的backlog值会导致高并发时毗连被拒绝,而过大的值会增加系统资源占用,需根据系统处理本领合理设置。
四、半毗连队列的限制
在 TCP 服务器中,半毗连队列的数目(即 SYN 队列)由内核的 tcp_max_syn_backlog 参数控制。
1. 半毗连队列(SYN队列):
[*]当客户端向服务器发送 SYN 哀求时,服务器将这个毗连哀求放入 半毗连队列(也称为 SYN 队列)。此队列用于存储尚未完成三次握手的毗连。
[*]一旦握手完成而且服务器准备好担当数据,毗连就会移入 全毗连队列(Accept Queue)。
2. tcp_max_syn_backlog 参数:
[*]作用: 控制半毗连队列的最大长度,即可以缓存的未完成三次握手的毗连数。
[*]默认值: 在大多数 Linux 系统中,默认值通常为 128,意味着最多可以缓存 128 个尚未完成三次握手的毗连。
[*]调解: 可以通过修改 /proc/sys/net/ipv4/tcp_max_syn_backlog 文件来调解此值。例如: echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog
大概在 sysctl.conf 中添加: net.ipv4.tcp_max_syn_backlog=2048
3. SYN 队列溢出:
[*]假如半毗连队列已满而且有新的 SYN 哀求到达,内核会抛弃这些毗连哀求,通常客户端会收到一个 TCP RST(重置) 消息,大概假如客户端重试,可能会耽误毗连。
[*]为了避免此情况,通常需要根据实际的网络负载来调解该参数,尤其是在高并发的服务器上。
4. 全毗连队列:
[*]在调用 listen() 函数时,backlog 参数设置的是 全毗连队列 的大小,即已完成三次握手的毗连的最大数目。它并不直接影响半毗连队列的大小。
[*]假如 全毗连队列 已满,accept() 会阻塞,直到队列中有空间为止。
总结:
[*]半毗连队列(SYN 队列)的大小是由 tcp_max_syn_backlog 参数控制。
[*]全毗连队列(Accept Queue)的大小是由 listen() 函数的 backlog 参数控制。
因此,半毗连队列和全毗连队列的长度由不同的参数控制,而服务器需要根据实际的负载情况合理设置这些参数,以确保高并发时的毗连性能和稳固性。
五、send函数的第四个参数是什么作用
send()函数的第四个参数是**flags**,用于指定发送操作的举动。通过设置不同的标志,应用步伐可以控制send()函数的具体举动。
send() 函数的原型
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
[*]sockfd:目的套接字的文件形貌符。
[*]buf:要发送的数据的缓冲区。
[*]len:要发送的数据长度。
[*]flags:控制发送举动的标志位(即第四个参数)。
常用的 flags 值
以下是一些常用的标志及其作用,它们可以组合使用(使用按位或操作符 |):
[*] MSG_DONTWAIT:
[*]使send()成为非阻塞操作。假如套接字的发送缓冲区已满,send()不会等待缓冲区空闲,而是立刻返回,返回值为-1,并设置errno为EAGAIN或EWOULDBLOCK。
[*]实用于非阻塞套接字,也可以暂时使阻塞套接字表现为非阻塞模式。
[*] MSG_OOB(Out-of-Band Data):
[*]发送告急数据(带外数据),仅实用于TCP协议。告急数据会优先于普通数据处理,但在实际应用中,带外数据的使用较少。
[*]常用于一些需要快速响应的特殊场景。
[*] MSG_NOSIGNAL:
[*]假如向已断开的毗连发送数据,通常会触发SIGPIPE信号,导致步伐停止。使用该标志可以抑制SIGPIPE信号,防止步伐瓦解。
[*]实用于需要处理网络停止且不希望信号干扰的场景。
[*] MSG_CONFIRM:
[*]仅实用于基于某些协议(如UDP)的发送,表示希望确认对端的存在,通常用于实现链路层的邻居确认。
[*]仅用于某些低层协议的特定场景,在常规TCP/UDP应用中较少使用。
[*] MSG_DONTROUTE:
[*]发送数据时,不查找路由表,直接将数据发送到与目的网络直接相连的接口。通常用于网络诊断和本地网络通信的场景。
[*]在大多数普通应用场景中很少使用。
[*] MSG_EOR(End of Record):
[*]仅用于某些基于记载的协议,表示本次send()调用发送的数据是一个逻辑记载的竣事。
[*]对于常见的TCP或UDP通信,这个标志不常用。
[*] MSG_MORE:
[*]表示应用步伐还有更多的数据要发送。在某些协议(如TCP)中,使用该标志时,内核会暂时将数据保留在缓冲区中,而不是立刻发送,以减少网络上的包数。
[*]适合分多次发送数据,但希望减少网络开销的场景。
示例:使用 MSG_DONTWAIT 和 MSG_NOSIGNAL
char message[] = "Hello, World!";
int result = send(sockfd, message, sizeof(message), MSG_DONTWAIT | MSG_NOSIGNAL);
if (result == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 缓冲区已满,发送失败
printf("Send would block, try again later.\n");
} else {
// 处理其他错误
perror("send");
}
}
总结
send()函数的第四个参数flags用于控制发送操作的举动。常见的标志包罗MSG_DONTWAIT(非阻塞发送)、MSG_OOB(发送告急数据)、MSG_NOSIGNAL(避免SIGPIPE信号)等。你可以根据具体应用场景使用不同的标志来改变send()的默认举动。
六、为什么握手要三次,挥手要四次,挥手中间的两次不能像握手那样归并在一起吗?
在TCP协议的三次握手和四次挥手过程中,虽然在三次握手时可以将SYN和ACK归并到一个数据包发送,但在四次挥手过程中,FIN和ACK通常不能归并到同一个数据包发送。这主要与TCP的毗连状态和双方通信的半关闭状态有关。
1. 三次握手(SYN 和 ACK 归并的缘故原由)
在三次握手中,通信双方需要同步序列号,建立可靠的毗连。具体过程是:
[*]第一次握手:客户端发送一个SYN包,表示哀求建立毗连,并转达初始序列号。
[*]第二次握手:服务器收到SYN后,回复一个包罗SYN和ACK的包。这里的ACK是对客户端SYN的确认,而SYN则是服务器哀求建立毗连的信号。因为SYN和ACK是针对不同的动作(SYN是服务器发起的,而ACK是对客户端哀求的确认),可以一起归并发送。
[*]第三次握手:客户端收到后,发送ACK确认,毗连建立。
这里之所以可以归并,是因为双方的状态在逻辑上是同步的,服务器既要发出自己的SYN,又要确认客户端的SYN,可以一起处理。
2. 四次挥手(ACK 和 FIN 通常不能归并的缘故原由)
四次挥手过程用于关闭TCP毗连,具体如下:
[*]第一次挥手:客户端发送一个FIN包,表示它要关闭毗连(数据传输竣事)。
[*]第二次挥手:服务器收到后,回复一个ACK,表示收到了客户端的FIN哀求,但服务器可能还在发送数据。
[*]第三次挥手:服务器发送完数据后,再发送一个FIN包,表示它也同意关闭毗连。
[*]第四次挥手:客户端收到服务器的FIN后,发送一个ACK包,确认关闭。
缘故原由:
[*] 毗连的半关闭状态: 在四次挥手过程中,TCP协议允许毗连进入半关闭状态,即:
[*]当客户端发送FIN哀求时,意味着客户端已经不再发送数据,但服务器还可以继续发送未完成的数据。
[*]客户端发送的FIN和服务器接收的ACK是两个不同的操作,它们代表了不同的状态。
在这个阶段,服务器回复的ACK只是表明收到了客户端的FIN,但服务器还没有准备好关闭毗连,因为可能仍然有数据需要发送。假如此时归并ACK和FIN,就意味着服务器已经准备好关闭毗连了,但实际上它可能还没有完成数据发送。
[*] 不同的时间点: ACK和FIN通常不会在同一时刻发生:
[*]客户端发FIN后,服务器需要立刻回复一个ACK,但是服务器可能还在发送数据,并未准备好关闭毗连。
[*]只有当服务器确认所有数据发送完毕后,它才会发送FIN来关闭毗连。这两个操作通常在不同的时间点发生,无法归并。
[*] 确保数据完整性: 在四次挥手中,分开ACK和FIN的发送有助于确保所有数据都能成功传输完毕。服务器通过先发送ACK确认收到客户端的关闭哀求,并在数据发送完毕后才发送FIN,可以避免数据丢失或中途停止传输。
3. 总结
[*]在三次握手中,SYN和ACK可以归并到一个数据包中发送,因为它们在逻辑上是并行的操作,且是在同一时刻发送的。
[*]在四次挥手中,ACK和FIN不能归并发送,因为它们通常发生在不同的时间点,表示不同的状态转换。ACK是对接收方收到FIN的确认,而FIN是表示发送方准备完全关闭毗连,这两者之间可能存在数据传输的耽误,因此分开发送有助于确保传输的可靠性和完整性。
4. close/shutdown与挥手报文的关系:
当recv函数返回0时表示收到了对方的FIN报文,此时close()调用后,会直接发出ACK + FIN。
但是用shutdown(sockfd, SHUT_RD)后只会发出 ACK,不会给发出FIN,还可以接着给对方发送数据。
七、shutdown函数 与 FIN 报文
是的,调用shutdown()函数时,根据调用参数,TCP毗连可以发送FIN报文,但这取决于shutdown()的具体使用方式。
1. shutdown()函数的作用
shutdown()函数用于部门或完全关闭一个已经建立的TCP毗连。它不同于close()函数,close()不但会关闭毗连,还会释放文件形貌符,而shutdown()允许步伐在不关闭文件形貌符的情况下关闭毗连的某一方向(发送或接收)。
shutdown()函数的原型:
int shutdown(int sockfd, int how);
其中:
[*]sockfd:要关闭的套接字形貌符。
[*]how:决定关闭毗连的方式。其值可以是以下之一:
[*]SHUT_RD (0):关闭接收方向,该套接字不再能接收数据。
[*]SHUT_WR (1):关闭发送方向,该套接字不再能发送数据,并发送FIN包。
[*]SHUT_RDWR (2):同时关闭发送和接收方向,等同于分别调用SHUT_RD和SHUT_WR。
2. FIN报文的发送
当shutdown()函数的how参数为SHUT_WR或SHUT_RDWR时,TCP协议会发送一个FIN报文,告诉对方主机发送方已经关闭,数据发送已完成,表明不会再有更多的数据从该端发送。
具体说明:
[*] SHUT_WR (1):关闭发送方向。当调用shutdown(sockfd, SHUT_WR)时,TCP协议栈会发送一个FIN报文,表示发送端不再发送数据。之后,这一端仍然可以接收对方的数据,但不能再发送任何数据。
[*] SHUT_RDWR (2):同时关闭发送和接收方向。调用shutdown(sockfd, SHUT_RDWR)时,发送FIN,且无法再接收对方的数据。此时,毗连相称于完全关闭,但文件形貌符不会被释放,应用步伐仍然可以继续使用文件形貌符做其他操作。
3. shutdown()与close()的区别
[*]shutdown()函数可以只关闭毗连的一部门(如只关闭发送而保留接收),而close()会完全关闭毗连并释放套接字文件形貌符。
[*]在调用close()时,假如还有数据没有发送完,TCP协议栈会继续尝试发送剩余数据,并终极发送FIN报文,完成四次挥手流程。
4. 典型使用场景
[*] shutdown(sockfd, SHUT_WR):当一个应用步伐完成了发送数据,但仍然希望接收对方的数据时,通常会调用这个函数。例如,HTTP协议中,服务器发送响应数据后可能会调用shutdown()来关闭发送方向,但仍然保留接收方向以读取客户端的哀求。
[*] shutdown(sockfd, SHUT_RDWR):用于完全关闭毗连,类似于close(),但不释放文件形貌符。
5. 总结
当调用shutdown(sockfd, SHUT_WR)或shutdown(sockfd, SHUT_RDWR)时,TCP会发送FIN报文,表示发送方已经完成数据传输,关闭了发送方向。
八、bind函数端标语的设置
端标语在网络协议中起着非常重要的作用,它们被用来标识不同的服务或应用步伐。端标语可以分为两大类:知名端口(Well-Known Ports)和动态或私有端口(Dynamic or Private Ports)。这些端标语由 Internet Assigned Numbers Authority (IANA) 管理,确保网络中的每个服务都能有唯一的端口标识。
端标语的分类
[*]知名端口(Well-Known Ports): 范围为 0 到 1023,通常分配给操作系统和知名的服务协议。
[*]注册端口(Registered Ports): 范围为 1024 到 49151,供用户和应用步伐使用。
[*]动态或私有端口(Dynamic or Private Ports): 范围为 49152 到 65535,通常用于暂时分配给客户端应用。
知名端口(0 - 1023)
这些端口通常由 IANA 分配给常用服务和协议,以下是一些常见的协媾和对应的端标语:
端标语协议 / 服务说明20FTP 数据传输(File Transfer Protocol)用于 FTP 数据传输21FTP 控制(File Transfer Protocol)用于 FTP 控制毗连22SSH(Secure Shell)用于安全远程登录23Telnet用于非加密的远程登录25SMTP(Simple Mail Transfer Protocol)用于邮件传输53DNS(Domain Name System)用于域名解析67DHCP 服务器端(Dynamic Host Configuration Protocol)用于 DHCP 服务器68DHCP 客户端(Dynamic Host Configuration Protocol)用于 DHCP 客户端69TFTP(Trivial File Transfer Protocol)用于轻量级的文件传输80HTTP(HyperText Transfer Protocol)用于 Web 服务(网页浏览)110POP3(Post Office Protocol version 3)用于邮件接收119NNTP(Network News Transfer Protocol)用于新闻组协议123NTP(Network Time Protocol)用于网络时间同步143IMAP(Internet Message Access Protocol)用于邮件接收(替代 POP3)161SNMP(Simple Network Management Protocol)用于网络设备管理194IRC(Internet Relay Chat)用于即时聊天443HTTPS(HyperText Transfer Protocol Secure)用于加密的 Web 服务(HTTPS)514Syslog用于网络设备和操作系统的日志记载520RIP(Routing Information Protocol)用于路由协议3389RDP(Remote Desktop Protocol)用于远程桌面访问 注册端口(1024 - 49151)
这些端口主要由软件供应商和开发者为其应用步伐所使用。IANA 对这些端口举行注册,但它们通常不属于标准化的、固定的协议。以下是一些常见的服务和对应的端标语:
端标语协议 / 服务说明1080SOCKS(SOCKS Proxy Protocol)用于署理服务1433Microsoft SQL Server用于 Microsoft SQL 数据库服务3306MySQL用于 MySQL 数据库服务3389Microsoft RDP用于远程桌面协议5432PostgreSQL用于 PostgreSQL 数据库服务5900VNC(Virtual Network Computing)用于虚拟网络计算(远程桌面控制)8080HTTP(Alternative Port)用于 Web 服务的备用端口(HTTP)8888HTTP(Alternative Port)用于 Web 服务的备用端口(HTTP) 动态或私有端口(49152 - 65535)
这些端口通常由操作系统或应用步伐动态分配给客户端步伐使用,尤其是在举行暂时毗连时。它们不固定分配给任何特定服务。通常在 TCP/IP 会话中,客户端通过使用这些端标语毗连到远程服务器的服务端口。
端标语范围说明49152 - 65535动态端口范围,用于暂时分配给客户端 端标语的使用说明
[*] 给用户的端标语:这些端标语由操作系统和服务步伐为用户提供,用来执行应用步伐或服务的访问。这些端标语一般需要符合特定协议,使用时需要确保没有冲突。
[*]例如:Web 服务使用 80 或 443 端口,邮件服务使用 25、110 或 143 端口。
[*] 给协议的端标语:协议端标语由 IANA(Internet Assigned Numbers Authority)分配,用于区分不同的网络协媾和服务。很多常见的协媾和服务有固定的端标语,比如 HTTP(80)、FTP(21)、SSH(22)等。
[*] 特定协议的端标语:很多协媾和应用步伐会规定固定的端标语,用于指定特定的服务。例如:
[*]HTTP/HTTPS 协议默认使用端口 80 和 443。
[*]FTP 使用端口 21 举行控制毗连,端口 20 用于数据毗连。
[*]SMTP 使用端口 25 发送邮件,POP3 使用端口 110 接收邮件。
[*] 动态分配端口:客户端与服务器建立毗连时,通常会使用动态端口(范围 49152 到 65535)。例如,在 HTTP 哀求中,客户端使用随机分配的端标语毗连服务器的端口 80 或 443。
端标语的重要性
[*]网络服务和协议标识:端标语资助操作系统区分不同的网络协媾和服务,使得同一台机器可以同时提供多个不同的服务。
[*]安全性考虑:某些服务使用的端标语可能存在安全漏洞,因此安全防护设备(如防火墙)通常会对端标语举行过滤,制止不安全的端口。
[*]端口扫描:攻击者通常通过端口扫描来查找开放的端口和运行的服务,进而探求潜在的攻击入口。
总结
[*]端标语是网络通信中的重要构成部门,允许不同的服务和应用步伐在同一台机器上并行运行。
[*]端标语分为知名端口、注册端口和动态端口,分别用于系统服务、应用步伐服务和暂时毗连。
[*]各种协媾和服务使用不同的端标语,IANA 负责管理这些端标语的分配。
九、TCP系统调用函数的 -- 阻塞性
在 TCP 编程中,某些网络操作可能会阻塞,即函数在没有完成操作之前会等待特定条件的发生。这些函数通常用于执行需要等待数据到达、毗连建立、大概毗连关闭等操作的使命。阻塞举动通常与网络状态、系统资源、以及协议自己的特性相干。
以下是一些常见的会阻塞的 TCP 相干函数,以及它们为什么会阻塞:
1. accept()
[*]阻塞缘故原由: accept() 用于在服务器端担当一个已经完成三次握手的毗连哀求。假如没有等待的毗连,它会阻塞,直到有客户端发起毗连哀求。
[*]何时阻塞: 当没有客户端毗连哀求到达时,accept() 会阻塞,直到有毗连哀求到来。
[*]代码示例: int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8080);
bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
listen(sockfd, 5);
int client_sock = accept(sockfd, NULL, NULL);// 阻塞直到有连接到来
if (client_sock == -1) {
perror("accept");
}
2. recv() / recvfrom() / read()
[*]阻塞缘故原由: 这些函数用于从套接字中接收数据。假如没有数据可读,它们会阻塞,直到有数据可用。recv() 在默认情况下会阻塞,直到接收到至少一个字节的数据。
[*]何时阻塞: 假如缓冲区中没有数据(例如,客户端没有发送数据),则会阻塞等待数据的到来。
[*]代码示例: char buffer;
int bytes_received = recv(client_sock, buffer, sizeof(buffer), 0);// 阻塞直到数据到达
if (bytes_received == -1) {
perror("recv");
}
3. send() / sendto() / write()
[*]阻塞缘故原由: 假如发送缓冲区已满,send() 或 write() 可能会阻塞,直到发送缓冲区有足够的空间来存储数据。特别是在网络拥堵大概接收方的速率跟不上发送速率时,发送函数可能会阻塞。
[*]何时阻塞: 当套接字处于阻塞模式且发送缓冲区已满时,send() 或 write() 会阻塞,直到缓冲区有空间。
[*]代码示例: const char *msg = "Hello, Client!";
int bytes_sent = send(client_sock, msg, strlen(msg), 0);// 阻塞直到数据被发送
if (bytes_sent == -1) {
perror("send");
}
4. connect()
[*]阻塞缘故原由: connect() 用于客户端与服务器建立 TCP 毗连。假如服务器没有响应或不可达,connect() 会阻塞,直到毗连成功建立大概超时。
[*]何时阻塞: 假如没有可用的远程服务器响应或服务器未准备好接收毗连,connect() 会阻塞,直到毗连成功或失败。
[*]代码示例: int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
server_addr.sin_addr.s_addr = inet_addr("192.168.1.100");
int result = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));// 阻塞直到连接成功
if (result == -1) {
perror("connect");
}
5. listen()
[*]阻塞缘故原由: listen() 是在 TCP 服务器端调用的,用于将套接字设为监听模式,等待客户端的毗连哀求。它自己不会阻塞,但会准备好接收毗连。在后续调用 accept() 时,才会阻塞。
[*]何时阻塞: listen() 自己不会阻塞,但它为 accept() 阻塞操作做好准备。
[*]代码示例: int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8080);
bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
listen(sockfd, 5);// 为后续的accept准备
6. shutdown()
[*]阻塞缘故原由: shutdown() 可以关闭套接字的某些操作(如读、写),并等待数据的完全传输或整理。假如你调用 shutdown() 来关闭写操作,它可能会阻塞,直到 TCP 将所有待发送的数据发送完毕。
[*]何时阻塞: 假如套接字有未发送的数据需要传输,shutdown() 会阻塞,直到数据传输完毕。
[*]代码示例: int result = shutdown(sockfd, SHUT_WR);// 关闭写端,阻塞直到所有数据被发送
if (result == -1) {
perror("shutdown");
}
为什么阻塞?
TCP 是一种面向毗连、可靠的数据传输协议,它保证数据的可靠交付,确保所有数据包按照顺序到达目的地,并通过流量控制、拥塞控制等机制避免网络过载。为确保这些特性,某些操作需要等待特定事件的发生,导致阻塞:
[*]等待数据到达:如在调用 recv() 时,系统必须等待数据从远程主机传输到本地。
[*]等待毗连建立:如在 accept() 和 connect() 中,系统必须等待对方准备好接收或发起毗连。
[*]缓冲区未满:如在发送数据时,假如发送缓冲区已满,系统会等待缓冲区腾出空间来举行数据传输。
如何避免阻塞?
[*] 非阻塞模式:可以将套接字设置为非阻塞模式,在这种模式下,调用函数不会阻塞。假如操作无法立刻完成,它会返回 EAGAIN 或 EWOULDBLOCK 错误,应用步伐可以做其他事情或稍后再试。
[*]代码示例: int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);// 设置套接字为非阻塞模式
[*] 超时设置:对于 connect()、recv() 等函数,可以设置超时时间,避免长时间阻塞。比如可以使用 select() 或 poll() 来实现超时检测。
[*] 多线程或异步 I/O:在多线程步伐中,阻塞的操作可以放在单独的线程中执行,主线程继续举行其他使命。使用异步 I/O 也是一种避免阻塞的方式,特别是在高性能网络应用中。
总结
TCP 编程中,很多函数会阻塞,尤其是与毗连、数据接收和发送相干的操作。accept()、recv()、send() 和 connect() 等函数在没有数据或毗连不可用时会阻塞,直到特定条件满足。为了避免阻塞,开发者可以使用非阻塞模式、超机会制或多线程来处理阻塞操作。
十、调用返回失败的情况分析
在 TCP 编程中,很多常用的函数会返回失败的情况,尤其是在网络条件不理想或系统资源不足时。相识每个函数返回失败时的具体错误缘故原由非常重要,这有助于调试和错误处理。以下是对常见的 TCP 函数返回失败时的错误情况的具体说明:
1. accept()
accept() 用于担当客户端毗连。当调用失败时,返回值为 -1,并设置 errno 以指示具体的错误缘故原由。
[*]EINVAL: 假如套接字未精确绑定(如 bind() 未调用)大概套接字范例不支持 accept()(例如,UDP 套接字),则返回此错误。
[*]ECONNABORTED: 假如先前的毗连被中止,accept() 返回此错误。
[*]EFAULT: 转达给 accept() 的地址指针无效。
[*]EINTR: 系统调用被信号停止。accept() 被停止时返回该错误。
2. recv() / recvfrom() / read()
这些函数用于从 TCP 套接字接收数据。当返回 -1 时,表示出现错误,errno 将设置为相应的错误代码。0时表示对方已经断开毗连。
[*]EAGAIN 或 EWOULDBLOCK: 套接字被设置为非阻塞模式,且没有数据可用时返回该错误。
[*]EBADF: 套接字无效,可能是已经关闭或未精确初始化。
[*]EINTR: 系统调用被信号停止。操作在信号处理步伐执行后被停止,导致 recv() 返回失败。
[*]ENOTCONN: 套接字未毗连,调用 recv() 时,TCP 套接字未完成毗连。
[*]ECONNRESET: 对方主机逼迫关闭毗连,TCP 毗连被重置,导致接收操作失败。
[*]ENOTSOCK: 目的文件形貌符不是一个套接字。
[*]EFAULT: 提供的缓冲区地址无效。
3. send() / sendto() / write()
这些函数用于向 TCP 套接字发送数据,失败时返回 -1,并设置 errno。
[*]EAGAIN 或 EWOULDBLOCK: 套接字被设置为非阻塞模式,且发送缓冲区已满,无法继续发送数据。
[*]EBADF: 套接字无效,可能是已经关闭或未精确初始化。
[*]EINTR: 系统调用被信号停止,导致 send() 被停止。
[*]ENOTCONN: 套接字未毗连时调用 send() 会失败。
[*]ECONNRESET: 对方主机逼迫关闭毗连,导致毗连重置,发送操作失败。
[*]ENOTSOCK: 目的文件形貌符不是一个套接字。
[*]EPIPE: 当发送数据到一个已经关闭的毗连时返回此错误,表示对方已经关闭了毗连,写入操作失败。
4. connect()
connect() 用于客户端建立与服务器的毗连。假如返回值是 -1,则表示毗连失败,errno 会被设置为特定错误值。
[*]ECONNREFUSED: 目的服务器拒绝毗连。通常是目的服务器未启动或未监听指定的端口。
[*]ETIMEDOUT: 毗连哀求超时。在指定时间内没有完成毗连。
[*]EINPROGRESS: 假如套接字是非阻塞模式且毗连正在举行中,这个错误会发生。不是错误,表示毗连正在举行。
[*]EAGAIN: 套接字设置为非阻塞模式时,毗连尝试会立刻返回 EAGAIN 错误,表示无法立刻毗连。
[*]EADDRINUSE: 本地地址已在使用中,无法为新毗连分配。
[*]ENETUNREACH: 网络不可达,可能是由于路由或网络设置题目。
[*]EHOSTUNREACH: 主机不可达,通常由于目的主机未开机或网络不可达。
[*]ENOTSOCK: 目的文件形貌符不是一个套接字。
5. listen()
listen() 用于在服务器端启动监听。失败时返回 -1,并设置 errno。
[*]EADDRINUSE: 假如指定的端口已被其他应用步伐占用,listen() 会失败并返回此错误。
[*]EINVAL: 假如套接字不是流式套接字(例如 UDP 套接字),则会发生此错误。
[*]ENOTSOCK: 传入的文件形貌符不是套接字。
6. shutdown()
shutdown() 用于关闭套接字的读写操作。假如返回 -1,则表示操作失败,errno 被设置为错误值。
[*]EBADF: 套接字无效,可能是已经关闭大概未精确初始化。
[*]EINTR: 系统调用被信号停止,shutdown() 被停止。
[*]ENOTSOCK: 目的文件形貌符不是一个套接字。
7. fcntl()
fcntl() 用于获取或设置套接字的属性,如设置非阻塞模式等。假如返回 -1,表示操作失败,errno 被设置为错误码。
[*]EBADF: 套接字无效,可能是已经关闭或未精确初始化。
[*]EINVAL: 无效的命令或参数。
[*]ENOTTY: 非法的文件形貌符范例,不支持该操作。
8. bind()
bind() 用于将套接字与本地地址(IP 和端口)绑定。假如返回 -1,表示绑定失败,errno 被设置为特定错误码。
[*]EADDRINUSE: 地址已被使用,无法绑定。
[*]EADDRNOTAVAIL: 本地地址不可用,可能由于没有该网络接口或地址设置题目。
[*]EBADF: 套接字无效。
[*]EINVAL: 无效的套接字范例,通常是由于套接字范例和协议不匹配。
总结
相识这些 TCP 函数返回失败时的错误缘故原由非常重要,有助于调试和错误处理。一般情况下,当函数返回 -1 时,errno 会提供失败的具体信息。开发者应该根据不同的错误代码举行适当的错误处理,例如通过重试、记载日志、关闭套接字等方式来恢复网络操作,确保步伐的健壮性。
十一、recv返回0时的具体说明
在 TCP 编程中,recv() 函数的返回值为 0 是一个非常重要的情况,它表示 对方关闭了毗连。这个情况常常被用来判断毗连是否已经正常关闭。
[*]recv() 返回 0:
[*]当调用 recv() 时,假如返回值是 0,这并不表示错误,而是表示毗连已经被对方关闭(也就是对方发送了一个 TCP FIN 包 来停止毗连),而且没有更多的数据可接收。
[*]这个返回值表示对方已经优雅地关闭了毗连,而且没有数据需要读取。
具体情况:
[*] TCP 毗连正常关闭:
[*]在正常的 TCP 毗连关闭过程中,通信双方会经过四次挥手(Four-way Handshake),具体来说:
[*]一方(通常是主动关闭的那方)发送一个 FIN 包,表示希望关闭毗连。
[*]接收方确认收到 FIN 包,并发送一个 ACK 包。
[*]接收方也会发送自己的 FIN 包,表示自己也准备关闭毗连。
[*]主动关闭的一方确认收到接收方的 FIN 包,完成毗连关闭过程。
在这个过程中,当 recv() 读取到接收到的 FIN 包 后,表示对方已经关闭了毗连,函数返回 0。
[*] recv() 返回 0 的例子: 下面是一个简朴的代码示例,演示如何使用 recv() 判断对方关闭毗连:
char buffer;
int bytes_received;
// 假设客户端已经连接到服务器
bytes_received = recv(client_sock, buffer, sizeof(buffer), 0);
if (bytes_received == 0) {
printf("The remote side has closed the connection gracefully.\n");
// 对方关闭了连接,进行相应的清理工作
close(client_sock);
} else if (bytes_received < 0) {
perror("recv failed");
} else {
// 处理接收到的数据
printf("Received %d bytes: %s\n", bytes_received, buffer);
}
在上面的代码中:
[*]假如 recv() 返回 0,表示对方已经正常关闭了毗连。此时,通常需要关闭自己的套接字并整理相干资源。
[*]假如 recv() 返回负值(< 0),表示发生了错误,可以通过 errno 获取错误缘故原由。
其他返回情况:
[*] 返回负值(< 0):
[*]假如 recv() 返回一个负值,通常表示发生了错误。常见的错误包罗:
[*]EINTR: 系统调用被信号停止。
[*]EAGAIN 或 EWOULDBLOCK: 套接字处于非阻塞模式,且没有数据可读取。
[*]ECONNRESET: 毗连被对方重置(例如,远程主机逼迫关闭了毗连)。
[*] 返回大于零的正数:
[*]假如返回一个大于 0 的值,表示成功接收到数据,值表示接收到的数据字节数。开发者可以处理这些数据。
为什么 recv() 返回 0 代表对方关闭了毗连?
这是因为在 TCP 协议中,毗连关闭是通过发送 FIN 包来实现的。此时,毗连的另一端会关照接收端自己已经没有数据发送,而且希望关闭毗连。当接收端收到这个 FIN 包后,recv() 返回 0,表示没有更多的数据可读。
[*]TCP FIN 包:当毗连的某一方发送 FIN 包时,它表示已经发送完所有数据而且希望关闭毗连。接收方接收到 FIN 包后,会回复一个 ACK 包,表示已经收到关闭哀求。此时,接收方的接收缓冲区为空,不再有数据传输,recv() 将返回 0,表示对方已关闭毗连。
总结:
[*]recv() 返回 0 表示对方已经关闭了毗连,通常是正常的毗连关闭过程。
[*]该返回值是用于 优雅地关闭毗连 的指示,表明没有更多数据可读,开发者可以整理资源并关闭自己的套接字。
[*]当遇到 0 时,通常需要举行关闭套接字、整理资源等操作。
0voice · GitHub
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]