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

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

相关文章

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

Kotlin 作用域函数apply、let、run、with、also使用指南

《Kotlin作用域函数apply、let、run、with、also使用指南》在Kotlin开发中,作用域函数(ScopeFunctions)是一组能让代码更简洁、更函数式的高阶函数,本文将... 目录一、引言:为什么需要作用域函数?二、作用域函China编程数详解1. apply:对象配置的 “流式构建器”最

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

SpringBoot使用OkHttp完成高效网络请求详解

《SpringBoot使用OkHttp完成高效网络请求详解》OkHttp是一个高效的HTTP客户端,支持同步和异步请求,且具备自动处理cookie、缓存和连接池等高级功能,下面我们来看看SpringB... 目录一、OkHttp 简介二、在 Spring Boot 中集成 OkHttp三、封装 OkHttp