03.网络编程入门

发布时间 2023-04-13 11:54:49作者: 哈哈嗨

1、概述

  1. 什么是网络编程?网络编程可以让程序与网络上的其他设备中的程序进行数据交互。

  2. 网络通信的基本模式:Client-Server(CS)、Browser/Server(BS)

    • CS模式

    • BS模式

  3. 网络通信三要素?

    • IP地址:设备在网络中的地址,是唯一的标识。

    • 端口:应用程序在设备中唯一的标识。

    • 协议:数据在网络中传输的规则,常见的协议有UDP协议和TCP协议。

2、网络通信三要素

  1. IP地址

    • IP(InternetProtocol):全称”互联网协议地址”,是分配给上网设备的唯一标志。常见的IP分类为:IPv4和IPV6

    • IP地址基本寻址思路:客户端使用网址访问服务端,比如访问百度 https://www.baidu.com/ ,该请求首先会被域名解析器拦截,将域名解析为对应的IP地址,返回给客户端,客户端通过IP地址找到服务器端。

    • IP地址形式:公网地址、和私有地址(局域网使用)。

      • 192.168.开头的就是常见的局域网地址,范围即为192.168.0.0--192.168.255.255,专门为组织机构内部使用。

      • 使用局域网,不能用于与其他局域网通信,但是在本局域网内,访问速度较快,可以提高效率。公网地址才可用于网络通信。

    • 特殊IP地址:IP常用命令:

    • IP地址操作类:InetAddress,此类表示IP地址,是在java中对IP地址的抽象。

  2. InetAddress类

    • 创建对象:使用getLocalHost获取本机IP地址对象或者getByName方法获取指定主机的IP地址对象。

    • 常用API

      • public static InetAddress getLocalHost(): 返回本主机的地址对象

      • public static InetAddness getByName(String host):得到指定主机的IP地址对象,参数是域名或者IP地址

      • public String getHostName():获取此IP地址的主机名

      • public String getHostAddress():返回IP地址字符串

      • public boolean isReachable(int timeout):在指定毫秒内连通该IP地址对应的主机,连通返回true。类似于ping指令

    • 使用示例:

      //获取本地Ip地址对象
      InetAddress localHost = InetAddress.getLocalHost();
      System.out.println(localHost.getHostAddress()); //输出本地Ip地址
      System.out.println(localHost.getHostName()); //输出主机名
      //获取百度的Ip地址对象
      InetAddress baidu = InetAddress.getByName("www.baidu.com");
      System.out.println(baidu.getHostAddress());
      //通过百度的IP地址获取IP地址对象
      InetAddress byName = InetAddress.getByName("157.148.69.74");
      System.out.println(byName.getHostAddress());
      
  3. 端口号:标识正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围是0~65535。

    • 端口类型:
      • 周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用880,FTP占用21)
      • 注册端口:1024~49151,分配给用户进程或某些应用程序。(如:Tomcat占用8080,MySQL占用3306)
      • 动态端口:49152到65535,之所以称为动态端口,是因为它一般不不固定分配某种进程,而是动态分配。
      • 注意:我们自己开发的程序选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。
  4. 通信协议:连接和通信数据的规则被称为网络通信协议

    • 网络通信协议有两套参考模型:

      • OSI参考模型:世界互联协议标准,全球通信规范,由于此模型过于理想化,未能在因特网上进行广泛推广。

      • TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。

    • 传输层的2个常见协议:

      • TCP(Transmission Control Protocol):传输控制协议

      • UDP(User Datagram Protocol):用户数据报协议

    • TCP协议特点:

      • 使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。

      • 传输前,采用“三次握手”方式建立连接,所以是可靠的。

      • 在连接中可进行大数据量的传输,数据量不确定。

      • 连接,发送数据都需要确认,目传输完毕后,还需释放已建立的连接,通信效率较低。

      • TCP协议通信场景:对信息安全要求较高的场景,例如文件下载、金融等数据通信。

    • TCP三次握手建立连接

    • TCP四次挥手断开连接

    • UDP协议特点:

      • UDP是一种无连接、不可靠传输的协议。

      • UDP将数据源IP、目的地IP和端口封装成数据包,不需要建立连接

      • UDP每个数据包的大小限制在64KB内

      • UDP发送不管对方是否准备好,接收方收到也不确认,故是不可靠的

      • UDP可以广播发送,发送数据结束时无需释放资源,开销小,速度快。

      • UDP协议通信场景:语音通话,视频会话等。

3、UDP通信

  1. DatagramPacket:数据包对象(数据的盘子,用来放数据的。)

    • 创建对象:

      • public DatagramPacket (byte[] buf, int length, InetAddress address, int port):创建发送端数据包对象。buf:要发送的内容,字节数组;length:要发送内容的字节长度;address:接收端的IP地址对象;port:接收端的端口号

      • public DatagramPacket(byte[] buf,int length):buf:用来存储接收的内容;length:能够接收内容的长度

  2. DatagramSocket:发送端和接收端对象(发送和接收数据的人)

    • 创建对象:

      • public DatagramSocket():创建发送端的Socket对象,系统会随机分配一个端口号。

      • public DatagramSocket(int port):创建接收端的Socket对象并指定端口号

    • DatagramSocket类成员方法:

      • public void send(DatagramPacket dp):发送数据包

      • public void receive(DatagramPacket p):接收数据包

  3. UDP快速入门:

    //发送端
    public static void main(String[] args) throws Exception {
        DatagramSocket send = new DatagramSocket();  //随机端口号
        byte arr[] =  "你好啊朋友".getBytes();
        //创建数据包,指定发送的端口 7777 和IP地址
        DatagramPacket packet = new DatagramPacket(arr,0,arr.length,InetAddress.getLocalHost(),7777);
        send.send(packet); //发送数据
        send.close(); //关闭管道释放资源。
    }
    //接收端
    public static void main(String[] args) throws Exception {
        DatagramSocket receive = new DatagramSocket(7777); //指定端口号7777
        byte arr[] = new byte[1024*64];
        DatagramPacket packet = new DatagramPacket(arr,arr.length);
        receive.receive(packet); //使用数据包接收信息
        System.out.println(new String(arr,0,packet.getLength())); //打印信息
        receive.close();
    }
    
  4. UDP的三种通信方式

    • 单播:单台主机与单台主机之间的通信

    • 组播:当前主机与所在网络的所有主机通信

    • 广播:当前主机与选定的一组主机的通信

  5. UDP如何实现广播?

    • 使用广播地址: 255.255.255.255

    • 具体操作:

      • 发送端发送的数据包的目的地写的是广播地址、且指定端口。(255.255.255.255 ,9999)

      • 本机所在网段的其他主机的程序只要匹配端口成功即就可以收到消息了。 (9999)

  6. UDP实现组播

    • 使用组播地址: 224.0.0.0 ~ 239.255.255.255

    • 具体操作:

      • 发送端的数据包的目的地是组播IP (例如: 224.0.1.1, 端口: 9999)

      • 接收端必须绑定该组播IP(224.0.1.1), 端口还要对应发送端的目的端口9999,这样即可接收该组播消息。

      • DatagramSocket的子类MulticastSocket可以在接收端绑定组播IP。

    • 示例:

      //定义发送端,广播消息
      public static void main(String[] args) throws Exception {
          DatagramSocket send = new DatagramSocket();
          byte arr[] =  "你好啊朋友".getBytes();
          //把消息广播到224.0.1.1地址,所有绑定到这个地址的主机都将接收到消息
          DatagramPacket packet = new DatagramPacket(arr,0,arr.length,InetAddress.getByName("224.0.1.1"),7777);
          send.send(packet);
          send.close();
      }
      
      //定义接收端,接收消息。
      public static void main(String[] args) throws Exception {
          MulticastSocket receive = new MulticastSocket(7777);
          //将主机绑定到广播地址 224.0.1.1,接收发送到该地址的消息。
          receive.joinGroup(InetAddress.getByName("224.0.1.1"));
      
          byte arr[] = new byte[1024*64];
          DatagramPacket packet = new DatagramPacket(arr,arr.length);
          receive.receive(packet);
      
          System.out.println(new String(arr,0,packet.getLength()));
          receive.close();
      }
      

4、TCP通信

  1. TCP通信模式演示

  2. Socket类(用户端)

    • 创建对象:public Socket(String host,int port),直接使用构造器创建。host 以及 port 代表服务器的地址。

    • Socket类成员方法:

      • OutputStream getOutputStream():获得字节输出流对象

      • InputStream getInputStream():获得字节输入流对象

  3. ServerSocket(服务端)

    • 创建对象:直接使用构造器public ServerSocket(int port),注册服务端端口

    • ServerSocket类成员方法:public Socket accept(),等待接收客户端的Socket通信连接,连接成功返回Socket对象与客户端建立端到端通信

  4. 快速入门

    • 客户端发送消息

      • 创建客户端的Socket对象,请求与服务端的连接。

      • 使用socket对象调用getOutputStream()方法得到字节输出流。

      • 使用字节输出流完成数据的发送。

    • 服务端接收消息:注册ServerScoket服务,接收socket对象,从Socket中读取数据。

      //编写客户端代码。
      public static void main(String[] args) throws Exception {
          System.out.println("开启客户端");
          //编写客户端代码
          //1、指定 ip地址、端口号建立连接
          Socket socket = new Socket("127.0.0.1",7777);
          //2、获取已连接的管道流
          OutputStream outputStream = socket.getOutputStream();
          PrintStream printStream = new PrintStream(outputStream);
          //3、发送数据。
          printStream.println("你好啊");
          printStream.flush(); //刷新管道,防止数据未发送。
      
      }
      //编写服务端代码
      public static void main(String[] args) throws Exception {
          System.out.println("开启服务端");
      
          //编写服务端代码
          //1、注册服务器
          ServerSocket serverSocket = new ServerSocket(7777);
          //2、必须调用accept方法,等待接收客户端的Socket连接请求,建立通信管道。
          Socket socket = serverSocket.accept();
          //3、获取字节输入流,用于读取客户端信息。
          InputStream input = socket.getInputStream();
          //4、封装成缓冲流,提高读取效率。
          BufferedReader reader = new BufferedReader(new InputStreamReader(input));
          String msg = null;
          if ((msg = reader.readLine())!=null){
              System.out.println(msg);
          }
      }
      
  5. 实现多发多收,一个客户端可以给服务器发多个消息。

    • 多发多收实现

      //编写客户端代码。
      public static void main(String[] args) throws Exception {
          System.out.println("开启客户端");
          Socket socket = new Socket("127.0.0.1",7777);
          OutputStream outputStream = socket.getOutputStream();
          PrintStream printStream = new PrintStream(outputStream);
          Scanner scanner = new Scanner(System.in);
          String msg = null;
          //循环输入信息,发送信息
          while (true){
              msg = scanner.next();
              printStream.println(msg);
              printStream.flush();
          }
      }
      //编写服务端代码。
      public static void main(String[] args) throws Exception {
          System.out.println("开启服务端");
          ServerSocket serverSocket = new ServerSocket(7777);
          Socket socket = serverSocket.accept();
          InputStream input = socket.getInputStream();
          BufferedReader reader = new BufferedReader(new InputStreamReader(input));
          String msg = null;
          //循环接收消息
          while ((msg = reader.readLine())!=null){
              System.out.println(msg);
          }
      }
      
    • 分析:之前我们的通信是否可以同时与多个客户端通信,为什么?不可以的。单线程每次只能处理一个客户端的Socket通信

    • 如何才可以让服务端可以处理多个客户端的通信需求?引入多线程。

  6. 让服务端可以处理多个客户端的通信需求

    • 分析

    • 代码实现

      //编写一个类,实现了Runnable接口,用于开启线程。
      public class SocketThread implements Runnable {
          private Socket socket;
          public SocketThread(Socket socket){this.socket = socket;}
          @Override
          public void run() {
      
              try {
                  System.out.println(socket.getInetAddress()+"来了:");
                  InputStream input = null;
                  input = socket.getInputStream();
                  BufferedReader reader = new BufferedReader(new InputStreamReader(input));
                  String msg = null;
                  //循环输入信息,发送信息
                  while ((msg = reader.readLine())!=null){
                      System.out.println(socket.getInetAddress()+":"+msg);
                  }
              } catch (IOException e) {
                  System.out.println(socket.getInetAddress()+"退出了");
              }
      
          }
      }
      //服务端
      public static void main(String[] args) throws Exception {
          System.out.println("开启服务端");
          ServerSocket serverSocket = new ServerSocket(7777);
          //不断去接收socket访问。
          while (true){
              Socket socket = serverSocket.accept();
              //每次接收到客户端的数据,就开启一个线程响应它。
              new Thread(new SocketThread(socket)).start();
          }
      
      }
      //客户端
      public static void main(String[] args) throws Exception {
          System.out.println("开启客户端");
          //编写客户端代码
          //1、指定 ip地址、端口号建立连接
          Socket socket = new Socket("127.0.0.1",7777);
          //2、获取已连接的管道流
          OutputStream outputStream = socket.getOutputStream();
          PrintStream printStream = new PrintStream(outputStream);
          //3、发送数据。
          Scanner scanner = new Scanner(System.in);
          String msg = null;
          while (true){
              msg = scanner.next();
              printStream.println(msg);
              printStream.flush();
          }
      }
      

5、网络编程案例

  1. TCP实战案例:即时通信,实现不同客户端之间的通信

    • 什么是即时通信?即时通信,是指一个客户端的消息发出去,其他客户端可以接收到。即时通信需要进行端口转发的设计思想,服务端需要把在线的Socket管道存储起来,一旦收到一个消息要推送给其他管道。

    • 分析:所有客户端都向服务端发送信息,指定要转发的对象,服务端将收到的客户端信息转发给特定的客户端对象。

    • 代码实现

      public class ServerReadThread implements Runnable{
          private Socket socket;
          private ArrayList<Socket> users;
          public ServerReadThread(Socket socket,ArrayList<Socket> users){
              this.socket = socket;
              this.users = users;
          }
          @Override
          public void run() {
              try {
                  InputStream inputStream = socket.getInputStream();
                  BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                  String msg;
                  while ((msg = reader.readLine())!=null){
                      //将读取到的数据再转发出去
                      printMsg(msg);
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
          //将信息转发出去
          public void printMsg(String msg) throws Exception{
              OutputStream outputStream;
              PrintStream printStream;
              for (Socket user : users) {
                  if(user != socket){
                      outputStream = user.getOutputStream();
                      printStream = new PrintStream(outputStream);
                      printStream.println(msg);
                      printStream.flush();
                  }
              }
          }
      }
      
      //注册服务器
      public static void main(String[] args) throws IOException {
      
          //注册服务器
          ServerSocket server = new ServerSocket(7777);
      
          Socket socket;
          while (true){
              socket = server.accept();
              System.out.println(socket.getRemoteSocketAddress()+"来了");
              users.add(socket);
              new Thread(new ServerReadThread(socket,users)).start();
          }
      }
      
      public class CustomerReadThread implements Runnable {
          private Socket socket;
          public CustomerReadThread(Socket socket){
              this.socket = socket;
          }
          @Override
          public void run() {
              try {
                  InputStream inputStream = socket.getInputStream();
                  BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                  String msg;
                  while ((msg = reader.readLine())!=null){
                      System.out.println("对方:"+msg);
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
      
      //开发客户端
      public static void main(String[] args) throws IOException {
      
          Scanner scanner = new Scanner(System.in);
      
          Socket socket = new Socket("127.0.0.1",7777);
      
          //创建一个线程,不断去监听收到的信息
          new Thread(new CustomerReadThread(socket)).start();
      
          //读取输入,发送给服务端
          String msg;
          PrintStream ps = new PrintStream(socket.getOutputStream());
          while (true){
              msg = scanner.next();
              ps.println(msg);
              ps.flush();
          }
      }
      
  2. TCP模拟BS系统

    • BS结构是什么样的,需要开发客户端吗?浏览器访问服务端,不需要开发客户端。

    • TCP通信如何实现BS请求网页信息回来呢?

      • 客户端使用浏览器发起请求(不需要开发客户端)

      • 服务端必须按照浏览器的协议规则响应数据。

      • 浏览器使用什么协议规则呢?HTTP协议(简单了解下)

    • BS系统架构

    • HTTP响应数据的协议格式

    • 模拟BS系统代码实现。

      public class ServerReadThread implements Runnable{
      
          private Socket socket;
          public ServerReadThread(Socket socket){
              this.socket = socket;
          }
      
          @Override
          public void run() {
              try {
                  //浏览器 已经与本线程建立了Socket管道
                  //  响应消息给浏览器显示
                  PrintStream ps=new PrintStream(socket.getOutputStream());
      
                  //必须响应HTTP协议格式数据,否则浏览器不认识消息
                  ps.println("HTTP/1.1 200 0K");   //协议类型和版本响应成功的消息!
                  ps.println("Content-Type:text/html;charset=UTF-8"); //响应的数据类型。文本/网页
                  ps.println();//必须发送一个空行,才可以响应数据回去给浏览器
      
                  ps.println("<h1>你好啊</h1>");
                  ps.close();
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      }
      public static void main(String[] args) throws IOException {
          //注册服务器
          ServerSocket server = new ServerSocket(7777);
          Socket socket;
          while (true){
              socket = server.accept();
              System.out.println(socket.getRemoteSocketAddress()+"来了");
              //开启一个线程,处理请求
              new Thread(new ServerReadThread(socket)).start();
          }
      }
      

      运行结果:

笔记内容来自哔哩哔哩 up 黑马程序员