网络编程相关函数深层次解析

2024-05-07 07:48

本文主要是介绍网络编程相关函数深层次解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

connect函数解析

TCP客户用connect函数来建立与TCP服务器的连接:

#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr* servaddr, socklen_t addrlen);
//成功返回0,失败返回-1并设置errno

客户端在调用connect之前不必非得调用bind函数,因为如果需要的话内核会确定源IP地址并选择一个临时端口号作为端口;

如果是TCP套接字,调用connect函数将触发TCP的三次握手过程,而且仅在连接建立成功或出错才返回:

  • 若TCP没有收到SYN分节的响应,则返回ETIMEOUT错误。举例,调用connect函数时,4.4BSD内核发送一个SYN,若无响应则等待6s后再发送一个,若仍无响应则等待24s后再发送一个。若总共等待了75s后仍未收到响应则返回本错误。

  • 若对客户的SYN的相应是RST(表示复位),则表明该服务器主机在我们指定的端口上没有进程在等待与之连接。这是一种硬错误,客户一收到RST就马上返回ECONNERFUSED错误;

  • 若客户发出的SYN在中间某个路由器上引发一个“destination unreachable”(目的地不可达)ICMP错误,则认为是一个软错误。客户主机内核会保存该消息,并按第一种情况所述的时间间隔继续发送SYN,若在某个规定时间内仍无响应,则把保存的信息(ICMP错误)作为EHOSTUNREACH或ENETUNREACH错误返回给进程,以下两种情况是可能的:

    • 按照本地系统的转发表,根本没有到达远程系统的路径;
    • connect调用根本不等待就返回;

按照TCP状态转移图,connect函数导致当前套接字从CLOSED状态转移到SYN_SENT状态,若成功,则转移到ESTABLISHED状态。若connect失败,则该套接字不再可用,必须关闭,不能对这样的套接字再次调用connect函数;

listen函数解析

listen函数仅由TCP服务器调用,其做两件事:

  • 当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用connect发起连接的客户套接字。listen函数把一个未连接的套接字转换为一个被动套接字,指示内核应该接受指向该套接字的连接请求。根据TCP状态转换图,调用listen导致套接字从CLOSED状态转换到LISTEN状态

  • listen函数的第二个参数规定了内核应该为相应套接字排队的最大连接个数:

    #include<sys/socket.h>int listen(int sockfd, int backlog);

为了理解其中的backlog参数,我们必须认识到内核为任何一个给定的监听套接字维护两个队列:

  • 未完成队列:每个这样的YSN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手,这些套接字处于SYN_RECV状态;
  • 已完成连接队列:每个已完成的TCP三路握手过程的客户对应其中的一项,这些套接字处于ESTABLISHED状态;

每当在未完成连接队列中创建一项时,来自监听套接字的参数就复制到即将建立的连接中,连接的创建机制是完全自动的。

  • 当来自客户的SYN到达时,TCP在未完成队列中创建一个新项,然后响应三路握手的第二个分节:服务器的SYN相应,其中捎带对可读SYN的ACK,这一项一直保留在文玩城连接队列中,直到三路握手的第三个分节(客户对服务器的SYN的ACK)到达,或者该项超时为止;

  • 如果三路握手成功,该项从未完成队列移到已完成队列的队尾,当进程调用accept时,已完成连接队列中的队头项将返回给进程,或者该队列为空,那么进程就被投入到睡眠,知道TCP在该队列中放入一项才唤醒它;

accept函数解析

函数原型为:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

如果已连接队列中没有等待的连接,套接字也没有被标记为non-blocking,accept()会阻塞调用函数直到连接出现,如果套接字被标记为non-blocking,队列中也没有等待的连接,accept()返回错误EAGAIN 或 EWOULDBLOCK

一般来说,实现时accept()为阻塞函数,当监听socket调用accept()时,它先到自己的receive_buf中查看是否有连接数据包:

  • 若有,把数据拷贝出来,删除接收到的数据报,创建新的socket与客户发来的地址建立连接;
  • 若没有,就阻塞等待;

考虑以下情况:如果监听队列中处于ESTABLISHED状态的连接对应的客户端出现网络异常(比如掉线),或者提前退出,那么服务器端对这个连接执行的accept调用是否成功?

accept只是从监听队列中取出连接,而不论连接处于何种状态(如上面的ESTABLISHED或者CLOSE_WAIT状态),更不关心任何网络状况的变化;

close关闭连接

函数原型是:int close(int fd);

fd参数是待关闭的连接,不过,close系统调用并非总是立即关闭一个连接,而是将fd的引用计数减1,只有当fd的引用计数为0时,才真正关闭连接。

如果无论如何都要立即终止连接(而不是将socket引用计数减1),可以使用shutdown系统调用:

#include<sys/socket.h>
int shutdown(int sockfd, int howto);

howto参数决定了shutdown的行为:

  • SHUT_RD:关闭sockfd上读的一半,应用程序不会再针对socket文件描述符执行读操作,并且将socket接收缓冲区的数据都丢弃;
  • SHUT_WR:关闭sockfd上写的一半,sockfd的发送缓冲区的数据会在真正关闭连接之前全部发送出去,应用程序不再对该sockfd执行写操作,连接处于半关闭状态;
  • SHUT_RDWR:同时关闭sockfd上的读和写;

带外标记

Linux内核检测到TCP紧急标志时,将通知应用程序由带外数据需要接收,内核通知应用程序带外数据到达有两种常见方式:

  • I/O复用产生的异常;
  • SIGURG信号;

但是,即使应用程序得知了有带外数据需要接收的通知,还需要知道带外数据在数据流中的具体位置,才能正确接收带外数据,可通过如下系统调用实现:

#include<sys/socket.h>
int sockatmark(int sockfd);

其判断sockfd是否处于带外标记,即下一个要被读取的数据是否是带外数据,如果是,sockatmark返回1,可以利用带MSG_OOB标志的recv调用来接收带外数据。否则,返回0;

socket选项

#include<sys/socket.h>
int getsockopt(int sockfd, int level, int option_name, void* option_value, socklen_t* restrict option_len);int setsockopt(int sockfd, int level, int option_name, const void* option_value, socklen_t option_len);

对服务器而言,有部分socket选项只能在调用listen系统调用前针对监听socket设置才有效,对监听socket设置这些选项,那么accept返回的连接socket将自动继承这些选项

  • socket选项的SO_REUSEADDR来强制使用被处于TIME_WAIT状态的连接占用的socket地址;

    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

  • SO_RCVBUF 和 SO_SNDBUF选项分别表示TCP缓冲区和发送缓冲区的大小,当我们用setsockopt设置TCP接收缓冲区和发送缓冲区的大小时,系统都会将其值加倍,并且不得小于某个最小值。TCP接收缓冲区最小值为256字节,发送缓冲区的最小值为2048字节,这样做确保一个TCP连接有足够的空闲缓冲区来处理拥塞;

   setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, sizeof(sendbuf));setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, sizeof(recvbuf));

这篇关于网络编程相关函数深层次解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/966758

相关文章

如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解

《如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解》:本文主要介绍如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别的相关资料,描述了如何使用海康威视设备网络SD... 目录前言开发流程问题和解决方案dll库加载不到的问题老旧版本sdk不兼容的问题关键实现流程总结前言作为

Python itertools中accumulate函数用法及使用运用详细讲解

《Pythonitertools中accumulate函数用法及使用运用详细讲解》:本文主要介绍Python的itertools库中的accumulate函数,该函数可以计算累积和或通过指定函数... 目录1.1前言:1.2定义:1.3衍生用法:1.3Leetcode的实际运用:总结 1.1前言:本文将详

轻松上手MYSQL之JSON函数实现高效数据查询与操作

《轻松上手MYSQL之JSON函数实现高效数据查询与操作》:本文主要介绍轻松上手MYSQL之JSON函数实现高效数据查询与操作的相关资料,MySQL提供了多个JSON函数,用于处理和查询JSON数... 目录一、jsON_EXTRACT 提取指定数据二、JSON_UNQUOTE 取消双引号三、JSON_KE

MySQL数据库函数之JSON_EXTRACT示例代码

《MySQL数据库函数之JSON_EXTRACT示例代码》:本文主要介绍MySQL数据库函数之JSON_EXTRACT的相关资料,JSON_EXTRACT()函数用于从JSON文档中提取值,支持对... 目录前言基本语法路径表达式示例示例 1: 提取简单值示例 2: 提取嵌套值示例 3: 提取数组中的值注意

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

MySQL 缓存机制与架构解析(最新推荐)

《MySQL缓存机制与架构解析(最新推荐)》本文详细介绍了MySQL的缓存机制和整体架构,包括一级缓存(InnoDBBufferPool)和二级缓存(QueryCache),文章还探讨了SQL... 目录一、mysql缓存机制概述二、MySQL整体架构三、SQL查询执行全流程四、MySQL 8.0为何移除查

在Rust中要用Struct和Enum组织数据的原因解析

《在Rust中要用Struct和Enum组织数据的原因解析》在Rust中,Struct和Enum是组织数据的核心工具,Struct用于将相关字段封装为单一实体,便于管理和扩展,Enum用于明确定义所有... 目录为什么在Rust中要用Struct和Enum组织数据?一、使用struct组织数据:将相关字段绑

使用Java实现一个解析CURL脚本小工具

《使用Java实现一个解析CURL脚本小工具》文章介绍了如何使用Java实现一个解析CURL脚本的工具,该工具可以将CURL脚本中的Header解析为KVMap结构,获取URL路径、请求类型,解析UR... 目录使用示例实现原理具体实现CurlParserUtilCurlEntityICurlHandler

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

深入解析Spring TransactionTemplate 高级用法(示例代码)

《深入解析SpringTransactionTemplate高级用法(示例代码)》TransactionTemplate是Spring框架中一个强大的工具,它允许开发者以编程方式控制事务,通过... 目录1. TransactionTemplate 的核心概念2. 核心接口和类3. TransactionT