【面试常考的网络编程之Socket、短连接与长连接、客户端与服务端网络通讯流程、Java网络编程之BIO、JDK网络编程BIO案例实战演练】

本文深入探讨了IO在开发中的重要性,详细介绍了Linux操作系统中的IO相关知识,包括PageCache、直接IO与缓存IO等。接着讲解了Java中的BIO、NIO以及网络编程中的Socket,分析了短连接与长连接的工作原理及其应用场景。最后,通过BIO的客户端和服务端实战案例,展示了如何进行同步阻塞式网络通信,并提出了使用线程池优化BIO模型的方法。
摘要由CSDN通过智能技术生成

一.知识回顾

【0.IO在开发中有着举足轻重的地位,所以我们非常有必要学习。IO性能基石专栏都整理好了,可根据需要进行学习!】
【1.性能基石之IO~~~Linux操作系统相关知识体系补充&虚拟文件系统&文件描述符&PageCache内核缓存页】
【2.性能基石之IO~~~Page Cache缓存页&直接IO、缓存IO、内存映射mmap&文件一致性问题&Dirty概念&解决方案&Buffer IO在堆内,堆外IO详细过程与mmap映射过程】
【3.性能基石之IO~~~BIO、NIO、多路复用选择器、多路复用选择器的实现方案select、poll、epoll、Java基于NIO实现的Selector多路复用选择器】
【4.Java中JDK必知必会的NIO网络编程、BIO、NIO、AIO、同步异步,阻塞非阻塞、NIO的三大核心组件、Selector、Channel、Buffer,单线程、多线程、主从Reactor模型】

二.面试常考的网络编程

2.1 面试常考的网络编程之Socket

背景知识:
我们在之前的文章中也大概的学习了关于Socket的基本知识,需要的话大家可以大概的再浏览一遍(链接在先下面,点击即可)。为了保证本篇文章的整体结构完整,我们就再来补充学习关于Socket的一些概念。

【Socket的基本概念&&Socket与TCP/IP协议关系&&网络编程接口Socket&&应用层、Socket抽象层运输层网络层链路层网络结构&客户端与服务端通过Socket交互过程】

Socket基本概念?
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

举个栗子:俩台主机节点之间如何通过Socket通信?
客户端的应用程序要能和服务端的应用程序通信,必须通过 Socket 建立连接,而建立 Socket 连接必须需要底层TCP/IP 协议来建立 TCP 连接。建立 TCP 连接需要底层 IP 协议来寻址网络中的主机。我们知道网络层使用的 IP 协议可以帮助我们根据 IP 地址来找到目标主机,但是一台主机上可能运行着多个应用程序,如何才能与指定的应用程序通信就要通过 TCP 或 UPD 的地址也就是端口号来指定。这样就可以通过一个 Socket 实例唯一代表一个主机上的一个应用程序的通信链路了。

在这里插入图片描述

2.2 面试常考的网络编程之短连接与长连接

2.2.1 短连接

建立连接的过程:

  1. 客户端与服务端建立连接
  2. 传输数据
  3. 数据传送完毕,关闭连接

Http短连接与Socket短链接的区别:

  • 传统HTTP是无状态的,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。
  • 短连接是指socket连接后发送后接收完数据后马上断开连接。
2.2.2 长连接

建立连接的过程:

  1. 客户端与服务端建立连接
  2. 传输数据
  3. 保持连接
  4. 关闭连接

长连接的基本概念:
长连接指建立Socket连接后不管是否使用都保持连接。

2.2.3 什么时候用长连接,什么时候使用短连接?

长连接:
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,下次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。
短连接
而像web网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像web网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源。

2.3 面试常考的网络编程之网络通讯流程

  1. 在通信编程里提供服务的叫服务端,连接服务端使用服务的叫客户端。

  2. 在开发过程中,如果类的名字有Server或者ServerSocket的,表示这个类是给服务端容纳网络服务用的,如果类的名字只有Socket的,那么表示这是负责具体的网络读写的。那么对于服务端来说ServerSocket就只是个场所,具体和客户端沟通的还是一个一个的socket,所以在通信编程里,ServerSocket并不负责具体的网络读写,ServerSocket就只是负责接收客户端连接后,新启一个socket来和客户端进行沟通。这一点对所有模式的通信编程都是适用的。

  3. 在通信编程里,作为开发者关注的主要就是三个事情,服务端提供IP和监听端口,客户端通过连接操作想服务端监听的地址发起连接请求,通过三次握手连接,如果连接成功建立,双方就可以通过套接字进行通信。

    • 连接(客户端连接服务器,服务器等待和接收连接)
    • 读网络数据
    • 写网络数据

三.JDK网络编程BIO案例实战演练

同步阻塞式代码,了解流程即可。

3.1 客户端代码
/**
 * 客户端
 */
public class Client {

    public static void main(String[] args) throws IOException {
        //客户端启动必备
        Socket socket = null;
        //实例化与服务端通信的输入输出流
        ObjectOutputStream output = null;
        ObjectInputStream input = null;
        //服务器的通信地址
        InetSocketAddress addr = new InetSocketAddress("127.0.0.1",10001);
        try{
            socket = new Socket();
            /*连接服务器*/
            socket.connect(addr);

            output = new ObjectOutputStream(socket.getOutputStream());
            input = new ObjectInputStream(socket.getInputStream());

            /*向服务器输出请求*/
            output.writeUTF("Hello,Server!");
            output.flush();

            //接收服务器的输出
            System.out.println(input.readUTF());
        }finally{
            if (socket!=null) socket.close();
            if (output!=null) output.close();
            if (input!=null) input.close();
        }
    }
}

3.2 服务端代码
/**
 *服务端
 */
public class Server {

    public static void main(String[] args) throws IOException {
        /*服务器必备*/
        ServerSocket serverSocket = new ServerSocket();
        /*绑定监听端口*/
        serverSocket.bind(new InetSocketAddress(10001));
        System.out.println("Server start.......");

        while(true){
           new Thread(new ServerTask(serverSocket.accept())).start();
        }
    }

    private static class ServerTask implements Runnable{

        private Socket socket = null;

        public ServerTask(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            /*拿和客户端通讯的输入输出流*/
            try(
                    ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                    ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())
            ){
                /*服务器的输入*/
                String userName = inputStream.readUTF();
                System.out.println(Thread.currentThread().getName()+":Accept clinet message:"+userName);
                //response
                outputStream.writeUTF("Hello,"+userName);
                outputStream.flush();

            }catch (Exception e){
                e.printStackTrace();
            }
            finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

问题分析:
以上代码是传统BIO通信模型:采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成后,通过输出流返回应答给客户端,线程销毁。即典型的一请求一应答模型,同时数据的读取写入也必须阻塞在一个线程内等待其完成。该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,Java中的线程也是比较宝贵的系统资源,线程数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就死掉了。

为了改进这种一连接一线程的模型,我们可以使用线程池来管理这些线程,实现1个或多个线程处理N个客户端的模型(但是底层还是使用的同步阻塞I/O),通常被称为“伪异步I/O模型“。

/**
 * 服务端-使用线程池
 */
public class ServerPool {

    private static ExecutorService executorService
            = Executors.newFixedThreadPool(
                    Runtime.getRuntime().availableProcessors());

    public static void main(String[] args) throws IOException {
        //服务端启动必备
        ServerSocket serverSocket = new ServerSocket();
        //表示服务端在哪个端口上监听
        serverSocket.bind(new InetSocketAddress(10001));
        System.out.println("Start Server ....");
        try{
            while(true){
                executorService.execute(new ServerTask(serverSocket.accept()));
            }
        }finally {
            serverSocket.close();
        }
    }

    //每个和客户端的通信都会打包成一个任务,交个一个线程来执行
    private static class ServerTask implements Runnable{

        private Socket socket = null;
        public ServerTask(Socket socket){
            this.socket = socket;
        }

        @Override
        public void run() {
            //实例化与客户端通信的输入输出流
            try(ObjectInputStream inputStream =
                    new ObjectInputStream(socket.getInputStream());
                ObjectOutputStream outputStream =
                    new ObjectOutputStream(socket.getOutputStream())){

                //接收客户端的输出,也就是服务器的输入
                String userName = inputStream.readUTF();
                System.out.println(Thread.currentThread().getName()+":Accept client message:"+userName);

                //服务器的输出,也就是客户端的输入
                outputStream.writeUTF("Hello,"+userName);
                outputStream.flush();
            }catch(Exception e){
                e.printStackTrace();
            }finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

3.3 运行结果

在这里插入图片描述
在这里插入图片描述
好了,到这里【面试常考的网络编程之Socket、短连接与长连接、客户端与服务端网络通讯流程、Java网络编程之BIO、JDK网络编程BIO案例实战演练】就先学习到这里,后期更多的内容持续学习创作中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

硕风和炜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值