Java网络编程入门


1. 网络编程概述

网络编程最主要的就是数据的交换处理,在网络编程的开发模型中一般分为客户端和服务端两个核心组成部分,客户端可以向服务端请求数据的发送,而服务端在接收到客户端的请求后,利用其自身的业务逻辑对请求数据进行处理,并且将处理后的数据响应给客户端

注意:上图中的Socket仅仅是一个通信层抽象的概念,它本身只提供了一些操作的核心接口,而具体的接口实现是由不同的协议来完成的,如TCP或者UDP

2. OSI七层网络模型

客户端与服务端之间的通信就是依靠Scoket来完成的,在通信的过程中以字节流的形式进行传输,客户端的数据输出对于服务端而言就是输入,反过来,服务端的响应对于客户端来说就是输入。

Java中的java.net包下提供了TCP/UDP的程序开发支持,在这些支持中封装了关于TCP/UDP中的数据编码处理操作,可以帮助开发者回避协议的实现细节,从而使得网络应用的开发更加简单。

C-S与B-S网络模型

依据Socket编程开发的实际应用形式,可以分为如下两种不同的开发模式

  • C-S(Client-Server):需要开发两套程序,一套是服务端程序,另外一套是客户端程序,在维护的时候服务端程序和客户端程序都需要进行更新。这种结构的使用特定的数据传输协议(由用户来自行指定)并使用一些非公开的端口,所以程序的安全性比较高
  • B-S(Browser-Server):基于浏览器实现的客户端应用,只需要开发一套服务端程序,维护升级的时候修改服务端的代码即可。这种结构的主要特点是维护和开发的成本相对较低,同时因为使用的是公共处理协议HTTP(基于TCP)的一种实现,所以程序的安全性较低

3. 开发网络程序

一个完整的网络程序一般分为两个核心组成部分客户端和服务端。服务端首先要开启服务应用,随后监听端口并等待客户端进行连接,客户端连接成功后,就可以实现两者的I/O通信处理,通信完成后客户端就可以与服务端断开连接。

TCP的连接与关闭

TCP是可靠的数据传输协议,在进行网络通信的时候必须建立可靠的连接,因此采用了三次握手的连接机制,客户端断开连接的时候必须采用四次挥手的方式。但是这样一来就造成了通信性能的下降。要注意到虽然TCP是基础的网络通信协议,但是其最大的性能缺点恰恰在于可靠的连接处理。

为了便于网络程序的开发,java.net提供了ServerSocketSocket两个处理类。

其中ServerSocket主要工作在服务端,需要与一个具体的端口进行绑定,随后在此端口上等待客户端的连接请求,对于服务端来讲,每一个连接请求都使用一个Socket对象实例进行描述,在获得客户端的Socket对象实例之后就可以通过InputStreamOutPutStream进行数据的接收与发送

下面程序:客户端连接到服务端之后,服务端向客户端发送一个字符串数据,数据发送完成后客户端断开与服务端的连接。

public static void main(String[] args) throws IOException {
    ServerSocket serverSocket = new ServerSocket(9999);//监听9999端口
    Socket client = serverSocket.accept();//等待客户端连接,连接建立成功后,将会得到一个socket对象用来描述此次连
    PrintStream out = new PrintStream(client.getOutputStream());//输出流
    out.println("hhh");
    client.shutdownOutput();//关闭输出流
    serverSocket.close();
}

注意到,当执行到accept() 方法的时候,当前的服务器应用都会进入到阻塞的状态,其实这是在等待客户端的连接,这是一种性能较差的设计方案

//服务端的简单实现
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9999);//监听9999端口
        int flag = 100;
        while(true){
            Socket client = serverSocket.accept();//等待客户端连接,连接建立成功后,将会得到一个socket对象用来描述此次连接,对于服务端来说,对应的是Output流
            PrintStream out = new PrintStream(client.getOutputStream());//输出流
            out.println("hhh");
            client.shutdownOutput();
            flag --;
            if(flag == -1){
                break;
            }
        }
        //client.shutdownOutput();//关闭输出流
        serverSocket.close();
    }
//客户端的简单实现
    public static void main(String[] args) throws IOException {
        for (int i = 0; i < 100; i++) {
            Socket client = new Socket("127.0.0.1",9999);
            //对应客户端来说,对应响应的是输入流
            Scanner scanner = new Scanner(client.getInputStream());
            if(scanner.hasNext()){
                System.out.println(scanner.next());
            }
        }
    }

4. ECHO程序模型

ECHO程序模型中,客户端接收键盘输入数据,并将此数据发送到服务端,服务端在接收到数据后对该数据进行响应(将客户端发送过来的数据发还给客户端),当用户不再需要进行交互的时候,可以输入exit表示交互结束。

    //服务端程序
    ServerSocket serverSocket = new ServerSocket(8379);
    Socket client = serverSocket.accept();
    Scanner scanner = new Scanner(client.getInputStream());
    PrintStream out = new PrintStream(client.getOutputStream());
    boolean flag = true;
    while(flag){
        if(scanner.hasNext()){
            String value = scanner.next().trim();
            if(value.equalsIgnoreCase("exit")){
                out.println("exit in code 1");
                flag = false;
                break;
            }
            out.println(value);
        }
    }
    serverSocket.close();
}
//客户端程序
    public static void main(String[] args) throws IOException {
        Socket client = new Socket("127.0.0.1",8379);
        Scanner scanner = new Scanner(client.getInputStream());
        PrintStream out = new PrintStream(client.getOutputStream());
        boolean flag = true;
        while (flag){
            String value = KeyBoardInputUtils.getString("Input:").trim();
            out.println(value);
            if(scanner.hasNext()){
                System.out.println("echo:"+scanner.next());
            }
            if(value.equalsIgnoreCase("exit")){
                flag = false;
            }
        }
        client.close();
    }

5. BIO网络模型(同步阻塞)

ECHO模型中,其程序的模型属于是单线程的应用(所有的逻辑全部写在主线程之中),所以只允许单个客户端进行访问,当有其他客户端进行访问的时候无法连接。

如果服务器希望可以同时处理若干个客户的请求,可以以多线程的方式运行,服务端可以为每一个连接的客户创建一个线程,每一个线程分别实现各自客户端的数据处理服务。

//定义多线程处理类
public class EchoHandler implements Runnable{
    private Socket client;
    public EchoHandler(Socket client){//主线程传递连接描述对象
        this.client = client;
    }
    @Override
    public void run() {
        try {
            Scanner scanner = new Scanner(client.getInputStream());
            PrintStream out = new PrintStream(client.getOutputStream());
            boolean flag = true;
            while (flag){
                if(scanner.hasNext()){
                    String value = KeyBoardInputUtils.getString("Input:");
                    if (value.equalsIgnoreCase("exit")) {
                        out.println("exit");
                        flag = false;
                    }else{
                        out.println(value);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ECHO引入线程模型

public static void main(String[] args) throws Exception {
    ServerSocket serverSocket = new ServerSocket(9999);
    boolean flag = true;
    while (flag){
        Socket client = serverSocket.accept();
        new Thread(new EchoHandler(client)).start();
    }
    serverSocket.close();
}

6. UDP网络编程

虽然TCP可以实现可靠的网络连接与数据传输,但是其在性能方面的约束有很大的影响。所以网络开发中还有UDP程序模型,UDP收据传输采用的是非可靠的连接,客户端基于订阅的方式与服务端进行连接,服务端在发送消息的时候采用广播的方式,但是客户端不一定可以接受到其发送的消息。

实现简单的UDP客户端与服务端

public class UDPClient {
    public static void main(String[] args) throws Exception {
        DatagramSocket client = new DatagramSocket(9999);
        byte[] data = new byte[1024];
        DatagramPacket packet = new DatagramPacket(data,data.length);
        System.out.println("-------waiting-----");
        client.receive(packet);
        System.out.println(new String(data,0,packet.getLength()));
        client.close();
    }
}
public class UDPClient {
    public static void main(String[] args) throws Exception {
        DatagramSocket client = new DatagramSocket(9999);
        byte[] data = new byte[1024];
        DatagramPacket packet = new DatagramPacket(data,data.length);
        System.out.println("-------waiting-----");
        client.receive(packet);
        System.out.println(new String(data,0,packet.getLength()));
        client.close();
    }
}

文章作者: 穿山甲
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 穿山甲 !
  目录