一、简单介绍一下OSI和TCP/IP结构和功能,分别用到哪些协议?

一般我们采用折中的思想,将计算系网络分为五层:

  • 应用层
  • 运输层
  • 网络层
  • 数据链路层
  • 物理层

1. 应用层

  • **应用层(application-layer)的任务是通过应用进程间的交互来完成特定网络应用。**应用层协议定义的是应用进程(进程:主机中正在运行的程序)间的通信和交互的规则。

常用的协议有

  • 域名系统DNS协议
  • 支持万维网应用的 HTTP协议
  • 支持电子邮件的 SMTP协议

简单介绍一下DNS协议

  • 负责将IP地址和域名相互映射,这样人们不需要记住复杂的IP地址,而只需要记住方便记忆的域名即可

简单介绍一下Http协议

  • 所有的 WWW(万维网) 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。

  • 支持客户/服务器模式,客户端可以发送请求,服务器会相应请求并返回数据

2. 运输层

  • 运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务

常用协议

  • 传输控制协议 TCP:面向连接的、可靠的
  • 用户数据协议 UDP:面向无连接的、不可靠的

3. 网络层

  • 网络层的任务就是选择合适的网间路由和交换结点, 确保数据及时传送。

常用协议

  • IP协议

4. 数据链路层

  • 两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。

5. 物理层

  • 在物理层上所传送的数据单位是比特。 物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。

二、TCP 三次握手和四次挥手

1. 为什么要三次握手?

  • 三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。

  • 第一次握手:建立连接时,客户端发送SYN包(syn=j)到服务器,并进入SYN_ SEND状态,等待服务器确认;

  • 第二次握手:服务器收到SYN 包,必须确认客户的SYN(ack=j+1) ,同时自己也发送一个SYN包(syn=k) ,即
    SYN+ACK包,此时服务器进入SYN_RECV状态:

  • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

目的是:为了初始化Sequence Number的初始值,同时保证建立可靠的连接

SYN和ACK中的Sequence Number初始化使用了随机值,而每个包都有个序号,超时重传机制根据这个序号来判断确认收到了是哪个包。在三次握手的时候,告诉了对方初始化的序号是多少,这样就可以根据序号对应接下来哪些包收到了。

首次握手的隐患—SYN超时

  • Server收到Client的SYN ,回复SYN-ACK的时候未收到ACK确认
  • Server不断重试直至超时, Linux默认等待63秒才断开连接
  • 可能会遭到SYN Flood攻击,即恶意用户发送一个SYN后,就下线,让服务端等到63秒,最后导致SYN等待队列被占满,无法和正常的用户建立连接

针对SYN Flood的防护措施

  • SYN队列满后,通过tcp_syncookies参数回发SYN Cookie
  • 若为正常连接则Client会回发SYN Cookie ,直接根据cookie建立连接,不会因为队列满而无法建立连接;如果是恶意用户,则不会发送Cookie

建立连接后, Client出现故障怎么办?

  • 保护机制:向对方发送保活探测报文,如果未收到响应则继续发送,尝试次数达到保活探测数仍未收到响应则中断连接

下面是三次握手的含义

  • 第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常

  • 第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常

  • 第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常

SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement)消息响应。这样在客户机和服务器之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务器之间传递。

2. 为什么要四次挥手?

  • 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送
  • 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号
  • 服务器-关闭与客户端的连接,发送一个FIN给客户端
  • 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1

举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束

服务器出现大量CLOSE_WAIT状态的原因?

  • 可能浏览器发送关闭请求后,服务器在忙于读写
  • 也可能代码出现了问题,特别是释放资源的代码忘记释放了资源

三、TCP和UDP的区别

  • TCP是面向连接,需要通过三次握手建立可靠连接,而UDP是面向无连接的
  • 可靠性:TCP有ARQ协议、超时重传等,可以保证其可靠性,而UDP没有这些东西,只是尽最大努力交付,不保证可靠交付
  • 有序性:TCP利用序列号可以保证其有序性,到达可能无序,最后会进行排序,而UDP无法保证有序性
  • 速度:TCP比较复杂所以比减慢,UDP比较快
  • 量级:TCP报文是20个字节,UDP报文是8个字节

UDP 在传送数据之前不需要先建立连接,远地主机在收到 UDP 报文后,不需要给出任何确认。虽然 UDP 不提供可靠交付,但在某些情况下 UDP 确是一种最有效的工作方式(一般用于即时通信),比如: QQ 语音、 QQ 视频 、直播等等

TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。

四、TCP 协议如何保证可靠传输

  1. 应用数据被分割成 TCP 认为最适合发送的数据块。
  2. TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
  3. 校验和: TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
  4. TCP 的接收端会丢弃重复的数据。
  5. 流量控制: TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制)
  6. 拥塞控制: 当网络拥塞时,减少数据的发送。
  7. ARQ协议: 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
  8. 超时重传: 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。

4.1 连续ARQ协议

对于ARQ协议,如果每个数据报都发送确认收到,将十分消耗网络资源,在此问题上,提出了连续ARQ协议:

  • 连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。

4.2 滑动窗口和流量控制

  • RTT:发送一个数据包到收到对应的ACK ,所花费的时间

  • RTO:重传时间间隔,将根据RTT进行改变

滑动窗口的作用

  • 保证TCP的可靠性:采用ARQ协议和超时重传机制保证可靠性

  • 实现流量控制:TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。

4.3 拥塞控制

  • 当网络比较拥堵时,会控制发送数据的速度,防止发送较大的文件导致网络瘫痪

  • 为了进行拥塞控制,TCP 发送方要维持一个 拥塞窗口(cwnd) 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。

有四种算法

  • 慢开始: 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd初始值为1,每经过一个传播轮次,cwnd加倍。
  • 拥塞避免: 拥塞避免算法的思路是让拥塞窗口cwnd缓慢增大,即每经过一个往返时间RTT就把发送放的cwnd加1.
  • 快重传与快恢复: 在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 FRR,如果数据包丢失了,TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段。有了 FRR,就不会因为重传时要求的暂停被耽误。 当有单独的数据包丢失时,快速重传和恢复(FRR)能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时,它则不能很有效地工作。

五、 在浏览器中输入url地址 ->> 显示主页的过程

总体来说分为以下几个过程:

  1. 浏览器解析域名查找IP地址,使用的协议是DNS协议,DNS过程为:浏览器缓存、路由器缓存、DNS缓存
  2. 与服务器三次握手建立TCP连接(在建立TCP协议时,需要发送数据,需要使用IP协议、IP数据包在路由器之间传输,路由器的选择使用的OSPF协议、路由器和服务器通信时,需要将IP地址转换成MAC地址,需要用到ARP协议)
  3. 向服务器发送HTTP请求
  4. 服务器处理请求并返回HTTP响应
  5. 可能会释放TCP连接,这取决于HTTP版本
  6. 浏览器解析渲染页面
  7. 连接结束

六、状态码

1XX 信息

  • 100 Continue :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应

2XX 成功“:

  • 200 OK
  • 204 No Content :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。
  • 206 Partial Content :表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容。

3XX 重定向

  • 301 Moved Permanently :永久性重定向
  • 302 Found :临时性重定向
  • 303 See Other :和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源。
    注:虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法,但是大多数浏览器都会在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。
  • 304 Not Modified :如果请求报文首部包含一些条件,例如:If-Match,If-Modified-Since,If-NoneMatch,If-Range,If-Unmodified-Since,如果不满足条件,则服务器会返回 304 状态码。
  • 307 Temporary Redirect :临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的
    POST 方法改成 GET 方法。

4XX 客户端错误

  • **400 Bad Request :**请求报文中存在语法错误。
  • **401 Unauthorized :**该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
  • 403 Forbidden :请求被拒绝,比如说IP被禁止
  • 404 Not Found:可能输入错误了URL

5XX 服务器错误

  • **500 Internal Server Error :**服务器正在执行请求时发生错误。
  • **503 Service Unavailable :**服务器暂时处于超负载或正在进行停机维护,现在无法处理请求,可能连接池满了或者服务器宕机

九、各协议与HTTP协议的关系

十、HTTP长连接和短连接

在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。

而从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码:

Connection:keep-alive

在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。

十一、Cookie的作用是什么?和Session有什么区别?

Cookie 和 Session都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。

Cookie 一般用来保存用户信息 比如①我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;②一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③登录一次网站后访问网站其他页面不需要重新登录。Session 的主要作用就是通过服务端记录用户的状态。 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。

Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。

Cookie 存储在客户端中,而Session存储在服务器上,相对来说 Session 安全性更高。如果要在 Cookie 中存储一些敏感信息,不要直接写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。

十二、HTTP 1.0和HTTP 1.1的主要区别是什么?

  1. 长连接 : 在HTTP/1.0中,默认使用的是短连接,也就是说每次请求都要重新建立一次连接。HTTP 是基于TCP/IP协议的,每一次建立或者断开连接都需要三次握手四次挥手的开销,如果每次请求都要这样的话,开销会比较大。因此最好能维持一个长连接,可以用个长连接来发多个请求。HTTP 1.1起,默认使用长连接 ,默认开启Connection: keep-alive。 HTTP/1.1的持续连接有非流水线方式和流水线方式 。流水线方式是客户在收到HTTP的响应报文之前就能接着发送新的请求报文。与之相对应的非流水线方式是客户在收到前一个响应后才能发送下一个请求。
  2. 错误状态响应码 :在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
  3. 缓存处理 :在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
  4. 带宽优化及网络连接的使用 :HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。

十三、URI和URL的区别是什么?

  • URI(Uniform Resource Identifier) 是统一资源标志符,可以唯一标识一个资源。
  • URL(Uniform Resource Location) 是统一资源定位符,可以提供该资源的路径。它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。

十四、HTTP 和 HTTPS 的区别?

如果有恶意的软件劫持了用户发送的请求,最后模仿服务器向浏览器发送响应,浏览器是无法识别的,这就是劫持。

  1. 端口 :HTTP的URL由“http://”起始且默认使用端口80,而HTTPS的URL由“https://”起始且默认使用端口443。

  2. HTTPS需要到CA申请证书,HTTP不需要

  3. 安全性和资源消耗

    HTTP协议运行在TCP之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS是运行在SSL/TLS之上的HTTP协议,SSL/TLS 运行在TCP之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。所以说,HTTP 安全性没有 HTTPS高,但是 HTTPS 比HTTP耗费更多服务器资源。

    • 对称加密:密钥只有一个,加密解密为同一个密码,且加解密速度快,典型的对称加密算法有DES、AES等;
    • 非对称加密:密钥成对出现(且根据公钥无法推知私钥,根据私钥也无法推知公钥),加密解密使用不同密钥(公钥加密需要私钥解密,私钥加密需要公钥解密),相对对称加密速度较慢,典型的非对称加密算法有RSA、DSA等。
    • 哈希算法:将任意长度的信息转换为固定长度的值,算法不可逆,比如说MD5算法

SSL(Security Sockets Layer ,安全套接层)

  • 为网络通信提供安全及数据完整性的一-种安全协议

  • 是操作系统对外的API , SSL3.0后更名为TLS

  • 采用身份验证和数据加密保证网络通信的安全和数据的完整性

HTTPS真的很安全吗?

  • 浏览器默认填充http:// ,请求到服务器后需要进行https的跳转,在建立起HTTPS连接之前存在一次明文的HTTP请求和重定向,有被劫持的风险
  • 可以使用HSTS ( HTTP Strict Transport Security )优化

HSTS的作用是强制客户端(如浏览器)使用HTTPS与服务器创建连接。 HSTS最为核心的是一个HTTP响应头(HTTP Response Header),正是它可以让浏览器得知,在接下来的一段时间内,当前域名只能通过HTTPS进行访问,并且在浏览器发现当前连接不安全的情况下,强制拒绝用户的后续访问要求。

比如设置在第一次HTTP连接中,得知要在接下来1年内在个对于当前域名及其子域名的后续通信应该强制性的只使用HTTPS,直到超过有效期为止。

同时,第一次的HTTP连接也可能会被劫持,所以浏览器中可以内置一个列表,放入那些必须使用https的域名,保证安全性

十五、HTTP/2.0

HTTP/1.x 缺陷,HTTP/1.x 实现简单是以牺牲性能为代价的

  • 客户端需要使用多个连接才能实现并发和缩短延迟;
  • 不会压缩请求和响应首部,从而导致不必要的网络流量;
  • 不支持有效的资源优先级,致使底层 TCP 连接的利用率低下。

二进制分帧层

  • HTTP/2.0 将报文分成 HEADERS 帧和 DATA 帧,它们都是二进制格式的。

服务端推送

  • HTTP/2.0 在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 page.html 页面,服务端就把 script.js 和 style.css 等与之相关的资源一起发给客户端。

首部压缩

  • HTTP/1.1 的首部带有大量信息,而且每次都要重复发送。
  • HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见过的首部字段表,从而避免了重复传输。
    不仅如此,HTTP/2.0 也使用 Huffman 编码对首部字段进行压缩。

十六、GET和POST的比较

  • 作用不同:GET 用于获取资源,而 POST 用于传输实体主体。
  • 参数不同:GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,URL长度可能会受到浏览器的限制,而 POST 的参数存储在实体主体中,长度没有限制。不能因为 POST 参数存储在实体主体中就认为它的安全性更高,因为照样可以通过一些抓包工具(Fiddler)查看。
  • 安全和幂等性:安全的 HTTP 方法不会改变服务器状态,也就是说它只是可读的,而幂等性指同样的请求被执行一次与连续执行多次的效果是一样的 。GET 方法是安全的也是幂等的,而 POST 却不是,因为 POST 的目的是传送实体主体内容,这个内容可能是用户上传的表单数据,上传成功之后,服务器可能把这个数据存储到数据库中,因此状态也就发生了改变。
  • 缓存:GET请求可以被缓存、被存储,保存在浏览器上,而POST是非幂等的,所以必须交由服务器处理

十七、Socket简介

  • Socket是对TCP/IP协议的抽象,是操作系统对外开放的接口

1. Socket的通讯流程

2. 利用Java实现Socket实现TCP和UDP

TCPServer

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //创建socket,并将socket绑定到65000端口
        ServerSocket ss = new ServerSocket(65000);
        //死循环,使得socket一直等待并处理客户端发送过来的请求
        while (true) {
            //监听65000端口,直到客户端返回连接信息后才返回
            Socket socket = ss.accept();
            //获取客户端的请求信息后,执行相关业务逻辑
            new LengthCalculator(socket).start();
        }
    }
}

LengthCalculator

public class LengthCalculator extends Thread {
    private Socket socket;

    public LengthCalculator(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //获取socket的输出流
            OutputStream os = socket.getOutputStream();
            //获取socket的输入流
            InputStream is = socket.getInputStream();
            int ch;
            byte[] buff = new byte[1024];
            //buff主要用来读取输入的内容,存成byte数组,ch主要用来获取读取数组的长度
            ch = is.read(buff);
            //将接收流的byte数组转换成字符串,这里获取的内容是客户端发送过来的字符串
            String content = new String(buff, 0, ch);
            System.out.println(content);
            //往输出流里写入获得的字符串的长度,回发给客户端
            os.write(String.valueOf(content.length()).getBytes());
            //不要忘记关闭输入输出流以及socket
            is.close();
            os.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

TCPClient

public class TCPClient {
    public static void main(String[] args) throws IOException {
        //创建socket,并指定连接的是本机的端口号为65000的服务器socket
        Socket socket = new Socket("127.0.0.1", 65000);
        //获取输出流
        OutputStream os = socket.getOutputStream();
        //获取输入流
        InputStream is = socket.getInputStream();
        //将要传递给server的字符串参数转换成byte数组,并数组写入到输出流中
        os.write("hello world".getBytes());
        int ch;
        byte[] buff = new byte[1024];
        //buff主要用来读取输入的内容,存成byte数组,ch主要用来获取读取数组的长度
        ch = is.read(buff);
        //将接收流的byte数组转换成字符串,这里是从服务端回发回来的字符串参数的长度
        String content = new String(buff, 0, ch);
        System.out.println(content);
        //不要忘记关闭输入输出流以及socket
        is.close();
        os.close();
        socket.close();
    }
}

UDPServer

public class UDPServer {
    public static void main(String[] args) throws IOException {
        //服务端接受客户端发送的数据报
        DatagramSocket socket = new DatagramSocket(65001); //监听的端口号
        byte[] buff = new byte[100]; //存储从客户端接受到的内容
        DatagramPacket packet = new DatagramPacket(buff, buff.length);
        //接受客户端发送过来的内容,并将内容封装进DatagramPacket对象中
        socket.receive(packet);
        byte[] data = packet.getData(); //从DatagramPacket对象 中获取到真正存储的数据
        //将数据从二进制转换成字符串形式
        String content = new String(data, 0, packet.getLength());
        System.out.println(content);
        //将要发送给客户端的数据转换成二进制
        byte[] sendedContent = String.valueOf(content.length()).getBytes();
        //服务端给客户端发送数据报
        //从DatagramPacket对象中获取到数据的来源地址与端口号
        DatagramPacket packetToClient = new DatagramPacket(sendedContent,
                sendedContent.length, packet.getAddress(), packet.getPort());
        socket.send(packetToClient); //发送数据给客户端
    }
}

UDPClient

public class UDPClient {
    public static void main(String[] args) throws IOException {
        //客户端发数据报给服务端
        DatagramSocket socket = new DatagramSocket();
        //要发送给服务端的数据
        byte[] buf = "Hello World".getBytes();
        //将IP地址封装成InetAddress对象
        InetAddress address = InetAddress.getByName("127.0.0.1");
        //将要发送给服务端的数据封装成DatagramPacket对象需要填写上ip地址与端口号
        DatagramPacket packet = new DatagramPacket(buf, buf.length, address,
                65001);
        //发送数据给服务端
        socket.send(packet);
        //客户端接受服务端发送过来的数据报
        byte[] data = new byte[100];
        //创建DtagramPacket对 象用来存储服务端发送过来的数据
        DatagramPacket receivedPacket = new DatagramPacket(data, data.length);
        //将接受到的数据存储到DatagramPacket对象中
        socket.receive(receivedPacket);
        //将服务器端发送过来的数据取出来并打印到控制台
        String content = new String(receivedPacket.getData(), 0,
                receivedPacket.getLength());
        System.out.println(content);
    }
}