关于java:JavaSE第23篇网络编程

46次阅读

共计 10609 个字符,预计需要花费 27 分钟才能阅读完成。

外围概述:在互联网时代,咱们所利用的大部分软件都是基于网络为根底的,那么软件之间是如何通过网络通信的呢?为什么咱们通过浏览器输出网址就能够看到网页?本篇咱们将会学习网络编程,让咱们可能从编程角度更底层的更好的了解软件之间的通信流程。

第一章:网络编程根底

1.1- 软件结构(理解)

C/ S 构造

C/ S 构造:全称为 Client/Server 构造,是指客户端和服务器构造。常见程序有QQ、迅雷等软件。

B/ S 构造

B/ S 构造:全称为 Browser/Server 构造,是指浏览器和服务器构造。常见浏览器有谷歌、火狐等。

两种架构各有劣势,然而无论哪种架构,都离不开网络的反对。网络编程,就是在肯定的协定下,实现两台计算机的通信的程序。

1.2- 网络通信协定(理解)

网络通信协定:通信协议是对计算机必须恪守的规定,只有恪守这些规定,计算机之间能力进行通信。这就 好比在路线中行驶的汽车肯定要恪守交通规则一样,协定中对数据的传输格局、传输速率、传输步骤等做了对立规定,通信单方必须同时恪守,最终实现数据交换。

TCP/IP 协定: 传输控制协议 / 因特网互联协定(Transmission Control Protocol/Internet Protocol),是 Internet 最根本、最宽泛的协定。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的规范。它的外部蕴含一系列的用于解决数据通信的协定,并采纳了 4 层的分层模型,每一层都呼叫它的下一层所提供的协定来实现本人的需要。

1.3- 协定分类(理解)

通信的协定还是比较复杂的,java.net 包中蕴含的类和接口,它们提供低层次的通信细节。咱们能够间接应用这些类和接口,来专一于网络程序开发,而不必思考通信的细节。

TCP

TCP:传输控制协议 (Transmission Control Protocol)。TCP 协定是面向 连贯 的通信协议,即传输数据之前,在发送端和接收端建设逻辑连贯,而后再传输数据,它提供了两台计算机之间牢靠无差错的数据传输。

三次握手:TCP 协定中,在发送数据的筹备阶段,客户端与服务器之间的三次交互,以保障连贯的牢靠。

  1. 第一次握手,客户端向服务器端收回连贯申请,期待服务器确认。
  2. 第二次握手,服务器端向客户端回送一个响应,告诉客户端收到了连贯申请。
  3. 第三次握手,客户端再次向服务器端发送确认信息,确认连贯。整个交互过程如下图所示。

实现三次握手,连贯建设后,客户端和服务器就能够开始进行数据传输了。因为这种面向连贯的个性,TCP 协定能够保障传输数据的平安,所以利用非常宽泛,例如下载文件、浏览网页等。

UDP

用户数据报协定 (User Datagram Protocol)。UDP 协定是一个面向 无连贯 的协定。传输数据时,不须要建设连贯,不论对方端服务是否启动,间接将数据、数据源和目的地都封装在数据包中,间接发送。每个数据包的大小限度在 64k 以内。它是不牢靠协定,因为无连贯,所以传输速度快,然而容易失落数据。日常利用中, 例如视频会议、QQ 聊天等。

1.4- 网络编程三要素(理解)

协定

协定:计算机网络通信必须恪守的规定,曾经介绍过了,不再赘述。

IP 地址

什么是 IP 地址:指互联网协议地址(Internet Protocol Address),俗称 IP。IP 地址用来给一个网络中的计算机设备做惟一的编号。如果咱们把“个人电脑”比作“一台电话”的话,那么“IP 地址”就相当于“电话号码”。

IP 地址的分类:

  • IPv4:是一个 32 位的二进制数,通常被分为 4 个字节,示意成 a.b.c.d 的模式,例如 192.168.65.100。其中 a、b、c、d 都是 0~255 之间的十进制整数,那么最多能够示意 42 亿个。
  • IPv6:因为互联网的蓬勃发展,IP 地址的需求量愈来愈大,然而网络地址资源无限,使得 IP 的调配越发缓和。有材料显示,寰球 IPv4 地址在 2011 年 2 月调配结束。为了扩充地址空间,拟通过 IPv6 从新定义地址空间,采纳 128 位地址长度,每 16 个字节一组,分成 8 组十六进 制数,示意成 ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,号称能够为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。

常用命令

查看本机 IP 地址,在控制台输出:ipconfig

查看网络是否连通,在控制台输出:

ping 空格 IP 地址
ping 220.181.57.216【非凡的 IP 地址】本机 IP 地址:127.0.0.1、localhost。

端口号

网络的通信,实质上是两个过程(应用程序)的通信。每台计算机都有很多的过程,那么在网络通信时,如何辨别这些过程呢?

如果说 IP 地址能够惟一标识网络中的设施,那么端口号就能够惟一标识设施中的过程(应用程序)了。

端口号:用两个字节示意的整数,它的取值范畴是 0 -65535。其中,0~1023 之间的端口号用于一些出名的网络服务和利用,一般的应用程序须要应用 1024 以上的端口号。如果端口号被另外一个服务或利用所占用,会导致以后程序启动失败。

利用 协定 + IP 地址 + 端口号 三元组合,就能够标识网络中的过程了,那么过程间的通信就能够利用这个标识与其它过程进行交互。

第二章:TCP 通信程序

2.1-TCP 通信程序介绍(理解)

TCP 通信能实现两台计算机之间的数据交互,通信的两端,要严格辨别为 客户端(Client) 服务端(Server)

两端通信时步骤:

  1. 服务端程序,须要当时启动,期待客户端的连贯。
  2. 客户端被动连贯服务器端,连贯胜利能力通信。服务端不能够被动连贯客户端。

在 Java 中,提供了两个类用于实现 TCP 通信程序:

  • 客户端:java.net.Socket 类示意。创立 Socket 对象,向服务端收回连贯申请,服务端响应申请,两者建设连贯开始通信。
  • 服务端:java.net.ServerSocket 类示意。创立 ServerSocket 对象,相当于开启一个服务,并期待客户端的连贯。

2.2-Socket 类(重要)

Socket 类:该类实现客户端套接字,套接字指的是两台设施之间通信的端点。

构造方法

public Socket(String host, int port) : 创立套接字对象并将其连贯到指定主机上的指定端口号。如果指定的 host 是 null,则相当于指定地址为回送地址。

回送地址(127.x.x.x) 是本机回送地址(Loopback Address),次要用于网络软件测试以及本地机过程间通信,无论什么程序,一旦应用回送地址发送数据,立刻返回,不进行任何网络传输。

Socket client = new Socket("127.0.0.1", 6666);

成员办法

  • public InputStream getInputStream():返回此套接字的输出流。

    • 如果此 Scoket 具备相关联的通道,则生成的 InputStream 的所有操作也关联该通道。
    • 敞开生成的 InputStream 也将敞开相干的 Socket。
  • public OutputStream getOutputStream():返回此套接字的输入流。

    • 如果此 Scoket 具备相关联的通道,则生成的 OutputStream 的所有操作也关联该通道。
    • 敞开生成的 OutputStream 也将敞开相干的 Socket。
  • public void close():敞开此套接字。

    • 一旦一个 socket 被敞开,它不可再应用。
    • 敞开此 socket 也将敞开相干的 InputStream 和 OutputStream。
  • public void shutdownOutput():禁用此套接字的输入流。

    • 任何先前写出的数据将被发送,随后终止输入流

2.3-ServerSocket 类(重要)

ServerSocket 类:这个类实现了服务器套接字,该对象期待通过网络的申请。

构造方法

构造方法:public ServerSocket(int port):应用该构造方法在创立 ServerSocket 对象时,就能够将其绑定到一个指 定的端口号上,参数 port 就是端口号。

ServerSocket server = new ServerSocket(6666);

成员办法

成员办法:public Socket accept():侦听并承受连贯,返回一个新的 Socket 对象,用于和客户端实现通信。该办法 会始终阻塞直到建设连贯。

2.4- 简略的 TCP 网络程序(重要)

TCP 通信剖析

  1. 【服务端】启动, 创立 ServerSocket 对象,期待连贯。
  2. 【客户端】启动, 创立 Socket 对象,申请连贯。
  3. 【服务端】接管连贯, 调用 accept 办法,并返回一个 Socket 对象。
  4. 【客户端】Socket 对象,获取 OutputStream,向服务端写出数据。
  5. 【服务端】Scoket 对象,获取 InputStream,读取客户端发送的数据。
  6. 【服务端】Socket 对象,获取 OutputStream,向客户端回写数据。
  7. 【客户端】Scoket 对象,获取 InputStream,解析回写数据。
  8. 【客户端】开释资源,断开连接

客户端向服务端发送数据

客户端代码

public class ClientTCP {public static void main(String[] args) throws Exception {System.out.println("客户端 发送数据");
        // 1. 创立 Socket (ip , port) , 确定连贯到哪里.
        Socket client = new Socket("localhost", 6666);
        // 2. 获取流对象 . 输入流
        OutputStream os = client.getOutputStream();
        // 3. 写出数据.
        os.write("你好么? tcp , 我来了".getBytes());
        // 4. 敞开资源 .
        os.close();
        client.close();}
}

服务端代码

public class ServerTCP {public static void main(String[] args) throws IOException {System.out.println("服务端启动 , 期待连贯 ....");
        // 1. 创立 ServerSocket 对象,绑定端口,开始期待连贯
        ServerSocket ss = new ServerSocket(6666);
        // 2. 接管连贯 accept 办法, 返回 socket 对象.
        Socket server = ss.accept();
        // 3. 通过 socket 获取输出流
        InputStream is = server.getInputStream();
        // 4. 一次性读取数据
          // 4.1 创立字节数组
        byte[] b = new byte[1024];
          // 4.2 据读取到字节数组中.
        int len = is.read(b);// 4.3 解析数组, 打印字符串信息
        String msg = new String(b, 0, len);
        System.out.println(msg);
        //5. 敞开资源.
        is.close();
        server.close();}
}

服务端向客户端回写数据

服务端代码

public class ClientTCP {public static void main(String[] args) throws Exception {System.out.println("客户端 发送数据");
        // 1. 创立 Socket (ip , port) , 确定连贯到哪里.
        Socket client = new Socket("localhost", 6666);
        // 2. 通过 Scoket, 获取输入流对象 
        OutputStream os = client.getOutputStream();
        // 3. 写出数据.
        os.write("你好么? tcp , 我来了".getBytes());
          // ============== 解析回写 =========================
          // 4. 通过 Scoket, 获取 输出流对象
          InputStream in = client.getInputStream();
          // 5. 读取数据数据
          byte[] b = new byte[100];
          int len = in.read(b);
          System.out.println(new String(b, 0, len));
        // 6. 敞开资源 .
          in.close();
        os.close();
        client.close();}
}

客户端代码

public class ServerTCP {public static void main(String[] args) throws IOException {System.out.println("服务端启动 , 期待连贯 ....");
        // 1. 创立 ServerSocket 对象,绑定端口,开始期待连贯
        ServerSocket ss = new ServerSocket(6666);
        // 2. 接管连贯 accept 办法, 返回 socket 对象.
        Socket server = ss.accept();
        // 3. 通过 socket 获取输出流
        InputStream is = server.getInputStream();
        // 4. 一次性读取数据
          // 4.1 创立字节数组
        byte[] b = new byte[1024];
          // 4.2 据读取到字节数组中.
        int len = is.read(b);// 4.3 解析数组, 打印字符串信息
        String msg = new String(b, 0, len);
        System.out.println(msg);
          // ================= 回写数据 =======================
          // 5. 通过 socket 获取输入流
           OutputStream out = server.getOutputStream();
          // 6. 回写数据
           out.write("我很好, 谢谢你".getBytes());
          // 7. 敞开资源.
          out.close();
        is.close();
        server.close();}
}

第三章:文件上传案例

3.1- 文件上传剖析

  1. 【客户端】输出流,从硬盘读取文件数据到程序中。
  2. 【客户端】输入流,写出文件数据到服务端。
  3. 【服务端】输出流,读取文件数据到服务端程序。
  4. 【服务端】输入流,写出文件数据到服务器硬盘中。
  5. 【服务端】获取输入流,回写数据。
  6. 【客户端】获取输出流,解析回写数据。

3.2- 客户端程序实现

public static void main(String[] args) throws IOException {
    // 1. 创立流对象
    // 1.1 创立输出流, 读取本地文件
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
    // 1.2 创立输入流, 写到服务端
    Socket socket = new Socket("localhost", 6666);
    BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());

    //2. 写出数据.
    byte[] b  = new byte[1024 * 8];
    int len ;
    while ((len  = bis.read(b))!=-1) {bos.write(b, 0, len);
    }
    // 敞开输入流, 告诉服务端, 写出数据结束
    socket.shutdownOutput();
    System.out.println("文件发送结束");
    // 3. ===== 解析回写 ============
    InputStream in = socket.getInputStream();
    byte[] back = new byte[20];
    in.read(back);
    System.out.println(new String(back));
    in.close();
    
    // 4. 开释资源
    socket.close();
    bis.close();}

3.3- 文件上传单线程服务器实现

public static void main(String[] args) throws IOException {System.out.println("服务器 启动.....");
        // 1. 创立服务端 ServerSocket
        ServerSocket serverSocket = new ServerSocket(6666);
        // 2. 循环接管, 建设连贯
        Socket accept = serverSocket.accept();
            /*
             *3. socket 对象进行读写操作  
             */
        try {
                //3.1 获取输出流对象
                BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
                //3.2 创立输入流对象, 保留到本地 .
                FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
                BufferedOutputStream bos = new BufferedOutputStream(fis);
                // 3.3 读写数据
                byte[] b = new byte[1024 * 8];
                int len;
                while ((len = bis.read(b)) != -1) {bos.write(b, 0, len);
                }

                // 4.======= 信息回写 ===========================
                System.out.println("back ........");
                OutputStream out = accept.getOutputStream();
                out.write("上传胜利".getBytes());
                out.close();
                //================================

                //5. 敞开 资源
                bos.close();
                bis.close();
                accept.close();
                System.out.println("文件上传已保留");
        } catch (IOException e) {e.printStackTrace();
        }
    }
}

3.4- 文件上传多线程服务器实现

文件上传的案例中,服务器只能为客户端服务器一次,之后服务器端程序就会完结。而咱们必须做到让服务器程序不能完结,时时刻刻都要为客户端服务。而且同时能够为多个客户端提供服务器,做到一个客户端就要开启一个信新的线程。

public static void main(String[] args) throws IOException{System.out.println("服务器 启动.....");
    // 1. 创立服务端 ServerSocket
    ServerSocket serverSocket = new ServerSocket(6666);
    // 2. 循环接管, 建设连贯
    while (true) {Socket accept = serverSocket.accept();
        /*
        3. socket 对象交给子线程解决, 进行读写操作
        Runnable 接口中, 只有一个 run 办法, 应用 lambda 表达式简化格局
        */
        new Thread(() -> {
            try{
            //3.1 获取输出流对象
            BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
            //3.2 创立输入流对象, 保留到本地 .
            FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
            BufferedOutputStream bos = new BufferedOutputStream(fis);
            // 3.3 读写数据
            byte[] b = new byte[1024 * 8];
            int len;
            while ((len = bis.read(b)) != -1) {bos.write(b, 0, len);
            }

            // 4.======= 信息回写 ===========================
            System.out.println("back ........");
            OutputStream out = accept.getOutputStream();
            out.write("上传胜利".getBytes());
            out.close();
            //================================

            //5. 敞开 资源
            bos.close();
            bis.close();
            accept.close();
            System.out.println("文件上传已保留");
            } catch (IOException e) {e.printStackTrace();
            }
        }).start();}
}

3.5- 文件上传服务器实现优化

频繁的创立线程会减少系统资源的开销,能够利用线程池进行再次优化。

public static void main(String[] args)  throws IOException{System.out.println("服务器 启动.....");
    ServerSocket serverSocket = new ServerSocket(6666);
    // 创立 10 个线程的线程池
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    
    while (true) {Socket accept = serverSocket.accept();
        // 提交线程执行的工作
        executorService.submit(()->{
            try{BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
            FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
            BufferedOutputStream bos = new BufferedOutputStream(fis);
            byte[] b = new byte[1024 * 8];
            int len;
            while ((len = bis.read(b)) != -1) {bos.write(b, 0, len);
            }
            System.out.println("back ........");
            OutputStream out = accept.getOutputStream();
            out.write("上传胜利".getBytes());
            out.close();
            bos.close();
            bis.close();
            accept.close();
            System.out.println("文件上传已保留");
            } catch (IOException e) {e.printStackTrace();
            }
        });
    }
}

第四章:模仿 B / S 服务器

模仿网站服务器,应用浏览器拜访本人编写的服务端程序,查看网页成果。

4.1- 案例剖析

我的项目中有一个 web 我的项目,咱们写服务端代码,而后通过浏览器输出地址127.0.0.1:8888/web/index.html

  • 筹备页面数据,web 文件夹。

    • 蕴含网页 html 文件
    • 蕴含图片
    • 蕴含 css 样式表
  • 咱们模仿服务器端,ServerSocket 类监听端口,应用浏览器拜访,查看网页成果

4.2-HTTP 协定

案例中需应用浏览器查看成果,浏览器和服务器之间是遵循 HTTP 协定的,咱们先对 HTTP 协定进行简略的介绍,在前期 JavaWeb 相干篇幅中再详解解释。

  • HTTP 协定,称为超文本传输协定。
  • 规定了客户端浏览器和服务器之间的协定。
  • HTTP 协定是 TCP 网络通信模型中应用层的协定。
  • 客户端浏览器被动向服务器发动申请,服务器收到后进行响应。
  • 客户端申请

    • 客户端在申请的信息的第一行中,携带了客户端想要申请的资源门路。
  • 服务器端响应

    • 响应中必须告知客户端响应的后果。
    • 200 状态码示意响应胜利。
    • Content-Type:text/html 告知浏览器响应的内容是文本 / 网页内容。

4.3- 代码实现

代码

public class WebSever {public static void main(String[] args) throws IOException {
    // 创立 ServerSocket 对象
   ServerSocket server = new ServerSocket(8888);
   while (true){new Thread(new Runnable() {
       @Override
       public void run() {
        try{
          // 获取 socket 对象
          Socket socket = server.accept();
          // 读取接管的内容
          BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
          String line = reader.readLine(); // GET /web/index.html HTTP/1.1
          String path = line.split(" ")[1].substring(1);
          // 创立本地字节输出流
          FileInputStream fis = new FileInputStream("day09_Socket\\" +path);
          // 创立网络输入流
          OutputStream os = socket.getOutputStream();
          // 写入 HTTP 协定响应头, 固定写法
          os.write("HTTP/1.1 200 OK\r\n".getBytes());
          os.write("Content‐Type:text/html\r\n".getBytes());
          // 必须要写入空行, 否则浏览器不解析
          os.write("\r\n".getBytes());
          int len = 0;
          byte[]bts = new byte[1024];

          while((len=fis.read(bts))!=-1){os.write(bts,0,len);
          }
          // 敞开资源
          os.close();
          fis.close();
          reader.close();
          socket.close();} catch (IOException e){e.printStackTrace();
        }
       }
     }).start();}
  }
}

成果

浏览器输出地址 127.0.0.1:8888/web/index.html 拜访

正文完
 0