java网络编程学习基础篇

Posted adventure.Li

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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的设计:

以上是关于java网络编程学习基础篇的主要内容,如果未能解决你的问题,请参考以下文章

java网络编程学习基础篇

java网络编程学习基础篇

Java学习笔记系列-基础篇-集合

编程——JAVA与JOK与IDE基础篇

Java 面试知识点解析——高并发编程篇

Java并发编程系列之一并发理论基础