【JavaEE】UDP协议与TCP协议

作者主页:paper jie_博客

本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。

本文于《JavaEE》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精力)打造,将基础知识一网打尽,希望可以帮到读者们哦。

其他专栏:《MySQL》《C语言》《javaSE》《数据结构》等

内容分享:本期将会分享网络编程中的UDP和TCP协议

目录

UDP协议段格式

校验方式

UDP的特点  

面向数据报

UDP使用注意事项

TCP协议段格式 

TCP协议重要机制

确认应答

超时重传

连接管理

建立连接

三次握手

 三次握手为什么不能是四次或者两次

三次握手的意义 

 断开连接

四次挥手

三次挥手与四次挥手相同之处和区别 

TCP状态

 滑动窗口

ack丢包

数据丢包

流量控制

 拥塞控制

延时应答

捎带应答

面向字节流

异常情况处理


UDP协议段格式

【JavaEE】UDP协议与TCP协议_第1张图片

源/目的端口号: 表示数据从哪个进程来,到哪个进程去.

16位UDP长度: 表示UDP有65000多个字节.这里UDP最大长度为64KB.

16位校验和: 这里使用CRC来完成校验.发送端填充,接受端校验不通过,则认为数据有问题,就会直接丢弃.

数据: 载荷部分,存放数据正文.

校验方式

1.CRC校验. 它是通过遍历的方式来完成的.通过用一个变量来将每一个字节的数据都+=,最后将这个数据发送给接受方,接受方再进行相同的方法来进行计算出来的值对比,如果相同数据就是正确的,如果不相同,则数据有问题. 缺点就是检查精度不够高,如果有两个或两个以上发生了bit翻转,可能校验的结果就会和之前一样.

2.md5算法.它是通过一系列的数学公式来进行计算的.它的三个特点:

1.定长: 不管原来的数据是多长,算出来的md5值还是固定长度.

2.分散: 在计算的过程中,只要数据有一点点相差,计算出来的md5值就会有很大差别.

3.不可逆: 给一个md5值,是很难再还原到原来的字符串.

UDP的特点  

UDP传输的过程类似于传信.

1. 无连接: 直接对端的IP和端口号就直接进行传输,不需要建立连接.

2.不可靠: 没有确认机制,没有重传机制.如果因为网络故障没有发送到对方.UDP也没有对应的措施.

3. 面向数据报: 不能灵活的控制读写数据的次数和数量.

面向数据报

应⽤层交给UDP多⻓的报⽂,UDP原样发送,既不会拆分,也不会合并
如果⽤UDP传输100个字节的数据:  如果发送端调⽤⼀次sendto,发送100个字节,那么接收端也必须调⽤对应的⼀次recvfrom,接收100个字节;⽽不能循环调⽤10次recvfrom,每次接收10个字节; 

UDP使用注意事项

UDP协议一次最大能传输的数据最大长度是64k.但是在现在的互联网环境下,是一个非常小的数字.

如果我们需要传输的数据超过64k,就需要在应用层手动的分包,多次发送,并在接受端手动拼接.

TCP协议段格式 

【JavaEE】UDP协议与TCP协议_第2张图片

源/目的端口号: 表示数据从哪个进程来,到哪个进程去.

32位序号/32位确认号: 数据的编号,用来确认下一个数据从哪里开始发送

4位TCP报头长度: 表示TCP头部有多少个4字节,所以TCP报头的最大长度为 4*15 = 60字节

保留位: 如果TCP长度需要扩展就会使用到保留位.

6位标志位:

URG: 紧急指针是否有效

ACK: 确认序号是否有效

PSH: 提示接收端应用程序从TCP缓冲区将数据读走

RST: 对方要求重新建立连接(复位报文段)

SYN: 请求建立连接(携带syn叫同步报文段)

FIN:通知对方本端要断开连接了(结束报文段)

16位窗口大小: 一次性可以传输的数据的大小

16位校验和: 发送端填充,CRC校验, 接受方校验不通过,就认为数据有问题,这里校验也会包含载荷部分.

16位紧急指针: 标识哪些数据是紧急数据

40字节头部选项: 可以设置一些需要的参数

TCP协议重要机制

确认应答

这里机制是TCP最核心的机制,是用来确保可靠性的.我们知道,在网络传输的过程中,有时会出现后发先至的情况.(在网络中,数据包从发送方到接收方的传输过程中走的路径可能不一样.不同的路径可能网速就不一样,这样可能就会造成数据包1线发送但是晚到的结果.)如果出现了这样的情况传输的数据就会混乱.为了解决上述的问题,就引入了序号和确认序号.对于数据进行编号,应答报文里就可以告诉发送方,这次应答的数据是哪个数据. 实际上,TCP的序号和确认序号都是按字节来进行编号的.应答报文中的确认序号是按照发送过去的最后一个字节的序号再加1来进行设定的. 

它就可以理解为: ack告诉发送方它当前收到了哪些数据,且接下来要从哪里开始发送数据. 且在接收方缓冲区中,接收的数据是会被排序的. 这里就可以让发送数据的顺序和接收顺序是一致的.

【JavaEE】UDP协议与TCP协议_第3张图片

这里的应答报文也叫做ACK报文.确认应答中,就是通过ack来反馈给发送方,当前数据正确收到了. 

超时重传

这个机制可以认为是确认应答的补充.

如果传输数据的过程是顺利的,通过ack就可以告诉发送方,当前数据成功收到. 但是在网络上,是会存在丢包的现象(丢包的原因就是网络传输的工具路由器和交换机在某个时刻需要转发大量的数据包,一但超出它的处理范围,就会将多出来的数据丢弃).如果数据包丢了,没有到达接收方,也就没有ack报文返回了.这个情况下,就需要超时重传了. TCP可靠性就是在对抗丢包.

处理过程就是发生方发了一个数据后,会等,在等的时间里收到了ack就是没有丢包.但如果等了好久,ack还没有收到.这时发送方就认为数据的传输出现了丢包了.当发送方认为丢包后,就会将刚才的数据包在发送一次(重传). 等待的过程是有一个时间阈值的,超过了就是超时.这里丢包会有两种情况.1种就是在发送的过程中数据包丢了,另一种就是返回的ack丢了.在发送方的角度来看,这里都是ack没收到.但是第二种情况就会出现接受方收到两份相同的数据.这在一些场景下是不允许的(比如扣款场景).

【JavaEE】UDP协议与TCP协议_第4张图片

为了处理这种情况.TCP socket在内核中会存在一块缓冲区.发送方的数据,是要先放到缓冲区中的.然后应用程序再调用read/next()才能读到数据. 当数据到达缓冲区的时候,接收方就会先判断一下当前缓冲区是不是已经有这个数据了(或者这个数据曾经在接受缓冲区中存在过). 如果已经存在或者存在过,就直接把重复发来的数据丢弃了.这样就可以确保不会读到重复的数据.而接受方判定这个数据是否重复的依据就是数据的序号.

1.数据还在缓冲区里时,就会拿着新收到的序号去和缓冲区中的所有数据对比一下,看看有没有相同的.如果有就会将新收到的数据丢弃.

2.数据在缓冲区中被读走了,这时新来的数据序号是无法在缓冲区中查找到的. 但是这里注意,应用程序在读数据的时候,是按照序号的先后顺序来连续读取的. 先读 0-1000, 1001-2000, 2001-3000....... 这里可以想象成缓冲区是带有优先级的阻塞队列. 这时socket api中就可以记入上一次读的最后一个字节的序号是多少. 如果新来的序号小于上一次最后字节的序号,就代表这个新的数据包一定读过了.这个时候就会判断它为重复的包,然后丢弃.

注意:

这里重传不是无限的,重传是有策略的重传.

1. 重传是有次数的,一但超过这个次数还没有ack,就会尝试重置连接,如果重置连接也失败,就会直接放弃连接.

2. 重传的超时时间阈值也不是固定的,这个时间会随着从重传次数的增加而增大(因为重传的次数越多,成功的记录就越大.要是后面2,3次后还要重传,就代表当前丢包的概率也太大了,再什么传成功的可能性也很小,就省点力气)

连接管理

连接管理分为建立连接和断开连接

建立连接

三次握手

我们知道TCP是有连接的.在应用层客户端上 socket = new Socket(serverIP, serverPort) 这个操作就是建立连接. 但上面的操作,它只是使用了socket 的api,真正的连接过程封装在传输层,也就是在操作系统内核中完成建立连接的过程的.

【JavaEE】UDP协议与TCP协议_第5张图片

那内核中是怎么完成建立连接的过程呢? 这就是我们常说的"三次握手". 这里的连接是抽象的,其本质就是让通讯双方都可以保存对方的相关信息.

建立连接的第一步(第一次握手): 客户端是主动的一方,第一次交互,一定是客户端先发起的,客户端会将一个syn报文发送给服务器,意思就是向服务器发起建立连接的请求. (这里的syn是一个特殊的TCP数据报,它没有载荷,不会携带应用层的数据.且6个标志位的第五位就会变成1. 这里syn虽然没有应用层数据,但是它会有TCP报头,IP报头,以太网数据帧,我们可以理解这里syn报文给服务器就是告诉服务器我是谁,我从哪里来)

建立连接的第二步(第二次握手): 在服务器收到服务端的syn报文后,服务器就会返回一个ack报文和一个syn报文.ack报文就是告诉客户端我收到了你的连接请求,而syn报文就是我接不接受你的连接,里面会包含它的相关信息.

【JavaEE】UDP协议与TCP协议_第6张图片

这里的连接过程,本质上就是通信双方各自给对方发一个syn,各自给对方回一个ack. 这里客户端要连接的信息第一个握手虽然已经告诉服务器了,但最终确立连接,确立后面要进行通信,是得等全部的连接流程走完的,握手环节结束了,服务器才会保存客户端的信息.

 三次握手为什么不能是四次或者两次

这里四次是可以的,但是没必要.三次已经可以让服务器和客户端知道双方的接受和发送能力正常.但是二次的话服务器这边就不能确定自己的发送能力和客户端的接受能力是不是正常的.

三次握手的意义 

1. 投石问路

三次握手可以先对通信通道进行投石问路,初步的确认一下通信链路是不是可以正常通行.不过这个作用比较有限,只能清楚最开始进行三次握手的通信情况.

2. 三次挥手可以检验通信双方,发送能力和接受能力是否正常(关注点在两端)

第一次挥手当客户端将syn报文发送给服务器后,服务器就可以确认自己的接收能力和对方的发送能力正常; 第二次挥手当服务器将ack和syn报文发送给客户端后,客户端就可以确认自己的发送接受能力和对方的发送接收能力正常; 第三次客户端把ack报文发送给服务器后,服务器就可以知道自己的发送,接收能力和对方的发送接收能力正常.

3. 三次握手过程中可以协商一些必要的参数

通信是客户端服务器两方共同的事情,其中的一些内容需要一致.而TCP中的很多参数是需要进行商讨的,一般都是在"选项"这个部分来体现的.这里面有一个很重要的就是TCP通信的起始序号. TCP一次通信过程中序号并不是从0开始计算的.而是会随机选择一个数,从这个数开始计算. 所有就算是同一个客户端和服务器,每次连接,开始的序号都不一样.(这里设计的初衷就是为了避免一种情况: 第一次连接的数据在传输的过程中堵车了,而这个过程中连接断开了. 后面又来了第二次连接, 数据报是按照ip和端口来识别的.当它发现这个第二次服务器的ip和端口号都一样时就会进去,但是这个时候早就是物是人非了. 这个数据不需要了,就需要进行丢弃. 而识别出这个要丢弃的数据就可以通过序号来区分了. 不同连接的序号就会差别很大,一眼就可以看出)

 断开连接

连接本质就是保存双方的信息,而数据多了就会用上数据结构. 而断开连接的目的就是将对端的信息从数据结构中删除/释放掉.

四次挥手

我们这里的四次挥手和三次握手不一样,三次握手是客户端需要先主动.而四次挥手可以先是客户端也可以是服务器. 这里调用socket.close() 就会触发FIN断开连接.如果进程结束(文件操作符表销毁了),也会触发FIN或者关闭socket文件.这里假设是客户端主动断开连接.

第一次挥手: 客户端从代码层面调用close()来断开连接,而内核中就会向服务器发送fin结束报文端.

第二次挥手: 服务器这端收到后内核中就会发送一份ack应答报文段告诉客户端它知道了.

第三次挥手: 服务器这端收到断开连接后就会先在代码层面调用close来断开连接,而内核中就会向客户端发送fin结束报文段.

第四次挥手: 当客户端收到服务器的fin报文段后在内核中就会发送一份ack告诉服务器它知道了.

这时连接就算真正断开了.

【JavaEE】UDP协议与TCP协议_第7张图片

三次挥手与四次挥手相同之处和区别 

相同: 都是通信双方给各自发起一个syn/fin报文段,各自在给对方返回ack报文段. 数据传输的顺序都一样. 

不同: 三次握手中间两次一定是可以合并的.(它们都是内核中收到syn之后就会立刻触发,这两个数据的触发,和代码层面无关.);  而四次挥手则不一定.(中间的ack报文段在收到FIN后就立刻发出了,而服务器的fin需要先在代码层面执行到close(),后再在内核中发送FIN报文段,这中间就可能会有比较久的时间间隔)  且三次握手一定得是客户端主动,而四次挥手可以是客户端也可以是服务器.

TCP状态

TCP状态就是用来描述这个TCP当前在干吗,处在哪个区域.

【JavaEE】UDP协议与TCP协议_第8张图片

LISTEN状态

表示服务器这边已经就绪,创建好了serverSocket,且也将端口号绑定好了. 就相当于手机充满电了,信号良好,随时可以有人打电话过来.可以直接接听.(我们可以在cmd中使用netstat -ano | findstr 端口号来查看)

ESTABLISHED状态

表示已确立的, 客户端和服务器已经建立连接完毕.相当于有人打电话,双方电话都打通了,都可以说话了.

【JavaEE】UDP协议与TCP协议_第9张图片

CLOSE_WAIT状态

表示接下来的代码需要调用close来发起fin. 这个状态是在收到对方的fin后才会进入的.

TIME_WAIT状态

表示本端发起断开连接(FIN)后,对端也给本端发送了FIN.这个时候就会进入TIME_WAIT状态.这里的作用就是给ACK重传留有一定的时间. 这个状态存在的意义就是防止最后一个ack丢包. 如果TIME_WAIT这个环节把TCP连接释放掉(保存对端信息的数据结构被销毁),这时要是ack丢包服务器就会重传FIN,而重传后FIN的ack就无法被返回了. 这里TIME_WAIT也是有时间的等待.最多就是2MSL, 客户端要是等待一个时间内没有重传,也就大概率意味着不会重传了.

【JavaEE】UDP协议与TCP协议_第10张图片

 滑动窗口

在确认应答机制下,每次发送方收到一个ack才会发送下一个数据.这样就会导致大量的时间都消耗在等待ack上了.而这里的滑动窗口就是为了解决这个问题的.滑动窗口就会说可以保证可靠传输的基础上,提高效率. 

它和之前相比,就是连续发送了一定的数据后,再来等ack.把多次请求的等待时间,使用了同一份时间来等待了,这样就减少了多次等待的时间.

【JavaEE】UDP协议与TCP协议_第11张图片

假设这里一次发送4个数据,就会有4个对应的ack,这4个有先有后,只要这里回来一个ack,服务器就再发送一个数据,这样回一个ack就发一个数据的情况加快数据后就像一个窗口了.且这里不管是回来哪个ack都会往后发送一个数据.

【JavaEE】UDP协议与TCP协议_第12张图片

但这里就会发生一些不可靠的情况,那滑动窗口是怎么解决的呢?

ack丢包

 这里假设有1 - 6000的数据我们分3段为一个窗口发送,这里如果其中一个ack丢了,是不用处理的,对可靠性是没有影响的.因为我们的确认序号中会告诉对端当前接受到哪部分数据了,你只需要将序号后面的数据传过来即可,因为接收缓冲区中是顺序存储的. 这里就算是后发先至的数据也是一样的.只需要将后面的数据发送过来即可,还是因为在接受缓冲区中会进行顺序存储,就算你先发的数据后面到也会将你排到前面去.

数据丢包

 当数据丢包后,这里就必须进行重传了,这里的重传叫做快速重传.假设这里有1  - 10000字节的数据需要传送,当1001 - 2000的数据丢包后,这时就算你收到了其他数据,返回的ack里还是1001.要是它一直没收到丢掉的数据包就会一直返回1001的ack. 在发送方多次收到索要1001的ack后,就会认为是1001的数据丢包了,就会进行重传. (这里的反复索要,就是在给1001这个数据留有等待的时间,要是多次索要后还没有到就相当于超时时间的判定了) 重传1001后,这时返回的确认序号就不是2001了,而是缓冲区中最大序号+1(这代表这之前的数据都收到了,因为接收缓冲区中是顺序存储的)

上面这种操作,整体的效率是非常高的.这里做到了针对性的重传,哪个丢了就重传哪个,已经收到的数据就不用重传了.这里就叫做快速重传.

【JavaEE】UDP协议与TCP协议_第13张图片

这里确认应答的超时重传和滑动窗口的快速重传是不冲突的,它们同时存在. 滑动窗口中也有确认应答,只不过把等待策略调整了一下,变成了批量的了. 如果发送的数据很少,就又会退化成确认应答.  

流量控制

这里,接受端的处理数据的能力是有限的,一但接收缓冲区被装满了,如果这个时候再发送数据过来.这些多出来的数据就会进行丢弃,这就产生了丢包.这就需要进行流量控制了.

TCP支持根据接受端的处理能力来决定发送端的发送数据的速度.在我们的16位窗口大小这里来存放窗口大小的信息,通过ack来反馈给发送方可以有多大的发送速度,这个字段在ack报文中才有意义.通过这个大小反馈给发送方接下来窗口要设置多少合适.(ACK中窗口大小值就是接收缓冲区剩余的空间大小). 这里如果接收缓冲区满了,窗口大小就会设置为0.发送端就会停止发送数据.不过发送端会定期发送一个探测窗口数据端给接受端,让接收端告诉发送端当前窗口大小.

真正的窗口大小其实不是64K,报头的选项中会包含一个参数,叫做窗口扩展因子,真正要设置的窗口大小是 16位窗口大小 * 2 ^ 窗口因子.

这里注意: 就算2001的ack包给发送主机收到了,里面显示接收缓冲区还有2000字节大小,这个时候发送端也不会发送数据了,因为除去收到的第一个ack应答报文,它还有两个数据给了接收端,这就代表知道这`两个数据到了接收端,接收端的缓冲区肯定就满了.

【JavaEE】UDP协议与TCP协议_第14张图片

 拥塞控制

拥塞控制和流量控制都是用来限制发送方发送数据的速率的. 流量控制是从接收方的角度来制约发送方的速率的,而拥塞控制是从通信路径网络情况来制约发送方的速率的. 这里如果发送方按照某个窗口大小发送数据后,出现了丢包,就认为中间的网络通道出现了拥堵,就会减少窗口大小. 如果没有出现丢包,就会视为中间的网络通道畅通,会加大窗口大小. 这里总的原则就是 流量控制和拥塞控制,谁产生的窗口小,就采用哪个窗口.

拥塞控制探测出窗口大小具体来说一般有以下几步:

第一步: 慢启动. 刚开始传输的数据,速率是比较小的,窗口大小也就比较小.这是因为网络的拥堵情况不知道,要是一开始就传输大量的数据,可能就会让本来就拥堵的网络带宽雪上加霜.

第二步: 如果上面这种情况没有出现丢包,这就说明网络还是良好的,就可以开始增大窗口了. 这时增大方式就是按照指数方式来进行增长的(原窗口 * 2) (这种情况的原因就是由于一开始的时候窗口很小,但是发现网络很通畅后可以通过这种方式来快速将窗口扩大,这样就可以保证传输的效率)

第三步: 上面的指数增长是不会一直进行下去的,如果一直进行下去因为增长太快,可以就会一下造成网络拥堵.它会有一个阈值,当窗口大小达到阈值时,这里指数增长就会变成线性增长. 线性增长就可以让当前窗口持久保持一个在比较高的速率下传输数据,且也不会一下就造成丢包.

第四步: 上述这种情况窗口也是在增长,积累到一定量后,传输速率也会变的很快,还是会引起丢包.这里一但出现丢包,就会将窗口大小重置,回到最开始的慢启动过程(这时又要开始从第一步到第四步不断循环). 且这里也会根据刚才丢包时窗口大小来重新设置指数增长到线性增长的阈值. (经典模式)

【JavaEE】UDP协议与TCP协议_第15张图片

延时应答

它也是基于滑动窗口来提高效率的. 这里结合滑动窗口,流量控制,再通过延时应答ack的方式,就可以把反馈的窗口大小再搞大一点.

它的做法就是接收方收到数据后不会立刻返回ack,而是会等待一会,等一会再返回ack.而这一会,就会给接收方应用程序更多时间来消化接收缓冲区的数据.

【JavaEE】UDP协议与TCP协议_第16张图片

延时应答的延时方式:

它有两种方式,一种是和时间有关,一种是和ack的数量有关. 一般每隔几个数据就返回一个ack,这样就可以起到延时应答的效果,也可以减少ack数量,起到节省开销的效果.但是它也会和时间相关,就算你的数量没到,但是时间到了的话也会返回ack.

捎带应答

这是基于延时应答引入的机制,也可以提高传输的效率.这里捎带应答就是尽可能的把可以合并的数据包合并起来,通过减少数据包的方式来提高效率.

这里我们要注意,很多时候客户端和服务器都长链接,在捎带应答的加持下,后续每次在返回响应数据的时候,可能就会触发捎带应答,就会将ack和业务数据合二为一. 但是!!!不是每次都会触发. 如果下一个数据超过了延时应答的延时时间,就不会触发.

这里,因为延时应答+捎带应答. 可能就会让后续的四次挥手,合并成三次

【JavaEE】UDP协议与TCP协议_第17张图片

面向字节流

在TCP中,是面向字节流传输数据的,当传输的数据到达接收方后,接收方就会根据Socket API来read数据.但时因为read过程非常的灵活,因为是(读字节),这可能就会在应用层中区别不出来当前的数据从哪里到哪里是一个完整的应用数据包. 

【JavaEE】UDP协议与TCP协议_第18张图片上面这种情况就叫做粘包. 解决粘包问题的关键就是明确包与包之间的边界.这里有两种常用的方法:

1. 通过特殊符号,作为分隔符. 见到分隔符就代表一个包结束了.(这里使用任意字符作为分隔符都可以,但是要保证它在正式数据中不会存在)

2. 指定包的长度. 比如可以在包开始的位置,加上一个特殊的空间来表示整个数据的长度.

在之前介绍的常用应用层协议的格式 xml,json,Protobuffer等都可以处理好粘包问题.

异常情况处理

1. 有一方进程崩溃:

这里,进程不管是正常结束,还是异常崩溃,都会触发到回收文件资源,关闭文件,这是系统自动完成的.这就会触发四次挥手. TCP连接的生命周期,可以比进程更长一些,虽然进程已经退出了,但是TCP连接还在,任然可以进行四次挥手.(这里虽然是异常崩溃,但实际上和正常的四次挥手没有什么区别,进程虽然没有了,但是通过系统中仍然会持有连接信息,可以完成后续的挥手过程的)

2. 有一方出现了正常关机

当有主机触发了关机,就会先强制终止所有的进程.终止进程自然就会触发4次挥手,虽然说4次挥手可能还米有完,系统就关闭了.但是至少会将第一个fin发送给对方,告诉对端这里要结束了. 对端收到fin后,也会进入释放连接的过程,返回ack,并发送fin,这里fin后就不会再收到ack了 fin没有收到ack后,就会进行重传,要是重传几次后还是没有ack,就会单方面释放连接信息.

3. 有一方出现了断电,导致突然关机

这里如果突然断电,就会来不及发送fin. 这里就会有两种情况:

1) 断电的是接收方,发送方会突然发现,没有ack返回了,就会进行重传.重传几次后还是不行TCP就会尝试复位重连(这里就是清除原来TCP中的各种临时数据,重新开始,需要用到tcp的复位报文段RST).这时重置后还是不行就会单方面放弃连接.

2) 断电是发送方, 接收段就需要区分发送方式挂了还是暂时没法数据. 如果一段时间没有收到对方的信息,就会触发心跳包来询问对方的情况(这里心跳包一般都是自己设计一份,而不是使用TCP的,因为TCP的心跳包周期太久了). 如果对端没有心跳了(没响应),这里本端也会尝试复位,不成功就会单方面释放连接.

4. 网线断开

这里就相当于3中的第一种情况和第二种情况都会触发. 发送方会按照重传 - 复位连接 - 释放连接来进行.接收方会按照 发送心跳包 - 复位连接 - 释放连接来进行.

你可能感兴趣的:(#,JavaEE,JAVA,服务器,网络,java)