Skip to main content

java面经-网络篇

·924 words·5 mins
WFUing
Author
WFUing
A graduate who loves coding.
Table of Contents

1. TCP和UDP有什么区别?TCP为什么是三次握手,而不是两次?
#

  • TCP(Transfer Control Protocol)是一种面向连接的、可靠的、传输层通信协议
    • 特点:
      • 好比是打电话,面向连接的,点对点的通信,
      • 高可靠的,
      • 效率比较低,占用的系统资源比较多。
  • UDP(User Dategram Protocol)是一种无连接的、不可靠的、传输层通信协议
    • 特点:
      • 好比是广播,不需要连接,发送方不管接收方有没有准备好,直接发消息,
      • 可以进行广播发送的,
      • 传输不可靠,有可能丢失消息,
      • 效率比较高,
      • 协议比较简单,占用的系统资源比较少。

TCP建立连接三次握手,断开连接四次挥手

如果是两次握手,可能会造成连接资源浪费的情况。三次握手是确保双方建立可靠连接的过程:

  • 认双方能够通信:第一次握手由客户端发起,用于告知服务器客户端请求建立连接。服务器收到后能确认客户端发送正常,并对客户端进行响应。
  • 确认服务器接收请求:第二次握手由服务器发起,用于告知客户端服务器已接收到连接请求,并准备好建立连接。客户端收到后可以确认服务器收到了自己的请求。
  • 确认客户端接收响应:第三次握手由客户端发起,用于告知服务器客户端已接收到服务器的响应,并准备好发送数据。服务器收到后可以确认客户端能够接收到服务器的响应。

TCP 断开连接需要四次挥手的原因如下:

  • 客户端请求断开连接:客户端发送 FIN 报文给服务器,表示客户端不再发送数据,但仍可以接收数据。
  • 服务器确认客户端的关闭请求:服务器收到 FIN 报文后,发送 ACK 报文确认收到,并进入 CLOSE_WAIT 状态。
  • 服务器关闭连接:服务器不再发送数据后,发送 FIN 报文给客户端,表示服务器也准备好断开连接。
  • 客户端确认服务器的关闭请求:客户端收到服务器的 FIN 报文后,发送 ACK 报文确认收到,并进入 TIME_WAIT 状态,等待可能延迟的 ACK 报文。在一段时间后,客户端关闭连接,服务器收到 ACK 后也关闭连接。

2. JAVA有哪几种IO模型?有什么区别?
#

  • BIO 同步阻塞IO。可靠性差,吞吐量地,适用于连接比较少且比较固定的场景。JDK1.4之前唯一选择。BIO 编程模型最简单。

import java.io.*;
import java.net.*;

public class BioServer {

    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("Server started, listening on port 8080...");

            while (true) {
                Socket clientSocket = serverSocket.accept();
                System.out.println("Accepted connection from " + clientSocket);

                BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));

                String request = reader.readLine();
                System.out.println("Received request: " + request);

                writer.write("Hello from server\n");
                writer.flush();

                clientSocket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Server started, listening on port 8080...
Accepted connection from Socket[addr=/127.0.0.1,port=56518,localport=8080]
Received request: Hello from client
import java.io.*;
import java.net.*;

public class BioClient {

    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost", 8080);
            System.out.println("Connected to server");

            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

            writer.write("Hello from client\n");
            writer.flush();

            String response = reader.readLine();
            System.out.println("Response from server: " + response);

            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Connected to server
Response from server: Hello from server
  • NIO 同步非阻塞IO。可靠性比较好,吞吐量也比较高,适用于连接比较多并且连接比较短(轻操作),例如聊天室。JDK1.4开始支持。NIO 编程模型最复杂。

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NioServer {

    public static void main(String[] args) {
        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(8080));
            serverSocketChannel.configureBlocking(false);
            System.out.println("Server started, listening on port 8080...");

            Selector selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while (true) {
                selector.select();
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectedKeys.iterator();

                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();

                    if (key.isAcceptable()) {
                        ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                        SocketChannel clientChannel = channel.accept();
                        clientChannel.configureBlocking(false);
                        clientChannel.register(selector, SelectionKey.OP_READ);
                        System.out.println("Accepted connection from " + clientChannel);
                    } else if (key.isReadable()) {
                        SocketChannel clientChannel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        int bytesRead = clientChannel.read(buffer);
                        if (bytesRead > 0) {
                            buffer.flip();
                            byte[] bytes = new byte[buffer.remaining()];
                            buffer.get(bytes);
                            String request = new String(bytes);
                            System.out.println("Received request: " + request);

                            ByteBuffer responseBuffer = ByteBuffer.wrap("Hello from server\n".getBytes());
                            clientChannel.write(responseBuffer);
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Server started, listening on port 8080...
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class NioClient {

    public static void main(String[] args) {
        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.connect(new InetSocketAddress("localhost", 8080));
            System.out.println("Connected to server");

            ByteBuffer buffer = ByteBuffer.allocate(1024);
            buffer.put("Hello from client\n".getBytes());
            buffer.flip();
            socketChannel.write(buffer);

            buffer.clear();
            socketChannel.read(buffer);
            buffer.flip();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            String response = new String(bytes);
            System.out.println("Response from server: " + response);

            socketChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Connected to server
Response from server: Hello from server
  • AIO 异步非阻塞IO。可靠性是最好的,吞吐量也是非常高。适用于连接比较多,并且连接比较长(重操作)。例如,相册服务器。JDK7版本才开始支持。AIO 编程模型相对 NIO 比较简单。

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class AioServer {

    public static void main(String[] args) {
        try {
            AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(8080));
            System.out.println("Server started, listening on port 8080...");

            serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
                @Override
                public void completed(AsynchronousSocketChannel clientChannel, Object attachment) {
                    serverSocketChannel.accept(null, this); // 接受下一个连接请求

                    try {
                        System.out.println("Accepted connection from " + clientChannel);

                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        buffer.put("Hello from server\n".getBytes());
                        buffer.flip();

                        clientChannel.write(buffer, null, new CompletionHandler<Integer, Object>() {
                            @Override
                            public void completed(Integer result, Object attachment) {
                                try {
                                    clientChannel.close();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }

                            @Override
                            public void failed(Throwable exc, Object attachment) {
                                exc.printStackTrace();
                            }
                        });
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void failed(Throwable exc, Object attachment) {
                    exc.printStackTrace();
                }
            });

            Thread.currentThread().join(); // 让主线程阻塞,保持服务器运行
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}
Server started, listening on port 8080...
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class AioClient {

    public static void main(String[] args) {
        try {
            AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
            socketChannel.connect(new InetSocketAddress("localhost", 8080), null, new CompletionHandler<Void, Object>() {
                @Override
                public void completed(Void result, Object attachment) {
                    System.out.println("Connected to server");

                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    buffer.put("Hello from client\n".getBytes());
                    buffer.flip();

                    socketChannel.write(buffer, null, new CompletionHandler<Integer, Object>() {
                        @Override
                        public void completed(Integer result, Object attachment) {
                            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                            socketChannel.read(readBuffer, null, new CompletionHandler<Integer, Object>() {
                                @Override
                                public void completed(Integer result, Object attachment) {
                                    readBuffer.flip();
                                    byte[] bytes = new byte[readBuffer.remaining()];
                                    readBuffer.get(bytes);
                                    String response = new String(bytes);
                                    System.out.println("Response from server: " + response);

                                    try {
                                        socketChannel.close();
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                }

                                @Override
                                public void failed(Throwable exc, Object attachment) {
                                    exc.printStackTrace();
                                }
                            });
                        }

                        @Override
                        public void failed(Throwable exc, Object attachment) {
                            exc.printStackTrace();
                        }
                    });
                }

                @Override
                public void failed(Throwable exc, Object attachment) {
                    exc.printStackTrace();
                }
            });

            Thread.currentThread().join(); // 让主线程阻塞,保持客户端运行
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}
Connected to server
Response from server: Hello from server

在一个网络请求中,客户端会发一个请求到服务端。

  • 同步、异步针对请求;
  • 阻塞、非阻塞针对客户端。
  1. 客户端发送了请求后,就一直等着服务端响应。客户端:阻塞。请求:同步。
  2. 客户端发送了请求后,就去干别的事情了。时不时的过来检查服务端是否给出了响应。客户端:非阻塞。请求:同步。
  3. 换成异步请求。客户端发送了请求后,就坐在椅子上,等服务端返回响应。客户端:阻塞。请求:异步。
  4. 客户端发送了请求后,就去干别的事情了。等到服务端给出响应后,再过来处理业务逻辑。客户端:非阻塞。请求:异步。

3. JAVA NIO 的几个核心组件是什么?分别有什么作用?
#

channel buffer selector

  • channel 类似于一个流。每个 channel 对应一个 buffer 缓冲区。 channel 会注册到 selector。
  • selector 会根据 channel 上发生的读写事件,将请求交由某个空闲的线程处理。 selector 对应一个或者多个线程。
  • buffer 和 channel 都是可读可写的。

4. select, poll 和 epoll 有什么区别?
#

他们是 NIO 中多路复用的三种实现机制,是由 Linux 操作系统提供的。

用户空间和内核空间:操作系统为了保护系统安全,将内核划分为两个部分,一个是用户空间,一个是内核空间。用户空间不能直接访问底层的硬件设备,必须通过内核空间。

文件描述符 File Descriptor(FD):是一个抽象的概念,形式上是一个整数,实际上是一个索引值。指向内核中为每个进程维护进程所打开的文件的记录表。当程序打开一个文件或者创建一个文件时,内核就会向进程返回一个FD。Unix,Linux。

  • select 机制:会维护一个 FD 的结合 fd_set。将 fd_set 从用户空间复制到内核空间,激活 socket。 x64 2048 。fd_set 是一个数组结构
  • poll 机制:和 select 机制差不多,把 fd_set 结构进行了优化,FD集合的大小就突破了操作系统的限制。poolfd结构来代替 fd_set,通过链表实现的。
  • epoll 机制:在 Linux 2.6 版本提出。event poll。epoll中不再扫描所有的 FD,只将用户关心的 FD 的事件存放到内核的一个事件表当中。这样,就可以减少用户空间和内核空间之间需要拷贝的数据。
操作方式 底层实现 最大连接数 IO 效率
select 遍历 受限于内核 一般
poll 遍历 链表 无上限 | 一般
epoll 事件回调 红黑树 无上限

java 的 NIO 当中使用的是哪种机制?

可以查看 DefaultSelectorProvider 源码。在 windows 下,WindowsSelectorProvider。而 Linux 下,根据 Linux 的内核版本2.6以上,就是 EPollSelectorProvider,否则就是默认的 PollSelectorProvider。

select 1984年出现,poll 1997年出现,EPoll 2002年出现。

5. 描述下 HTTP 和 HTTPS 的区别。
#

  • HTTP:是互联网上应用最广泛的一种网络通信协议,基于TCP,可以是浏览器工作更为高效,减少网络传输。
  • HTTPS:是HTTP的加强版,可以认为是 HTTP + SSL(Secure Socket Layer)。在HTTP 的基础上增加了一系列的安全机制。一方面保证数据传输安全,另一方面对访问者增加了验证机制。是目前现行架构下,最为安全的解决方案。

主要区别:

  1. HTTP 的连接是简单无状态的,HTTPS 的数据传输是经过证书加密的,安全性更高。
  2. HTTP 是免费的,而 HTTPS 需要申请证书,而证书通常是需要收费的,并且费用一般不低。
  3. 使用的端口不一样,HTTP 默认的是80端口,而HTTPS默认是443端口。

HTTPS的缺点:

  1. HTTPS 的握手协议比较费时,所以会影响服务的响应速度以及吞吐量。
  2. HTTPS 也并不是完全安全的。他的证书体系其实并不是完全安全的。并且HTTPS在面对DDOS 这样的攻击时,几乎起不到任何作用。
  3. 证书需要费钱,并且功能越强大的证书费用越高。