python网络编程详解

news2024/11/28 10:51:10

最近在看《UNIX网络编程 卷1》和《FREEBSD操作系统设计与实现》这两本书,我重点关注了TCP协议相关的内容,结合自己后台开发的经验,写下这篇文章,一方面是为了帮助有需要的人,更重要的是方便自己整理思路,加深理解。

理论基础

OSI网络模型

OSI模型是一个七层模型,实际工程中,层次的划分没有这么细致。一般来说,物理层和数据层对应着硬件和设备驱动程序,例如网卡和网卡驱动。传输层和网络层由操作系统内核实现,当用户进程需要通过网络传输数据,通过系统调用的方式让内核将数据封装为相应的协议格式,进而调用网卡驱动传输数据。顶上三层对应具体的网络应用协议:FTP、HTTP等,这些应用层协议不需要知道具体的通信细节。

传输层

在实际工程中,我们常用的应用层服务(例如:HTTP服务、数据库服务、缓存服务)通信的直接底层就是传输层,下图是一些常用命令涉及的通信协议。

IPv4(Internet Protocol version 4)全称是网际协议版本4,它使用32地址,平时常说的IP协议就是指IPv4,类似于192.168.99.100的地址可以看成4位256进制数据,也就是32网络地址。但随着网络设备爆炸式增长,32地址面临这用完的风险,IPv6(Internet Protocol version 6)应运而生。IPv6使用128位地址,但IPv4地址耗尽的问题有了新的解决方案,目前普遍使用的还是IPv4,IPv6全面取代IPv4还有很长的距离。

UDP (User Datagram Protocol),全称用户数据报协议。UDP提供面向无连接的服务,客户端和服务端不存在任何长期的关系。UDP不提供可靠的通信,它不保证数据报一定送达,也不保证数据包送达的先后顺序,也不保证每份数据报只送达一次。虽然UDP可靠性差,但是消耗资源少,适用在网络环境较好的局域网中,例如不需要精确统计的监控服务(eg: Statsd)。由于使用了UDP,客户端每次打点统计只需要一次发送UDP数据报的IO开销,服务性能损失很小,而且在内网环境数据包一般都能正常到达服务端,也能保证较高的可行度。

TCP(Transmission Control Protocl),全称传输控制协议。和UDP相反,TCP提供了面向连接的服务,而且提供了可靠性保障。平常我们使用的应用层协议,例如HTTP,FTP等,几乎都是建立在TCP协议之上,深入了解TCP的细节对于开发高质量的后台开发和客户端开发都有很好的借鉴意义。下面开始重点介绍TCP协议的细节。

TCP协议

状态转换

为了提供可靠的通信服务,TCP通过三次分节建立连接,四次分节关闭连接,心跳检查判断连接是否正常,因此需要记录连接的状态,TCP一共定义了11种不同的状态。

通过netstat命令可以查看所有的tcp状态。

三路握手

在三路握手之前,服务器必须准备好接收外来的连接。这通常通过调用bindlisten完成被动打开,此时服务进程有一个套接字处于LISTEN状态。在客户端发通过调用connect送一个SYN分节后,服务进程必须确认(ACK)此分节,同时也发送一个SYN分节,这两步在同一分节中完成,通过上面的转台扭转图,可以知道服务进程中会生成一个处于SYN_RCVD状态的套接字。当再次收到客户端的ACK分节后,服务端的套接字状态转变为ESTABLISHED。

客户端通过connect函数发起主动打开,在此之前客户端套接字状态为CLOSED。调用connect导致客户TCP发送一个SYN分节,此时套接字状态有CLOSED变为SYN_SENT,在收到服务器的SYN和ACK后,客户端socket再发送ACK分节,套接字状态变为ESTABLISHED,此时connect返回。

备注:SYN分节中除了有序列号之外,还会有最大分节大小、窗口规模选项、时间戳等TCP参数,具体可以参考协议详细规定。

终止连接

上图展示了客户端执行主动关闭的情形,实际上无论客户端还是服务器,都可以执行主动关闭。一般情况下客户端执行主动关闭较多,所以使用客户端主动关闭为例讲解。

客户端调用close,执行主动关闭时,发送FIN分节,此时客户端套接字状态由ESTABLISED变为FIN_WAIT_1。服务器收到这个FIN,会执行被动关闭,并向客户端发送ACK,FIN的接受也作为一个文件结束符传递给服务进程,如果此时服务进程调用套接字的方法,无论缓存区是否有数据都会返回EOF,服务端套接字状态由ESTABLISED变为为CLOSE_WAIT。客户端接收到ACK后,客户端套接字状态由FIN_WAIT_1变为FIN_WAIT_2。

一段时间后,当服务进程调用close或者shutdown时,也会发生送FIN分节,服务端套接字状态由CLOSE_WAIT变为LAST_ACK。客户端在接收到FIN分节后,发送ACK分节,客户端套接字状态由FIN_WAIT_2变为TIME_WAIT。服务器段接收到客户端的ACK分节,状态变成CLOSED。

在某些情况下,第二和第三分节可能会合并发送。调用close可能会触发主动关闭,当进程正常或者非正常退出时,内核会将该进程所使用的文件描述符对应的打开次数执行减一操作,当某个文件打开次数为0时,也就是说所有的进程都没有使用此文件时,也会触发TCP的主动关闭操作。

TIME_WAIT状态

在终止连接的过程中,主动关闭方套接字最终的状态是TIME_WAIT,在经过2MSL(maximun segment lifetime,每个IP数据报都包含一个跳限的字段,表明数据报能经过的路由最大个数,因此默认每个数据报在因特网中有一个最大存活时间)时间后状态才变为CLOSED,为什么这样设计呢?

这样的设计出于两个考虑:

  1. 可靠地实现TCP全双工连接的终止。上图的四次分节关闭连接是在正常流程,实际情况中,任何一次分节都可能出现发送失败的情况。主动关闭方最后的一个ACK分节可能会因为路由问题发送失败,为了保证可靠性,需要重新发送保证另一方正确关闭套接字,因此此时的状态不能为CLOSED。
  2. 允许老的重复分界在网络中消失。加入10.10.89.9的3400端口和206.168.12.12的80端口建立了一个TCP连接,此连接中断后,之前发送的TCP分节可能因为路由循环的问题还在因特网中游荡,而此时这两个机器相同的端口再建立起新的连接后,原来在网络中游荡的分解会对新的连接造成干扰。为了避免这种情况,设置一个2MSL的超时时间,保证之前还在网络中游荡的数据包完全消失。

套接字编程

下图是C语言的套接字函数,考虑Python的socket库只是底层C库的简单封装,接口参数大同小异,而且Python方便上手调试,语法上也更通俗易懂,所以本文使用Python的socket库作为讲解实例。

socket

socket是python套接字类,通过构造函数生成套接字对象,构造函数签名如下

其中family参数指协议族;type参数指套接字类型;protocol值协议类型,或者设置为0,以选择所给定family和type组合的系统默认值;fileno指文件描述符(我从来没用过)。

family说明
AF_INETIPv4协议
AF_INET6IPv6协议
AF_LOCALUnix域协议
AF_ROUTE路由套接字
AF_KEY密钥套接字
type说明
SOCK_STREAM字节流套接字
SOCK_DGRAM数据包套接字
SOCK_SEQPACKET有序分组套接字
SOCK_RAW原始套接字
protocol说明
IPPROTO_TCPTCP传输协议
IPPROTO_UDPUDP传输协议

并非所有套接字family和type的组合都是有效的,下表给出了一些有效的组合和对应的协议,其中标的项也是有效的,但是没有找到便捷的缩略词,而空白项是无效组合。

connect

connect用于客户端和服务器建立连接,函数签名如下:

客户端在调用connect之前不必非得调用bind函数,内核会确定源IP地址,并选择一个临时端口作为源端口。如果使用TCP协议,connect将激发TCP的三路握手过程,TCP状态由CLOSED变为SYN_SENT,最终变为ESTABLISHED,在三路握手的过程中,可能会出现下面几种情况导致connect报错。connect失败则套接字不可用,必须关闭,不能对这样的套接字再次调connect函数。

  • TCP客户端没有是收到SYN分节响应,一般发生在服务端backlog队列已满的情况下,服务器会对收到的SYN分节不做任何处理。客户端等待一段时间后会重新发送SYN分节,直到等待时间超过上限,才会抛出ETIMEDOUT错误(对应的python异常是TimeoutError)。
  • 对客户端SYN的响应是RST,表明服务端在指定的端口上没有进程在等待与之连接,客户端马上会抛出ECONNRFUSED错误。下图是用python连接一个未使用的端口,抛出异常ConnectionRefusedError,该异常错误号码111,errno中查找正是ECONNRFUSED对应的错误码。

  • 如果发出的SYN在中间的吗某个路由器上引发了目的地不可达错误,客户端会等待一段时间后重新发送,直到等待时间超过上限(和第一种情况类似),此时会抛出ENETUNREACH或者EHOSTUNREACH错误。下图为关闭本机网络后,用python调用connect,由于网络不可达,异常的错误码为101,errno中查找正是ENETUNREACH错误码。

bind

bind方法把一个本地协议地址赋予给一个套接字,方法签名如下:

在不调用bind的情况下,内核会确定IP地址,并分配临时端口,这种情况很适合客户端,因此客户端在调用connect之前不调用bind方法。而服务端需要一个确定的ip和端口,因此需要调用bind指定地址和端口。一般情况下,服务器都有多个ip地址,除了环路地址127.0.0.1外,还有局域网和公网地址,如果bind绑定的是环路地址127.0.0.1,则只有本机通过环路地址才能访问,如果需要通过任一ip地址都能访问到,可以绑定通配地址0.0.0.0。当指定的端口为0时,内核会分配一个临时端口。

如果端口已经在使用,会抛出EADDRINUSE(errno对应错误码是98)异常,可以通过设置SO_REUSEADDR和SO_REUSEPORT这两个套接字参数让多个进程使用同一个TCP连接。

listen

当创建一个套接字时,默认为主动套接字,也就是说,是一个将调用connect发起连接的客户套接字。listen方法把一个未连接的套接字转换为一个被动套接字,指示内核应接受指向该套接字的状态请求。根据TCP状态转换图,调用listen导致套接字从CLOSED状态转换到LISTEN状态。此方法参数规定了内核应该为相应套接字排队的最大连接个数,在bind之后,并在accept之前调用。

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

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

RTT指的是未连接队列中的任何一项在队列中的存活时间。linux下的backlog指的是已完成连接队列的容量,如果服务器长时间未调用accept从此队列中取走数据,当新的客户端通过三路握手重新建立连接时,服务器不会处理收到的SYN分节,而客户端会一直等待并不断重试直到超时。在服务器负载很大的情况下,就会造成客户端连接时间长,所以需要合理设置backlog大小。

accept

accept用于从已完成连接队列头返回下一个已完成连接,如果已完成连接队列为空,那么进程会被投入睡眠(套接字为阻塞方式)。

accept会自动生成一个全新的文件描述符,代表与所返回客户的TCP连接。需要注意的是,此处有两个套接字对象,一个是监听套接字,一个返回的已连接套接字。区分这两个套接字很重要,一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在,内核为每个由服务器进程接受的客户连接创建一个已连接套接字(也就是说TCP三路握手已经完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字会被关闭。

close

close方法用来关闭套接字,方法签名如下:

需要注意的是,close方法并不一定会触发TCP的四分组连接终止序列,当一个已连接套接字被多个进程打开时,关闭套接字只会导致此进程相应描述符的计数值减1,只有所有进程都将该套接字关闭后,套接字的引用计数值小于1以后,系统内核才会开始终止连接操作,这一点在多进程开发过程中需要格外注意。如果确实想在某个TCP连接上发送FIN触发主动关闭,可以调用shutdown方法。

send

send方法用于TCP发送数据,方法签名如下:

每一个TCP套接字都有一个发送缓冲区,默认大小通过socket.SO_SNDBUF查看,当某个进程调用send时,内核从该应用进程的缓冲区复制所有数据到所写套接字的发送缓冲区,如果该套接字的发送缓冲区容不下该应用进程的所有数据(或是应用进程的缓冲区大小大于套接字的发送缓冲区,或是套接字的发送缓冲区已有其他数据),该应用进程将被投入睡眠(套接字阻塞的情况),内核将不从系统调用返回,直到应用进程缓冲区的所有数据都复制到套接字发送缓存区。当对端确认收到数据后,会发送ACK分节,随着对端ACK的不断到达,本端TCP才能从套接字发送缓存区中丢弃已确认的数据。

在类似于HTTP的应用层协议中,客户端在发送完请求数据之后,可以调用s.shutdown(socket.SHUT_WR)告诉服务端所有的数据已经发送完成,服务端通过recv会读取到空字符串,之后就可以处理请求数据了。

recv

recv方法用于TCP接收数据,方法签名如下:

每一个TCP套接字也都有一个接受缓存区,默认大小通过socket.SO_RCVBUF查看。当某个进程调用recv而且缓存区没有数据时,该进程会被投入睡眠(套接字阻塞的情况),内核将不从系统调用返回。

在《Unix网络编程》中,所有C语言调用acceptread, write函数都会检查errno是否等于EINTR,这是因为进程在执行这些系统调用的时候可能会被信号打断,导致系统调用返回。而我自己用python2.7尝试的时候发现并没有此问题,猜测是python针对系统调用被信号打断的情况。

IO多路复用

在做服务器开发的时候,经常会碰到处理多个套接字的情形,此时可以通过多进程或这多线程的模型解决此问题。用一个主进程或者主线程负责监听套接字,其它每个进程或线程负责一个已连接套接字,这样还可以利用操作系统的线程切换实现多并发,提高机器利用率。但是机器资源有限,不可能无限制的生成新线程或进程,IO多路复用应运而生。当内核一旦发现进程指定的一个或者多个IO条件就绪,它就通知进程。

IO模型

Unix下有5中IO模型:

  1. 阻塞式IO
  2. 非阻塞式IO
  3. IO复用
  4. 信号驱动IO
  5. 异步IO

已读取数据为例,讲解这物种IO模型的区别。每次读取数据包括以下两个阶段,而这五种模型的不同之处也体现在这两个阶段不同的处理。

  1. 等待数据准备好
  2. 从内核想进程复制数据

阻塞式IO

socket套接字默认就是阻塞式IO。以recvfrom为例,用户进程通过系统调用获取TCP数据,如果套接字缓存区没有数据,系统调用不会返回,造成用户进程一直阻塞。直到缓存区有可用数据,内核将缓存区数据拷贝至用户进程空间,系统调用才会返回。

非阻塞式IO

python可以通过调用s.setblocking(False)或者s.settimeout(0.0)将一个套接字设置为非阻塞式IO。以recvfrom为例,当没有可用的数据时,用户进程不会阻塞,而是马上抛出EWOULDBLOCK错误(或者EAGAIN,对应的errno错误码都是11),只有当数据复制到内核空间后,才会正确返回数据。

IO多路复用

在有多个IO操作时,先阻塞于select调用,等待数据报套接字变为可读,然后再通过recvfrom把缓存区数据复制到用户进程空间。和阻塞是IO相比,当处理的套接字个数较少的时候,多路复其实没有性能上的优势,它的优势在于可以方便操作很多套接字。

信号驱动式IO

通过信号处理的方式读取数据。

异步IO

当数据包被复制到用户进程后,用户通过callback的方式获取数据。

模型对比

可以发现,前四种IO模型——阻塞式IO、非阻塞式IO、IO复用、信号驱动IO都是同步IO模型,因为真正的IO操作(recvfrom)将阻塞进程,只有异步IO模型才不会导致用户进程阻塞。

python使用

较早的时候使用的多路复用是select函数,但是由于时间复杂度较高,很快就被其他的函数替代:linux下的epoll,unix下的kqueue,windows下的iocp。为了屏蔽不同系统下的不同实现,跨平台的第三方库出现:libuv、libev、libevent等,这些库根据平台的不同,调用不同的底层代码。

如果想直接使用底层的epoll或者select,它们封装在python的select库中;libuv、libev都有相应的python封装,库名叫做pyuv、pyev,通过pip安装后即可使用。

python示例

一般情况下,为了提升服务的承载量,都会采用进程+IO多路复用或者线程+IO多路复用的开发模式。IO多路复用是为了一个并发单位管理多个套接字,而多进程或者多线程是为了充分利用多核。由于GIL的存在,python多线程模型并不能充分多核,因此我们常见的wsgi server,例如:gunicorn、uwsgi、tornado等都是使用的多进程+IO多路服用开发模式。

tornado使用epoll管理多个套接字,gunicorn和uwsgi都可以使用gevent,gevent是一个python网络库,用greenlet做协程切换,每个协程管理一个套接字,主协程通过libevent轮询查找可用的套接字。因为gevent可以通过monkey patch将socket设置为非阻塞模式,因此当服务器有数据库、缓存或者其他网络请求的时候,相比tornado,uwsgi和gunicorn可以充分利用这部分的阻塞时间。和gunicorn相比,uwsgi是c语言实现,直观感觉这三个server的性能应该是:uwsgi > gunicorn > tornado,和网上的benchmark大致匹配。

| |50|100|150|200|250|300|350|400|450|
|---|---|---|---|---|---|---|---|---|---|---|
| libev| 92| 181| 269.9| 355.2| 362.6| 367.1| 373.8| 378.5| 315(3%)|
| thread| 88.9| 180.5| 266.1| 354.8| 428.9| 460.2| 486.5(2%)| 477.9(7%)| 486.5(22%)|

横坐标是连接个数,纵坐标是qps,括号内的数字表示错误率。在连接数较少的情况下,使用libev管理socket和多线程性能相差不大,在连接数超过200后,libev模型的请求耗时会增加,导致qps增加的并不多,但是线程模型在连接数很多的情况下,会导致部分请求一直得不到处理,在连接个数350的时候就会出现部分请求超时,而libev模型在450的时候才会出现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/364249.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ElasticSearch Script 操作数据最详细介绍

文章目录ElasticSearch Script基础介绍基础用法List类型数据新增、删除nested数据新增、删除根据指定条件修改数据根据指定条件修改多个字段数据-查询条件也使用脚本根据指定条件删除nested中子数据数据根据条件删除数据删除之后结果创建脚本,通过脚本调用根据条件查…

.net7窗口编程c#2022实战(1)-zip压缩精灵(1)

目录 创建ZIP精灵项目拖控件OpenFileDialog 类压缩与解压缩编写我们自己的代码其它参考内容创建ZIP精灵项目 VS2022中新建项目。 为窗体取一个标题名称 拖控件 左边工具栏里选择控件 拖三个按钮控件和一个listbox控件

动态规划问题汇总(一)

基本步骤 文章目录基本步骤509. 斐波那契数70. 爬楼梯746. 使用最小花费爬楼梯62.不同路径63. 不同路径 II343. 整数拆分96.不同的二叉搜索树509. 斐波那契数 递归版本 class Solution {public int fib(int n) {if(n0){return 0;}if(n1){return 1;}return fib(n-1)fib(n-2);} …

【华为OD机试模拟题】用 C++ 实现 - 求字符串中所有整数的最小和

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

2023年2月22日 [随记] 理想、面包

一些简单的吐槽,可以当个故事看一下。 文章目录简单的经历书籍清单这些是买了看过的买了没有仔细看的眨眼间也从业2年11个月多一点(就当是三年了),在2023年1月初,距离过年还有两周的时间,因为一些个人原因裸…

拓扑排序的思想?用代码怎么实现

目录 一、拓扑排序的思想 二、代码实现(C) 代码思想 核心代码 完整代码 一、拓扑排序的思想 以西红柿炒鸡蛋这道菜为例,其中的做饭流程为: 中间2 6 3 7 4的顺序都可以任意调换,但1和5必须在最前面,这是…

详细介绍React生命周期和diffing算法

事件处理 1.通过onXxx属性指定事件处理函数(注意大小写) React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —— 为了更好的兼容性;React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ——为了的高效。 2.通过event.target得到发生事件的DOM…

数据挖掘,计算机网络、操作系统刷题笔记54

数据挖掘,计算机网络、操作系统刷题笔记54 2022找工作是学历、能力和运气的超强结合体,遇到寒冬,大厂不招人,可能很多算法学生都得去找开发,测开 测开的话,你就得学数据库,sql,orac…

搭建kafka集群

Kafka集群依赖ZK,需要先启动ZK集群 机器:hadop101,hadoop102, hadoop103 【1】在hadoop101解压: tar -zxvf kafka_2.12-2.4.1.tgz -C ../module/ 【2】在hadoop101修改server.properties配置: #指定broker的id,类似zk…

亚马逊云科技SageMaker:实现自动、可视化管理迭代

现如今,AI正在成为跨时代的技术,在数字经济发展中登上舞台,发挥关键作用。在Gartner发布的《2022年新兴技术成熟度曲线》*报告中,AIGC(即AI Generated Content,人工智能自动生成内容)被列为2022…

微搭使用笔记(四) 通过循环展示组件+json配置生成表单及数据获取

背景及整体思路 上篇文章我们通过微搭提供的数据模型完成了问卷表单页面的创建和数据采集,相对来说除了数据模型配置略显复杂外其他的倒还算方便。 本文我们通过for循环加上json文件配置的方式实现一个通用表单页面,如果更换了表单只需要替换掉json配置…

stm32 VM8978 音乐播放

一、WAV文件 1、WAV文件简介 2、WAV文件的解析 二、WM8978 1、WM8978介绍 2、WM8978特点 3、WM8978接口 4、WM8978框架 5、 WM8978 寄存器 三、IIS详解 1、IIS介绍 2、 IIS 的特点 3、IIS框架 4、 音频协议 5、 IIS Philips 标准 6、 IIS 时钟 四、音乐播放硬件…

力扣-删除重复的电子邮箱

大家好,我是空空star,本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目:196. 删除重复的电子邮箱二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其…

2.22Linux系统搭建

一.搭建Linux运行环境需要部署Java程序到服务器上,这样程序才能被外面的用户访问到1.安装jdkyum install develop x86_642.安装tomcat1)下载好,通过xshell直接拖到服务器上,依赖了rz命令2)解压缩unzip命令3)使.sh都有可执行权限chmod x *.sh4)启动 sh startup.sh5) 验证 ①ps a…

MAC配置pycharm

Mac配置pycharm 恢复出厂配置 # configuration rm -rf ~/library/preferences/JetBrains/ # caches rm -rf ~/library/caches/JetBrains/ # plugins rm -rf ~/library/application support/JetBrains/ # logs rm -rf ~/library/logs/JetBrains/文件头部 Python #!/usr/bin/e…

Mind+Python+Mediapipe项目——AI健身之跳绳

原文:MindPythonMediapipe项目——AI健身之跳绳 - DF创客社区 - 分享创造的喜悦 【项目背景】跳绳是一个很好的健身项目,为了获知所跳个数,有的跳绳上会有计数器。但这也只能跳完这后看到,能不能在跳的过程中就能看到,…

【Linux】virtualbox获取虚拟机串口日志方法,值得收藏

环境 宿主机:redhat 7.8 virtualbox :6.1.10 虚拟机:UOS 1050u1a x86 一、virtualbox设置 在串口栏中勾选 []启用串口 端口编号选择COM1 端口模式选择裸文件 Port/File Path: 填上 /tmp/box 也就是说我们在宿主机器的/tmp/中创建了vbox的…

C语言知识总结

" "和’ 的比较 " "视为字符串,且编译器在后面自动加上’\0’ 则视为单个字符,整型 1、本质区别 双引号里面的是字符串, 而单引号里面的代表字符。 2、输出区别 str “a”输出的就是a这个字母; str ‘a’…

GSON入门篇(内含教学视频+源代码)

GSON入门篇(内含教学视频源代码) 教学视频源代码下载链接地址:https://download.csdn.net/download/weixin_46411355/87474475 目录GSON入门篇(内含教学视频源代码)教学视频源代码下载链接地址:[https://d…

j6-IO流泛型集合多线程注解反射Socket

IO流 1 JDK API的使用 2 io简介 输入流用来读取in 输出流用来写出Out 在Java中,根据处理的数据单位不同,分为字节流和字符流 继承结构 java.io包: File 字节流:针对二进制文件 InputStream --FileInputStream --BufferedInputStre…