# 基础知识
如果你对于计算机网络相关知识还不够了解,请查看以下内容。
点击查看内容
计算机网络
计算机网络,指将计算机等设备用通信线路互连成网络系统,从而实现众多设备之间信息的互通。
- 根据计算机网络的规模大小和延伸范围,可以分为:
- 局域网 LAN
 - 城域网 MAN
 - 广域网 WAN
 
 - 根据计算机网络的拓扑结构,可以分为:
- 星型网络
 - 总线型网络
 - 环型网络
 - 树型网络
 - 星型环型网络
 
 - 根据计算机网络的传输介质,可以分为:
- 双绞线网络
 - 同轴电缆网
 - 光纤网
 - 卫星网
 
 
计算机网络分层
根据国际标准化组织提出的“开放系统互连参考模型”,计算机网络被分为: * 物理层 * 数据链路层 * 网络层 * 传输层 * 会话层 * 表示层 * 应用层网络编程三要素
- IP 地址
 - 端口
 - 协议
 
IP 地址
IP 地址用于唯一地标识网络中的通信实体,两个通信实体不能共用 IP 地址。 在基于 IP 协议网络中进行网络传输,都应该使用 IP 地址进行设备的标识。
端口
网络中的通信往往是应用程序之间的通信,并且一个通信实体往往有着多个应用程序。
端口用于唯一地标识通信实体中的应用程序,两个应用程序不能共用端口。
端口号可以为 0~65535,通常分为以下三类:
- 公认端口:
0~1023 - 注册端口:
1024~49151 - 动态端口/私有端口:
49152~65535 
协议
在计算机网络中,连接和通信的规则被称为网络通信协议。它对数据的传输格式、传输速率、传输步骤等做了统一的规定,只有通信双方均遵守规定才能完成数据通信。
常见的协议有:
- UDP 协议:不可靠,差错控制开销小,传输大小限制在 64kb 以下,不需要建立连接
 - TCP 协议:可靠,差错控制开销大,传输大小无限制,需要建立连接
 
什么是 InetAddress
JAVA 提供了 InetAddress 类用于标识 IP 地址。
获取 InetAddress 对象
InetAddress 类没有构造方法,通过静态方法来获取 InetAddress 对象。
| 方法 | 说明 | 
|---|---|
| getByName(String str) | 根据主机名 / IP 地址的字符串获取对象 | 
| getByAddress(byte[] bytes) | 根据原始 IP 地址获取对象 | 
| getLocalHost() | 获取本机 IP 地址对应的对象 | 
常用方法
| 方法 | 说明 | 
|---|---|
| getHostAddress() | 获取字符串形式的 IP 地址 | 
| getHostName() | 获取 IP 地址对应的主机名 | 
| isReachable(int time) | 判断是否能够 | 
# UDP 通信
# UDP 协议
UDP 协议,又称用户数据报协议,是无连接通信协议,是一种不可靠的网络协议。在传输时,数据的发送端和接收端不建立逻辑连接。
提示
当发送端发送数据时,不会事先确认接收端是否存在,也不会管接收端是否受到数据; 当接收端接收数据时,并不会向发送端发出反馈。
使用 UDP 协议进行通信,消耗资源小,通信效率高,因此适用于实时性强、数据完整性要求不高的数据传输。
# Java 中的 UDP
使用 DatagramSocket 代表 UDP 中的 Socket,用于发送和接收数据报; 使用 DatagramPacket 代表 UDP 中的 数据报,用于被发送和接收。
# DatagramSocket
构造器
| 方法 | 说明 | 
|---|---|
| DatagramSocket() | 创建对象,并将对象绑定至本机 IP,随机端口 | 
| DatagramSocket(int prot) | 创建对象,并将对象绑定至本机 IP,指定端口 | 
| DatagramSocket(int prot, InetAddress inetAddress) | 创建对象,并将对象绑定至指定 IP,指定端口 | 
常用方法
| 方法 | 说明 | 
|---|---|
| send(DatagramPacket datagramPacket) | 发送数据报 | 
| receive(DatagramPacket datagramPacket) | 接收数据报 | 
注意
DatagramSocket 送数据和接收数据时仅填入一个数据报参数,DatagramSocket 并不关心数据的来源和去向,只是进行了发送和接收的动作。
# DatagramPacket
构造器
用于发送的 DatagramPacket 对象:
| 方法 | 说明 | 
|---|---|
| DatagramPacket(byte[] bytes, int length, InetAddress inetAddress, int prot) | 用 bytes 数组中 0~length-1 的数据建立 DatagramPacket 对象,并指定去向 | 
| DatagramPacket(byte[] bytes, int offset, int length, InetAddress inetAddress, int prot) | 用 bytes 数组中 offset~length-1 的数据建立 DatagramPacket 对象,并指定去向 | 
用于接收的 DatagramPacket 对象:
| 方法 | 说明 | 
|---|---|
| DatagramPacket(byte[] bytes, int length) | 用于接收数据的对象,数据会放入 bytes 数组的 0~length-1 处 | 
| DatagramPacket(byte[] bytes, int offset, int length) | 用于接收数据的对象,数据会放入 bytes 数组的 offset~length-1 处 | 
常用方法
虽然 UDP 协议并不会在发送和接收端之间建立通信,但可以通过 DatagramPacket 的方法来获取部分信息。
| 方法 | 说明 | 
|---|---|
| getAddress() | 在发送端,获取目标 IP ;在接收端,获取发送端 IP | 
| getProt() | 在发送端,获取目标端口;在接收端,获取发送端端口 | 
# 发送数据
- 用数据建立 DatagramPacket 对象,并指定去向
 - 创建 DatagramSocket 对象
 - 调用 DatagramSocket 对象的 
send()方法发送数据 - 关闭 DatagramSocket 对象
 
// 用数据建立 DatagramPacket 对象,并指定去向
byte[] bytes = 数据.getBytes();
int length = bytes.length;
InetAddress inetAddress = InetAddress.getByName("目标IP");
int prot = 端口号
DatagramPacket datagramPacket = new DatagramPacket(bytes, length, inetAddress, prot);
// 创建 DatagramSocket 对象
DatagramSocket datagramSocket = new DatagramSocket();
// 调用 DatagramSocket 对象的 send() 方法发送数据
datagramSocket.send(datagramPacket);
// 关闭 DatagramSocket 对象
datagramSocket.close();
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 接收数据
- 创建用于接收数据的 DatagramSocket 对象,指定端口号
 - 创建 DatagramSocket 对象
 - 调用 DatagramSocket 对象的 
receive()方法接收数据 - 关闭 DatagramSocket 对象
 
// 创建用于接收数据的 DatagramSocket 对象,指定端口号
DatagramSocket datagramSocket = new DatagramSocket(端口号);
// 创建 DatagramSocket 对象
byte[] bytes = new byte[1024];
int length = bytes.length;
DatagramPacket datagramPacket = new DatagramPacket(bytes, length);
// 调用 DatagramSocket 对象的 receive() 方法接收数据
datagramSocket.receive(datagramPacket);
// 关闭 DatagramSocket 对象
datagramSocket.close();
 2
3
4
5
6
7
8
9
10
11
12
13
下面通过一个案例来理解
 发送端
import java.io.IOException;
import java.net.*;
public class SendData {
    public static void main(String[] args) throws IOException {
        // 数据包
        byte[] bytes = "测试数据测试数据".getBytes();
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.56.1"), 16666);
        DatagramSocket datagramSocket = new DatagramSocket();
        datagramSocket.send(datagramPacket);
        datagramSocket.close();
    }
}
 2
3
4
5
6
7
8
9
10
11
12
13
14
接收端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.Arrays;
public class GetData {
    public static void main(String[] args) throws IOException {
        DatagramSocket datagramSocket = new DatagramSocket(16666);
        byte[] bytes = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
        datagramSocket.receive(datagramPacket);
        System.out.println(new String(datagramPacket.getData(), 0, datagramPacket.getLength()));
        datagramSocket.close();
    }
}
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# TCP 通信
提示
先连接,再传输
# TCP 协议
TCP 协议是一种可靠的网络协议,通过 TCP 协议可以实现可靠无差别的数据传输。
 连接阶段
在通信前需要进行连接,此时需要明确客户端和服务器端,由客户端发起连接请求,经过服务器端和客户端的三次握手之后,建立网络虚拟链路。
 通信阶段
在网络虚拟链路建立完成后,无需再区分服务器端和客户端,而是通过各自的 Socket 进行通信。
# 三次握手
在 TCP 协议中,通信之前需要先建立网络虚拟链路,由客户端向服务器端发出连接请求,经过三次握手后完成连接的创建。
- 第一次握手:客户端向服务器端发出连接请求
 - 第二次握手:服务器端收到请求后,向客户端发出通知,表明收到了连接请求
 - 第三次握手:客户端再次向服务器端发出确认信息,确认连接
 
# Java 中的 TCP
在建立连接时,使用 ServerSocket 代表服务器,Socket 代表客户端;
在进行传输时,通过 Socket 产生的 IO 流来进行网络通信。
# 服务器端
ServerSocket
ServerSocket 代表服务器端。
ServerSocket 对象用于监听来自客户端的连接请求,建立连接并返回对应的 Socket 对象。
 构造器
| 方法 | 说明 | 
|---|---|
| ServerSocket(int prot) | 用指定端口构造 ServerSocket | 
| ServerSocket(int prot, int backlog) | 用指定端口构造 ServerSocket,设置连接请求队列的最大长度 | 
| ServerSocket(int prot, int backlog, InetAddress inetAddress) | 用指定 IP,指定端口构造 ServerSocket,设置连接请求队列的最大长度 | 
提示
当机器存在多个 IP 地址时,允许显式指定 ServerSocket 要绑定的 IP 地址。
常用方法
| 方法 | 说明 | 
|---|---|
| accept() | 用于从连接请求队列中取出一个连接请求,并创建与客户端相对应的 Socket 对象;通常情况下,服务器端应该通过循环不断接收请求;如果连接请求队列为空,则会一直等待,直至收到连接请求。 | 
| close() | 用于关闭 ServerSocket 对象 | 
# Socket
Socket 是对网络通信中两个端点的抽象,在服务器端与客户端建立网络虚拟链路后,通过 Socket 进行数据的传输。
 服务器端的 Socket
服务器端的 accept() 方法会进行连接,并返回与连接的客户端 Socket 对象相对应的 Socket 对象。
 客户端的 Socket
通过以下构造方法构造 Socket 对象。
| 方法 | 说明 | 
|---|---|
| Socket(InetAddress inetAddress, int prot) | 创建连接到指定 IP,指定端口的 Socket(使用 inetAddress 对象描述指定 IP) | 
| Socket(String str, int prot) | 创建连接到指定 IP,指定端口的 Socket(使用主机名 / IP 地址的字符串描述指定 IP) | 
| Socket(InetAddress inetAddress, int prot, InetAddress localInetAddress, int localProt) | 创建连接到指定 IP,指定端口的 Socket(使用 inetAddress 对象描述指定 IP),并且指定本地 IP 和本地端口 | 
| Socket(String str, int prot, InetAddress localInetAddress, int localProt) | 创建连接到指定 IP,指定端口的 Socket(使用主机名 / IP 地址的字符串描述指定 IP),并且指定本地 IP 和本地端口 | 
常用方法
| 方法 | 说明 | 
|---|---|
| getInputStream() | 获取 Socket 对象对应的输入流 | 
| getOutputStream() | 获取 Socket 对象对应的输出流 | 
| shutdownInput() | 关闭输入流 | 
| shutdownOutput() | 关闭输出流 | 
连接超时
如果希望为 Socket 对象设置连接服务器的超时时长。
提示
经过指定时间后,若还没有连接上,则认为连接超时
应该先创建一个无连接的 Socket 对象,再调用 connect() 方法连接服务器端,调用方法时传入超时时长参数。
Socket socket = new Socket();
socket.connect(new InetSocketAddress(IP地址, 端口号), 超时时长);
 2
读写超时
Socket 对象提供了 setSoTimeout() 用于设置超时时长。
设置之后如果读、写操作超过时间限制,将会抛出异常,程序可以捕获并进行处理。
Socket socket = new Socket(IP地址, 端口号);
socket.setSoTimeout(超时时长);
 2
# 连接
服务器端
public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(端口号);
        Socket socket = serverSocket.accept();
        ···
    }
}
 2
3
4
5
6
7
客户端
public class user {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket(IP地址, 端口号);
        ···
    }
}
 2
3
4
5
6
# 通信
输出
// 获得输出流
OutputStream outputStream = socket.getOutputStream();
// 通过输出流输出内容
outputStream.write("内容".getBytes());
 2
3
4
5
输入
// 获得输入流
InputStream inputStream = socket.getInputStream();
// 通过输入流读入内容
byte[] bytes = new byte[1024];
int length = inputStream.read(bytes);
System.out.println(new String(bytes, 0, length));
 2
3
4
5
6
7
# 客户端的多线程
在实际的应用中,客户端和服务器端之间需要保持长时间通信,不断地进行信息的输入和输出。此时如果不启用多线程,将会发生堵塞的情况。
为了使一个服务器端可以同时服务于多个客户端,可以加入多线程:
 多线程类
public class ServerThread extends Thread {
    private Socket socket;
	// 通过构造方法获得当前连接的socket
    public ServerThread(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        ···做些什么···
    }
}
 2
3
4
5
6
7
8
9
10
11
12
13
服务器端
public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(端口号);
        // 通过循环不断接收请求
        while (true) {
        	// 建立连接,获得socket
            Socket socket = serverSocket.accept();
            // 新建线程,传入socket,启动
            new ServerThread(socket).start();
        }
    }
}
 2
3
4
5
6
7
8
9
10
11
12