---
url: /doc/Java/je5yltv3/index.md
---
网络通信：两台设备之间通过网络实现数据传输。

`java.net` 包下提供了一系列类或接口，供程序员使用，完成网络通信

## 19.1 网络的相关概念

**网络**

网络：两台或多台设备通过一定物理设备连接起来构成了网络

根据网络覆盖范围的不同，对网络进行分类：

* 局域网：覆盖范围最小，仅覆盖一个教室·机房
* 城域网：覆盖范围较大，可覆盖一个城市
* 广域网：覆盖范围最大，可以覆盖全国，甚至全球。万维网 是广域网的代表

**IP 地址**

IP 地址：用于唯一标识网络中的每台计算机 / 主机

查看 IP 地址：`ipconfig`

IPv4 是 4 个字节（32 位）表示。每个字节范围是 \[0,255]

IP 地址的表示形式：点分十进制（xx.xx.xx.xx），每个十进制数范围是 \[0,255]

IP 地址的组成 = 网络地址 + 主机地址

* A 类：0 + 7 位网络号 + 24 位主机号（0.0.0.0 ~ 127.255.255.255）
* B 类：1 + 0 + 14 位网络号 + 16 位主机号（128.0.0.0 ~ 191.255.255.255）
* C 类：1 + 1 + 0 + 21 位网络号 + 8 位主机号（192.0.0.0 ~ 223.255.255.255）
* D 类：1 + 1 + 1 + 0 + 28 位多播组号（224.0.0.0 ~ 239.255.255.255）
* E 类：1 + 1 + 1 + 1 + 0 + 27 位（留待后用）（240.0.0.0 ~ 247.255.255.255）

IPv6 是互联网工程任务组设计的用于替代 IPv4 的下一代 IP 协议。其地址数量可以为全世界每一粒沙子编上一个地址

IPv4 最大的问题在于网络地址资源有限，严重制约了互联网的应用和发展。IPv6 的使用，不仅能解决网络地址资源数量的问题，也解决了多种接入设备接入互联网的障碍

IPv6 使用 16 个字节（128 位）表示地址。

表示形式有：

* 冒分十六进制表示法： （X:X:X:X:X:X:X:X）

  `:` 之间的部分，出现 0 开头的场合，那些 0 可以省略

* 0 位压缩表示法：把连续的 `0` 压缩为 `::`，这个压缩只能出现一次（X::X:X）

* 内嵌 IPv4 地址表示法：前 96 位 用冒分十六进制表示，后面 32 位 用 IPv4 的点分十进制（X:X:X:X:X:XX:d.d.d.d）

**子网掩码**

只用一个 IP 地址，无法分辨网络部分与主机部分的分界线。因此，使用子网掩码来表示分界线。

这个场合，对应的网络部分的子网掩码的二进制数字设为 1

[![img](https://i-melody.github.io/img/Java_InputImage/子网掩码图_19.1.webp)](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==)
*(子网掩码图\_19.1)*

此外，还能把子网掩码与 IP 地址组合

* 在 IP 地址后加斜线及网络部分二进制数字数（IPV4）：192.168.15.1/16
* IPv6：X:X:X:X:X:X:X:X/64

通过更改子网掩码，可以细分网络为多个子网。

[![img](https://i-melody.github.io/img/Java_InputImage/子网图_19.1.webp)](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==)
*(子网图\_19.1)*

**保留地址**

IP 还定义了一套特殊的地址格式，称为保留地址，这些保留地址不分配给任何主机。

|    网络号    | 主机号 |   地址类型   |      举例       |       用途       |
| :----------: | :----: | :----------: | :-------------: | :--------------: |
|     全 0     |  全 0  |   本机地址   |     0.0.0.0     |    启动时使用    |
|     任意     |  全 0  |   网络地址   |    61.0.0.0     |   标识一个网络   |
|     任意     |  全 1  | 直接广播地址 | 129.21.255.255  | 在特定网络上广播 |
|     全 1     |  全 1  | 有线广播地址 | 255.255.255.255 |  在本网段上广播  |
| 第一段为 127 |  任意  |   回送地址   |    127.0.0.1    |       测试       |

**私有地址**

私有地址：与 IP 地址（全局地址）相比，在不同的网络中可以重复的地址。

私有地址是以下范围中的地址。这些地址不能作为全局地址使用：

* 10.0.0.0 ~ 10.255.255.255
* 172.16.0.0 ~ 172.31.255.255
* 192.168.0.0 ~ 192.168.255.255

将私有地址连接到全局地址的方法：

* NAT：一种私有地址与全局地址一一对应的机制
* NAPT：一种用一个全局地址连接多个计算机的机制

**域名**

示例：http://bbs.tianya.cn/post-house-252774-1.shtml

为了方便记忆，解决记忆 IP 的困难

IP 地址根据 HTTP 协议 映射成域名

通过 DNS（Domain Name System）服务将域名转化为 IP 地址

**端口号**

用于标识计算机上某个特定的网络程序

表示形式：以整数形式，范围 \[0,65535]

\==0 ~ 1024 已经被占用，不要使用。比如 ssh 22、ftp 21、smtp 25、http 80==

\==常见的网络程序端口号：==

* tomcat：8080
* mysql：3306
* oracle：1521
* sqlserver：1433

**网络通信协议**

协议（TCP/IP）

TCP/IP：传输控制协议 / 因特网互联协议（Transmission Control Protocol / Internet Protocol），又叫 网络通讯协议。这个协议是 Internet 最基本的协议、Internet 国际互联网络的基础。简单来讲，就是由 网络层的 IP 协议 和传输层的 TCP 协议 组成

[![img](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==)](https://i-melody.github.io/img/Java_InputImage/数据封装图_19.1.webp)

*(数据封装图\_19.1)*

| OSI 模型（理论） | TCP/IP 模型（实际使用） | TCP/IP 模型各层对应协议  |
| ---------------- | ----------------------- | ------------------------ |
| 应用层           | 应用层                  | HTTP、ftp、telent、DNS…… |
| 表示层           | 应用层                  | 同上                     |
| 会话层           | 应用层                  | 同上                     |
| 传输层           | 传输层（TCP）           | TCP、UDP……               |
| 网络层           | 网络层（IP）            | IP、ICMP、ARP……          |
| 数据链路层       | 物理 + 数据链路层       | Link                     |
| 物理层           | 物理 + 数据链路层       | 同上                     |

### 19.1.1 TCP 和 UDP

**TCP**

传输控制协议

1. 使用 TCP 协议前，须先建立 TCP 连接，形成传输数据通道。TCP 通信是一对一通信
2. 传输前，采用 “三次握手” 方式，==是可靠的==
3. TCP 协议进行通信的两个应用进程：客户端、服务端
4. 在连接中可进行大数据量的传输。传输前，先确认要交流的数据量。那个数据量、数据窗口取较小方的数值。
5. 发送方没有收到接收方的确认应答时，（在一定次数内）会再次发送数据包
6. 传输完毕，需释放已建立的连接，效率低

**UDP**

用户数据协议

1. 将 数据、源、目的 封装成数据包，不需要建立连接。可以同时向多个接收方发送
2. \==每个数据包大小限制在 64K 以内，不适合传输大量数据==
3. 因无需连接，所以==是不可靠的==
4. 接收方无需发送确认应答
5. 发送数据结束时无需释放资源（因为不是面向连接的），速度快

## 19.2 `InetAddress` 类

**相关方法**

* `getLocalHost`：获取本机 `InetAddress` 对象 //静态方法
* `getByName`：根据指定主机名 / 域名获取 IP 地址对象 //静态方法
* `getHostName`：获取 `InetAddress` 对象的主机名/域名
* `getHostAddress`：获取 `InetAddress` 对象的地址

## 19.3 Socket

1. 套接字（Socket）开发网络应用程序被广泛采用，以至于成为了事实上的标准
2. 通信的两端都要有 Socket，是两台机器间通信的端点
3. 网络通信其实就是 Socket 间的通信
4. Socket 允许程序把网络连接当成一个流，数据在两个 Socket 间通过 IO 传输
5. 一般主动发起通信的应用程序属于客户端，等待通信请求的为服务端

### 19.3.1 TCP 网络通信编程

1. 基于客户端——服务端的网络通信
2. 底层使用的是 TCP / IP 协议
3. 应用场景距离：客户端发送数据，服务端接收并显示
4. 基于 Socket 的 TCP 编程

**下面，示范一个 服务端**

```java
public void server() throws IOException{
 ServerSocket serverSocket = new ServerSocket(9000);		//[1]
 Socket clientSocket = serverSocket.accept();	   			//[2]
 //下面是输入流，不解释了
 InputStream inputStream = clientSocket.getInputStream();
 System.out.println(clientSocket.getInetAddress());
 int n;
 byte[] b = new byte[1024];
 byte[] B = new byte[0];
 while ((n = inputStream.read(b, 0, 1024)) != -1) {
     B = Arrays.copyOf(B, B.length + n);
     for (int i = 0; i < n; i++) {
         B[B.length - n + i] = b[i];
     }
 }

 System.out.println(new String(B));
 InputStream.close();
 Socket.close();
 serverSocket.close();					//[3]
}
```

1. `ServerSocket serverSocket = new ServerSocket(9000);`

这个语句用以监听 9000 这个端口，且==只会监听一次==

如果想要一直保持监听状态,像下面的案例，==将会一直在设置的端口监听，每次循环都会返回一个 socket。==

```java

while (true) {//不停监听9000接口
            System.out.println("服务端等待接收消息...");
            Socket socket = serverSocket.accept()

            ...				//对返回的socket进行处理
}
```

细节：这里要求该端口没有被其他服务占用。

2. `Socket clientSocket = serverSocket.accept();`

   \==这个语句用以接收连接的 `Socket`。没有连接时，程序会阻塞在这里。==

   \==细节：此处 `accept()` 可以返回多个 `Socket`，即多并发==

3. `serverSocket.close();`

   结束监听后，务必关闭！

**下面，示范一个客户端**

```java
public void client() throws IOException{
 String serverIP = "192.168.3.16";								//[1]
 Socket socket = new Socket(serverIP, 9000);					//[2]
 OutputStream outputStream = socket.getOutputStream();
 outputStream.write("hello,Server!".getBytes(StandardCharsets.UTF_8));
 socket.shutdownOutput();										//[3]
 outputStream.close();
 socket.close();
}
```

1. 这个 IP 是我的本机地址。代表的是 服务端 地址

2. `Socket socket = new Socket(serverIP, 9000);`

   表示访问指定 IP 的 9000 端口

3. \==`socket.shutdownOutput();`==

   这里是输出一个结束标记。若不如此做，socket 就不知道是否数据发送完成

   \==特别的，由 字节流 输出的场合，`writer.newLine()` 可以替代结束标记，后面要使用`writer.flush()`。但是这个场合，接收必须是 读取单行`reader.readLine()`==,不支持 while 循环读取，while 读取场合还是需要使用==`socket.shutdownOutput();`==

案例：

1.编写一个服务端，和一个客户端 2.服务器端在 8888 端口监听 3.客户端连接到服务端，发送一张图片 eqie.png 4.服务器端接收到客户端发送的图片，保存到 sc 下，发送"收到图片"再退出 5.客户端接收到服务端发送的"收到图片"，再退出 6.该程序要求使用 StreamUtils..java

```java
public class TCPFileUploadClient {
    public static void main(String[] args) throws Exception {

        //客户端连接服务端 8888，得到Socket对象
        Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
        //创建读取磁盘文件的输入流
        //String filePath = "e:\\qie.png";
        String filePath = "e:\\abc.mp4";
        BufferedInputStream bis  = new BufferedInputStream(new FileInputStream(filePath));

        //bytes 就是filePath对应的字节数组
        byte[] bytes = StreamUtils.streamToByteArray(bis);

        //通过socket获取到输出流, 将bytes数据发送给服务端
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        bos.write(bytes);//将文件对应的字节数组的内容，写入到数据通道
        bis.close();
        socket.shutdownOutput();//设置写入数据的结束标记

        //=====接收从服务端回复的消息=====

        InputStream inputStream = socket.getInputStream();
        //使用StreamUtils 的方法，直接将 inputStream 读取到的内容 转成字符串
        String s = StreamUtils.streamToString(inputStream);
        System.out.println(s);


        //关闭相关的流
        inputStream.close();
        bos.close();
        socket.close();

    }
}
```

```java
public class TCPFileUploadServer {
    public static void main(String[] args) throws Exception {

        //1. 服务端在本机监听8888端口
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务端在8888端口监听....");
        //2. 等待连接
        Socket socket = serverSocket.accept();


        //3. 读取客户端发送的数据
        //   通过Socket得到输入流
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        byte[] bytes = StreamUtils.streamToByteArray(bis);
        //4. 将得到 bytes 数组，写入到指定的路径，就得到一个文件了
        String destFilePath = "src\\abc.mp4";
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
        bos.write(bytes);
        bos.close();

        // 向客户端回复 "收到图片"
        // 通过socket 获取到输出流(字符)
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        writer.write("收到图片");
        writer.flush();//把内容刷新到数据通道，对于字符流，一般写入的时候想要马上看到一般需要flush()
        socket.shutdownOutput();//设置写入结束标记

        //关闭其他资源，一般和socket有关的都放在最后
        writer.close();
        bis.close();
        socket.close();
        serverSocket.close();
    }
}
```

```java
/**
 * 此类用于演示关于流的读写方法
 */
public class StreamUtils {
    /**
     * 功能：将输入流转换成byte[]， 即可以把文件的内容读入到byte[]
     * @param is
     * @return
     * @throws Exception
     */
    public static byte[] streamToByteArray(InputStream is) throws Exception{
        ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
        byte[] b = new byte[1024];//字节数组
        int len;
        while((len=is.read(b))!=-1){//循环读取
            bos.write(b, 0, len);//把读取到的数据，写入bos
        }
        byte[] array = bos.toByteArray();//然后将bos 转成字节数组
        bos.close();
        return array;
    }
    /**
     * 功能：将InputStream转换成String
     * @param is
     * @return
     * @throws Exception
     */

    public static String streamToString(InputStream is) throws Exception{
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder builder= new StringBuilder();
        String line;
        while((line=reader.readLine())!=null){
            builder.append(line+"\r\n");
        }
        return builder.toString();

    }

}
```

#### #19.3.1.1 netstat 指令

1. `netstat -an` 可以查看当前主机网络情况，包括端口监听情况和网络连接情况

2. `netstat -an | more` 可以分页显示

3. `netstat -anb` 可以显示占用端口的应用

4. 要求在 dos 控制台下执行

5. Listening 表示某个端口在监听。

   如果有一个外部程序连接到该端口，就会显示一条连接信息 Established

#### #19.3.1.2 TCP 连接秘密

**当客户端连接到服务端后，实际上客户端也是通过一个端口和服务端进行通讯的。这个端口由 TCP/IP 来分配，是不确定的，随机的。**

### 19.3.2 UDP 网络通信编程

1. 类 `DatagramSocket` 和 `DatagramPacket` 实现了基于 UDP 协议网络程序
2. 没有明确的服务端和客户端，演变成数据的发送端和接收端
3. UDP 数据报通过数据报套接字 `DatagramSocket` 发送和接收。系统不保证 UDP 数据报一定能安全送到目的地，也不能确定什么时候能抵达
4. `DatagramPacket` 对象封装了 UDP 数据报，在数据报中包含了发送端的 IP 地址和端口号以及接收端的 IP 地址和端口号
5. 接收到 `DtagramPacket` 对象时，需要进行拆包，取出数据
6. `DatagramSocket` 可以指定在哪个端口接收数据
7. UDP 协议中每个数据报都给出了完整的地址信息，因此==无需发送方和接收方的连接==

**下面，示范一个接收端**

```java
DatagramSocket ds = new DatagramSocket(9000);					//[1]
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);	//[2]
System.out.println("萝茵 聆听中……");
ds.receive(dp);													//[3]
int len = dp.getLength();
bytes = dp.getData();
System.out.println("萝茵听到了如下内容：\n" + new String(bytes, 0, len));
ds.close();														//[4]
```

1. `DatagramSocket ds = new DatagramSocket(9000);`

   以 9000 这个端口作为监听端口

2. `DatagramPacket dp = new DatagramPacket(bytes, bytes.length);`

   构建 `DatagramPacket` 对象，准备接收数据

3. `ds.receive(dp);`

   监听信息，放到刚刚创建的 `DatagramPacket` 对象，==如果没有发送端发送消息，会导致阻塞==

4. `ds.close()`

   要记得关闭呦 ★ ~

**下面，示范一个发送端**

```java
System.out.println("萝茵，大声喊道：你好，世界！");
DatagramSocket ds = new DatagramSocket(8000);					//[1]
InetAddress ia = InetAddress.getByName(serverIP);
byte[] bytes = "你好，世界".getBytes(StandardCharsets.UTF_8);
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, ia, 9001);
																//[2]
ds.send(dp);													//[3]
System.out.println("声音在虚无中回荡着……");
ds.close();														//[4]
```

1. `DatagramSocket ds = new DatagramSocket(8000);`

   以 8000 这个端口作为发送端口

2. `DatagramPacket dp = new DatagramPacket(bytes, bytes.length, ia, 9001);`

   把要发送的数据、数据长度、对象地址、对象端口 放到包里

3. `ds.send(dp);`

   走你 ★ ~

4. `ds.close();`

   鸟尽弓藏

***A 和 B 端都可以是接收端或发送端，没有客户端和服务器的概念。***

```java
public class DatagramSocketA {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(6666);
        byte[] bytes = new byte[1024];
        DatagramPacket data = new DatagramPacket(bytes,bytes.length);
        socket.receive(data);					//【1】如果没有发送端发送信息，将会导致阻塞，即代码不会进行下去。
        int len = data.getLength();
        bytes = data.getData();
        System.out.println(new String(bytes,0,len));
        byte[] buffer = "好的，明天见".getBytes();
        DatagramPacket datagramPacket = new DatagramPacket(buffer,0,buffer.length,
                InetAddress.getByName("DESKTOP-DP2R4CS"),5555);
        socket.send(datagramPacket);
        socket.close();
        System.out.println("A端退出...");
    }
}
```

```java
public class DatagramSocketB {
    public static void main(String[] args) throws IOException {

        DatagramSocket socket = new DatagramSocket(5555);

        byte[] bytes = "hello,明天吃火锅".getBytes();

        DatagramPacket packet = new DatagramPacket(bytes,0,bytes.length,
                InetAddress.getByName("DESKTOP-DP2R4CS"),6666);

        socket.send(packet);

        byte[] buffer = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(buffer,buffer.length);
        socket.receive(datagramPacket);
        int length = datagramPacket.getLength();
        byte[] data = datagramPacket.getData();
        System.out.println(new String(data,0,length));
        socket.close();
        System.out.println("B端退出...");

    }
}
```

## 附录

### 项目开发流程

#### #1 需求分析

**需求分析师（懂技术 + 懂行业）**

1. 需求分析报告

   * 项目功能
   * 客户要求

#### #2 设计阶段

**架构师 / 项目经理**

1. 设计工作

   * UML 类图
   * 流程图
   * 模块设计
   * 数据库设计
   * 架构

2. 原型开发

3. 组建团队

#### #3 实现阶段

**程序员 / 码农**

1. 完成架构师的模块功能
2. 测试自己的模块

#### #4 测试阶段

**测试工程师**

1. 单元测试
2. 测试用例
3. 白盒测试
4. 黑盒测试
5. 集成测试

#### #5 实施阶段

**实施工程师（开发能力 / 环境配置部署能力）**

1. 把项目正确地部署到客户的平台，并保证运行正常
2. 身体好

#### #6 维护阶段

1. 发现 bug 并解决
2. 项目升级

# [项目-多用户即时通信系统](D:\TyporaMarkdown\JAVA\Java项目\多用户通信系统.md)
