Java: 网络编程基础

网络编程基础

TCP客户端

  • Java在客户端提供的TCP套接字的类为Socket Socket构造方法:Socket(String, int),和提供的服务器IP和端口号绑定
  • Socket常用实例方法:
    • 如果不用构造方法,也可使用connect()发起TCP连接
    • getInputStream()getOutputStream():获取对应的输入流和输出流对象 其中OutputStream对象应该及时flush() 通常使用InputStreamReaderOutputStreamWriter将字节流转化为字符流并传递指定的字符集处理

TCP服务端

  • Java在服务端提供的TCP套接字的类为ServerSocket ServerSocket构造方法:ServerSocket(int),和提供的端口号绑定
  • ServerSocket常用实例方法:
    • accept()阻塞式地等待请求,收到请求后返回Socket对象,它和客户端的Socket对象相关联 建议使用try-with-resources语法块构造ServerSocket对象避免调用close()
    • getPort()getInetAddress():返回服务器绑定的本地端口号、本地IP地址 getLocalSocketAddress():返回服务器绑定的IP地址与端口号组成的SocketAddress对象

UDP客户端与服务端

  • Java在客户端和服务端提供的UDP套接字的类为DatagramSocket DatagramSocket的构造方法同ServerSocket,但是注意,由于TCPUDP是两种不同的协议,不同协议使用的端口号是互不干扰的,它们的相同点在于使用16位的端口号
  • DatagramPacket:这个类对象作为数据报的抽象,是数据传输的中介
    • 构造方法:DatagramPacket(byte[], int, InetAddress, int) 和一个byte[]数组绑定,后两个参数可选,和待发送的IP与端口绑定
    • getData()、getOffset()、getLength():获取数据、偏移、长度 通常用new String(packet.getData(), packet.getOffset(), packet.getLength(), Charset)解析收到的数据报
    • setData(byte[]):设置存储的数据
  • DatagramSocket常用实例方法:
    • receive(DatagramPacket):收取一个UDP数据报赋值给提供的引用 接收后,这个DatagramPacket对象的目标IP和端口会被替换为发送方的IP与端口
    • send(DatagramPacket):发送该数据报到提供的数据报对象指向的IP与端口
    • connect(InetAddress, int):使当前的套接字对象绑定一个默认的目标IP及端口,它并不像TCP那样会发起连接,而只是在本地进行记录 连接后,send()发送的DatagramPacket不需要有绑定的目标IP,且接收时会过滤掉这个IP及端口以外的数据报 为了方便,通常在客户端使用connect()过滤掉不需要的包 而服务端自然不需要也不应该设置默认的目标IP,由receive()自动设置即可
    • disconnect():删除本地的默认连接

HTTP客户端

  • 直接使用TCPUDP套接字灵活性很强,但应用层协议需要自己制定,在安全性和性能上没有设计好的协议简易而安全,例如要开发一个网页客户端,使用HTTP协议是最好不过的 但是处理HTTP报文需要检查和解析响应行、响应头、响应体,十分麻烦 早期JDK通过HttpURLConnection访问HTTP,但是不好用,Java 11后提供java.net.http简化开发
  • HttpClient构造方法:建议通过静态方法创建
    • HttpClient.newHttpClient():使用默认配置构建

    • HttpClient.newBuilder().build():使用HttpClient.Builder构建器构建 这种方法是最常用的,可以在build()前链式调用一系列方法自定义配置,例如:

      1
      2
      3
      4
      5
      6
      7
      HttpClient client = HttpClient.newBuilder()
      .version(Version.HTTP_2) // HTTP版本
      .followRedirects(Redirect.NORMAL) // 重定向策略
      .connectTimeout(Duration.ofSeconds(20)) // 超时时长
      .proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80))) // 正向代理
      .authenticator(Authenticator.getDefault()) // HTTP身份认证
      .build(); // 返回由该Builder构造的HttpClient对象

      其中RedirectAuthenticatorHttpClient的内部枚举,包含一系列重定向策略与身份认证策略

  • 创建HTTP请求:一个HttpRequest对象代表一个请求报文,通过静态方法HttpRequest.newBuilder()创建构建器,这个构建器包含若干配置方法:
    • uri(URI):设置目标网络资源
    • header(String, String):设置某个请求头的键值 也可以通过headers()一次性设置多个请求头
    • GET()POST(BodyPublisher):设置请求方式,默认是GET(),因此前者通常会被省略 BodyPublishers是工厂类,用于创建BodyPublisher对象,常用方法有ofString()
  • 处理HTTP响应:一个HttpResponse对象代表一个响应报文,通过HttpClient对象的send()发送请求报文后阻塞式地(同步式地)获取最终响应报文
    • 该类是泛型类,泛型类型表示响应体被解析而成的类型,由send()时提供的BodyHandler参数决定解析类型,例如: HttpResponse<String> resp = client.send(req, BodyHandlers.ofString())
    • statusCode()获取响应码
    • headers():获取只读的响应头集合HttpHeaders
    • previousResponse():获取前一次响应,用于回溯重定向链
    • version()、uri()、request():更多获取信息的方法

异步网络编程

了解RMI

  • RMI(Remote Method Invotation,远程方法调用)是java.rmi提供的远程调用API,仅需了解即可 远程调用十分常用,这使得客户端代码无需有接口的实现类,而只需要声明接口即可正常调用,实现类由服务端提供

  • 远程方法调用的服务接口(即服务器端)需要继承Remote空接口且所有方法需要抛出受检异常RemoteException,创建实例后,通过UnicastRemoteObject转化为RMI服务,通过Registry注册到本地的端口上 而客户端需要通过RegistrygetRegistry(host, port)lookup(serv_name)获取服务以及服务接口

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 服务端: 包含RemoteDemo接口, RemoteDemoImpl实现类
    // 在本地1999号端口上启动一个RMI注册表
    Registry registry = LocalRegistry.createRegistry(1999);
    // 将普通接口转化为RMI服务接口
    RemoteDemo serv = new RemoteDemoImpl();
    RemoteDemo serv_obj = UnicastRemoteObject.exportObject(serv, 0);
    // 将该RMI服务注册到注册表中
    registry.rebind("Serv_name", serv_obj);

    // 客户端: 只包含RemoteDemo接口
    // 获取目标IP及端口的注册表
    Registry registry = LocalRegistry.getRegistry("localhost", 1999);
    // 获取服务, 需要强制转换
    RemoteDemo remoteDemo = (RemoteDemo) registry.lookup("Serv_name");