网络通信三要素
什么是网络编程?
- 网络编程可以让程序与网络上的其他设备中的程序进行数据的交互
网络通信的基本模式
- 常见的通信模式有如下两种形式:Client-Service(CS)、Browser-Service(BS)
-
实现网络编程的三要素
- IP地址:设备在网络中的地址,是唯一的标识。
- 端口:应用程序在设备中的唯一标识。
- 协议:数据在网络中传输的规则。常见的有UDP协议和TCP协议。
IP地址
- IP(Internet Protocol):全程“互联网协议地址”,是分配给上网设备的唯一标识。
- 常见的IP分类为IPv4和IPv6
- IPv6:128位(16个字节),号称可以为地球的每一粒沙子编号。
- Ipv6分成8个整数,每个整数用四个十六进制位表示,数之间用冒号(:)分开
-
IP地址的形式:
- 公网地址、私有地址(局域网使用)
- 192.168.开头就是常见的局域网地址,范围为192.168.0.0---192.168.255.255,专门为组织机构使用。
IP常用的命令
- ipconfig:查看本机ip地址
- ping IP地址:检查网络是否连通
特殊IP地址
- 本机IP:127.0.0.1或者localhost:称为送回地址也可称为本地回环地址,只会寻找当前所在本机
InetAddress的使用
此类表示Interne协议(IP)地址
InetAddress的API如下
名称 | 说明 |
public static InetAddress getLocalHost() | 返回本机的地址对象 |
public static InetAddress getByName(String host) | 返回指定主机的IP地址对象,参数是域名或者ip地址 |
public String getHostName() | 获取此IP地址的主机名 |
public String getHostAddress() | 返回IP地址字符串 |
public boolean isReachable(int timeout) | 在指定毫秒内连通该IP地址对应的主机,联通成功返回true |
IP地址的代表类是:InetAddress类
获取本机对象:public static InetAddress getLocalhost()
端口号
- 端口号:标识正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围是0-65535
端口类型
- 周知端口:0-1023,被预先定义的知名应用占用(如:HTTP占用:80、FTP占用:21)
- 注册端口:1024-49151,分配给用户进程或者某些应用程序。(Tomcat占用8080、MySQL占用3306)
- 动态端口号:49152-65535,之所以被称为动态端口号,是因为它一般不固定分配某些进程,而是动态分配
注意:我们自己开发的程序选择注册端口号,且一个设备不能出现两个程序的端口号一样,否则出错
网络通信协议有两套参考模型
- OSI参考模型:世界互联协议标准,全球通信规范,由于此模型过于理想化,未能在互联网上广泛的推行
- TCP/IP参考模型:实际上的国际标准
OSI参考模型 | TCP/IP参考模型 | 各层对应 | 面向操作 |
应用程 | 应用层 | HTTP、FTP、DNS、SMTP... | 应用程序需要关注的:浏览器、邮箱。程序员一般在此层进行开发 |
表示层 | |||
会话层 | |||
传输层 | 传输层 | TCP、UDP | 选择使用TCP、UDP协议 |
网络层 | 网络层 | IP、ICMP... | 封装源和目标IP,进行路径选择 |
数据链路层 | 数据链路层+物理层 | 物理寻址、比特流 | 物理设备中传输 |
物理层 |
传输层的2个常见协议
- TCP(Transmission Control Protocol):传输控制协议
- UDP(User Datagram Protocol):用户数据协议
TCP协议特点
- 使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。
- 传输前,采用“三次握手”方式建立连接,所以是可靠的。
- 在连接中可进行大数据量的传输
- 连接、发送数据都需要确认,且传输完毕之后,还需要释放已建立的连接,通信效率较低。
TCP协议通信场景
- 对信息安全要求较高的场景,例如:文件下载、金融等数据通信。
TCP的三次握手建立连接
TCP的四次挥手断开连接
UDP协议:
- UDP是一种无连接、不可靠传输的协议。
- 将数据源IP、目的地IP和端口封装成数据包,不需要建立连接
- 将每个数据包大小限制在64KB内
- 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
- 可以广播发送,发送数据结束时无需释放资源,开销小,速度快。
UDP协议通信场景
- 语音通话、视频通话等
通信协议是什么?
- 计算机网络中,连接和通信数据的规则被称为网络通信协议
UDP通信
UDP通信:快速入门
构造器 | 说明 |
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 创建发送端数据包对象 buf:要发送的内容,字节数组 length:要发送内容的字节长度 address:接收端的ip地址 port:接收端的端口号 |
public DatagramPacket(byte[] buf, int length) | 创建发送端数据包对象 buf:用来存储接收的内容 length:能够接收内容的字节长度 |
DatagramSocket:发送端和接收端对象
构造器 | 说明 |
public void send(DAatagramPacket dp) | 发送数据包 |
public void receive(DatagramPacket p) | 接收数据包 |
使用UDP通信实现:发送消息、接收消息
需求:客户端实现步骤
- 创建DatagramSocket对象(发送端对象)
- 创建DatagramPacket对象封装需要发送的数据(数据包对象)
- 使用DatagramSocket对象的send方法传入DatagramPacket对象
- 释放资源
UDP通信:多发多收
需求:客户端实现步骤
- 创建DatagramSocket对象(发送端对象)
- 使用while死循环不断接受用户的数据输入,如果用户输入exit则退出程序
- 如果用户输入的不是exit,吧数据封装成DatagramPacket
- 使用DatagramSocket对象的send方法将数据包对象进行发送
- 释放资源
接收端实现步骤:
- 创建DatagramSocket对象并指定端口(端口接收对象)
- 创建DatagramPacket对象接收数据(数据包对象)
- 使用while死循环不断进行第一次
- 使用DatagramSocket对象的receive方法传入DatagramPacket对象
UDP的接收端为什么可以接收数据包,无所谓是哪个发送端的数据包
UDP通信-广播、组播
UDP的三种通信方式
- 单播:单台主机与单台主机之间的通信
- 广播:当前主机与所在网络中所有主机通信
- 组播:当前主机与选定的一组主机的通信
UDP如何实现广播
- 使用广播地址:255.255.255.255
- 发送端发送的数据包的目的地写的是广播地址、且指定端口。(255.255.255.255,9999)
- 本机所在网段的其他主机的程序只要匹配端口成功即可收到消息(9999)
UDP如何实现组播
- 使用组播地址:224.0.0.0 ~ 239.255.255.255
- 发送端的数据包的目的地是组播IP(例如:224.0.0.1,端口:9999)
- 接收端必须绑定该组播IP(224.0.0.1),端口还要对应发送端的目的端口9999,这样即可接收该组播消息
- DatagramSock的子类MulticastSocket可以在接收端绑定组播IP
TCP通信快速入门
编写客户端代码
在Java中只要是使用java.net.Socket的类实现通信,底层即使用了TCP协议
客户端数据发送消息
客户端实现步骤
- 创建客户端的Socket对象,请求与服务端连接
- 使用socket对象调用getOutputStream()方法得到字节输出流
- 使用字节输出流完成数据的发送
- 释放资源:关闭socket管道
什么是线程通信、如何实现
- 所谓线程通信就是线程间相互发送数据,线程通信通常通过共享一个数据的方式实现
- 线程间会根据共享数据的情况决定自己该怎么做,以及通知其他线程应该怎么做
线程通信常见模型
- 生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费数据
- 要求:生产者线程生产数据后,唤醒消费者,然后等待自己‘消费者消费完该数据后,唤醒生产者,然后等待自己
步骤:客户端发送数据
- 创建客户端的Socket对象,请求与服务端的连接
- 使用socket对象调用getOutputStream()方法得到字节输出流
- 使用字节输出流完成数据的发送
- 释放资源:关闭socket管道
Tcp通信的客户端的代表类是谁?
- socket类
- public Socket(String host ,int port)
构造器 | 说明 |
public ServerSocket(int port) | 注册服务端口 |
方法 | 说明 |
public Socket accept() | 等待接收客户端的Socket通信连接 连接成功返回Socket对象与客户端建立端到端的通信 |
TCP通信服务端用的代表类?
- ServerSocket类,注册端口
- 调用accept()方法阻塞等待接收客户端连接,得到Socket对象
TCP通信的基本原理?
- 客户端怎么发,服务端就怎么收
- 客户端如果没有消息,服务端会进入阻塞等待
- Socket一方关闭或者出现异常,对方Socket也会失效或者出错
TCP通信-多发多收消息
需求:使用TCP通信方式实现:多发多收消息
- 可以使用死循环控制服务端收完消息继续等待接收下一个消息
- 客户端可以使用死循环等待用户不断输入消息
- 客户端一旦使用exit,关闭客户端程序,并释放资源
注意:一个线程只能与一个客户端进行通信
多发多收如何实现?
- 客户端使用循环反复分发送消息
- 服务端使用循环反复的接收消息
TCP通信-同时接收多个客户端消息
实现服务端接收多个客户端的消息
- 主线程定义了循环负责接收客户端Socket管道连接
- 每接收到一个Socket通信管道后分配一个独立的线程负责处理它
创建三个类:服务端(ServerDome1)、客户端(ClientDome)、子线程(ServerReaderThread)
服务端(ServerDome1)
public class ServerDome1 {
public static void main(String[] args) {
try {
System.out.println("=====服务端开启成功=====");
// 1.注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// 2.定义一个死循环,由主线程不断的接收客户端socket管道对象
while (true){
// 2.每接受一个客户端socket管道对象交给一个独立的线程去处理
Socket socket = serverSocket.accept();
// 3.开始创建独立线程处理socket
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端(ClientDome)
public class ClientDome {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1",7777);
//从socket管道对象中获取一个字节输出流,负责发送数据
OutputStream os = socket.getOutputStream();
//把低级流转换成高级流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true){
System.out.println("请说:");
String msg = sc.nextLine();
//发送消息
ps.println(msg);
ps.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
子线程(ServerReaderThread)
public class ServerReaderThread extends Thread {
private Socket socket;
//定义有参构造,接收传过来的socket对象
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
//从socket管道中得到一个字节输入流
InputStream inputStream= socket.getInputStream();
//把字节输入流转换成缓冲字符输入流
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
//按照行读取消息
String msg;
while ((msg = br.readLine()) !=null){
System.out.println(socket.getInetAddress()+"说了:" + msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
TCP通信-使用线程池优化
以上的通信架构存在问题:
- 客户端与服务端的线程模型是N-N的关系
- 客户端并发越多,系统瘫痪的越快
public class ServerDome1 {
//使用静态变量记住一个线程池对象
private static ExecutorService pool = new ThreadPoolExecutor(3, 5, 6,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(2),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
System.out.println("=====服务端开启成功=====");
// 1.注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// 2.定义一个死循环,由主线程不断的接收客户端socket管道对象
while (true){
// 2.每接受一个客户端socket管道对象交给一个独立的线程去处理
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+"他来了,他上线了");
//将接收到的socket封装成任务对象,交给线程池排队
//任务对象负责读取消息
Runnable target = new ServerReaderRunnable(socket);
pool.execute(target);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
线程池的优势:
- 服务端可以复用线程处理多个客户端,可以避免系统的瘫痪
- 适合客户端通信时长较短的场景
TCP通信实战案例-即时通信
即时通信的含义,要实现怎么设计:
- 即时通信是指一个客户端的消息发送出去,其他客户端都可以接收到
- 之前消息都是发送给服务端的
- 即时通信需要进行端口转发的设计思想
即时通信的含义,要实现怎么样的设计
- 即时通信是指一个客户端的消息发出去,其他客户端可以接收到
- 即时通信需要进行端口转发的设计思想
- 服务端需要把在线的Socket管道存储起来
- 一旦收到一个消息要推送给其他管道
TCP实战案例-模拟BS系统
之前客户端都是cs架构,客户端实现需要自己开发实现
BS架构是浏览器访问服务端,不需要开发客户端
TCP通信如何实现BS请求网页信息回来呢?
- 客户端使用浏览器发起请求(不需要开发客户端)
- 服务端必须按照浏览器的协议规则响应数据
- 浏览器使用http协议规则