# 基础知识
如果你对于计算机网络相关知识还不够了解,请查看以下内容。
点击查看内容
计算机网络
计算机网络,指将计算机等设备用通信线路互连成网络系统,从而实现众多设备之间信息的互通。
- 根据计算机网络的规模大小和延伸范围,可以分为:
- 局域网 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