Java 网络编程基础

前言

一个网络请求、服务之间的调用都需要进行网络通讯,在日常开发时我们可能并不会关心我们的服务端是怎么接收到请求的、调用别的服务是怎么调用的,都是直接使用现成的框架或工具,比如,Tomcat、Dubbo、OkHttp等提供网络服务的框架。作为程序员,我们还是要知其然知其所以然。本文将介绍在 Java 中如何进行网络编程以及网络编程的基础知识。

什么是网络编程

网络编程是指利用网络协议和技术实现计算机应用程序之间的通信、数据传输、交换,如TCP/IP协议、HTTP协议、Socket编程等,像 Java、C、C++、Python 这些语言都提供了网络编程的API和库函数,可以便捷地进行网络应用程序的开发。

网络编程基础知识

网络通讯流程

网络通讯过程通常包括以下几个步骤:

  1. 建立连接:通讯双方在网络中建立连接,即在物理层和链路层上进行握手,确认通讯协议和传输参数。

  2. 数据传输:建立连接后,数据可以在通讯双方之间进行传输。数据传输过程中,需要进行分段、封装、逐层封装、加密和校验等。

  3. 数据接收:数据接收方需要先解析、解封装和验证传输数据的正确性,然后对数据进行处理,包括存储和响应等。

  4. 断开连接:在数据传输完成后,通讯双方需要在网络中断开连接,释放资源,并进行必要的后续操作。

长连接和短连接

长连接和短连接是指客户端和服务器端网络连接的不同方式。

长连接指在客户端和服务器端之间建立一条长期保持的连接。一旦建立连接后,客户端和服务器端就可以持续交换数据,而不需要每次发送请求都重新建立连接。长连接通常用于需要频繁交换数据的场合,如在线游戏、聊天室和实时视频等。

短连接指客户端和服务器端之间在完成一次请求后立即断开连接。每次发送请求都需要重新建立连接。短连接通常用于只需要偶尔交换数据的场合,如HTTP请求、电子邮件和浏览网页等。

长连接可以减少连接建立的开销,提高数据传输效率,但可能会浪费一定的网络资源。短连接则可以节省网络资源,但会增加连接建立的开销和数据传输的延迟。

Socket

所谓 Socket (套接字),就是对网络中不同主机上的应用进程之间进行双向通信的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口。

在这里插入图片描述

Socket 接口把复杂的 TCP/IP 协议族隐藏在后面,对开发人员来讲,通过调用一组接口就可完成网络通信。

Java 网络编程

Java提供了一个强大的网络编程模型和丰富的API来实现网络应用程序,主要基于Socket编程,提供了 ServerSocket 和 Socket 两种Socket,分别用于实现服务器端和客户端。ServerSocket负责绑定IP地址,监听端口,Socket负责发起连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。代码如下:

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){
           //serverSocket.accept()时阻塞,直到接收到客户端的请求
           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() {
            /*socket.getInputStream() 为客户端的输出流*/
            try(
                    ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                    ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())
            ){
                
                String msg = inputStream.readUTF();
                System.out.println("Accept clinet message:"+msg);
				/*服务器的响应*/
                outputStream.writeUTF("Hello,"+msg);
                outputStream.flush();

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

        }
    }
}
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("i‘m Client");
            output.flush();

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

        }
    }
}

以上代码是传统BIO通信模型:采用 BIO 通信模型的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接、处理、响应,典型的一请求一应答模型。该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,线程数量快速膨胀,系统的性能将急剧下降,随着访问量的继续增大,系统最终就会宕机。

总结

对网络编程有所了解后,也就大概知道Tomcat、Dubbo这样的框架最基本的实现原理,拿 Tomcat 举例,无非就是作为服务端监听一个端口,当这个端口有请求时对参数进行接收处理,处理后响应给客户端。当然这种成熟的框架是非常复杂的,有很多细节要考虑,比如数据的序列化问题、性能问题、安全问题、稳定性、扩展性、可靠性等。