网络通信三要素
此笔记来之与黑马.B站的视频是真的高
基本的通信架构
- 基本的通信架构有2种形式:CS架构(Client 客户端/ Server 服务端)、BS架构( Browser 浏览器/ Server 服务端)。
IP 地址
IP(InternetProtocol):全称 “互联网协议地址”,是分配给上网设备的唯一标志。
IP 地址有两种形式:IPv4, IPv6
⚠️ 右上角框框为 运营商 id
公网 IP, 内网 IP
- **公网 IP:**是可以连接互联网的 IP 地址;内网 IP:也叫局域网 IP,只能组织机构内部使用。
- 192.168.开头的就是常见的局域网地址,范围即为 192.168.0.0–192.168.255.255,专门为组织机构内部使用。
特殊 IP 地址:
- 127.0.0.1、localhost:代表本机 IP,只会寻找当前所在的主机。
IP 常用命令:
- ipconfig:查看本机IP地址。
- ping IP地址:检查网络是否连通。
InnetAddress (IP 地址)
端口号
标记正在计算机设备上运行的应用程序的,被规定为一个 16位 的二进制,范围是 0 ~ 65535。
分类
-
周知端口:0 ~ 1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21)
-
**注册端口:**1024 ~ 49151,分配给用户进程或某些应用程序。
-
动态端口:49152 到 65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配。
⚠️ 注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。
通信协议
网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议。
开放式网络互联标准:OSI 网络参考模型
- OSI 网络参考模型:全球网络互联标准
传输层的2个通信协议
- UDP(User Datagram Protocol):用户数据报协议;TCP(Transmission Control Protocol):传输控制协议。
UDP协议
特点:无连接、不可靠通信。诵信效率高!语音诵话视频直播
- 不事先建立连接,数据按照包发,一包数据包含:自己的 IP、程序端口,目的地 IP、程序端口和数据(限制在 64KB 内)等。
- 发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠的。
TCP 协议
- 特点:面向连接、可靠通信。
- TCP 的最终目的:要保证在不可靠的信道上实现可靠的传输。
- TCP 主要有三个步骤实现可靠传输:三次握手建立连接,传输数据进行确认,四次挥手断开连接。
四次挥手
ContentsUDP通信-快速入门
Java提供了一个 java.net.DatagramSocket 类来实现 UDP 通信。
public class Client {public static void main(String[] args) throws Exception {// 1. 创建客户对象(发数据出去的人)DatagramSocket socket = new DatagramSocket();// 2. 创建数据包对象封装要发出去的数据(创建一个数据包)/** public DatagramPacket(byte buf[], int length,InetAddress address, int port)参数一: 封装要发出去的数据参数二:发送出去的数据大小(字节个数)参数三:服务端的 IP 地址(找到服务端主机)参数四:服务端程序的端口*/byte[] byres = "我是快乐的客户端,我爱你 abc".getBytes();DatagramPacket packet = new DatagramPacket(bytes, bytets.length,InetAddress.getLocalHost(), port: 6666);// 3. 正式发送这个数据包的数据出去了socket.send(packet);System.out.println("客户端数据发送完毕~~");socket.close(); // 释放数据!}
}class Server {public static void main(Stirng[] args) throws Exception {System.out.println("-----服务器端启动");// 1. 创建一个服务端口(创建一个接数据包的人) 注册端口DatagramSocker socket = new DatagramSocket(port: 6666);// 2. 创建一个数据包对象,用于接收数据的(创建一个数据包)byte[] buffer = new byte[1024 * 64]; // 64 KBDatagramPacket packet = new DatagramPacket(buffer.length);// 3. 开始正式使用数据包来接收客户端发来的数据socket.receive(packet);// 4.从字节数组中,把接收到的数据直接打印出来// 接收多少就倒出多少int len = packet.getLength();String res = new String(buffer, 0, len);System.out.println(rs);System.out.println(packet.getAddress().getHostAddresss());System.out.prinlnt(packet.getPort());socket.close(); //释放资源}
}
UDP 通信-多发多收
public class Client {public static void main(String[] args) throws Exception {// 1. 创建客户对象(发数据出去的人)DatagramSocket socket = new DatagramSocket();// 2. 创建数据包对象封装要发出去的数据(创建一个数据包)/** public DatagramPacket(byte buf[], int length,InetAddress address, int port)参数一: 封装要发出去的数据参数二:发送出去的数据大小(字节个数)参数三:服务端的 IP 地址(找到服务端主机)参数四:服务端程序的端口*/Scanner scanner = new Scanner(System.in);while (true) {System.out.println("请说: ");String msg = scanner.nextLine();if ("exit".equals(msg)) {break;}byte[] byres = msg.getBytes();DatagramPacket packet = new DatagramPacket(bytes, bytets.length,InetAddress.getLocalHost(), port: 6666);// 3. 正式发送这个数据包的数据出去了socket.send(packet);System.out.println("客户端数据发送完毕~~");}socket.close(); // 释放数据!}
}class Server {public static void main(Stirng[] args) throws Exception {System.out.println("-----服务器端启动");// 1. 创建一个服务端口(创建一个接数据包的人) 注册端口DatagramSocker socket = new DatagramSocket(port: 6666);// 2. 创建一个数据包对象,用于接收数据的(创建一个数据包)byte[] buffer = new byte[1024 * 64]; // 64 KBDatagramPacket packet = new DatagramPacket(buffer.length);while (true) {System.out.println("服务端启动");// 3. 开始正式使用数据包来接收客户端发来的数据socket.receive(packet);// 4.从字节数组中,把接收到的数据直接打印出来// 接收多少就倒出多少int len = packet.getLength();String res = new String(buffer, 0, len);System.out.println(rs);System.out.println(packet.getAddress().getHostAddresss());System.out.printtln(packet.getPort()); System.out.println("--------------分割线---------------");}socket.close(); //释放资源}
}
TCP 通信-快速入门
Java 提供了一个 java.net.Socket 类来实现 TCP 通信。
public class Client {public static void main(String[] args) throws Exception {// 1. 创建 Socket 对象,并同时请求与服务端程序的连接Socket socket = new Socket("127.0.0.1", 8888);// 2.从 socket 通信管道中得到一个字节输出流,用来发数据给服务端程序OutputStream os = socket.getOutputStream();// 3.把低级的字节输出流包装成数据输出流DataOutputStream dos = new DataOutputStream(os);// 4.开始写数据出去了dos.writeUTF("在一起, 好吗?");dos.close();socket.close(); // 释放连接资源}
}public class Server {public static void main(String[] args) {}
}
服务端是通过 java.net包 下的 ServerSocket 类来实现的。
public class Server {public static void (String[] args) throws Exception {// 1. 创建 ServerSocket的对象, 同时为服务器注册端口ServerSocket serverSocket = new ServerSocket(port: 8888);// 2.使用 serverSocket 对象,调用一个 accept 方法,等待客户端的连接请求Socket socket = serverSocket.accept();// 3. 从socket通信管道中得到一个字节输入流InputStream is = socket.getInputStream();// 5.使用数据输入流读取客户端发送过来的消息String rs = dis.readUTF();System.out.println(rs);System.out.println(socket.getRemoteSocketAddress())dis.close();socket.close();}
}
TCP 通信-多发多收
public class Client {public static void main(String[] args) throws Exception {// 1. 创建 Socket 对象,并同时请求与服务端程序的连接Socket socket = new Socket("127.0.0.1", 8888);// 2.从 socket 通信管道中得到一个字节输出流,用来发数据给服务端程序OutputStream os = socket.getOutputStream();// 3.把低级的字节输出流包装成数据输出流DataOutputStream dos = new DataOutputStream(os);Scanner sc = new Scanner(System.in);while (true) {System.out.println("请说: ");String msg = sc.nextLine();// 一旦用户输入了 exit, 就退出了客户端程序if ("exit".equals(msg)) {System.out.println("欢迎您下次光临!! 退出成功!");break;}// 4.开始写数据输出了dos.writeUTF(msg);dos.flush();}dos.close();socket.close(); // 释放连接资源}
}
public class Server {public static void (String[] args) throws Exception {// 1. 创建 ServerSocket的对象, 同时为服务器注册端口ServerSocket serverSocket = new ServerSocket(port: 8888);// 2.使用 serverSocket 对象,调用一个 accept 方法,等待客户端的连接请求Socket socket = serverSocket.accept();// 3. 从socket通信管道中得到一个字节输入流InputStream is = socket.getInputStream();// 4.把原始的字节输入流包装成数据输入流DataInputStream dis = new DataInputStream(is);while (true) {// 5.使用数据输入流读取客户端发送过来的消息try {String rs = dis.readUTF();System.out.println(rs);} catch (Exception e) {e.printStackTrace();System.out.println(socket.getRemoteSocketAddress() + "离线了");break;}}dis.close();socket.close();}
}
TCP 通信-同时接收多个客户端
public class Client {public static void main(String[] args) throws Exception {// 1. 创建 Socket 对象,并同时请求与服务端程序的连接Socket socket = new Socket("127.0.0.1", 8888);// 2.从 socket 通信管道中得到一个字节输出流,用来发数据给服务端程序OutputStream os = socket.getOutputStream();// 3.把低级的字节输出流包装成数据输出流DataOutputStream dos = new DataOutputStream(os);Scanner sc = new Scanner(System.in);while (true) {System.out.println("请说: ");String msg = sc.nextLine();// 一旦用户输入了 exit, 就退出了客户端程序if ("exit".equals(msg)) {System.out.println("欢迎您下次光临!! 退出成功!");break;}// 4.开始写数据输出了dos.writeUTF(msg);dos.flush();}dos.close();socket.close(); // 释放连接资源}
}
public class Server {public static void main(String[] args) {System.out.println("--------服务器启动成功--------------");// 1. 创建ServerSocket的对象,同时为服务端注册端口ServerSocket serverSocket = new ServerSocket(8888);while (true) {// 2. 使用serverSocket对象,调用一个 accept 方法,等待客户端连接请求Socket socket = serverSocket.accept();// 3. 把这个客户端对应的socket通信管道,交给一个独立的线程负责处理new ServerReaderThread(socket).start();}}
}
public class ServerReaderThread extends Thread {private Socket socket;public ServerReaderThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {InputStream is = socket.getInputStream();DataInputStream dis = new DataInputStream(is);while (true) {String msg = dis.readUTF();System.out.println(msg);}} catch (IOException e) {e.printStackTrace();}}
}
TCP通信-综合案例
即时通信-群聊
public class Client {public static void main(String[] args) throws Exception {// 1. 创建 Socket 对象,并同时请求与服务端程序的连接Socket socket = new Socket("127.0.0.1", 8888);// 创建一个独立的线程 负责随机从socket中接收服务端发送过来的消息new ClentReaderThread(socket).start();// 2.从 socket 通信管道中得到一个字节输出流,用来发数据给服务端程序OutputStream os = socket.getOutputStream();// 3.把低级的字节输出流包装成数据输出流DataOutputStream dos = new DataOutputStream(os);Scanner sc = new Scanner(System.in);while (true) {System.out.println("请说: ");String msg = sc.nextLine();// 一旦用户输入了 exit, 就退出了客户端程序if ("exit".equals(msg)) {System.out.println("欢迎您下次光临!! 退出成功!");break;}// 4.开始写数据输出了dos.writeUTF(msg);dos.flush();}dos.close();socket.close(); // 释放连接资源}@Overridepublic void run() {try {InputStream is = socket.getInputStream();DataInputStream dis = new DataInputStream(is);while (true) {try {String msg = dis.readUTF();System.out.println(msg);} catch (Exception e) {System.out.println("自己下线了: " + socket.getRemoteSock());}}}}
}
public class ClentReaderThread extends Thread {private Socket socket;public ClentReaderThread(Socket socket) {this.socket = socket;}
}
public class Server {public static List<Socket> onLineSockets = new ArrayList<>();public static void main(String[] args) {System.out.println("--------服务器启动成功--------------");// 1. 创建ServerSocket的对象,同时为服务端注册端口ServerSocket serverSocket = new ServerSocket(8888);while (true) {// 2. 使用serverSocket对象,调用一个 accept 方法,等待客户端连接请求Socket socket = serverSocket.accept();onLineSockets.add(socket); System.out.println("有人上线了: " + socket.getRemoteSocketAddress());// 3. 把这个客户端对应的socket通信管道,交给一个独立的线程负责处理new ServerReaderThread(socket).start();}}
}
public class ServerReaderThread extends Thread {private Socket socket;public ServerReaderThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {InputStream is = socket.getInputStream();DataInputStream dis = new DataInputStream(is);while (true) {String msg = dis.readUTF();System.out.println(msg);sendMsgToAll(msg); // 发送给所有的客户端}} catch (IOException e) {System.out.println("有人下线了: " + socket.getRemoteSocketAddress());Server.onLineSockets.remove(socket);e.printStackTrace();}}private void sendMsgToAll(String msg) throws IOExecption {// 发送给所有在线的 socket 管道接收for (Socket onLineSocket : Server.onLineSockets) {OUtputStream os = onLineSocket.getOutputStream();DataOutputStream dos = new DataOutputStream(os);dos.write(msg);dos.flush();}}
}
实现一个 BS 架构(浏览器+程序)
要求从浏览器中访问服务器, 并立即让服务器响应一个很简单的网页给浏览器展示, 网页内容就是“黑马程序员666”
public class ServerReaderThread extends Thread {private Socket socket;public SeverReaderThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {// 立即响应一个网页内容:"黑马程序员"给浏览器展示try {OutputStream os = socket.getOutputStream();PrintStream ps = new PrintStream(os);ps.println("HTTP/1.1 200 OK");ps.println("Content-Type:text/html;charset=UTF-8");ps.println(); // 必须换行ps.println("<div style="color:red;font-size:120px;text-align:center" 黑马程序员磊哥</div>);} cathch (Exception e) {e.printStackTrace();}}
}
线程池优化 BS 架构
可以参考博主这篇JUC笔记
public class Server {public static List<Socket> onLineSockets = new ArrayList<>();public static void main(String[] args) {System.out.println("--------服务器启动成功--------------");// 1. 创建ServerSocket的对象,同时为服务端注册端口ServerSocket serverSocket = new ServerSocket(8888);// 创建出一个线程池,负责处理通信管道的任务ThreadPoolExecutor pool = new ThreadPoolExecutor(16*2, 16*2, new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());while (true) {// 2. 使用serverSocket对象,调用一个 accept 方法,等待客户端连接请求Socket socket = serverSocket.accept();pool.execute(new ServerReaderRunnable(socket))}}
}
public class ServerReaderThread extends Runnable {private Socket socket;public SeverReaderThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {// 立即响应一个网页内容:"黑马程序员"给浏览器展示try {OutputStream os = socket.getOutputStream();PrintStream ps = new PrintStream(os);ps.println("HTTP/1.1 200 OK");ps.println("Content-Type:text/html;charset=UTF-8");ps.println(); // 必须换行ps.println("<div style="color:red;font-size:120px;text-align:center" 黑马程序员磊哥</div>);}}
}