简介
通信架构
网络通信三要素
IP地址对象-InetAddress
常见API
// 获取本机的InetAddress实例
InetAddress address = InetAddress.getLocalHost();
// 获取计算机名
System.out.println(address.getHostName());
// 获取IP地址
System.out.println(address.getHostAddress());
// 获取指定主机的InetAddress实例
InetAddress address2 = InetAddress.getByName("www.baidu.com");
// 获取计算机名
System.out.println(address2.getHostName());
// 获取IP地址
System.out.println(address2.getHostAddress());
// 判断本地主机与指定主机是否可达 ping www.baidu.com
boolean reachable = address2.isReachable(1000);
System.out.println("是否可达:" + reachable);
端口Port
端口就是port,当统一IP的端口冲突时,需要更换端口号
协议
OSI网络参考模型
传输层的两个协议
分类
UDP协议
TCP协议
TCP-三次握手
- 第一次握手证明:客户端具有发送能力,服务器端具有接受能力
- 第二次握手证明:服务器段具有发送能力,客户端具有接受能力
TCP-四次挥手
UDP 快速入门
构造器API
客户端
public class Client {
public static void main(String[] args) throws SocketException, UnknownHostException {
// 1. 创建一个DatagramSocket对象 客户端
DatagramSocket socket = new DatagramSocket();
// 2. 创建一个DatagramPacket对象
// 参数一:数据 字节数组
// 参数二:数据的长度 字节长度
// 参数三:目的地址 服务端ip地址
// 参数四:目的端口 服务端端口号
byte[] data = "Hello World".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getLocalHost(), 6666);
// 3. 发送数据
try {
socket.send(packet);
} catch (Exception e) {
e.printStackTrace();
}
// 4. 关闭资源
socket.close();
}
}
服务器段
// 服务器端
public class Server {
public static void main(String[] args) throws SocketException {
// 1. 创建一个DatagramSocket对象,表示数据包的接收端口
DatagramSocket socket = new DatagramSocket(6666);
// 2. 创建一个DatagramPacket对象,表示接收的数据包
// 64kb的数据包
byte[] data = new byte[1024 * 64];
// 参数一:数据 字节数组
// 参数二:数据的长度 字节长度
DatagramPacket packet = new DatagramPacket(data, data.length);
// 3. 使用数据包 接收数据
try {
socket.receive(packet);
System.out.println(new String(data, 0, packet.getLength()));
// 甚至可以知道发送端的ip地址和端口号
System.out.println(packet.getAddress().getHostAddress());
System.out.println(packet.getPort());
} catch (Exception e) {
e.printStackTrace();
}
// 4. 关闭资源
socket.close();
}
}
UDP 反复发送
使用while死循环就可以一直发送
// 客户端
public class Client {
public static void main(String[] args) throws SocketException, UnknownHostException {
// 1. 创建一个DatagramSocket对象 客户端
DatagramSocket socket = new DatagramSocket();
byte[] data = null;
Scanner scanner = new Scanner(System.in);
while (true) {
// 2. 创建一个DatagramPacket对象
// 参数一:数据 字节数组
// 参数二:数据的长度 字节长度
// 参数三:目的地址 服务端ip地址
// 参数四:目的端口 服务端端口号
System.out.println("请输入要发送的数据:");
String str = scanner.nextLine();
data = str.getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getLocalHost(), 6666);
// 3. 发送数据
try {
socket.send(packet);
System.out.println("数据发送成功!");
} catch (Exception e) {
e.printStackTrace();
}
if (str.equals("exit")) {
break;
}
}
// 4. 关闭资源
// socket.close();
}
}
// 服务器端
public class Server {
public static void main(String[] args) throws SocketException {
// 1. 创建一个DatagramSocket对象,表示数据包的接收端口
DatagramSocket socket = new DatagramSocket(6666);
// 2. 创建一个DatagramPacket对象,表示接收的数据包
// 64kb的数据包
byte[] data = new byte[1024 * 64];
// 参数一:数据 字节数组
// 参数二:数据的长度 字节长度
DatagramPacket packet = new DatagramPacket(data, data.length);
// 3. 使用数据包 接收数据
while (true){
try {
socket.receive(packet);
System.out.println(new String(data, 0, packet.getLength()));
// 甚至可以知道发送端的ip地址和端口号
System.out.println(packet.getAddress().getHostAddress());
System.out.println(packet.getPort());
} catch (Exception e) {
e.printStackTrace();
}
if (new String(data, 0, packet.getLength()).equals("exit")) {
break;
}
}
// 4. 关闭资源
// socket.close();
}
}
TCP通信
简介
客户端API
客户端代码
// tcp客户端
public class Client {
public static void main(String[] args) throws IOException {
// 1. 创建一个Socket对象
// 参数一:服务端的ip地址
// 参数二:服务端的端口号
Socket socket = new Socket("127.0.0.1", 8888);
// 2. 通过Socket对象获取一个输出流
OutputStream outputStream = socket.getOutputStream();
// 3. 把低级的字节输出流包装成高级的数据输出流
// 这一步不是必须得,但是高级的数据输出流可以更方便的操作数据
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
// 4. 使用数据输出流向服务端发送数据
dataOutputStream.writeUTF("Hello world!");
// 5. 关闭资源
// dataOutputStream关闭的时候会自动关闭outputStream
dataOutputStream.close();
socket.close();
}
}
服务端API
代码
// tcp服务端
public class Server {
public static void main(String[] args) throws IOException {
// 1. 创建服务端对象
// 参数:服务端的端口号
ServerSocket server = new ServerSocket(8888);
// 2. 服务端一直处于监听状态,等待客户端的连接
// accept()方法是一个阻塞方法,会一直等待客户端的连接
Socket socket = server.accept();
// 3. 通过Socket对象获取一个输入流
InputStream inputStream = socket.getInputStream();
// 4. 将原始的字节输入流包装成高级的数据输入流
// 这一步不是必须的,但是高级的数据输入流可以更方便的操作数据
DataInputStream dataInputStream = new DataInputStream(inputStream);
// 5. 读取客户端发送过来的数据
String data = dataInputStream.readUTF();
System.out.println("客户端发送过来的数据:" + data);
System.out.println("客户端的ip地址:" + socket.getInetAddress().getHostAddress()
+ ",客户端的端口号:" + socket.getPort());
// 6. 关闭资源
// dataInputStream关闭的时候会自动关闭inputStream
dataInputStream.close();
socket.close();
server.close();
}
}
TCP的两个人无限交流
使用while true进行死循环即可
客户端
- dataOutputStream.flush();将内存中的输出流刷出去,防止内存中有残留的数据
// tcp客户端
public class Client {
public static void main(String[] args) throws IOException {
// 1. 创建一个Socket对象
// 参数一:服务端的ip地址
// 参数二:服务端的端口号
Socket socket = new Socket("127.0.0.1", 8888);
// 2. 通过Socket对象获取一个输出流
OutputStream outputStream = socket.getOutputStream();
// 3. 把低级的字节输出流包装成高级的数据输出流
// 这一步不是必须得,但是高级的数据输出流可以更方便的操作数据
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("请输入要发送的数据:");
String s = scanner.nextLine();
// 4. 使用数据输出流向服务端发送数据
dataOutputStream.writeUTF(s);
dataOutputStream.flush();
if (s.equals("exit")) {
System.out.println("客户端退出");
// 5. 关闭资源
// dataOutputStream关闭的时候会自动关闭outputStream
dataOutputStream.close();
socket.close();
break;
}
}
}
}
服务端
// tcp服务端
public class Server {
public static void main(String[] args) throws IOException {
// 1. 创建服务端对象
// 参数:服务端的端口号
ServerSocket server = new ServerSocket(8888);
// 2. 服务端一直处于监听状态,等待客户端的连接
// accept()方法是一个阻塞方法,会一直等待客户端的连接
Socket socket = server.accept();
// 3. 通过Socket对象获取一个输入流
InputStream inputStream = socket.getInputStream();
// 4. 将原始的字节输入流包装成高级的数据输入流
// 这一步不是必须的,但是高级的数据输入流可以更方便的操作数据
DataInputStream dataInputStream = new DataInputStream(inputStream);
while (true) {
// 5. 读取客户端发送过来的数据
String data = dataInputStream.readUTF();
System.out.println("客户端发送过来的数据:" + data);
System.out.println("客户端的ip地址:" + socket.getInetAddress().getHostAddress()
+ ",客户端的端口号:" + socket.getPort());
if (data.equals("exit")) {
System.out.println("客户端退出");
// 6. 关闭资源
// dataInputStream关闭的时候会自动关闭inputStream
dataInputStream.close();
socket.close();
server.close();
break;
}
}
}
}
TCP- 一台服务器和多客户端通信
上述代码是没有办法实现这个需求的, Socket socket = server.accept();不论放在哪都不合适
- 放在while循环的外面:只会产生一个socket,就只会接受
一个客户端
的数据- 放在while循环的里面:accept方法是阻塞方法,会被accept
一直阻塞
本质原因:服务器端只有一个主线程,accept方法是一个阻塞方法,一次只能处理一个消息
实现思路
- 修改
服务端
主线程
:死循环不断获取客户端的链接
- 开辟
子线程
:不断从客户端的链接中获取数据
(多个客户端同时交互的时候,关闭其中一个客户端的时候,不要把服务器端关了)
改写服务器端
// tcp服务端
public class Server {
public static void main(String[] args) throws IOException {
// 1. 创建服务端对象
// 参数:服务端的端口号
ServerSocket server = new ServerSocket(8888);
while(true){
// 2. 服务端一直处于监听状态,等待客户端的连接
// accept()方法是一个阻塞方法,会一直等待客户端的连接
Socket socket = server.accept();
// 3. 将socket独享交给独立的线程去负责
ServerReaderThread serverReaderThread = new ServerReaderThread(socket);
serverReaderThread.start();
}
}
}
处理数据的线程类
public class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try (
// 1. 通过Socket对象获取一个输入流
InputStream inputStream = socket.getInputStream();
// 2. 将原始的字节输入流包装成高级的数据输入流
// 这一步不是必须的,但是高级的数据输入流可以更方便的操作数据
DataInputStream dataInputStream = new DataInputStream(inputStream);
) {
while (true) {
// 3. 读取客户端发送过来的数据
String data = dataInputStream.readUTF();
System.out.println("客户端发送过来的数据:" + data);
System.out.println("客户端的ip地址:" + socket.getInetAddress().getHostAddress()
+ ",客户端的端口号:" + socket.getPort());
if (data.equals("exit")) {
System.out.println("客户端退出");
// 4. 关闭资源
socket.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}