java网络编程学习基础篇

一、基础背景

时代背景

自2000年左右,Web的快速发展以及2010左右的云原生和云计算的提出,网络通信的重要性越来越凸显出来;

  • 对于用户来说:软件的响应速度和体验是越来越重要的,而网络通信是决定响应速度关键因素之一。
  • 对于分布式系统:由于需要解决用户的大规模计算、存储需求,便采用分治的手段,进行分布式系统的构建。但分布式也具有一定的困难,例如一致性协调问题,通信的效率问题以及容错等,其中通信也是主要的考虑因素,因此现有大多数RPC框架都需要去解决该问题,使节点之间通信尽可能的快。
  • 对于Java开发者:拥有网络编程技术,将打开一扇通往世界的窗,多线程、数据结构解决的是内部(单机)的数据组织和计算、存储,而网络编程将会将视角放大到整个世界的计算、存储。

重要知识

  • 基本的网络通信模型、通信单位
  • TCP、UDP的通信过程
  • 序列化、IO的理解
  • 应用协议的理解(HTTP、WS、FTP)
  • RPC远程通信框架的理解(常见RPC :grpc,thrift)
  • 理解Docker中的网络模型
  • 考虑的基本问题:超时,请求与响应,C\S模型

相关书籍

  • 计算机网络基础(谢希仁),并采用cisco tracert进行搭建基础的网络拓扑,理解宏观的通信过程。
  • 网络是怎么连接的,较为贴切、易懂地理解网络通信
  • Unix网络编程,从OS角度去审视、分析网络编程
  • netty权威指南,构建Java高性能通信框架

二、Java的相关知识

基本IO

对于计算机通信的过程,实际上计算机将其抽象为IO,从字面上理解即输入、输出;该模型可理解为 A — B;A和B可以为机器、进程、文件等端或点,中间的线则是通信的通道,即IO流,Channel,管道,物理上的光缆等,而在其中还需要进行传输信息,信息的载体即数据也是决定其传输效率的关键。因此网络编程基本围绕以下几点讨论:

  • 端点的处理:File,Socket,进程,机器,数据库(本质也是file),考虑阻塞、非阻塞等,IO模型
  • 消息的处理:对象,字节(byte[])、xml、JSON等,序列化问题
  • 通信的协议:通信的标准协议,跨语言、跨机器等考虑

基本要点

  • 理解字节流和字符流
  • 理清IO的方向
  • IO的开销很大,注意关闭流
  • Java中的基础IO和NIO(JDK1.5后提出)

简单实践

进行文本的写入和读出;此外还可进行采用递归删除文件(或其他端节点)、创建文件等。

public class IoDemo {
    private final String FILE_PARENT_PATH = "E:\\JavaProjects\\LearnProjects\\java-backend\\java-base\\src\\main\\java\\com\\lyf\\network\\netty\\io";
    
    @Test
    public void testWriteToFile(){
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(FILE_PARENT_PATH+"\\test.txt");
            fileOutputStream.write("hello".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fileOutputStream!=null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    @Test
    public void testReadFromFile(){
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(FILE_PARENT_PATH+"\\test.txt");
            byte[] bytes = new byte[1024];
            fileInputStream.read(bytes);
            System.out.println(new String(bytes));
        } catch (IOException e) {
            e.printStackTrace();
            
        }finally {
            if(fileInputStream!=null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
}

Socket通信-传输层

在Java中提供了传输层的通信能力;

  • TCP:端:Socket 、ServerSocket;消息:IO流,写字节流
  • UDP: 端:DatagramSocket ;消息:datagramPacket;

理解要点

  • TCP和UDP的基本通信模型,理解TCP是如何保证可靠性的(可采用wireshark进行抓包看看)。
  • 理解机器的IP和进程的确定的Port端口,进程冲突即端口冲突也是常见问题。
    在这里插入图片描述在这里插入图片描述

简单实践

设计一个简单的TCP和UDP通信(关于UDP的通信可靠保证,可参考nacos的早期推送模型com.alibaba.nacos.naming.remote.udp ),可在此基础上进行简单的聊天室设计。
在这里插入图片描述

public class TcpUdpTests {
    
    @Test
    public void testUdpServer() {
        try {
            DatagramSocket datagramSocket = new DatagramSocket(90, InetAddress.getLocalHost());
            // datagramSocket.bind(new InetSocketAddress(100));
            DatagramPacket datagramPacket = new DatagramPacket("receive".getBytes(), 4);
            datagramSocket.receive(datagramPacket);
            System.out.println("receive::" + Arrays.toString(datagramPacket.getData()));
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    @Test
    public void testUdpClient() {
        try {
            DatagramSocket datagramSocket = new DatagramSocket(80);
            //datagramSocket.connect(new InetSocketAddress(90));
            DatagramPacket datagramPacket = new DatagramPacket("hello".getBytes(), 0, 5, InetAddress.getLocalHost(),
                    90);
            System.out.println("发送UDP报");
            datagramSocket.send(datagramPacket);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

应用层通信

@Test
    public void testApplicationProtUrl() {
        URL url = null;
        InputStream inputStream = null;
        try {
            url = new URL("http://www.baidu.com");
            URLConnection connection = url.openConnection();
            inputStream = connection.getInputStream();
            byte[]bytes = new byte[1024];
            inputStream.read(bytes);
            System.out.println(new String(bytes));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
  • HttpClient
      httpClient = HttpClient.newHttpClient();
        HttpRequest.BodyPublisher bodyPublisher = new HttpRequest.BodyPublisher() {
            @Override
            public long contentLength() {
                return 0;
            }
        
            @Override
            public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
            
            }
        };
        httpRequest = HttpRequest.newBuilder().POST(bodyPublisher)
                .header("Content-Type", "application/json")
                .uri(URI.create("http://localhost:8080/test"))
                .build();

更多具体可以直接看jdk-的java.net包。

常见的通信框架

  • Apache的HttpClient
  • OkHttp
    对比可参考:该文章

RCP框架

现有问题

  • Java的序列化问题:序列化的流比较大、开销时间比较大
  • Java的自带框架通信效率比较低:阻塞问题
    带来NIO的设计,提出selector\channel(解决端的问题)、buffer(解决传输介质、消息的问题)的概念。在此基础上Java领域netty进行友好地封装。

理解序列化的问题(具体见 github java-base 模块 com.lyf.network.netty.sequence包下):

class Person implements Serializable{
    @java.io.Serial
    private static final long serialVersionUID = -6849794470754667720L;
    
    private String username;
    
    private Integer age;
    
    public String getUsername() {
        return username;
    }
    
    public void setUsername(String username) {
        this.username = username;
    }
    
    public Integer getAge() {
        return age;
    }
    
    public void setAge(Integer age) {
        this.age = age;
    }
    
    byte[] codeByNio(){
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        byte[] val = username.getBytes(StandardCharsets.UTF_8);
        buffer.putInt(val.length);
        buffer.put(val);
        buffer.putInt(age);
        buffer.flip();
        byte[] rs = new byte[buffer.remaining()];
        buffer.get(rs);
        return rs;//buffer.array();
    }
    
}

public class JavaSerialTests {
    @Test
    public void testStreamSize() throws IOException {
        Person person = new Person();
        person.setAge(0);
        person.setUsername("Alan");
        
        //java序列化
//        ObjectOutputStream objectOutputStream = ObjectOutputStream.nullOutputStream();//new ObjectOutputStream();
//        objectOutputStream.writeObject(person);
        ByteArrayOutputStream baos  = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);
        objectOutputStream.writeObject(person);
        objectOutputStream.flush();
        objectOutputStream.close();
        byte[] bytes = baos.toByteArray();
       
        System.out.println(bytes.length+":"+new String(bytes));
    
        System.out.println(person.codeByNio().length+":"+new String(person.codeByNio()));
        // java 附带很多Java相关的信息导致?流过大
//        202:�� sr %com.lyf.network.netty.sequence.Person��8z;�8 L aget Ljava/lang/Integer;Lusernamet Ljava/lang/String;xpsr java.lang.Integer⠤���8 I valuexr java.lang.Number������  xp    t Alan
//        12:   Alan
    }
    
    /**
     * cost by java serial:202
     * cost by buffer:105
     * @throws IOException
     */
    @Test
    public void testSpeed() throws IOException {
        final int loop = 10000;
        Person person = new Person();
        person.setAge(0);
        person.setUsername("Alan");
        long c1 = System.currentTimeMillis();
        for(int i=0;i<loop;i++){
            ByteArrayOutputStream baos  = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);
            objectOutputStream.writeObject(person);
            objectOutputStream.flush();
            objectOutputStream.close();
            byte[] bytes = baos.toByteArray();
        }
        System.out.println("cost by java serial:"+(System.currentTimeMillis()-c1));
        c1 = System.currentTimeMillis();
        for(int i=0;i<loop;i++){
            person.codeByNio();
        }
        System.out.println("cost by buffer:"+(System.currentTimeMillis()-c1));
    }
}

  • 采用NIO和Netty进行简单的通信:

  • 分析nacos中的Grpc的设计:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值