基础知识

OSI和TCP分层模型
图 1 OSI和TCP分层模型

  • IP 地址IP 地址是一个 32 位(32bit)或 128 位的二进制数,用于唯一地标识网络中的一个通信实体IPv4 地址被分成了 A、B、C、D、E 五类IPv4 地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a, b, c, d 都是 0~255 之间的十进制整数IPv6 有 3 种表示方法,分别是冒分十六进制表示法(如 0:0:0:0:0:0:0:0)、0 位压缩表示法、内嵌 IPv4 地址表示法表示本机:127.0.0.1 localhost 本机 IP本地局域网:10.0.0.0 - 10.255.255.255、192.168.0.0 - 192.168.255.255
  • 端口端口是一个 16 位的整数,用于表示数据交给哪个通信程序处理,分为:
    • 公认端口:从 0 到 1023
    • 注册端口:从 1024 到 49151
    • 动态和/或私有端口:从 49152 到 65535
  • 协议Java 默认提供了对 file、ftp、gopher、http、https、jar、mailto、netdoc 协议的支持
  • socket(套接字)源 IP 地址和目的 IP 地址以及源端口号和目的端口号的组合不同主机之间的进程进行双向通信的端点
  1. // 获取非回环地址
  2. for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
  3. NetworkInterface intf = en.nextElement();
  4. for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
  5. InetAddress inetAddress = enumIpAddr.nextElement();
  6. if (!inetAddress.isLoopbackAddress() && !inetAddress.isLinkLocalAddress() && inetAddress.isSiteLocalAddress()) {
  7. System.out.println(inetAddress.getHostAddress());
  8. }
  9. }
  10. }
  11. // org.springframework.cloud.commons.util.InetUtils
  12. InetUtilsProperties target = new InetUtilsProperties();
  13. String ipAddress = new InetUtils(target).findFirstNonLoopbackHostInfo().getIpAddress();

Socket 通信

Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层,它是一组接口在设计模式中,Socket 其实就是一个门面模式,它把复杂的 TCP/IP 协议族隐藏在 Socket 接口后面,对用户来说,一组简单的接口就是全部,让 Socket 去组织数据,以符合指定的协议

Socket通信
图 2 Socket通信

Java 的基本网络支持

  • java.net 包下

InetAddress

  • 代表 IP 地址

  • 类方法InetAddress getByName(String host):根据主机获取对应的 IP 对象InetAddress getLocalHost():类方法,获取本机 IP 地址对应的 IP 对象isReachable():测试是否可以在指定时间内到达该地址

  • 实例方法String getHostAddress():返回该 InetAddress 实例对应的 IP 地址字符串String getHostName():获取此 IP 地址的主机名String getCanonicalHostName():获取此 IP 地址的全限定域名

URLDecoder 和 URLEncoder

  • 用于完成 URL 地址中普通字符串和 application/x-www-form-urlencoded MIME 字符串之间的相互转换

  • String URLDecoder.decode(String s, String enc):使用指定字符集将特殊字符串转换成普通字符串(解码)

  • String URLEncoder.encode(String s, String enc):使用指定字符集将普通字符串转换成特殊字符串(编码)

URL 和 URLConnection

URL

  • URI 实例代表一个统一资源标识符,不能用于定位任何资源,唯一作用是解析(resolve)
  • URL 对象代表一个统一资源定位符,通过定位的方式确定一个资源
  • URL 可以由协议名、主机、端口和资源名组成格式:protocol://host:port/resourceName,如 http://www.crazyit.org/index.php

  • 创建 URL 对象URL(String spec):根据 String 表示形式创建 URL 对象URL(String protocol, String host, int port, String file):根据指定协议、主机、端口号(-1,表示使用指定协议的默认端口)和资源文件(如 "/index.php")创建 URL 对象

  • 访问 URL 对应的资源String getFile():获取该 URL 的资源名String getHost():获取该 URL 的主机名String getPath():获取该 URL 的路径部分int getPort():获取该 URL 的端口号String getProtocol():获取该 URL 的协议名称String getQuery():获取该 URL 的査询字符串部分URLConnection openConnection():返回一个 URLConnection 对象,它代表了与 URL 所引用的远程对象的连接InputStream openStream():打开与此 URL 的连接,并返回一个用于读取该 URL 资源的输入流

URLConnection

  • 封装访问远程网络资源一般方法的类
  • 通过 URLConnection 实例向该 URL 发送请求、读取 URL 引用的资源
  • 实例方法InputStream getInputStream():返回该 URLConnection 对应的输入流,用于获取 URLConnection 响应的内容OutputStream getOutputStream():返回该 URLConnection 对应的输出流,用于向 URLConnection 发送请求参数

  • 发送 GET 请求时只需将请求参数放在 URL 字符串之后,以?隔开,程序直接调用 URLConnection 对象的 connect() 方法即可

  • 发送 POST 请求,则需要先设置 doln 和 doOut 两个请求头字段的值,再使用 URLConnection 对应的输出流来发送请求参数

基于 TCP 协议的网络编程

  • TCP/IP 通信协议是一种可靠的网络协议,它在通信的两端各建立一个 Socket,从而在通信的两端之间形成网络虚拟链路
  • TCP 协议:面向连接(经历三次握手)、传输可靠(保证数据正确性、数据顺序)、用于传输大量数据(字节流模式)、速度慢,建立连接需要开销较多(时间、系统资源)
  • IP 协议负责将消息从一个主机传送到另一个主机,消息在传送的过程中被分割成一个个的小包
  • TCP 协议负责收集这些信息包,并将其按适当的次序放好传送
  • 服务器端通过 ServerSocket 建立监听,客户端通过 Socket 连接到指定服务器后,通信双方就可以通过 IO 流进行通信

    Socket通信模型
    图 3 Socket通信模型

使用 ServerSocket 创建 TCP 服务器端

  • 构造器ServerSocket(int port):用指定的端口 port 来创建一个 ServerSocket
  • 监听来自客户端连接请求Socket accept():如果接收到一个客户端 Socket 的连接请求,该方法将返回一个与客户端 Socket 对应的 Socket,否则该方法将一直处于等待状态,线程也被阻塞InetAddress getInetAddress():返回此服务器端的 IP 对象
  • 服务器端应该为每个 Socket 单独启动一个线程,每个线程负责与一个客户端进行通信
  1. // 创建一个 ServerSocket,用于监听客户端 Socket 的连接请求
  2. ServerSocket ss = new ServerSocket(8888);
  3. // 采用循环不断接受来自客户端的请求
  4. while (true) {
  5. // 每当接受到客户端 Socket 的请求,服务器端也对应产生一个 Socket
  6. Socket s = ss.accept();
  7. System.out.println("连接的客户端地址:" + s.getInetAddress());
  8. BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
  9. System.out.println("客户端传来数据:" + in.readLine());
  10. PrintStream out = new PrintStream(s.getOutputStream(), true);
  11. out.println("hello!"); // 发送消息
  12. in.close();
  13. out.close();
  14. s.close();
  15. }

使用 Socket 进行通信

  • Java 使用 Socket 对象来代表两端的通信端口,并通过 Socket 产生 IO 流来进行网络通信
  • 使用 Socket 的构造器来连接到指定服务器Socket(InetAddress/String remoteAddress, int port):创建连接到指定远程主机、远程端口的 Socket,使用本地主机的默认 IP 地址,系统动态分配的端口Socket(InetAddress/String remoteAddress, int port, InetAddress localAddr, int localPort):创建连接到指定远程主机、远程端口的 Socket,并指定本地 IP 地址和本地端口
  • 获取输入流和输出流InputStream getInputStream():返回该 Socket 对象对应的输入流,让程序通过该输入流从 Socket 中取出数据OutputStream getOutputStream():返回该 Socket 对象对应的输出流,让程序通过该输出流向 Socket 中输出数据InetAddress getInetAddress():返回该 Socket 对象的的 IP 对象
  • 客户端应该单独启动一个线程,该线程专门负责读取服务器端数据
  1. Socket socket = new Socket("127.0.0.1", 8888);
  2. PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
  3. out.println(new BufferedReader(new InputStreamReader(System.in)).readLine()); // 发送消息
  4. BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  5. System.out.println("服务端传来的数据:" + in.readLine());
  6. out.close();
  7. in.close();
  8. socket.close();

基于 UDP 协议的网络编程

  • UDP 协议:面向无连接、传输不保证可靠(丢包)、用于传输少量数据(数据包模式)、速度快。发送端和接收端
  • UDP 协议的主要作用是完成网络数据流和数据包之间的转换
  • 使用 DatagramSocket 来发送、接收数据包(DatagramPacket)使用 MulticastSocket 来实现多点广播通信

使用 DatagramSocket 发送、接收数据包

  • DatagramSocket 的构造器DatagramSocket():创建一个 DatagramSocket 实例,并将该对象绑定到本机默认 IP 地址、系统动态分配的端口DatagramSocket(int port, InetAddress laddr):创建一个 DatagramSocket 实例,并将该对象绑定到指定 IP 地址、指定端口

  • 接收和发送数据void receive(DatagramPacket p):从该 DatagramSocket 中接收数据包void send(DatagramPacket p):以该 DatagramSocket 对象向外发送数据包

使用 DatagramPacket 来代表数据包

  • DatagramPacket 的构造器DatagramPacket(byte[]buf, int length):以一个空数组来创建 DatagramPacket 对象,该对象的作用是接收 DatagramSocket 中的数据DatagramPacket(byte[] buf, int offset, int length):以一个空数组来创建 DatagramPacket 对象,并指定接收到的数据放入 buf 数组中时从 offset 开始,最多放 length 个字节DatagramPacket(byte[] buf, int length, InetAddress addr, int port):以包含数据的数组来创建一个用于发送的 DatagramPacket 对象,创建该 DatagramPacket 对象时还指定了 IP 地址和端口(该数据包的目的地)DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port):以包含数据的数组来创建一个用于发送的 DatagramPacket 对象,指定发送 buf 数组中从 offset 开始,总共 length 个字节

  • 获取发送者的 IP 地址和端口InetAddress getAddress():返回此数据包的目标机器/发送主机的 IP 地址int getPort():返回此数据包的目标机器/发送主机的端口SocketAddress getSocketAddress():返回此数据包的目标 SocketAddress 或发送此数据包的主机的 SocketAddress