【JavaSE】网络编程:IO演进之路、多线程版BIO、线程池版BIO

尚硅谷JavaSE笔记合集

文章名链接
【JavaSE】异常文章地址
【JavaSE】常用类:String、LocalDateTime…文章地址
【JavaSE】枚举文章地址
【JavaSE】注解文章地址
【JavaSE】集合框架文章地址 | HashMap源码解析 | List相关实现类源码解析
【JavaSE】泛型文章地址
【JavaSE】IO流文章地址 | 字符编码详解
【JavaSE】网络编程,BIO需求演进文章地址
【JavaSE】反射文章地址
【JavaSE】jdk8新特性文章地址

一、I/O演进之路

1.1 基本说明

I/O模型:就是用什么样的通道或者说是通信模式和架构进行数据的传输和接收,很大程度上决定了程序 通信的性能 , Java共支持3种网络编程的I/O模型:BlO、NIO、AlO

实际通信需求下,要根据不同的业务场景和性能需求决定选择不同的I/O模型

1.2 I/O模型

1.2.1 BIO(同步并阻塞)

  • 服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理
  • 如果这个连接不做任何事情会造成不必要的线程开销

在这里插入图片描述

1.2.2 NIO(同步非阻塞)

  • 服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器
  • 多路复用器轮询到连接有I/O请求就进行处理

在这里插入图片描述

1.2.3 AIO(异步非阻塞)

  • AIO 也就是 NIO 2,在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是说 AIO 模式不需要selector 操作,而是是事件驱动形式,也就是当客户端发送数据之后,会主动通知服务器,接着服务器再进行读写操作。
  • 一般适用于连接数较多且连接时间较长的应用

1.3 适用场景分析

  • BIO:连接数目少且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用 中, jDK1.4以前的唯一选择,但程序简单易理解。
  • NIO:连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器 间通讯等。 编程比较复杂,jDK1 .4开始支持。
  • AIO:连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并 发操作, 编程比较复杂,JDK7开始支持。

二、网络通信要素

2.1 概述

/**
 * 一、网络编程中有两个主要的问题:
 * 	1.如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
 * 	2.找到主机后如何可靠高效地进行数据传输
 *
 * 二、网络编程中的两个要素:
 * 	1.对应问题一:IP和端口号
 * 	2.对应问题二:提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)
 */
  • 通信双方地址:IP、端口号

  • 一定的规则(即:网络通信协议。有两套参考模型)

    • OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广
    • TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。

在这里插入图片描述

在这里插入图片描述

2.2 Socket

2.2.1 IP-InetAddress

  • IP 地址:唯一的标识 Internet 上的计算机(通信实体)

    • 本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost
    • IP地址分类方式1:IPV4和IPV6
      • IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如192.168.0.1
      • IPV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示,数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
    • IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。192.168.开头的就是私有地址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用
    • 特点:不易记忆
  • Internet上的主机有两种方式表示地址:

    • 域名(hostName):www.atguigu.com
    • IP地址(hostAddress):202.108.35.210
  • InetAddress类对象含有一个Internet主机地址的域名和IP地址:www.atguigu.com和202.108.35.210。

  • 域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,这样才能和主机建立连接。-------域名解析

在这里插入图片描述

/**
 * IP+端口号:得出一个网络套接字-Socket
 *
 *	- InetAddress类表示IP地址,两个子类:Inet4Address、Inet6Address
 *		1.InetAddress实例化:getByName(String host) 、 getLocalHost()
 *		2.InetAddress常用方法:getHostName() / getHostAddress()
 *
 * 	- 端口号:正在计算机上运行的进程。
 * 		要求:不同的进程有不同的端口号
 * 		范围:被规定为一个 16 位的整数 0~65535。
 *
 */
public class InetAddressTest {
    @Test
    public void test1() {
        try {
            //1.InetAddress实例化
            InetAddress local1=InetAddress.getByName("127.0.0.1");
            InetAddress local2 = InetAddress.getByName("192.168.10.31");
            InetAddress local3=InetAddress.getLocalHost();
            InetAddress local4 = InetAddress.getByName("www.atguigu.com");
            System.out.println(local1); //127.0.0.1:/127.0.0.1
            System.out.println(local2); //192.168.10.31:/192.168.10.31
            System.out.println(local3); //LAPTOP-81H2I9OV/192.168.100.1
            System.out.println(local4); //www.atguigu.com/111.12.25.237
            //2.InetAddress常用方法
            System.out.println(local1.getHostAddress()); //127.0.0.1
            System.out.println(local1.getHostName()); //activate.navicat.com
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }
}

2.2.2 端口号

  • 端口分类:

    • 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
    • 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
    • 动态/私有端口:49152~65535
  • 端口号与IP地址的组合得出一个网络套接字:Socket

/**
 * 	端口号:标识正在计算机上运行的进程
 * 		要求:不同的进程有不同的端口号
 * 		范围:被规定为一个 16 位的整数 0~65535。
 */

2.3 网络协议

2.3.1 概述

  • 网络通信协议

    计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。

  • 问题:网络协议太复杂

    计算机网络通信涉及内容很多,比如指定源地址和目标地址,加密解密,压缩解压缩,差错控制,流量控制,路由控制,如何实现如此复杂的网络协议呢?

  • 通信协议分层的思想

    在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系。各层互不影响,利于系统的开发和扩展。

2.3.2 TCP和UDP

  • 传输层协议中有两个非常重要的协议:

    • 传输控制协议TCP(Transmission Control Protocol)
    • 用户数据报协议UDP(User Datagram Protocol)。
  • TCP/IP 以其两个主要协议:传输控制协议(TCP)和网络互联协议(IP)而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。

  • IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信。

  • TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即物理链路层、IP层、传输层和应用层。

  • TCP协议:

    • 使用TCP协议前,须先建立TCP连接,形成传输数据通道
    • 传输前,采用“三次握手”方式,点对点通信,是可靠的
    • TCP协议进行通信的两个应用进程:客户端、服务端。
    • 在连接中可进行大数据量的传输传输完毕,需释放已建立的连接,效率低

在这里插入图片描述

在这里插入图片描述

  • UDP协议:

    • 将数据、源、目的封装成数据包,不需要建立连接
    • 每个数据报的大小限制在64K内
    • 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
    • 可以广播发送
    • 发送数据结束时无需释放资源,开销小,速度快

三、TCP网络编程

客户端-服务端是完全同步,完全藕合的

3.1 单发单收

/**
 * 服务端:客户端发送消息,服务端接收消息
 */
public class Server {
    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动===");
            //1.定义一个ServerSocket对象进行服务端的端口注册
            ServerSocket ss = new ServerSocket(9999);
            //2. 监听客户端的Socket连接请求
            Socket socket = ss.accept();
            //3.从socket管道中得到一个字节输入流对象
            InputStream is = socket.getInputStream();
            //4.把字节输入流包装成一个缓存字符输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            /*while ((msg = br.readLine()) != null){
                System.out.println("服务端接收到:" + msg);
            }*/
            if ((msg = br.readLine()) != null){
                System.out.println("服务端接收到:" + msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        try {
            //1.创建Socket对象请求服务端的连接
            Socket socket = new Socket("127.0.0.1",9999);
            //2.从Socket对象中获取一个字节输出流
            OutputStream os = socket.getOutputStream();
            //3.把字节输出流包装成一个打印流
            PrintStream ps = new PrintStream(os);
            //ps.print("hello World! 服务端,你好");
            ps.println("hello World! 服务端,你好");
            ps.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
===服务端启动===
服务端接收到:hello World! 服务端,你好

3.2 多发多收

/**
 * 服务端:服务端可以反复的接收消息,客户端可以反复的发送消息
 */
public class Server {
    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动===");
            //1.定义一个ServerSocket对象进行服务端的端口注册
            ServerSocket ss = new ServerSocket(9999);
            //2. 监听客户端的Socket连接请求
            Socket socket = ss.accept();
            //3.从socket管道中得到一个字节输入流对象
            InputStream is = socket.getInputStream();
            //4.把字节输入流包装成一个缓存字符输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println("服务端接收到:" + msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        try {
            //1.创建Socket对象请求服务端的连接
            Socket socket = new Socket("127.0.0.1",9999);
            //2.从Socket对象中获取一个字节输出流
            OutputStream os = socket.getOutputStream();
            //3.把字节输出流包装成一个打印流
            PrintStream ps = new PrintStream(os);
            Scanner sc = new Scanner(System.in);
            while (true){
                System.out.print("请说:");
                String msg = sc.nextLine();
                ps.println(msg);
                ps.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
客户端:
请说:hello
请说:what are you doing?
请说:

服务端:
===服务端启动===
服务端接收到:hello
服务端接收到:what are you doing?

3.3 多客户端-多线程

/**
 * 服务端:服务端可以实现同时接收多个客户端的Socket通信需求
 * 	 - 服务端每接收到一个客户端socket请求对象之后都交给一个独立的线程来处理客户端的数据交互需求
 */
public class Server {
    public static void main(String[] args) {
        try {
            //1.注册端口
            ServerSocket ss = new ServerSocket(9999);
            //2.定义一个死循环,负责不断的接收客户端的Socket的连接请求
            while(true){
                Socket socket = ss.accept();
                //3.创建一个独立的线程来处理与这个客户端的socket通信需求
                new ServerThreadReader(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**
 * 服务端线程类
 */
public class ServerThreadReader extends Thread {
    private Socket socket;
    public ServerThreadReader(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            //从socket对象中得到一个字节输入流
            InputStream is = socket.getInputStream();
            //使用缓存字符输入流包装字节输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println(msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        try {
            //1.请求与服务端的Socket对象连接
            Socket socket = new Socket("127.0.0.1",9999);
            //2. 得到一个打印流
            PrintStream ps = new PrintStream(socket.getOutputStream());
            //3. 使用循环不断的发送消息给服务端接收
            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();
        }
    }
}
===client1:
请说:
ppp
请说:
你在干嘛?
请说:
我是第一个client
请说:
===client2:
请说:
lll
请说:
还钱!!!
请说:
我是第二个client
请说:
===client3:
请说:
我是第三个client
请说:
===server:
lll
ppp
你在干嘛?
还钱!!!
我是第二个client
我是第一个client
我是第三个client

3.4 文件上传

/**
 * 服务端:服务端开发,可以实现接收客户端的任意类型文件,并保存到服务器端磁盘
 */
public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(8888);
            while(true){
                Socket socket = ss.accept();
                //交给一个独立的线程来处理与这个客户端的文件通信需求
                new ServerReadThread(socket).start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
/**
 * 服务端Socket线程任务类
 */
public class ServerReadThread extends Thread {
    private Socket socket;
    public ServerReadThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            //1.得到一个数据输入流来读取客户端发送过来的数据
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            //2.读取客户端发送过来的文件类型
            String suffix = dis.readUTF();
            System.out.println("服务端已经成功接收到了文件类型:" + suffix);
            //3.定义一个字节输出管道,负责把客户端发来的文件数据写出去
            OutputStream os = new FileOutputStream("C:\\Users\\Lenovo\\Desktop\\server\\" + UUID.randomUUID().toString() + suffix);
            //4.从数据输入流中读取文件数据,写出到字节输出流中去
            byte[] buffer = new byte[1024];
            int len;
            while ((len = dis.read(buffer)) > 0){
                os.write(buffer,0,len);
            }
            os.close();
            System.out.println("服务端接收文件保存成功!");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
/**
 * 客户端:实现客户端上传任意类型的文件数据给服务端保存起来
 */
public class Client {
    public static void main(String[] args) {
        try(InputStream is = new FileInputStream("C:\\Users\\Lenovo\\Desktop\\1.jpg");){
            //1.请求与服务端的Socket连接
            Socket socket = new Socket("127.0.0.1", 8888);
            //2.把字节输出流包装成一个数据输出流(DataOutputStream可以做分段数据发送)
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            //3.先发送上传文件的后缀给服务器
            dos.writeUTF(".jpg");
            //4.把文件数据发送给服务端进行接收
            //InputStream is = new FileInputStream("C:\\Users\\Lenovo\\Desktop\\1.jpg");
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) > 0){
                dos.write(buffer,0,len);
            }
            dos.flush();
            socket.shutdownOutput();//通知服务端,我客户端这边的数据已经发送完毕了
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
服务端已经成功接收到了文件类型:.jpg
服务端接收文件保存成功!

3.5 伪异步-线程池

/**
 * 服务端:伪异步通讯架构-线程池
 *  - 思路:服务端没接收到一个客户端socket请求对象之后都交给一个独立的线程来处理客户端的数据交互需求
 */
public class Server {
    public static void main(String[] args) {
        try {
            //1.注册端口
            ServerSocket ss = new ServerSocket(9999);
            //2.定义一个死循环,负责不断的接收客户端的Socket的连接请求
            //初始化一个线程池对象
            HandlerSocketServerPool pool = new HandlerSocketServerPool(3,10);
            while(true){
                Socket socket = ss.accept();
                //3.把socket对象交给一个线程池进行处理
                //把socket封装成一个任务对象交给线程池处理
                Runnable target = new ServerRunnableTarget(socket);
                pool.execute(target);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**
 * 线程池处理类
 */
public class HandlerSocketServerPool {
    //1. 创建一个线程池的成员变量用于存储一个线程池对象
    private ExecutorService executorService;
    //2.初始化线程池对象
    public HandlerSocketServerPool(int maxThreadNum, int queueSize){
        executorService = new ThreadPoolExecutor(3,maxThreadNum,120, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(queueSize));
    }
    //3.提供一个方法来提交任务给线程池的任务队列来暂存,等待线程池来处理
    public void execute(Runnable target){
        executorService.execute(target);
    }
}
/**
 * Socket任务类
 */
public class ServerRunnableTarget implements Runnable {
    private Socket socket;
    public ServerRunnableTarget(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        //处理接收到的客户端socket通信需求
        try {
            //1.从socket管道中得到一个字节输入流对象
            InputStream is = socket.getInputStream();
            //2.把字节输入流包装成一个缓存字符输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while((msg = br.readLine()) != null){
                System.out.println("服务端收到:" + msg);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        try {
            //1.请求与服务端的Socket对象连接
            Socket socket = new Socket("127.0.0.1",9999);
            //2. 得到一个打印流
            PrintStream ps = new PrintStream(socket.getOutputStream());
            //3. 使用循环不断的发送消息给服务端接收
            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();
        }
    }
}

服务端收到:client1
服务端收到:client2
服务端收到:client3
服务端收到:client4
java.net.SocketException: Connection reset
at java.base/java.net.SocketInputStream.read(SocketInputStream.java:186)
at java.base/java.net.SocketInputStream.read(SocketInputStream.java:140)
at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at java.base/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at java.base/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
at java.base/java.io.InputStreamReader.read(InputStreamReader.java:185)
at java.base/java.io.BufferedReader.fill(BufferedReader.java:161)
at java.base/java.io.BufferedReader.readLine(BufferedReader.java:326)
at java.base/java.io.BufferedReader.readLine(BufferedReader.java:392)
at com.zhangxudong.ServerRunnableTarget.run(ServerRunnableTarget.java:23)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
服务端收到:client5

3.6 端口转发-群发

/**
 * 服务端:端口转发-保存客户端的Socket
 */
public class Server {
    //定义一个静态集合
    public static List<Socket> allSocketOnLine = new ArrayList<>();
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(9999);
            while (true){
                Socket socket = ss.accept();
                //把登录的客户端socket存入到一个在线集合中去
                allSocketOnLine.add(socket);
                //为当前登录成功的socket分配一个独立的线程来处理与之通信
                new ServerReaderThread(socket).start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
/**
 * 服务端线程处理类
 */
public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            //1.从socket中去获取当前客户端的输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println("服务器收到消息:" + msg);
                //2.服务端接收到了客户端的消息后,需要推送给所有的当前在线的socket
                sendMsgToAllClient(msg,socket);
            }
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("当前有人下线了!");
            //从在线socket集合中移除本socket
            Server.allSocketOnLine.remove(socket);
        }
    }
    /**
     * 把当前客户端发送来的消息推送给全部在线的socket
     * @param msg
     */
    private void sendMsgToAllClient(String msg,Socket socket) throws Exception {
        for(Socket sk : Server.allSocketOnLine){
            //只发送给除自己以外的其他客户端
            if(socket != sk){
                PrintStream ps = new PrintStream(sk.getOutputStream());
                ps.println(msg);
                ps.flush();
            }
        }
    }
}
/**
 * 客户端
 */
public class Client {
    public static void main(String[] args){
        try {
            //1.请求与服务端的Socket对象连接
            Socket socket = new Socket("127.0.0.1", 9999);
            //收消息
            Thread clientThread = new ClientReaderThread(socket);
            clientThread.start();
            while (true){
                //发消息
                OutputStream os = socket.getOutputStream();
                PrintStream ps = new PrintStream(os);
                //3. 使用循环不断的发送消息给服务端接收
                Scanner sc = new Scanner(System.in);
                //System.out.print("client send message:");
                String msg =sc.nextLine();
                ps.println(msg);
                ps.flush();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
/**
 * 客户端线程类
 */
public class ClientReaderThread extends Thread {
    private Socket socket;
    public ClientReaderThread(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try{
            while (true){
                InputStream is = socket.getInputStream();
                //4.把字节输入流包装成一个缓存字符输入流
                BufferedReader br = new BufferedReader(new InputStreamReader(is));
                String msg;
                /* while ((msg = br.readLine()) != null){
                    System.out.println("client recive message:" + msg);
                }*/
                if((msg = br.readLine()) != null){
                    //System.out.println("client recive message:" + msg);
                    System.out.println(msg);
                }
            }
        }catch (Exception e){
        }
    }
}
# 服务端
服务器收到消息:大家好,我是客户端一
服务器收到消息:哈哈哈哈
服务器收到消息:大家好,我是client2
服务器收到消息:嘻嘻嘻嘻
服务器收到消息:hello everyone
服务器收到消息:i am client3

#客户端一
大家好,我是客户端一 --发送
哈哈哈哈 --发送
大家好,我是client2 --接收
嘻嘻嘻嘻 --接收
hello everyone --接收
i am client3 --接收

#客户端二
大家好,我是客户端一 --接收
哈哈哈哈 --接收
大家好,我是client2 --发送
嘻嘻嘻嘻 --发送
hello everyone --接收
i am client3 --接收

#客户端三
大家好,我是客户端一 --接收
哈哈哈哈 --接收
大家好,我是client2 --接收
嘻嘻嘻嘻 --接收
hello everyone --发送
i am client3 --发送

四、UDP网络编程

例题1:发送与接收

/**
 * 这里涉及到的异常,应该使用try-catch-finally处理
 */
public class UdpTest1 {
    @Test
    public void send() throws IOException {
        //1.创建套接字
        DatagramSocket socket=new DatagramSocket();
        //2.创建数据包
        byte[] buf="你好,服务器!".getBytes();
        DatagramPacket packet=new DatagramPacket(buf,0,buf.length,InetAddress.getByName("127.0.0.1"),3222);
        //3.发送数据
        socket.send(packet);
        //4.关闭资源
        socket.close();
    }
    @Test
    public void receive() throws IOException {
        //1.创建套接字
        DatagramSocket socket=new DatagramSocket(3222);
        //2.创建空数据包接收数据
        byte[] buf=new byte[1024];
        DatagramPacket packet=new DatagramPacket(buf,0,buf.length);
        socket.receive(packet);
        //3.显示数据
        System.out.println(new String(buf,0,packet.getLength()));
        //4.关闭资源
        socket.close();
    }
}

例题2:服务端读取图片并发送给客户端,客户端保存图片到本地

/**
 * 这里涉及到的异常,应该使用try-catch-finally处理
 */
public class UdpTest2 {
    @Test
    public void send() throws IOException {
        //1.创建套接字
        DatagramSocket socket=new DatagramSocket();
        //2.读取图片并发送
        FileInputStream fis=new FileInputStream("QQ图片20200223235928.jpg");
        byte[] buf=new byte[1024];
        int length;
        while((length=fis.read(buf))!=-1){
            DatagramPacket packet=new DatagramPacket(buf,0,length, InetAddress.getByName("127.0.0.1"),3222);
            socket.send(packet);
        }
        //4.关闭资源
        fis.close();
        socket.close();
    }
    @Test
    public void receive() throws IOException {
        //1.创建套接字
        DatagramSocket socket=new DatagramSocket(3222);
        //2.接收图片并保存
        FileOutputStream fos=new FileOutputStream("7.jpg");
        byte[] buf=new byte[1024];
        DatagramPacket packet= new DatagramPacket(buf,0,buf.length);
        while(true){
            socket.receive(packet);
            fos.write(buf,0,packet.getLength());
            //当发送方数据塞不满长度1024的字节数组时,即是最后一次传输
            if(packet.getLength()<packet.getData().length){
                break;
            }
        }
        //4.关闭资源
        socket.close();
        fos.close();
    }
}

例题3:客户端给服务器发送文本,服务端会将文本转成大写再返回给客户端

/**
 * 这里涉及到的异常,应该使用try-catch-finally处理
 */
public class UdpTest3 {
    @Test
    public void send() throws IOException {
        //1.创建套接字
        DatagramSocket socket=new DatagramSocket(3333);
        //2.发送文本
        byte[] buf="hello udp!".getBytes();
        DatagramPacket packet=new DatagramPacket(buf,0,buf.length, InetAddress.getByName("127.0.0.1"),3222);
        socket.send(packet);
        //3.接收信息
        DatagramPacket mess= new DatagramPacket(buf,0,buf.length);
        socket.receive(mess);
        System.out.println(new String(buf));
        //4.关闭资源
        socket.close();
    }
    @Test
    public void receive() throws IOException {
        //1.创建套接字
        DatagramSocket socket=new DatagramSocket(3222);
        //2.接收文本
        byte[] buf=new byte[1024];
        DatagramPacket packet= new DatagramPacket(buf,0,buf.length);
        socket.receive(packet);
        String message=new String(buf,0,buf.length).toUpperCase();
        //3.返回给发送方
        DatagramPacket mess=new DatagramPacket(message.getBytes(),0,message.length(), InetAddress.getByName("127.0.0.1"),3333);
        socket.send(mess);
        //4.关闭资源
        socket.close();
    }
}

五、URL编程

5.1 基本使用

/**
 * - URL:统一资源定位符,对应着互联网的某一资源地址
 * - 格式:
 *  	http://127.0.0.1:8080/work/164.jpg?username=subei
 *  	协议   主机名    端口号  资源地址           参数列表
 * - 基本使用
 *		1.getProtocol() :获取协议名
 *		2.getHost() :获取主机名
 *		3.getPort() :获取端口号
 *		4.getPath() :获取文件路径
 *		5.getFile() :获取文件名
 *		6.getQuery() :获取查询名
 */
public class UrlTest {
    @Test
    public void test1(){
        try {
            URL url=new URL("http://localhost:8080/examples/1.jpg?name=1");
            //1:http
            System.out.println(url.getProtocol());
            //2:localhost
            System.out.println(url.getHost());
            //3:8080
            System.out.println(url.getPort());
            //4:/examples/1.jpg
            System.out.println(url.getPath());
            //5:/examples/1.jpg?name=1
            System.out.println(url.getFile());
            //6:name=1
            System.out.println(url.getQuery());

            URL url1=new URL("http://192.168.10.31:8080/examples/1.jpg?name=1");
            System.out.println(url1.getProtocol());
            //2:192.168.10.31
            System.out.println(url1.getHost());
            System.out.println(url1.getPort());
            System.out.println(url1.getPath());
            System.out.println(url1.getFile());
            System.out.println(url1.getQuery());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }
}

5.2 URL资源下载

public class UrlDownloadTest {
    @Test
    public void test(){
        HttpURLConnection connection = null;
        InputStream is = null;
        FileOutputStream fos= null;
        try {
            //1.通过url获取连接
            URL url=new URL("http://localhost:8080/examples/1.jpg?name=1");
            connection = (HttpURLConnection) url.openConnection();
            connection.connect();
            is = connection.getInputStream();
            //2.创建输出流
            fos = new FileOutputStream("8.jpg");
            //3.下载
            byte[] buf=new byte[20];
            int length;
            while((length=is.read(buf))!=-1){
                fos.write(buf,0,length);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关闭资源
            connection.disconnect();
            if (is!=null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos!=null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

5.3 URI、URL、URN

  • URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。而URL是uniform resource locator,统一资源定位符,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。而URN,uniform resource name,统一资源命名,是通过名字来标识资源,比如mailto:java-net@java.sun.com。也就是说,URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL和URN都是一种URI。
  • 在Java的URI中,一个URI实例可以代表绝对的,也可以是相对的,只要它符合URI的语法规则。而URL类则不仅符合语义,还包含了定位该资源的信息,因此它不能是相对的。

在这里插入图片描述

六、解决课前疑惑

BIO、NIO、AIO区别

BIO:
	- 同步并阻塞
	- 客户端-服务端是完全同步,完全藕合的。如果这个连接不做任何事情会造成不必要的线程开销
NIO:
	- 同步非阻塞,客户端的连接请求都会注册到多路复用器上
	- 多路复用器轮询到连接有I/O请求就进行处理
AIO:
	- 异步非阻塞
	- 由操作系统先完成再通知服务器应用启动线程进行处理
  • BIO方式:适用于连接数目比小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用 中, jDK1.4以前的唯一选择,但程序简单易理解。
  • NIO方式:适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器 间通讯等。 编程比较复杂,jDK1 .4开始支持。
  • AIO方式:使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并 发操作, 编程比较复杂,JDK7开始支持。

如何实现响应浏览器的请求?

/**
 * 参考第五章:TCP网络编程服务器、TCP网络编程客户端
 * 		1.TCP网络编程服务端启动后,使用TCP网络编程客户端访问
 * 		2.浏览器请求充当了客户端。实现访问并将访问路径等信息根据http协议封装并输出到服务端
 * 		3.服务端收到请求解析输入的信息,并返回信息(通常是html文件的字符串)
 * 		4.由于拼写html过于繁琐,使用jsp代替
 * 模拟:
 *      1.浏览器访问(客户端):http://localhost:3222/hello?name=123
 *      2.服务器收到(服务端):
 *          GET /hello?name=123 HTTP/1.1
 *          Host: localhost:3222
 *          Connection: keep-alive
 *          sec-ch-ua: "Chromium";v="106", "Microsoft Edge";v="106", "Not;A=Brand";v="99"
 *          sec-ch-ua-mobile: ?0
 *          sec-ch-ua-platform: "Windows"
 *          Upgrade-Insecure-Requests: 1
 *          User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.42
 *          Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,...;q=0.8,application/signed-exchange;v=b3;q=0.9
 *          Sec-Fetch-Site:none
 *          Sec-Fetch-Mode:navigate
 *          Sec-Fetch-User:?1
 *          Sec-Fetch-Dest:document
 *          Accept-Encoding:gzip,deflate,br
 *          Accept-Language:zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
 */

Servlet为什么是Tomcat定义的类,只有引入Tomcat的包才能使用?而不是jdk自带的

Servlet是一个接口,是网络编程的规范。
Servlet对网络编程进行了解耦,让网络编程的开发、维护成本等大大降低。
所以,不能说Servlet是Tomcat的类。就像设计模式,大家都在用。如果自己编写一个服务器,也可以设计像Servlet一样的接口。
为什么不是jdk自带的,因为需求不一样,jdk已经提供网络编程的底层实现。那如何设计自己的服务器就由需求决定了。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愿你满腹经纶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值