网站单页制作,成都设计公司排行榜,广告设计自学教程,广州软件外包一、操作函数简介
在 Linux 中#xff0c;TCP#xff08;传输控制协议#xff09;操作涉及多种系统调用和函数#xff0c;通常用来创建套接字、连接、发送/接收数据、关闭连接等。以下是一些常用的 TCP 操作函数和它们的简要说明#xff1a;
1. socket()
函数原型: int…一、操作函数简介
在 Linux 中TCP传输控制协议操作涉及多种系统调用和函数通常用来创建套接字、连接、发送/接收数据、关闭连接等。以下是一些常用的 TCP 操作函数和它们的简要说明
1. socket()
函数原型: int socket(int domain, int type, int protocol);功能: 创建一个新的套接字socket它是与网络通信相关的基本对象。参数: domain: 协议族如 AF_INET 用于 IPv4AF_INET6 用于 IPv6。type: 套接字类型如 SOCK_STREAM 表示 TCPSOCK_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_TCPTCP 层。optname: 选项名称如 SO_RCVBUFSO_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控制块TCBstruct 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连接的不同状态但队列中的成员并不是直接的TCBstruct 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连接的TCBstruct 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连接的TCBstruct 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/somaxconn4. 实际例子
假设你调用了如下的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_backlog20483. 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_OOBOut-of-Band Data 发送紧急数据带外数据仅适用于TCP协议。紧急数据会优先于普通数据处理但在实际应用中带外数据的使用较少。常用于一些需要快速响应的特殊场景。 MSG_NOSIGNAL 如果向已断开的连接发送数据通常会触发SIGPIPE信号导致程序终止。使用该标志可以抑制SIGPIPE信号防止程序崩溃。适用于需要处理网络中断且不希望信号干扰的场景。 MSG_CONFIRM 仅适用于基于某些协议如UDP的发送表示希望确认对端的存在通常用于实现链路层的邻居确认。仅用于某些低层协议的特定场景在常规TCP/UDP应用中较少使用。 MSG_DONTROUTE 发送数据时不查找路由表直接将数据发送到与目标网络直接相连的接口。通常用于网络诊断和本地网络通信的场景。在大多数普通应用场景中很少使用。 MSG_EOREnd 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 控制连接22SSHSecure Shell用于安全远程登录23Telnet用于非加密的远程登录25SMTPSimple Mail Transfer Protocol用于邮件传输53DNSDomain Name System用于域名解析67DHCP 服务器端Dynamic Host Configuration Protocol用于 DHCP 服务器68DHCP 客户端Dynamic Host Configuration Protocol用于 DHCP 客户端69TFTPTrivial File Transfer Protocol用于轻量级的文件传输80HTTPHyperText Transfer Protocol用于 Web 服务网页浏览110POP3Post Office Protocol version 3用于邮件接收119NNTPNetwork News Transfer Protocol用于新闻组协议123NTPNetwork Time Protocol用于网络时间同步143IMAPInternet Message Access Protocol用于邮件接收替代 POP3161SNMPSimple Network Management Protocol用于网络设备管理194IRCInternet Relay Chat用于即时聊天443HTTPSHyperText Transfer Protocol Secure用于加密的 Web 服务HTTPS514Syslog用于网络设备和操作系统的日志记录520RIPRouting Information Protocol用于路由协议3389RDPRemote Desktop Protocol用于远程桌面访问
注册端口1024 - 49151
这些端口主要由软件供应商和开发者为其应用程序所使用。IANA 对这些端口进行注册但它们通常不属于标准化的、固定的协议。以下是一些常见的服务和对应的端口号
端口号协议 / 服务说明1080SOCKSSOCKS Proxy Protocol用于代理服务1433Microsoft SQL Server用于 Microsoft SQL 数据库服务3306MySQL用于 MySQL 数据库服务3389Microsoft RDP用于远程桌面协议5432PostgreSQL用于 PostgreSQL 数据库服务5900VNCVirtual Network Computing用于虚拟网络计算远程桌面控制8080HTTPAlternative Port用于 Web 服务的备用端口HTTP8888HTTPAlternative Port用于 Web 服务的备用端口HTTP
动态或私有端口49152 - 65535
这些端口通常由操作系统或应用程序动态分配给客户端程序使用尤其是在进行临时连接时。它们不固定分配给任何特定服务。通常在 TCP/IP 会话中客户端通过使用这些端口号连接到远程服务器的服务端口。
端口号范围说明49152 - 65535动态端口范围用于临时分配给客户端
端口号的使用说明 给用户的端口号这些端口号由操作系统和服务程序为用户提供用来执行应用程序或服务的访问。这些端口号一般需要符合特定协议使用时需要确保没有冲突。 例如Web 服务使用 80 或 443 端口邮件服务使用 25、110 或 143 端口。 给协议的端口号协议端口号由 IANAInternet Assigned Numbers Authority分配用于区分不同的网络协议和服务。许多常见的协议和服务有固定的端口号比如 HTTP80、FTP21、SSH22等。 特定协议的端口号许多协议和应用程序会规定固定的端口号用于指定特定的服务。例如 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[1024];
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[1024];
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