一、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.创建DatagramSocketDatagramSocket 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.定义数据报socketDatagramSocket datagramSocket = new DatagramSocket(8080);// 2.定义接收数据报的对象byte[] receiveContent = new byte[1024 * 64]; // UDP一次最多只能传输64KBDatagramPacket 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.创建组播socketMulticastSocket 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.创建组播socketMulticastSocket 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.创建socketDatagramSocket 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.创建socketDatagramSocket 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 socketServerSocket 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);// 存储所有的socketVector<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(浏览器/服务端)
- 只需要浏览器访问不同网址就可以访问不同服务器
- 访问即可使用,但是不适合大型游戏,因为资源的临时加载的,所以比较耗时