网络通信三要素
此笔记来之与黑马.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 KB
DatagramPacket 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 KB
DatagramPacket 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;
}
@Override
public 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(); // 释放连接资源
}
@Override
public 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;
}
@Override
public 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;
}
@Override
public 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;
}
@Override
public 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>);
}
}
}