一、UDP
特点:
① 用户数据报协议(User Datagram Protocol)
② UDP是面向无连接通信协议
③ 速度快,一次只能传输64KB数据,数据不安全,容易丢失
(1)单播
一对一
客户端:
- 定义socket
- 封装数据报
- 发送到目标主机
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class UDPClient {
public static void main(String[] args)throws SocketException, IOException {
// 1.创建DatagramSocket
DatagramSocket datagramSocket = new DatagramSocket(60001);
// 2.封装要传输的数据
String content = "hello";
InetAddress targetHost = InetAddress.getLocalHost();
int targetPort = 8080;
DatagramPacket datagramPacket = new DatagramPacket(content.getBytes(), content.length(), targetHost, targetPort);
// 3.发送数据
datagramSocket.send(datagramPacket);
}
}
服务端:
- 定义socket
- 定义接收数据报的DatagramPacket
- 接收数据报
- 解析数据报
import java.io.IOException;
import java.net.*;
public class UDPServer {
public static void main(String[] args) throws IOException {
// 1.定义数据报socket
DatagramSocket datagramSocket = new DatagramSocket(8080);
// 2.定义接收数据报的对象
byte[] receiveContent = new byte[1024 * 64]; // UDP一次最多只能传输64KB
DatagramPacket datagramPacket = new DatagramPacket(receiveContent, receiveContent.length);
while (true) {
// 3.接收数据报(阻塞等待)
datagramSocket.receive(datagramPacket);
// 4.解析数据报
byte[] data = datagramPacket.getData();
int length = datagramPacket.getLength();
InetAddress address = datagramPacket.getAddress();
SocketAddress socketAddress = datagramPacket.getSocketAddress();
int port = datagramPacket.getPort();
System.out.println("data: " + new String(data, 0, length));
System.out.println("length: " + length);
System.out.println("ip port:" + address + " " + port);
System.out.println("socketAddress: " + socketAddress);
System.out.println(receiveContent == data);
}
}
}
(2)组播
给一组电脑发消息
组播地址:224.0.0.0 ~ 239.255.255.255
其中224.0.0.0 ~ 224.0.0.255为预留的组播地址
客户端:
- 创建组播socket(MulticastSocket)
- 封装数据报文(ip填写组ip)
- 发送报文
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException {
// 1.创建组播socket
MulticastSocket ms = new MulticastSocket(60001);
// 2.封装数据报文
byte[] data = new byte[64 * 1024];
InetAddress targetHost = InetAddress.getByName("224.0.0.1"); // 组地址
DatagramPacket dp = new DatagramPacket(data, data.length, targetHost, 8080);
String content = "hello";
dp.setData(content.getBytes(), 0, content.length());
// 3.发送报文
ms.send(dp);
}
}
服务端
- 创建组播socket(MulticastSocket)
- 将当前服务加入到指定ip组
- 创建接收数据的报文对象DatagramPacket
- 等待接收客户端发来的数据
- 解析报文
import java.io.IOException;
import java.net.*;
public class Server {
public static void main(String[] args) throws IOException {
// 1.创建组播socket
MulticastSocket ms = new MulticastSocket(8080);
// 2.给当前服务加入组
ms.joinGroup(InetAddress.getByName("224.0.0.1"));
// 3.创建接收数据的报文
byte[] data = new byte[64 * 1024];
DatagramPacket dp = new DatagramPacket(data, data.length);
while (true) {
// 4.接收数据
ms.receive(dp);
// 5.解析数据
SocketAddress socketAddress = dp.getSocketAddress();
int length = dp.getLength();
int offset = dp.getOffset();
System.out.println(socketAddress + ": " + new String(data, offset, length));
}
}
}
(3)广播
向局域网下所有的服务发送报文
只需要将单播的发送ip改为255.255.255.255就可以了
客户端
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
/*
广播
*/
public class Client {
public static void main(String[] args) throws IOException {
// 1.创建socket
DatagramSocket ds = new DatagramSocket(60001);
// 2.封装数据报
byte[] data = new byte[64 * 1024];
DatagramPacket dp = new DatagramPacket(data, data.length, InetAddress.getByName("255.255.255.255"), 8080);
String content = "hello";
dp.setData(content.getBytes(), 0, content.length());
// 3.发送数据报
ds.send(dp);
}
}
服务端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
import java.net.SocketException;
public class Server {
public static void main(String[] args) throws IOException {
// 1.创建socket
DatagramSocket ds = new DatagramSocket(8080);
// 2.创建接收数据的报文
byte[] data = new byte[64 * 1024];
DatagramPacket dp = new DatagramPacket(data, data.length);
while (true) {
// 3.接收数据
ds.receive(dp);
// 4.解析报文
SocketAddress socketAddress = dp.getSocketAddress();
int length = dp.getLength();
int offset = dp.getOffset();
System.out.println(socketAddress + ": " + new String(data, offset, length));
}
}
}
二、TCP
TCP是一种可靠的通信协议,在通信的两端各建立一个Socket对象,通信之前连接已经建立,数据是通过IO流进行网络传输的
特点:
1. 传输控制协议(Transmission Control Protocol)
2. TCP协议是面向连接的协议
3. 速度慢,没有限制,数据安全
(1)简单通信
客户端
- 创建socket建立连接
- 通过IO流通道进行网络传输
import java.io.*;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) throws IOException {
// 1.创建socket,与服务端建立连接
Socket socket = new Socket("127.0.0.1", 8080);
// 2.获取输入输出流
// 获取输出流
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
// 获取输入流
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 3.通过输出流发送数据
String data = "hello";
out.println(data);
// 4.通过输入流等待服务端回应
String res = in.readLine();
System.out.println("回应:" + res);
}
}
服务端
- 创建ServerSocket,监听指定端口
- 等待与客户端建立连接
- 通过输入流读取客户端传递的数据,通过输出流给客户端响应
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) throws IOException {
// 1.创建server socket
ServerSocket serverSocket = new ServerSocket(8080);
// 2.等待客户端建立连接
Socket accept = serverSocket.accept();
// 3.获取输入输出流
BufferedReader in = new BufferedReader(new InputStreamReader(accept.getInputStream()));
PrintWriter out = new PrintWriter(accept.getOutputStream(), true);
while (true) {
// 4.等待客户端的数据
String msg = in.readLine();
System.out.println(msg);
// 5.通过输出流给客户端响应
out.println("好的,我收到了");
}
}
}
(2)聊天室案例
客户端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class ChatClient {
private static final Scanner sc = new Scanner(System.in);;
public static void main(String[] args) throws IOException {
// 输入用户名
System.out.print("请输入你的聊天名称:");
String user = sc.next();
// 建立socket连接
Socket socket = new Socket("127.0.0.1", 10000);
// 通过输出流注册用户
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println(user);
// 开启一个线程接收服务端发来的消息
Thread serverThread = new Thread(() -> {
// 获取输入流
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 读取传来的消息
while (true) {
// 通过输入流读取客户端传递的数据
String msg = br.readLine();
if (msg == null || msg.trim().equals("")) {
continue;
}
// 打印在控制台
System.out.println("\n"+ msg);
System.out.print("\n"+ user + ": ");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
});
serverThread.start();
// 与用户交互的控制台
while (true) {
System.out.print("\n" + user + ": ");
// 获取用户的输入消息
String msg = sc.nextLine();
if (msg == null || msg.trim().equals("")) {
continue;
}
// 发送给服务端
out.println(msg);
}
}
}
服务端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.LocalDateTime;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ChatRoomServer {
private static final ExecutorService executorService = Executors.newFixedThreadPool(16);
public static void main(String[] args) throws IOException {
// 创建ServerSocket监听10000端口
ServerSocket serverSocket = new ServerSocket(10000);
// 存储所有的socket
Vector<Socket> sockets = new Vector<>();
ConcurrentHashMap<String, Socket> socketMap = new ConcurrentHashMap<>();
System.out.println("===============聊天室服务端创建完毕==============");
// 一直等待客户端来建立连接
while (true) {
// 等待客户端建立连接
Socket socket = serverSocket.accept();
// 将建立好连接的客户端交给线程池去执行聊天任务
executorService.submit(() -> {
String user = null;
try {
// 字节流作为字符流读取
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 输出流
PrintWriter out = new PrintWriter(socket.getOutputStream());
// 读取用户名称
user = br.readLine();
// 用户已经存在
if (socketMap.contains(user)) {
out.printf("用户'%s'已经存在!", user);
socket.close();
return;
}
// 添加用户进入聊天室
sockets.add(socket);
socketMap.put(user, socket);
// 告诉所有用户来新人了
String notify = LocalDateTime.now() + ": 来新人了!!!欢迎小伙伴【" + user + "】";
System.out.println(notify);
// 将消息同步给其他用户
for (Socket soc : sockets) {
// 自己发的消息,不用通知自己
if (soc == socket) {
continue;
}
PrintWriter printWriter = new PrintWriter(soc.getOutputStream(), true);
printWriter.println(notify);
}
// 循环等待读取用户消息
while (true) {
// 等待用户发来消息
String content = br.readLine();
if (content == null || content.trim().equals("")) {
continue;
}
String msg = user + ": " + content;
System.out.println(msg);
// 将消息发送给其他人
for (Socket soc : sockets) {
if (soc == socket) {
continue;
}
PrintWriter printWriter = new PrintWriter(soc.getOutputStream(), true);
printWriter.println(msg);
}
}
} catch (IOException e) {
// 出现异常,将用户踢出
sockets.remove(socket);
if (user != null) {
socketMap.remove(user);
System.out.printf("用户'%s'退出聊天室", user);
}
throw new RuntimeException(e);
}
});
}
}
}
tcp三次握手(保证连接的建立)
- 客户端向服务端发出连接请求(SYN),等待服务器确认
- 服务端响应给客户端(ACK,SYN)代表接收到请求并询问客户端
- 客户端向服务端确认信息(ACK),连接建立
tcp四次挥手(确保连接断开,且数据处理完毕)
- 客户端向服务器发送断开连接的请求
- 服务端向客户端响应,表示收到了客户端的取消请求,然后处理剩余数据
- 服务端向客户端确认取消
- 客户端发送确认消息,连接取消
三、拓展
IP地址
IPv4
① 32位长度,分为4组,比如192.168.1.101,总共不到43亿个IP
② 利用局域网分配IP缓解IP不够用的问题
③ 向127.0.0.1发送数据不会经过路由器,如果192.168.1.100是当前电脑的IP,向这个地址发送数据会经过路由器
IPv6
① 128位,16位为一组,共分为8组,冒分十六进制表示法
如:2001:0DB8:0000:0023:0008:0008:200C:417A,省略无效的0 -> 2001:DB8:0:23:8:8:200C:417A
② 特殊情况:如果计算出的16进制表示形式中间有多个连续的0,则会进行0位压缩,
如:FF01:0:0:0:0:0:0:1101 -> FF01::1101
常见软件架构:
C/S:Client/Server(客服端/服务端)
- 在用户本地需要下载并安装客户端程序,远程有一个服务端程序
- 软件更新比较麻烦,需要客户端下载资源
B/S:Browser/Server(浏览器/服务端)
- 只需要浏览器访问不同网址就可以访问不同服务器
- 访问即可使用,但是不适合大型游戏,因为资源的临时加载的,所以比较耗时