【JavaEE初阶 — 网络编程】TCP流套接字编程

      c96f743646e841f8bb30b2d242197f2f.gif

ddb5ae16fc92401ea95b48766cb03d96.jpeg692a78aa0ec843629a817408c97a8b84.gif


    TCP流套接字编程    


    1. TCP & UDP 的区别    


TCP 的核心特点是面向字节流,读写数据的基本单位是字节 byte 


    2 API介绍    


    2.1 ServerSocket    


     定义     


ServerSocket 是创建 TCP 服务端 Socket 的API。


     构造方法     


方法签名
 
方法说明
 
ServerSocket(int port)创建一个服务端流套接字Socket,并绑定到指定端口

所以服务器启动,需要绑定端口号


     方法      


方法签名
 
方法说明
 
 Socket accept()
 
开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待
 
 void close() 
 
关闭此套接字


    2.2  Socket    


  • Socket 是客户端 Socket ,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。

  • 不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

     构造方法     


方法签名方法说明
Socket(String host, int port)
 
创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

这个方法的两个参数,都是服务器的IP & 端口,这个版本的构造方法,就是给客户端用的,服务器怎么通过这个类来构造对象呢?后续再来看;


     方法      


方法签名方法说明
InetAddress getlnetAddress()
 
返回套接字所连接的地址
 
InputStream getlnputStream()
 
返回此套接字的输入流
 
OutputStream getOutputStream()
 
返回此套接字的输出流

TCP没有 send(),receive() 这样的操作,但是 TCP 调用 getlnputStream() 会得到个 InputStream 对象;调用 getOutputStream(),得到一个 OutputStream 对象,这两个对象是字节流对象

虽然 Socket 自身没有读写操作,但是 Socket 可以拿到字节流对象,就可以通过字节流对象,来进行读写操作;


     3. 通过TCP实现回显服务器        


     TCP Echo Server    


    创建关联对象    


通过构造方法,绑定关联的端口号 (和 UDP类似,都是在构造对象的时候,绑定端口号)


    实现 start()    


    处理客户端发送的连接    


TCP 和 UDP 服务器 start() 的主循环的第一步有所区别:


  • UDP 进入主循环,就可以直接处理请求,根据请求计算响应,把响应返回客户端;
  • TCP服务器 ,进入主循环后,因为 TCP 是有连接的,所以第一步是先处理客户端发来的连接;
  • 这个连接就类似于打电话,在客户端打电话给服务器时,服务器要先接通电话,才可以进行后续的正常通信;

所以TCP服务器进入主循环的第一步,就是进行接通电话的操作,而拨号操作,是客户端来完成的;

调用 ServerSocket 对象底下的 accept() 方法,起到接听电话的作用;


还需要接收 accept() 方法的返回值:

  • 如果客户端和服务器确实已经建立连接了,那 accept() 是可以拿到这个请求连接的;
  • 如果客户端没有发起连接,那么 accept() 就会产生阻塞 ,和前面的 receive() 类似;

TCP 服务器后续通过对clientSocket 进行读写数据,来和客户端进行通信;


    进一步理解 ServerSocket 和 Socket 的职责划分    


    处理一个客户端的连接        

处理连接的过程比较复杂,因此我们把这个操作封装成一个方法;

可能会涉及到多个客户端的请求和响应,如果服务器接收到多个请求,就要返还给客户端多个响应;


    服务器与客户端成功连接,打印日志: 

这两个方法可以拿到对端(客户端)的 IP & 端口号;


    获取输入流对象&输出流对象     

打印出客户端的IP&端口号后,就需要进一步地处理客户端的请求和响应,需要借助 Socket 类内置的 InputStream & OutputStream 来处理这些请求和响应

这里获取到的是输入流对象,后续提供这个对象,来读取客户端的请求;


接下来获取输出流对象,并且处理异常:


拿到输入流对象&输出流对象后,后续读取请求,就使用输入流对象;返回响应,就把响应的内容写入输出流对象中; 


接下来,在 try 的代码块中,实现读取请求和返回响应的操作,这些操作分成三步

  1. 读取请求并解析 
  2. 根据请求计算响应
  3. 返回响应给客户端

因为在一次连接中,这三个操作可能会解析多次,所以我们提供 while 循环来处理


    读取请求    

下列操作,读取到的请求是一个字节数组,还需要手动把字节数组再转成字符串,才方便后续的处理和打印:


我们可以借助 Scanner 来进行更简单直接的读取操作,既可以读请求,读出来的请求又已经是一个字符串;

 

  • 把刚刚从 Socket 中拿到的 InputStream 填入 Scanner 中,后续通过 Scanner 直接读取请求中的内容;
  • 如果 Scanner 没有再读取到数据,说明连接断开,就可以结束循环了

所以读取请求,可以直接借助 read(),也可以借助 Scanner 来辅助完成

    补充     

  • Scanner 可以控制处理台输入,又可以控制处理文件的输入,还可以控制处理网络的输入;
  • Scanner 的构造方法:

  • Scanner 的构造方法,填入的是一个InputStream 对象

    根据请求计算响应    

  • 当前编写的是一个回显服务器代码,所以可以直接在计算响应的逻辑返回请求即可


     返回响应给客户端    

下列写法,会直接拿到 response 中的字节数组,然后通过 outputStream 提供的 write(),来写入输出流对象即可;

这种计算响应的方法,是提供字节的方式填充输出流对象;


除了上述写法,我们还可以利用字符流的方式:


这里的 writer 和 System.out 起到的的效果类似,所以 println,printf 等等都可以通过调用


    打印返回响应的日志和连接断开的日志    


    服务器一次连接可处理多个请求的原理   

服务器 start() 的代码块中, process() 方法处理请求返回响应的逻辑,相当于嵌套了两层 while 循环:

因此,可以在服务器与客户端的一次连接中,服务器处理多个请求;

如果在一次连接中,客户端发送多次请求,服务器就返还多个响应(打一次电话可以说一句话或者很多句话);


    补充     

  • 一个连接一个请求(短连接),一个连接多个请求(长连接);
  • 因为连接过程的开销非常大,所以在日常开发中,更主流的是长连接,一个连接处理多个请求;
  • 就好比锁消除,针对要加锁的多个逻辑,每个逻辑都进行加锁,开销非常大,所以更科学的做法是把这些逻辑合在一起,只进行一次加锁;(和领导汇报工作成果,应该在一次电话中一次性汇总完毕,而不是打多次电话。每次电话只汇报一个成果)

    TCP Echo Client    


     创建 Socket 对象    

Socket 在客户端和服务器都可以使用,服务器的 Socket 通过调用accept()拿到,但是客户端的 Socket 就需要通过实例来创建对象;

     实现客户端构造方法    

在客户端的构造方法中,传入服务器的IP和端口号;

传到构造方法中的字符串IP地址(类似127.0.0.1这样的字符串),不需要任何转换;


对比UDP 的客户端,TCP客户端在构造方法在实例Socket对象后,就会在底层和对端建立TCP连接,连接好后,服务器会记录对端的信息(实例化Socket对象时传入的IP和端口号);

因此,服务器的IP和端口号,在TCP客户端中就不需要再创建变量来保存了;


    从控制台中读取请求,发送给服务器    


    从控制台中读取用户输入信息作为请求    

为了实现客户端能够和服务器在一次连接的情况下,发送多次请求,我们设置一个循环:

这步操作可以读取刚刚输入控制台的一行信息,读取到的信息作为客户端的请求;


    拿到输入流&输出流    

之后就把这个请求写入 Socket 对象中,写的时候也需要拿到Socket对象的 InputStream 输入流& OutputStream 输出流;


为了使用方便,可以对拿到的输入流和输出流再套一层壳


    完善循环逻辑     

所以在主循环中,第一步操作是从控制台中读取用户输入,把读取到的输入设置为请求:


第二步就是把请求发送给服务器


第三步,就是读取服务器返回的响应,并且把读取到的响应打印到控制台


    客户端与服务器交互过程    


    区分客户端与服务器的 Socket 对象    

下列服务器和客户端的两个 socket 对象,分别在不同进程中,甚至在不同主机中,因此绝对不是同一个对象;

这两个对象存在密切的关联关系,可以把这两个 socket 对象理解为两部电话:

  • 接通这两部电话后,从A听筒说话,B可以听见;从B听筒说话,A可以听见(从一边对Socket对象写数据,另一边的 Socket 对象就可以读到);
  • 但是这两个对象绝对不是同一部电话;

    处理细节问题    

    问题一:冲刷缓冲区     

完善 main 方法


程序运行结果


关掉客户端:


再启动一次客户端,并且发送一个数据,并且一敲回车,发现没有反应:


为什么没有反应呢?因为其实刚刚客户端代码,并没有真的把请求发送出去:

这个操作只是把数据放到 “发送缓冲区” 中,还没有真正写入网卡里;


  •  发送缓冲区其实就是一块内存空间,对网络/硬盘写数据是一个非常低效的操作,如果频繁地调用这些比较低效的操作,程序运行是非常缓慢的;
  • 为了提高效率,就引入一个内存缓冲区,把要写入的数据都放入缓冲区中,再统一进行发送,这样可以减小写硬盘和写网络的次数;
  • 但是提高效率的同时,也会产生副作用,就是调用 writer.println 这样的操作,并没有真正地触发发送数据操作,而只是把数据写入缓冲区;
  • 当然,把数据写入缓冲区,而不是直接发送这样的行为,是 PrintWriter的行为,如果不套壳,是可以直接发送的;
  • 但是在实际开发中,广泛使用了缓冲区这样的概念,调用flush()来刷新缓冲区这个操作是非常关键的;

如何真正地把数据发送出去呢?我们要使用刷新操作(调用 flush() 方法来冲刷缓冲区),把缓冲区的数据强制写入 IO 设备中:


客户端服务器交互结果


    问题二:针对 hasNext 对一个完整请求/响应设置标识符    

 println 的操作,会自动加上一个 \n : 


但是如果在这个代码中不加这个 \n,直接使用 print行不行呢?


我们重新启动一下客户端,并且发送内容,发现客户端又没有反应了,并且服务器也没有读到信息:


造成上述原因,是因为 next() 的问题,修改成 print 后,客户端输入的数据也是发送到服务器上了,并且服务器也收到了,但是服务器并没有真正处理,因为服务器有一个hasNext()判断:


    补充    

  • hasNext() 的行为是,判断当前收到的数据是否包含“空白符”,什么是空白符呢?
  • 换行,回车,空格,制表符,翻页符......都是空白符;
  • 遇到空白符,hasNext()才会认为是一个完整的 next,否则在遇到空白符之前,hasNext() 都会阻塞。

所以刚刚在修改成 print 之后,发送的内容是不包含空白符的内容,hasNext() 就会阻塞而无法进入下面读取请求的逻辑;


    总结    


  • 使用 println,是在约定一个请求/响应,是在使用 \n 作为结束标记,对端在读取数据的时候,也会在读取到 \n 时,判断读取到一个完整的请求/响应;
  • 这是我们在使用TCP时,特别需要注意的事项,并且和UDP不一样;
  • UDP是以 DatagramPacket 作为单位的,但是TCP则是以字节为单位,但是实际上一个请求,往往是以多个字节构成的;
  • 到底多少个字节为一个完整的请求/响应,就需要程序员想办法标记出来,引入分割符是标记一次完整请求/响应的典型方式,不一定是换行,也可以是其他分割符; 

    问题三:根据不同Socket的生命周期判断是否需要手动关闭    

在TCP服务器刚刚编写的代码中,涉及两种Socket:

  • ServerSocket的生命周期,贯彻整个服务区进程,不需要手动关闭


  • clientSocket 的生命周期是一次连接,而不是整个服务器进程;

  • 所以每个客户端连接,都会创建一个新的 clientSocket,每个客户端断开,这个对象就应该 close() 了,但是当前代码并没有对 clientSocket 进行 close() 释放;
  • 没有在每次连接结束后,对 clientSocket 进行关闭,就会造成文件资源泄漏的问题(文件一直在打开,而不进行关闭,在打开到一定程度,会把文件描述表耗尽,就无法继续打开新文件)

  • 在 poccessConnection() 方法的逻辑执行完毕之后,我们就可以对 clientSocket 进行关闭

    总结    

  • 对于是否需要手动关闭 Socket ,需要我们分析请求它的生命周期是跟随整个进程,还是跟随某个环节;
  • 如果是跟随整个进程,那么可以不手动关闭 Socket;如果是每个请求都会创建应该 Socket,或者每一次连接都会创建一个 Socket,或者某一个环节的执行周期,会创建一个Socket,这样的情况,就需要我们手动关闭 Socket;
    问题四:服务器无法同时等待 accept() &等待已连接的客户端发送响应    

一个服务器能同时给多个客户端提供服务,那么刚刚编写的TCP服务器也可以处理多个请求吗?我们关掉客户端,再重新启动多个客户端:


    补充:修改同时启动多个客户端的IDEA设置    



IDEA 会默认只启动一个客户端,再启动别的客户端,会先关闭上一个启动的客户端;我们通过这里的设置,就可以让 IDEA 启动多个客户端;

设置好后,我们再来重新启动两个客户端, 并且先后发送请求:

结果在第二个客户端发送请求时,又卡住了,并且第一个创建的客户端多次发送请求的操作是没问题的,服务器都会有一个正常响应,但是第二个客户端无论怎么发送请求,都不会有响应:

并且我们通过服务器日志,可以发现,在第二个客户端上线时,并没有再次打印日志;

如果我们关掉第一个客户端,第二个客户端发送的请求,会马上被服务器接收,并且返回响应

所以当前代码,TCP服务器在同一时刻,只能处理一个客户端发送的请求;


针对上面出现的问题,我们对服务器的处理请求,返回响应这一块代码的关键逻辑进行分析:

如果同一时刻,有多个客户端对一个服务器进行连接,那么第一个和服务器连接的客户端1:

所以当前这个服务器代码,如果已经在处理一个客户端的请求,就没办法处理另一个客户端的请求,服务器代码会卡在循环中,无法重新调用 accept()连接新的服务器,直到连接的客户端退出,导致循环终止;


    总结    


  • 当前服务器代码,无法同时等待 accept 和 等待用户请求;在等待客户端发送请求的时候,没办法等待 accept() ,这个时候,如果有新的客户端连接,也无法接听电话(一个专业销售没办法在给先来的顾客讲解产品的时候,又去接待低级销售后面揽入店的顾客);
  • 因此,服务器的代码,导致服务器一次只能处理一个客户端发送的请求,这个代码是不合理的; 

    4. 服务器引入多线程    


如果只是单个线程,无法同时响应多个客户端;为了解决这个问题,此处给每个客户端都分配一个线程;


在服务器主线程中,就只是进行 accept(),每次有新的客户端 accept() 连接成功,就创建一个新的线程,又新线程负责完成,后续对客户端 Socket 对象的引用 clientSocket 的读写操作;

引入线程之后,重新启动服务器和两个客户端,可以发现服务器打印了两个带着不同端口号的日志;


因此,在引入线程之后,服务器可以一边等待请求,一边等待 accept() 连接新的客户端;



    5. 服务器引入线程池    


客户端连接,服务器就会创建新线程,客户端断开连接,客户端就会销毁线程,为了避免频繁创建销毁线程,也可以引入线程池;

线程池


对于在服务器引入线程池,一般不会使用 newFixedThreadPool,因为会创建一个固定线程数的线程池,意味着同时处理的客户端连接数目就固定了;

把任务都交到线程池中,线程池已经预先创建好了一些线程,提前创建好的线程就可以立刻投入工作,从而减少再去创建线程的开销;


线程不是越多越好,如果线程数量过多,CPU的利用率无法再被提高,还会导致系统调度速度下降;并且创建线程也是需要系统资源的,系统总体资源是有限的(一个主机差不多只能创建几千个线程);

无论是多线程,还是线程池,一个线程都对应一个客户端,并且一个主机创建的线程数目是有上限的,那如果有成千上万个客户端,同时访问一个服务器(一台主机),该怎么办呢?

IO多路复用/IO多路连接(这里不重点讲解,因为JVM中没有原生的 IO 多路复用 API,而是把API重新封装,已经不仅仅是多路复用了,后续会详细讲解针对 Java 的 IO多路复用所使用的 NIO,Netty 等知名网络框架,当前讲的IO是 BIO/Blocking IO);


    扩展    


基于BIO(同步阻塞IO)的长连接会一直占用系统资源。对于并发要求很高的服务端系统来说,这样的消耗是不能承受的。

  • 由于每个连接都需要不停的阻塞等待接收数据,所以每个连接都会在一个线程中运行。
  • 一次阻塞等待对应着一次请求、响应,不停处理也就是长连接的特性:一直不关闭连接,不停的处理请求。

实际应用时,服务端一般是基于NIO(即同步非阻塞IO)来实现长连接,性能可以极大的提升。 


    6. 长短连接    


TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接;


    长连接和短连接的概念    


  • 短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。
  • 长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据。

    长连接和短连接区别    


  • 建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时的,长连接效率更高。
  • 主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。
  • 两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室,实时游戏等。

   

   c96f743646e841f8bb30b2d242197f2f.gif

692a78aa0ec843629a817408c97a8b84.gif

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

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

相关文章

idea新建springboot web项目

idea新建springboot web项目 写在前面开始项目结构定义依赖初始化创建完成修复配置文件内容乱码修改配置文件名称更新配置文件内容为yml格式 配置项目启动项启动项目 写在前面 以下操作以IntelliJ IDEA 2022.3.3版本为例,其他版本应该大体相似。 开始 项目结构定义…

Docker 容器网络创建网桥链接

一、网络:默认情况下,所有的容器都以bridge方式链接到docker的一个虚拟网桥上; 注意:“172.17.0.0/16”中的“/16”表示子网掩码的长度为16位,它表示子网掩码中有16个连续的1,后面跟着16个连续的0。用于区分IP地址中的网络部分和主机部分; 二、为何自定义网络? 加入自…

智能产品综合开发 - 温湿度检测

1 实训选题目的 本次实训选择的题目是“温湿度检测系统”,旨在提升我们对日常生活中实际应用场景的观察力、问题描述能力、分析力和产品设计能力。通过本项目,我们将能够将所学的人工智能专业知识进行综合应用,包括但不限于Linux操作系统、Py…

人工智能如何改变你的生活?

在我们所处的这个快节奏的世界里,科技融入日常生活已然成为司空见惯的事,并且切实成为了我们生活的一部分。在这场科技变革中,最具变革性的角色之一便是人工智能(AI)。从我们清晨醒来直至夜晚入睡,人工智能…

vue3 ajax获取json数组排序举例

使用axios获取接口数据 可以在代码中安装axios包,并写入到package.json文件: npm install axios -S接口调用代码举例如下: const fetchScore async () > {try {const res await axios.get(http://127.0.0.1:8000/score/${userInput.v…

Vue前端开发-动态插槽

不仅父组件可以通过插槽方式访问并控制子组件传入的数据,而且可以控制传入父组件时插槽的名称,从而使不同的插槽根据名称的不同,使用场景也不同,例如在一个小区详细页中,可以根据小区类型,调用不同名称的详…

SqlServer强制转换函数TRY_CONVERT和TRY_CAST

SqlServer强制转换函数TRY_CONVERT和TRY_CAST的介绍和案例分享 1、本节内容 CAST 和 CONVERT TRY_CAST TRY_CONVERT 适用于: SQL ServerAzure SQL 数据库Azure SQL 托管实例Azure Synapse Analytics 分析平台系统 (PDW)Microsoft Fabric 中的 SQL 分析端点Micro…

PaddleOCR:一款高性能的OCR工具介绍

一、引言 随着人工智能技术的不断发展,光学字符识别(OCR)技术在各行各业得到了广泛应用。OCR技术能够将图片、扫描件等非结构化数据中的文字信息提取出来,转换为可编辑的文本格式。在我国,百度开源了一款优秀的OCR工具…

如果在docker 容器中安装ros遇到的问题

1.在容器内部无法修改时间,需要在宿主机外边修改时钟。修改时钟: hwclock --systohc或者执行 date -s "2024-11-24 19:25:10"2.容器内部内置有opencv4.5版本,需要卸载,重新安装4.2.0版本。记录折腾好久的卸载过程。 …

LLM*:路径规划的大型语言模型增强增量启发式搜索

路径规划是机器人技术和自主导航中的一个基本科学问题,需要从起点到目的地推导出有效的路线,同时避开障碍物。A* 及其变体等传统算法能够确保路径有效性,但随着状态空间的增长,计算和内存效率会严重降低。相反,大型语言…

ACL的原理与配置

ACL技术概述 ACL;访问控制列表 技术背景: 园区重要服务器资源被随意访问,容易泄露机密,造成安全隐患 病毒侵入内网,安全性降低 网络宽带被各类业务随意挤占,服务质量要求高的宽带得不到保障&#xff0…

【配置】pycharm运行的项目如何修改名称(项目名称、模块名称)

当我们需要修改项目名称、模块名称的时候,能够在网上找一些修改的方法,但是有没有很保守但很使用的方法可以解决这个问题呢? 创建项目 通过pycharm创建一个django的项目 创建之后的项目目录: 更改项目名称: 往往以…

【新人系列】Python 入门(十四):文件操作

✍ 个人博客:https://blog.csdn.net/Newin2020?typeblog 📝 专栏地址:https://blog.csdn.net/newin2020/category_12801353.html 📣 专栏定位:为 0 基础刚入门 Python 的小伙伴提供详细的讲解,也欢迎大佬们…

MySQL 启动失败问题分析与解决方案:`mysqld.service failed to run ‘start-pre‘ task`

目录 前言1. 问题背景2. 错误分析2.1 错误信息详解2.2 可能原因 3. 问题排查与解决方案3.1 检查 MySQL 错误日志3.2 验证 MySQL 配置文件3.3 检查文件和目录权限3.4 手动启动 MySQL 服务3.5 修复 systemd 配置文件3.6 验证依赖环境 4. 进一步优化与自动化处理结语 前言 在日常…

IDEA Maven 打包找不到程序包错误或找不到符号,报错“程序包不存在“

参考文章:https://blog.csdn.net/yueeryuanyi/article/details/14211090 问题:IDEA Maven 打包找不到程序包错误或找不到符号,报错“程序包不存在“编译都没问题 解决思路 – >【清除缓存】 1. 强制刷新Maven缓存 选择 Maven 标签,Exe…

通过 SSH 进行WordPress网站的高级服务器管理

我在管理hostease的服务器时,时常需要通过SSH登录服务器进行修改。而在网站管理中,SSH不仅是一个基础工具,更是高级用户用来精细化管理和优化服务器的重要工具。通过SSH,你可以深入监控服务器的性能、精细管理系统资源&#xff0c…

分布式搜索引擎之elasticsearch单机部署与测试

分布式搜索引擎之elasticsearch单机部署与测试 1.部署单点es 1.1.创建网络 因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络: docker network create es-net1.2.加载镜像 这里我们采用elasticsearch的7.12.1版本的…

WPF+MVVM案例实战与特效(三十)- 封装一个系统日志显示控件

文章目录 1、运行效果2、日志控件封装1、文件创建2、DisplayLogPanel.xaml 代码3、using System;3、using System;3、数据模型4、枚举类型3、案例实现1、LogPanelWindow.xaml2、LogPanelViewModel.cs4、总结1、运行效果 2、日志控件封装 1、文件创建 打开 Wpf_Examples ,在 …

第二节——计算机网络(四)物理层

车载以太网采用差分双绞线车载以太网并未指定特定的连接器,连接方式更为灵活小巧,能够大大减轻线束重量。传统以太网一般使用RJ45连接器连接。车载以太网物理层需满足车载环境下更为严格的EMC要求,100BASE-T1\1000BASE-T1对于非屏蔽双绞线的传…

使用vcpkg自动链接tinyxml2时莫名链接其他库(例如boost)

使用vcpkg自动链接tinyxml2时莫名链接其他库(例如boost) vcpkg的自动链接功能非常方便,但在某些情况下会出现过度链接的问题。 链接错误症状 以tinyxml2为例,程序中调用tinyxml2的函数后,若vcpkg中同时存在opencv和…