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

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

相关文章

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

一文解析C#中的StringSplitOptions枚举

《一文解析C#中的StringSplitOptions枚举》StringSplitOptions是C#中的一个枚举类型,用于控制string.Split()方法分割字符串时的行为,核心作用是处理分割后... 目录C#的StringSplitOptions枚举1.StringSplitOptions枚举的常用

Python函数作用域与闭包举例深度解析

《Python函数作用域与闭包举例深度解析》Python函数的作用域规则和闭包是编程中的关键概念,它们决定了变量的访问和生命周期,:本文主要介绍Python函数作用域与闭包的相关资料,文中通过代码... 目录1. 基础作用域访问示例1:访问全局变量示例2:访问外层函数变量2. 闭包基础示例3:简单闭包示例4

MyBatis延迟加载与多级缓存全解析

《MyBatis延迟加载与多级缓存全解析》文章介绍MyBatis的延迟加载与多级缓存机制,延迟加载按需加载关联数据提升性能,一级缓存会话级默认开启,二级缓存工厂级支持跨会话共享,增删改操作会清空对应缓... 目录MyBATis延迟加载策略一对多示例一对多示例MyBatis框架的缓存一级缓存二级缓存MyBat

前端缓存策略的自解方案全解析

《前端缓存策略的自解方案全解析》缓存从来都是前端的一个痛点,很多前端搞不清楚缓存到底是何物,:本文主要介绍前端缓存的自解方案,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、为什么“清缓存”成了技术圈的梗二、先给缓存“把个脉”:浏览器到底缓存了谁?三、设计思路:把“发版”做成“自愈”四、代码

Java集合之Iterator迭代器实现代码解析

《Java集合之Iterator迭代器实现代码解析》迭代器Iterator是Java集合框架中的一个核心接口,位于java.util包下,它定义了一种标准的元素访问机制,为各种集合类型提供了一种统一的... 目录一、什么是Iterator二、Iterator的核心方法三、基本使用示例四、Iterator的工

Python中isinstance()函数原理解释及详细用法示例

《Python中isinstance()函数原理解释及详细用法示例》isinstance()是Python内置的一个非常有用的函数,用于检查一个对象是否属于指定的类型或类型元组中的某一个类型,它是Py... 目录python中isinstance()函数原理解释及详细用法指南一、isinstance()函数