网络编程02

ops/2024/12/16 0:35:28/

1. 回显服务器——UDP

一个 UDP 的客户端/服务器通信的程序——回显服务器(echo server):

这个程序只是单纯地调用 Socket API

1)让客户端给服务器发送一个请求,请求就是从控制台输入的字符串

2)服务器收到字符串后,就会把这个字符串返回个客户端,客户端再显示出来

1. 服务器:

服务器和客户端都需要创建Socket对象

服务器的Socket一般要显示指定一个端口

客户端的Socket一般不能显示指定(系统自动分配一个随机的端口)

客户端的端口号是不确定的,交给系统进行分配即可

如果手动指定端口,可能会和其他程序的端口号起冲突

此时Socket对象就绑定到这个指定的端口

先构造一个空的对象,传递到方法内部,由receive内部对这个数据进行填充

DatagramPacket对象来承载从网卡读来的数据

接收数据的时候,需要一个内存来保存这个数据(DatagramPacket内部不能自行分配内存空间)

因此需要把空间创造好,交给DatagramPacket处理

此时服务器一旦启动,就会立即执行到这里的receive方法,客户端的请求可能还没来

receive会直接阻塞,直到客户端发来请求为止

String为取这个区间内的字节,构造成一个String

String request = new String(ruquestPacket.getData(),0,ruquestPacket.getLength());

此处的 ruquestPacket.getLength() 是收到数据的真实长度(取决于发送方发送了多少数据)

 此处是服务器程序的核心步骤,但现在实现的是回显,相当于是把请求当成相应

UDP 是无连接的,每次发送的时候,重新指定数据要发送到哪里去

new DatagramPocket() 
构造数据报,指定数据内容,也指定数据报要发给谁
response.getBytes(),response.getBytes().length   数据的内容
ruquestPacket.getSocketAddress()       请求的地址(IP地址和端口号)

response.getBytes().length     

此处使用.getBytes(),是因为进行网络传输的时候,是使用字节的

如果是英文字符,字节和字符的个数是一样的,包含中文就不一样了(字符集)

此处是打印出一个日志

2. 客户端:

此处使用

String request = scanner.next();

是用为了判断不同的数据报,以换行符 /n 为标准

总结:

1)上述代码中为什么没有close

socket文件是文件描述符中的一个表项,每打开一个文件,就会占用一个位置

文件描述符,是在PCB上进行的

private DatagramSocket socket = null;

这个socket是在整个程序运行过程中都是需要使用的(不能提前关闭)

当socket不需要使用的时候,程序也就要结束了

进程结束的时候,文件描述符就会随之摧毁(PCB摧毁)

随着销毁的过程,socket被系统自动回收

文件泄露的时机:

代码中频繁打开文件,但是不关闭

在一个进程的运行过程中,不断打开积累的文件,逐渐消耗文件描述符里的内容,最终消耗殆尽

如果进程的生命周期很短,打开一下没多久就关闭了,也不算泄露

文件泄露一般在服务器一边,在客户端影响不大

2. 3个 DatagramPacket 的构造方法:让数据报带上内容+数据的目的地址

a. 只指定字节数组缓冲区(服务器接受请求的时候使用,客户端接收响应的时候使用)

服务器返回响应给客户端

b. 指定字节数组缓冲区,同时指定一个 InetAddress 对象(这个对象同时包含IP和端口号)

c. 指定字节数组缓冲区,同时指定IP+端口号

需要把IP地址转换一下

1)服务器先启动,服务器启动后,就会进入循环,执行到receive这里并阻塞(此时客户端还没启动)

2)客户端启动,先会进入while循环,执行scanner.next(),并且在这里阻塞

当用户在控制台输入字符串后,next就会返回,从而构造请求数据并发送出来

3)客户端发出数据后:

服务器:从receive中返回,进一步的执行解析请求为字符串,执行process操作,执行sand操作

客户端:继续往下执行,执行到receive,等待服务器的响应

4)客户端收到从服务器返回的数据之后,就会从receive中返回

执行这里的打印操作,也就把响应显示出来了

5)服务器完成一次循环之后,又执行到receive这里

客户端完成一次循环之后,又执行到scanner.next()这里

双双进入阻塞

2. 词典——UDP

start方法中,调用process方法(this.process)

当前是子类引用调用start,this就是指向子类的引用。虽然this是父类的类型,但是实际指向的是子类引用,调用process,自然会执行到子类的方法

虽然没有修改start方法的内容,但是仍然可以确保按照新版本的process来执行 

3. 回显服务器——CDP

两个关键的类:

1. ServerSocket  给服务器使用,使用这个类来绑定端口号

2. Socket  既会给服务器使用,也会给客户端使用

这两类都是用来表示socket文件的(抽象了网卡这样的硬件设备)  

1)TCP 是字节流的,传输的基本单位是 byte

2)UDP 每次发送数据都得手动在send方法中指定目标的地址(UDP 自身没有存储这个信息)

      TCP 不需要上述过程,前提是需要把连接给建立上

3)连接的建立不需要代码干预,是系统内核自动负责完成的

对应用程序来说:
客户端,主要是发起“建立连接”动作

服务器,主要是把建立好的链接从内核中拿到应用程序里

内核中有一个队列(可以视为一个阻塞队列)

如果有客户端,和服务器建立连接,这时服务器的应用程序是不需要做出任何操作的(无感知),内核直接完成建立连接的细节流程(三次握手)。完成流程后,就会在内核的队列中(这个队列是每一个serverSocket都有的一个队列)排队

应用程序想要和这个客户端进行通信,就需要通过一个accept方法把内核中队列中已经建立好的链接对象,拿到应用程序中

实现流程:

getPort()                                     得到对端的端口(客户端)

getInetAddress()                         得到对端的IP(客户端)

getaLocalAddress()                    得到本地的IP(服务器)

getLocalPort()                             到本地的端口(服务器)

clientSocket.getInputStream()  
clientSocket.getOutputStream()

InputStream 和 OutputStream 是字节流,可以通过这两个对象,完成数据的“发送”和“接收”

通过 InputStream 进行read操作——接收

通过 OutputStream 进行write操作——发送                          以字节为单位进行传输

空白字符是一类特殊的字符——换行,回车符,制表符,翻页符,垂直制表符...

后续客户端发起请求,会以空白符作为结束标志(约定使用\n)

TCP 是字节流通信方式,每次传输/读取多少字节都很灵活

需要手动制定出,从哪到哪是一个完整的数据报

每循环一次处理一个数据报

在该过程中存在内存泄露—— clientSocket 这个对象没有进行 close

DatagramSocket 和 ServerSocket 都是在程序中的,只有这么一个对象,生命周期贯穿整个程序

clientSocket 则是再循环中,每次有一个新的客户端建立连接,都会创建出新的clientSocket

并且这个socket最多使用到该客户端退出(断开连接)

try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream())

只是关闭了 clientSocket 上自带的流对象,并没有关闭socket本身

——>需要在try方法末尾,加上close,保证当前这里的socket能够被正确关闭掉

 客户端的实现:

默认情况下,IDEA只允许一个代码创建一个进程

通过上述方法就可以在一个代码中同时创建多个进程

当启动两个客户端同时连接服务器:

其中一个客户端(先启动的客户端),一切正常

另一个客户端(后启动的客户端),无法与服务器建立按连接(服务器不会提示“建立连接”,也不会针对请求做出回应)

第一个客户端过来后,accept就返回了,得到一个clientSocket,进入processConnection

又进入一个while循环,在这个循环中,需要反复处理客户端发来请求数据。如果客户端这会没发请求,服务器的代码就阻塞在scanner.hasNext()这里

此时此刻,第二个客户端也来建立连接了,此时连接是成功建立的(内核负责)

建立成功后,连接对象就会在内核的队列里等待代码通过accept把连接取出来,在代码中处理

——>

第一个客户端会使服务器处于processConnection内部

进一步的也就使当前的第一层循环,无法第二次执行到accept

非得等到第一个客户端退出,processConnection才能结束,从而执行第二次accept

——>

创建新的新的线程,让新的线程调用processConnection

主线程就可以继续执行下一次accept了,新线程内部负责processConnection内部的循环

此时意味着,每次有一个客户端,就得分配一个新的线程

 

刚才出现这个问题的关键在于两重循环在一个线程里

进入第二重循环的时候,无法继续执行第一个循环

UDP 版本的服务器,当时只有一个循环,不存在类似的问题


http://www.ppmy.cn/ops/142246.html

相关文章

Layer Norm 提升训练稳定性的原理:解决权重初始值敏感性问题(中英双语)

Layer Norm 提升训练稳定性的原理与数值模拟 在深度学习模型中,权重初始值对训练过程的稳定性影响极大,尤其在深层网络和长序列任务中,初始值不当会导致梯度消失或爆炸的问题,进而导致训练不稳定。Layer Normalization (Layer No…

开源分布式系统追踪-00-overview

分布式跟踪系列 CAT cat monitor 分布式监控 CAT-是什么? cat monitor-02-分布式监控 CAT埋点 cat monitor-03-深度剖析开源分布式监控CAT cat monitor-04-cat 服务端部署实战 cat monitor-05-cat 客户端集成实战 cat monitor-06-cat 消息存储 skywalking …

基于小程序实现地图定位、轨迹绘制、地图标点、快捷导航、唤醒导航APP、开箱即用

目录 前言研究背景与意义研究目标与内容研究方法与技术路线小程序地图组件介绍定位技术与原理轨迹绘制技术地图标注与标记功能地图定位与轨迹绘制功能实现定位功能设计与实现获取用户当前位置总结说明代码块前言 研究背景与意义 地图定位和轨迹追踪作为智能手机中常见的功能之…

【JAVA】Java项目实战—Java EE项目:企业资源规划(ERP)系统

在企业管理中,企业资源规划(ERP)系统是不可或缺的工具。它能够帮助企业高效管理各种资源,包括人力资源、财务资源和库存等。Java作为一种成熟的编程语言,因其跨平台特性、强大的生态系统以及良好的社区支持&#xff0c…

git 推送远程仓库 master -> master (push declined due to repository rule violations)

问题概述 从报错信息中看出,提交中包含了秘密,提交被拒绝了,从提供的网址Working with push protection from the command line - GitHub Docs 中找到原因。原来是提交中包含了github的Personal access tokens被拒绝了。 解决方法 rebase …

GitHub 开源仓库推荐:poe2skills

poe2skills是一个专为《流放之路 2》玩家和开发者设计的开源项目。它收集了游戏中所有的技能和被动宝石信息,帮助玩家更好地理解和利用游戏中的各种机制。对于那些希望深入挖掘游戏潜力的玩家来说,这个仓库无疑是一个宝贵的资源。 功能亮点 全面的技能数…

【Linux】常用资源监控工具

最常用的三个命令 人性化实时监控cpu、内存、进程等资源使用情况 htop 实时监控 GPU watch -n 1 nvidia-smi 人性化查看硬盘分区使用情况 df -h Linux 系统资源监控指南 1. CPU 监控 1.1 实时监控命令 # top - 经典的实时系统监控工具 top# htop - 更友好的 top 替代品&a…

【开源免费】基于Vue和SpringBoot的渔具租赁系统(附论文)

本文项目编号 T 005 ,文末自助获取源码 \color{red}{T005,文末自助获取源码} T005,文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 渔…