网络编程
- 网络编程
- 网络编程三要素 🍐 ❤️
- UDP通讯 🚀 🍐
- TCP通讯 🍐 ✏️完成案例即可
- TCP和UDP的区别 🍐 ❤️
前置知识
- 完成过字节流复制文件的案例
- 理解字节流复制文件的传输过程
- 理解文件是由字节组成的
- 曾经有过发送图片给朋友,却因为网络问题,失败的经历(如图👉)
一、网络编程概述 🚩
网络编程:编写的应用程序可以与网络上其他设备中的应用程序进行数据交互
网络编程的作用:
- 如我们经常用的微信收发消息就需要用到网络通信的技术
- 如我们打开浏览器可以浏览各种网络、视频等也需要用到网络编程的技术
Java提供的网络编程的解决方案:
- 在java.net包下提供了诸多类,实现网络编程
网络通信的基本架构:
- CS架构(Client 客户端/Server服务端)
- BS架构(Brower 浏览器/Server服务端)
- CS架构的特点: CS架构需要用户在自己的电脑或者手机上安装客户端软件,然后由客户端软件通过网络连接服务器程序,由服务器把数据发给客户端,客户端就可以在页面上看到各种数据了。
BS架构的特点: BS架构不需要开发客户端软件,用户只需要通过浏览器输入网址就可以直接从服务器获取数据,并由服务器将数据返回给浏览器,用户在页面上就可以看到各种数据了。
不管是CS、还是BS都是需要用到网络编程的相关技术
二、网络编程三要素 🚩 🍐 ❤️
网络编程三要素:IP地址、端口号、通信协议
如下图所示:假设现在要从一台电脑中的微信上,发一句“你愁啥?”到其他电脑的微信上,流程如下
1.先通过ip地址找到对方的电脑
2.再通过端口号找到对方的电脑上的应用程序
3.按照双方约定好的规则发送、接收数据
1️⃣ 2.1 IP地址
IP(Ineternet Protocol)全称互联网协议地址,是分配给网络设备的唯一表示。
IP地址分为:IPV4地址、IPV6地址
IPV4地址由32个比特位(4个字节)组成,如果下图所示,但是由于采用二进制太不容易阅读了,于是就将每8位看成一组,把每一组用十进制表示(叫做点分十进制表示法)。所以就有了我们经常看到的IP地址形式,如:192.168.1.66
如果想查看本机的IP地址,可以在命令行窗口(win+R cmd),输入ipconfig
命令查看,如下图所示
经过不断的发展,现在越来越多的设备需要联网,IPV4地址已经不够用了(255 * 255 * 255 * 255=约42亿)
IPV6采用128位二进制数据来表示(16个字节),号称可以为地球上的每一粒沙子编一个IP地址,
IPV6比较长,为了方便阅读,每16位编成一组,每组采用十六进制数据表示,然后用冒号隔开(称为冒分十六进制表示法),如下图所示
现在的网络设备,一般IPV4和IPV6地址都是支持的。
域名
域名是一个IP地址上的面具 。一个域名的目的是便于记忆和沟通的一组服务器的地址
如:百度的域名:https://www.baidu.com/open in new window 对应的ip地址是:14.119.104.189
域名如何找到匹配IP?
域名和IP其实是一一对应的,由运营商来管理域名和IP的对应关系。我们在浏览器上敲一个域名时,首先由运营商的域名解析服务,把域名转换为ip地址,再通过IP地址去访问对应的服务器设备。
本地回送地址:
127.0.0.1
,方便测试使用(因为电脑没插网线或者没连接Wifi的时候没有分配ip地址)
点击查看常见的几个关于控制台命令
ipconfig: 查看本机的ip地址
ping 域名/ip 检测当前电脑与指定的ip是否连通
ping命令出现以下的提示,说明网络是通过的
使用Java代码获得本机的IP地址
Java中也有一个类用来表IP地址,这个类是InetAddress类
public class InetAddressTest {
public static void main(String[] args) throws Exception {
// 1、获取本机IP地址对象的
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());
System.out.println(ip1.getHostAddress());
// 2、获取指定IP或者域名的IP地址对象。
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
// ping www.baidu.com
System.out.println(ip2.isReachable(6000));
}
}
作业
🚩
- 使用win+R 输入cmd 打开控制台,输入ipconfig 查看本机的ip地址
- 或者打开网络适配器,查看网络详情,查看本机地址。或者打开飞秋查看你同学的网络地址
- 在Java类中使用InetAddress的api 获得本机地址(方法:getHostAddress)
2️⃣ 2.2 端口号
端口号
指的是计算机设备上运行的应用程序的标识,被规定为一个16位的二进制数据,范围(0~65535)
端口号分类:了解
- 周知端口:0~1023,被预先定义的知名应用程序占用(如:HTTP占用80,FTP占用21)
- 注册端口:1024~49151,分配给用户经常或者某些应用程序
- 动态端口:49152~65536,之所以称为动态端口,是因为它一般不固定分配给某进程,而是动态分配的。
常见的端口: 开发常见 👈
- 80端口 是http协议的端口
- 443端口 是https协议的端口
- 3306端口 是mysql数据库的端口
- 22端口 是文件上传的端口
注意:同一个计算机设备中,不能出现两个应用程序,用同一个端口号
3️⃣ 2.4 协议
协议
网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议。
作用:
**协议的种类:**了解传输层协议
只要按照OSI网络参考模型制造的设备,就可以在国际互联网上互联互通
- UDP协议特点
- TPC协议特点
三次握手如下图所示 :目的是确认通信双方,手法消息都是正常没问题的
四次挥手如下图所示:目的是确保双方数据的收发已经完成,没有数据丢失
三、UDP通信代码(案例) 🚩
学习基于UDP协议通信的程序
UDP入门案例前置知识:
- 了解 UDP是面向无连接的、不需要确认双方是否存在,是不可靠的协议
- Java提供了一个类叫
DatagramSocket
来完成基于UDP协议的收发数据 - 使用
DatagramSocket
收发数据时,数据要以数据包的形式体现,一个数据包限制在64KB以内大小有限制 - 有发送端和接收端2个程序
1️⃣ 3.1 入门程序 ✏️
需求:客户端程序发一个字符串数据给服务端,服务端程序接收数据并打印。
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[] bytes = "发送端:我是老王,你在吗".getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length
, InetAddress.getLocalHost(), 22222);
// 3、开始正式发送这个数据包的数据出去了
socket.send(packet);
System.out.println("客户端数据发送完毕~~~");
socket.close(); // 释放资源!
}
}
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("----服务端启动----");
// 1、创建一个服务端对象(创建一个接韭菜的人) 注册端口
DatagramSocket socket = new DatagramSocket(22222);
// 2、创建一个数据包对象,用于接收数据的(创建一个韭菜盘子)
byte[] buffer = new byte[1024 * 64]; // 64KB.
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 3、开始正式使用数据包来接收客户端发来的数据
socket.receive(packet);
// 4、从字节数组中,把接收到的数据直接打印出来
// 接收多少就倒出多少
// 获取本次数据包接收了多少数据。
int len = packet.getLength();
String rs = new String(buffer, 0 , len);
System.out.println(rs);
socket.close(); // 释放资源
}
}
2️⃣ 3.2 进阶程序 ✏️ 增加一个while循环
需求:实现客户端不断的发数据,而服务端能不断的接收数据,客户端发送exit时客户端程序退出。
/**
* 目标:完成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) {
String msg = scanner.next();
// 一旦发现用户输入的exit命令,就退出客户端
if("exit".equals(msg)){
System.out.println("欢迎下次光临!退出成功!");
socket.close(); // 释放资源
break; // 跳出死循环
}
DatagramPacket packet = new DatagramPacket(msg.getBytes(), msg.getBytes().length
, InetAddress.getLocalHost(), 22222);
// 3、开始正式发送这个数据包的数据出去了
socket.send(packet);
}
}
}
/**
* 目标:完成TCP通信快速入门-服务端开发:实现1发1收。
*
* 1、创建ServerSocket的对象,同时为服务端注册端口。 ServerSocket(port);
* 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
* 3、从socket通信管道中得到一个字节输入流。
* 4、使用一个数组接收到传过来的数据,然后通过转成字符串
* 5、关流和释放资源
*
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("----服务端启动----");
// 1、创建一个服务端对象(创建一个接韭菜的人) 注册端口
DatagramSocket socket = new DatagramSocket(22222);
// 2、创建一个数据包对象,用于接收数据的(创建一个韭菜盘子)
byte[] buffer = new byte[1024 * 64]; // 64KB.
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (true) {
// 3、开始正式使用数据包来接收客户端发来的数据
socket.receive(packet);
// 4、从字节数组中,把接收到的数据直接打印出来
// 接收多少就倒出多少
// 获取本次数据包接收了多少数据。
int len = packet.getLength();
String rs = new String(buffer, 0 , len);
System.out.println(packet.getAddress().getHostAddress()+":"+rs);
System.out.println("--------------------------------------");
}
}
}
作业
🚩 1. 参考下列提示,完成UDP入门案例(1发1接)
- 发送端
/**
* 目标:完成UDP通信快速入门:实现1发1收。
*
*
* 1. 创建客户端对象(发韭菜出去的人) DatagramSocket
* 2. 创建数据包对象封装要发出去的数据(创建一个韭菜盘子) DatagramPacket
* 3. 开始正式发送这个数据包的数据出去了 send
* 4. 释放资源! close
*
*/
public class Client {
public static void main(String[] args) throws Exception {
// 书写代码
}
}
- 接收端
/**
* 目标:完成UDP通信快速入门-服务端开发
* 1. 创建一个服务端对象(创建一个接韭菜的人) 注册端口 DatagramSocket
* 2. 创建一个数据包对象,用于接收数据的(创建一个韭菜盘子) DatagramPacket(数组,数组长度)
* 3. 开始正式使用数据包来接收客户端发来的数据 receive(包裹)
* 4. 从包裹中获得数据,并且解析成String(byte数组,0,包裹的长度)
* 5. 释放资源
*/
public class Server {
public static void main(String[] args) throws Exception {
// 书写代码
}
}
四、TCP通信代码(案例) 🚩 ✏️
学习TCP通信的代码完成网络通讯
前置知识:
- socket协议是面向连接、通讯可靠的协议
- socket协议需要三次握手建立连接,断开需要4次
Socket完成TCP通信的流程:
- 当创建Socket对象时,就会在客户端和服务端创建一个数据通信的管道,在客户端和服务端两边都会有一个Socket对象来访问这个通信管道。
- 现在假设客户端要发送一个
在一起
给服务端,客户端这边先需要通过Socket对象获取到一个字节输出流,通过字节输出流写数据到服务端 - 服务端这边通过Socket对象可以获取字节输入流,通过字节输入流就可以读取客户端写过来的数据,并对数据进行处理。
- 服务端处理完数据之后,假设需要把
没感觉
发给客户端端,那么服务端这边再通过Socket获取到一个字节输出流,将数据写给客户端 - 客户端这边再获取输入流,通过字节输入流来读取服务端写过来的数据。
注意:写代码之前一定要理解上图,不然输入和输出流,容易晕!
1️⃣ 4.1 TCP入门程序 说一句话
/**
* 目标:完成TCP通信快速入门-客户端开发:实现1发1收。
*/public class Client {
public static void main(String[] args) throws Exception {
// 1、创建Socket对象,并同时请求与服务端程序的连接。(ip和端口是服务端的)
Socket socket = new Socket("127.0.0.1", 22222);
// 2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序。
OutputStream os = socket.getOutputStream();
// 4、开始写数据出去了
os.write("在一起,好吗?".getBytes());
os.close();
socket.close(); // 释放连接资源
}
}
/**
* 目标:完成TCP通信快速入门-服务端开发:实现1发1收。
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-------");
// 1、创建ServerSocket的对象,同时为服务端注册端口。
ServerSocket serverSocket = new ServerSocket(22222);
// 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输入流。
InputStream is = socket.getInputStream();
// 4创建一个byte数组接收数据
byte[] buffer=new byte[1024];
// 5、使用数据输入流读取客户端发送过来的消息
// 获得数据长度
int msglen = is.read(buffer);
String msg= new String(buffer, 0, msglen);
System.out.println(socket.getRemoteSocketAddress()+":"+msg);
System.out.println("---------------------------------");
is.close();
socket.close();
}
}
2️⃣ 4.2 TCP入门程序 说多句话
实现客户端可以反复的发消息出去,实现服务端反复接收消息
- 反复发,反复接收消息,说明要用到循环----> 死循环
- 死循环说明程序无法停止,输入exit出发break 跳出循环,结束程序
/**
* 目标:完成TCP通信快速入门-客户端开发:实现客户端可以反复的发消息出去
*/public class Client {
public static void main(String[] args) throws Exception {
// 1、创建Socket对象,并同时请求与服务端程序的连接。(ip和端口是服务端的)
Socket socket = new Socket("127.0.0.1", 22222);
// 2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序。
OutputStream os = socket.getOutputStream();
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 一旦用户输入了exit,就退出客户端程序
if("exit".equals(msg)){
System.out.println("欢迎您下次光临!退出成功!");
os.close();
socket.close();
break;
}
// 4、开始写数据出去了
os.write(msg.getBytes());
os.flush();
}
}
}
/**
* 目标:完成TCP通信快速入门-服务端开发:实现服务端反复发消息
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-------");
// 1、创建ServerSocket的对象,同时为服务端注册端口。
ServerSocket serverSocket = new ServerSocket(22222);
// 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输入流。
InputStream is = socket.getInputStream();
while (true) {
// 4创建一个byte数组接收数据
byte[] buffer=new byte[1024];
try {
// 5、使用输入流读取客户端发送过来的消息
int msglen = is.read(buffer);
String msg= new String(buffer, 0, msglen);
System.out.println(socket.getRemoteSocketAddress()+":"+msg);
System.out.println("---------------------------------");
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "离线了!");
is.close();
socket.close();
break;
}
}
}
}
3️⃣ 4.3 TCP入门程序 线程池优化 🍐 ✏️
为了让服务端能够支持多个客户端通信,就需要用到多线程技术。具体的实现思路如下图所示:每当有一个客户端连接服务端,在服务端这边就为Socket开启一条线程取执行读取数据的操作,来多少个客户端,就有多少条线程。按照这样的设计,服务端就可以支持多个客户端连接了。
/**
* 目标:完成TCP通信快速入门-客户端开发:实现客户端可以反复的发消息出去
* 1. 和上述案例一样
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1、创建Socket对象,并同时请求与服务端程序的连接。(ip和端口是服务端的)
Socket socket = new Socket("127.0.0.1", 22222);
// 2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序。
OutputStream os = socket.getOutputStream();
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 一旦用户输入了exit,就退出客户端程序
if("exit".equals(msg)){
System.out.println("欢迎您下次光临!退出成功!");
os.close();
socket.close();
break;
}
// 4、开始写数据出去了
os.write(msg.getBytes());
os.flush();
}
}
}
/**
* 目标:完成TCP通信快速入门-服务端开发:要求实现与多个客户端同时通信。
* 1. 在死循环内,使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
* 2. 使用线程池处理多个人的消息(发送端不变)
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-------");
// 1、创建ServerSocket的对象,同时为服务端注册端口。
ServerSocket serverSocket = new ServerSocket(22222);
ExecutorService executorService = Executors.newFixedThreadPool(5);
while (true) {
// 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
System.out.println("有人上线了:" + socket.getRemoteSocketAddress());
// 3、把这个客户端对应的socket通信管道,交给一个独立的线程负责处理。
executorService.submit(new ServerReaderRunnable(socket));
}
}
}
/**
* 读取消息的执行程序
*/
public class ServerReaderRunnable implements Runnable {
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
byte[] bytes=new byte[1024];
while (true) {
// 使用数据输入流读取客户端发送过来的消息
try {
int len = is.read(bytes); //传过来的数据长度
String s = new String(bytes, 0, len);
System.out.println(Thread.currentThread().getName()+"线程负责:"+socket.getRemoteSocketAddress()+":"+s);//打印一下
} catch (Exception e) {
System.out.println("bbq:");
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}