目录
一、网络编程
1. 常用命令
ipconfig---查看本机ip地址
ping 域名/地址---检查指定域名网络是否连接正常,查看延迟
特殊IP地址127.0.0.1: 称为"回地址"也称"本机回环地址", 代表本机的IP地址, 测试使用
2. 网络编程三要素
网络编程三要素
1. IP地址: 设备在网络中的地址, 是一个唯一标识
2. 端口号: 应用程序在设备中的唯一标识
3. 协议: 数据在网络中传输的规则, 常见协议有UDP协议和TCP协议
IP
IP全称"互联网协议地址", 也称"IP地址", 是分配给上网设备的数字标签
常见的IP分类为IPv4和IPv6
平时我们用的是域名呀, 怎么不是IP地址?
计算机 -> 域名 -> DNS服务器 -> DNS服务器解析域名为IP -> 返回给计算机
计算机 -> IP -> 目标服务器 -> 目标服务器返回数据
端口
端口的注意事项:
1. 端口号用两个字节(整数)表示, 取值范围是0-65535
2. 其中0-1024的被一些知名的网络服务或者应用占用
3. 我们自己使用1024-65535之间的即可
4. 一个端口号只能被一个应用程序使用,
协议
计算机网络中, 连接和通信的规则被称为网络"通信协议"
络通信协议的分类
1. UDP协议: 用户数据报协议, 面向无连接, 速度快一次最多发送64K, 数据不安全易丢失
2. TCP协议: 传输控制协议, 面向连接, 速度慢没有大小限制, 数据安全
3. Inetaddress类
Inetaddress类作用?
为了方便我们对IP地址的获取和操作, Inetaddress也表示Internet协议地址(IP地址)Inetaddress没有对外提供构造方法, 方法有静态和非静态的
对于这种类, 都会提供一个静态方法返回该类对象
常用方法
1. static Inetaddress getByName(String host); 获取主机名的IP对象
2. String getHostName(); 获取主机名
3. String getHostAddress(); 获取字符串类型的IP地址
代码示例
public class MyInetAddress {
public static void main(String[] args) throws UnknownHostException {
InetAddress address = InetAddress.getByName("lihuan");
System.out.println(address); //lihuan/10.254.2.93
System.out.println("地址"+address.getHostAddress()); //地址10.254.2.93
System.out.println("主机名"+address.getHostName()); //主机名lihuan
}
}
二、UDP协议
2.1 UDP发送端
UDP发送步骤:
1. 创建发送端的DatagramSocket对象
2. 创建数组, 并将数据使用DatagramPacket打包
3. 调用DatagramSocket的send方法发送数据
4. 释放资源
代码示例
public class ClientDemo {
public static void main(String[] args) throws IOException {
//1. 创建发送端的DatagramSocket对象
DatagramSocket ds = new DatagramSocket();
//2.创建数据
String message = "你好,我是小李";
//3.将字符串转换为字节数组形式
byte[] bytes = message.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
//4.设置端口
int port = 10086;
//5.将数据使用DatagramPacket打包
//DatagramPacket(字节数组,内容长度,InetAddress对象,端口号)
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
//6.调用DatagramSocket的send方法发送打包好的数据
ds.send(dp);
//7.释放资源
ds.close();
}
}
2.2 UDP接收端
UDP接收数据步骤:
1. 创建接收端的DatagramSocket对象, 指定接收数据的端口
2. 创建一个新箱子DatagramPacket, 用于接收数据
3. 调用DatagramSocket的receive方法接收数据, 放入新箱子中
4. 解析数据包, 将数据展示在控制台
5. 释放资源
代码示例
public class ServerDemo {
public static void main(String[] args) throws IOException {
//1.创建接收端的DatagramSocket对象,传入发送端的端口
DatagramSocket ds = new DatagramSocket(10086);
//2.创建数组,将数据放进数组
byte[] bytes = new byte[1024];
//3.创建新的包,将这个数组放进新的包
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
System.out.println("-------正在接收数据------");
//4.接收数据报
ds.receive(dp);
System.out.println("-------已接收到数据------");
//String s= new String(bytes, 0, dp.getLength());
//5.解析数据,并将数据打印在控制台
System.out.println(new String(bytes, 0, dp.getLength()));
//6.释放资源
ds.close();
}
}
2.3 练习
需求:
UDP发送端发送键盘录入的数据, 直到输入"886", 发送数据结束
UDP接收端由于不知道什么时候发送停止, 使用死循环一直接收
1. 发送端
代码示例
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建接收端的DatagramSocket对象
DatagramSocket ds = new DatagramSocket();
System.out.println("请输入要发送的内容");
Scanner sc= new Scanner(System.in);
while (true) {
String message = sc.nextLine();
if(message.equals("888")) {
break;
}
//将输入的内容转换为字节数组
byte[] bytes = message.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 10086;
//创建DatagramPacket对象,将数组放入包中
//DatagramPacket(字节数组,内容长度,InetAddress对象,端口号)
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
//发送数据
ds.send(dp);
}
}
}
2. 接收端
代码示例
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建DatagramSocket接收端对象,将端口放进去
DatagramSocket ds = new DatagramSocket(10086);
//要持续接收数据,所以使用死循环
while (true) {
//创建新的包,放入数据
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
System.out.println("-------正在接收数据------");
//接收数据
ds.receive(dp);
System.out.println("-------已接收到数据------");
//String s= new String(bytes, 0, dp.getLength());
//解析数据
System.out.println(new String(bytes, 0, dp.getLength()));
}
}
}
2.4 UDP三种通讯方式
1. 单播: 一个发送端 -> 路由器 -> 一个接收端
2. 组播: IPv4的概念, 在IPv6中称为"多播", 一个发送端 -> 路由器 -> 一组接收端
3. 广播: 一个发送端 -> 路由器 -> 所有接收端
2.4.1. 单播
以前写的都是单播方式,只有一个接收端
2.4.2. 组播
UDP的组播
发送端: 需要指定组播地址, 而不是端口号
组播地址: 224.0.0.0-239.255.255.255
预留地址: 224.0.0.0-224.0.0.255(计算机用了我们不能用)从224.0.1.0开始朝后
接收端: 有一组计算机, 指定IP
1. 组播发送端
组播发送端步骤
1. 创建组播发送端的DatagramSocket对象
2. 创建数组, 并将数据使用DatagramPacket打包, 这次InetAddress对象为组播地址
3. 调用DatagramSocket的send方法发送数据
4. 释放资源
代码示例
public class ClientDemo {
public static void main(String[] args) throws IOException {
//1.创建组播发送端的DatagramSocket对象
DatagramSocket ds = new DatagramSocket();
//2.创建字节数组,并将数据使用DatagramPacket打包,InetAddress对象为组播地址
InetAddress address = InetAddress.getByName("224.0.1.0");
int port = 10086;
byte[] bytes ="组播发送".getBytes();
//3.调用DatagramSocket的send方法发送数据
DatagramPacket dp = new DatagramPacket(bytes,0,bytes.length,address,port);
//4.调用DatagramSocket的send方法发送数据
ds.send(dp);
ds.close();
}
}
2. 组播接收端
组播接收端步骤
1. 创建组播接收端的MulticastSocket对象, 指定接收数据的端口
2. 创建一个新箱子DatagramPacket, 用于接收数据
3. 使用MulticastSocket的joinGroup方法, 指定组播地址
4. 调用MulticastSocket的receive方法接收数据, 放入新箱子中
5. 解析数据包, 将数据展示在控制台
6. 释放资源
代码示例
public class ServerDemo {
public static void main(String[] args) throws IOException {
//1.创建组播接收端的MulticastSocket对象,指定接收端的端口
MulticastSocket ms = new MulticastSocket(10086);
//2.创建新的包DatagramPacket,接收数据
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
//3.使用MulticastSocket的joinGroup方法,指定组播地址
ms.joinGroup(InetAddress.getByName("224.0.1.0"));
//4.解析数据包,打印数据
ms.receive(dp);
System.out.println(new String(bytes,0,dp.getLength()));
//5.释放资源
ms.close();
}
}
2.4.3 广播
UDP的广播
广播地址255.255.255.255
1. 发送端
广播发送端步骤
1. 创建广播发送端的DatagramSocket对象
2. 创建数据, 并将数据使用DatagramPacket打包, InetAddress对象为广播地址
3. 调用DatagramSocket的sent方法发送数据
4. 释放资源
代码示例
public class ClientDemo {
public static void main(String[] args) throws IOException {
//1.创建广播发送端的DatagramSocket对象
DatagramSocket ds = new DatagramSocket();
//2.创建数组,并将数据使用DatagramPacket打包,InetAddress对象为广播地址
byte[] bytes = "广播发送".getBytes();
DatagramPacket dp = new DatagramPacket(bytes,bytes.length, InetAddress.getByAddress("255.255.255.255"),10086);
//3.调用DatagramSocket的send方法发送数据
ds.send(dp);
//4.释放资源
ds.close();
}
}
2. 接收端
广播接收端步骤
1. 创建接收端的DatagramSocket对象, 指定接收数据的端口
2. 创建一个新箱子DatagramPacket, 用于接收数据
3. 调用DatagramSocket的receive方法接收数据, 放入新箱子中
4. 解析数据包, 将数据展示在控制台
5. 释放资源
代码示例
public class ServerDemo {
public static void main(String[] args) throws IOException {
//1.创建接收端的DatagramSocket对象, 指定接收数据的端口
DatagramSocket ds= new DatagramSocket(10086);
//2.创建一个新的DatagramPacket,接收数据
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
//3.调用DatagramSocket的receive方法接收数据,并放入新的包
ds.receive(dp);
//4.解析数据包,并打印
System.out.println(new String(bytes,0,dp.getLength()));
//5.释放资源
ds.close();
}
}
三、TCP协议
TCP通信原理
TCP通信协议是一种可靠的网络协议, 它在通信的两端各建立一个Socket对象
通信之前要保证连接已经建立才可以, 否则报错
通过Socket产生IO流来进行网络通信
3.1 TCP发送端
TCP发送端(客户端)步骤
1.创建一个客户端对象Socket ,构造方法绑定服务器的IP地址和端口号2.使用Socket 对象中的方法getOutputStream()获取网络字节输出流Outputstream 对象
3.使用网络字节输出流Outputstream 对象中的方法write() ,给服务器发送数据
4.使用Socket 对象中的方法getInputStream()获取网络字节输入流Inputstream)对象
5.使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
6.释放资源Socket
注意:
1.客户端和服务器端进行交互,必须使用Socket 中提供的网络流,不能使用自己创建的流对象
2.当我们创建客户端对象Socket 的时候,就会去请求服务器和服务器经过3次握手建立连接通路 这时如果服务器没有启动,那么就会抛出异常 如果服务器已经启动,那么就可以进行交互了
代码示例
public class ClientDemo {
public static void main(String[] args) throws IOException {
//1.创建服务端Socket对象,指定端口
Socket socket =new Socket("127.0.0.1",10086);//给定目标主机和端口
//2.获取输出流,写对象
//通过socket对象获取输出流,写数据
OutputStream os = socket.getOutputStream();
//3.获取输入流,读取数据
os.write("hello".getBytes());
//4.释放资源
socket.close();
os.close();
}
}
3.2 TCP接收端
服务器端必明确一件事情,必的知道是哪个客端请求的服务器 所以可以使用accept 方法获取到请求的客端对象Socket 成员方法 : Socket accept ()听并接受到此套接字的连接。
TCP接收端(服务器)步骤
1.创建服务器ServerSocket 对象和系统要指定的端口号2.使用ServerSocket)对象中的方法accept ,获取到请求的客端对象Socket
3.使用Socket对象中的方法getInputStream ()获取网络字节输入流InputStream 对象
4.使用网络字节输入流InputStream 对象中的方法read,读取客户端发送的数据
5.使用对象中的方法getOutputStream ()获取网络节输出流OutputStream 对象
6.使用网络字节输出流OutputStream 对象中的方法write 给客端回写数据
7.释放资源(Socket ,ServerSocket )
代码示例
public class ServerDemo {
public static void main(String[] args) throws IOException {
//1.创建服务端ServerSocket对象,指定端口
//通过ServerSocket对象,监听是否有客户连接
ServerSocket ss = new ServerSocket(10086);
//2.等待客户连接
//通过accept对象获取输入流,读数据
Socket accept =ss.accept();//accept是你阻塞的,如果等不到连接,会一直等
//3.获取输入流,读取数据
InputStream is = accept.getInputStream();
int b;
while ((b=is.read())!=-1) {
System.out.print((char)b);
}
//4.释放资源
ss.close();
is.close();
accept.close();
}
}
3.3 TCP原理分析
TCP通信原理分析及注意
1. accept方法是阻塞的, 作用就是等待客户端连接
2. 客户端创建对象并连接服务器, 此时是通过"三次握手"保证跟服务器之间的连接
3. 针对客户端讲是往外写(输出流), 针对服务器是往里读(输入流)
4. read方法也是阻塞的
5. 客户端在关流时, 会写入一个结束标记作为终止, 服务器读到结束标记才会结束读的动作
6. 客户端最后断开连接时, 通过"四次挥手"来保证连接终止
3.4 TCP的三次握手
TCP三次握手: 应用于客户端创建对象并连接服务器时
第一次: 客户端向服务器发出请求, 等待服务器确认
第二次: 服务器向客户端反馈响应, 告知客户端收到了请求
第三次: 客户端向服务器再次发出确认信息, 建立连接
3.5 TCP 的四次挥手
TCP四次挥手: 客户端最后断开连接时
第一次: 客户端向服务器发出取消连接的请求
第二次: 服务器向客户端反馈响应, 告知客户端收到了取消连接的请求
第三次: 服务器向客户端再次发出确认取消连接的请求信息 (已经存在了连接, 做数据最后的处理)
第四次: 客户端向服务器再次发出确认取消信息, 取消连接
3.6 练习1
练习1需求:
客户端发送数据, 接收服务器反馈
服务器接收数据, 给出反馈
1. 发送端
代码示例
public class ClientDemo {
public static void main(String[] args) throws IOException {
//1.创建客户端Socket对象,将ip地址和端口传入
Socket socket = new Socket("127.0.0.1",10086);
//2.通过Socket对象获取字节输出流,发送数据
OutputStream os = socket.getOutputStream();
//3.将字符串转换为字节数组,写入输出流对象
os.write("hello".getBytes());
/*String s= "hello";
byte[] bytes = s.getBytes();
System.out.println(Arrays.toString(bytes));
[104, 101, 108, 108, 111]*/
//关闭输出流, 写一个结束标记, 对Socket无影响
socket.shutdownOutput();
//字符流读取反馈数据
BufferedReader br =new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while ((line = br.readLine())!=null){
System.out.println(line);
}
socket.close();
os.close();
br.close();
}
}
2. 接收端
ServerSocket的accept()方法从连接请求队列中取出一个客户的连接请求,然后创建与客户连接的Socket对象,并将它返回。
如果队列中没有连接请求,accept()方法就会一直等待,直到接收到了连接请求才返回。
接下来,服务器从Socket对象中获得输入流和输出流,就能与客户交换数据。
当服务器正在进行发送数据的操作时,如果客户端断开了连接,那么服务器端会抛出一个IOException的子类SocketException异常
代码示例
public class ServerDemo {
public static void main(String[] args) throws IOException {
//1.创建服务器端ServerSocket对象,将端口传入
ServerSocket ss = new ServerSocket(10086);
//2.等待连接
Socket accept = ss.accept();
//3.获取输入流对象,读取数据
InputStream is = accept.getInputStream();
int b;
while ((b = is.read())!=-1){
System.out.print((char)b);
}
//4.字符流写反馈数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bw.write("你谁啊");
bw.newLine();
bw.flush();
//5.释放资源
is.close();
bw.close();
accept.close();
ss.close();
}
}
3.7 练习2
练习2需求:
客户端将本地文件上传到服务器, 接收服务器的反馈
服务器接收客户端上传的文件, 上传完毕后给出反馈
1. 客户端
代码示例
public class ClientDemo {
public static void main(String[] args) throws IOException {
//1.创建客户端Socket对象,指定地址和端口
Socket socket =new Socket("127.0.0.1",10086);
//2.创建输入流,读取本地文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("day15\\1.png"));
//3.网络中的输出流,将数据写到服务器
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
int b;
while ((b=bis.read())!=-1){
bos.write(b);
}
//4.给服务器一个结束标记,表示文件已经传输完毕
socket.shutdownOutput();
//5.网络中的输入流,接收反馈并打印
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while ((line=br.readLine())!=null){
System.out.println(line);
}
//6.释放资源
socket.close();
bis.close();
}
}
2. 服务端
代码示例
public class ServerDemo {
public static void main(String[] args) throws IOException {
//1.创建服务端ServerSocket对象
ServerSocket ss = new ServerSocket(10086);
//2.监听连接
Socket accept = ss.accept();
//3.网络中的输入流,从客户端读取数据
BufferedInputStream bis =new BufferedInputStream(accept.getInputStream());
//4.本地的输出流,将接收的数据写到本地
BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream("day15\\copy.jpg"));
int b;
while ((b=bis.read())!=-1){
bos.write(b);
}
//5.网络中的输出流,给出反馈
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
//6.释放资源
ss.close();
accept.close();
bos.close();
}
}
四、服务端优化
4.1 循环优化
循环优化案例: 服务器一直接收数据, 客户端可以多次上传
1. 客户端没有变化
2. 服务端
服务端接收数据加入while循环
代码示例
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10086);
while (true) {
Socket accept = ss.accept();
BufferedInputStream bis =new BufferedInputStream(accept.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream("day15\\copy.jpg"));
int b;
while ((b=bis.read())!=-1){
bos.write(b);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
accept.close();
bos.close();
}
//ss.close();
}
}
4.2 UUID优化
UUID优化案例: 在服务端的文件名,中加入随机数,可以使多次运行代码后产生的文件不会被覆盖
UUID是Java提供的类, 可以生成一个随机且唯一的值 (可能重复,但几率太小, 可以忽略不计)
1. public static UUID randomUUID(); 生成一个随机值
2. toString(); 返回随机值的字符串表现形式
1. 客户端没有变化
2. 接收端
代码示例
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10086);
while (true) {
Socket accept = ss.accept();
BufferedInputStream bis =new BufferedInputStream(accept.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream("day15\\"+ UUID.randomUUID().toString().replace("-","") +".jpg"));
int b;
while ((b=bis.read())!=-1){
bos.write(b);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
accept.close();
bos.close();
}
}
//ss.close();
}
4.3 多线程优化
多线程优化案例: 通过多线程实现多个客户端同时上传文件
1. 客户端没有变化
2. 服务端
代码示例
public class ServerDemo {
public static void main(String[] args) throws IOException {
//1.创建客户端ServerSocket对象,传入端口
ServerSocket ss = new ServerSocket(10086);
//2.循环接收数据
while (true) {
Socket accept = ss.accept();
//3.创建线程类
ThreadSocket ts = new ThreadSocket(accept);
//4.开启线程,
new Thread(ts).start();
}
}
}
3. 创建Runable 的实现类ThreadSocket
代码示例
public class ThreadSocket implements Runnable {
private Socket accept;
public ThreadSocket(Socket accept) {
this.accept = accept;
}
@Override
public void run() {
BufferedOutputStream bos = null;
try {
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
bos = new BufferedOutputStream(new FileOutputStream("day15\\" + UUID.randomUUID().toString().replace("-", "") + ".jpg"));
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
accept.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bos != null) {
try {
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (accept != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
4.4 线程池优化
线程池优化案例:
1. 客户端没有变化
2. Runable 的实现类ThreadSocket没有变化
3. 服务端
代码示例
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10086);
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,
10,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
while (true) {
Socket accept = ss.accept();
ThreadSocket ts = new ThreadSocket(accept);
pool.submit(ts);
}
}
}