Java笔记15 - 网络编程

网络编程基础

  • 计算机网络: 两台或者更多台计算机组成的网络, 在同一个网络中, 任意两台中计算机可以直接通信, 所有计算机都需要遵守同一种网络协议.
  • 计算机接入互联网, 必须使用TCP/IP协议
  • TCP/IP协议泛指互联网协议, 最重要的两个协议: TCP协议和IP协议

IP地址

  • IP地址用于唯一标识一个网络接口
  • IPV4: 采用32位地址; IPV6: 采用128位地址
  • 127.0.0.1: 指向本机
  • 公网IP地址和内网IP地址. 公网ip可以直接访问, 内网ip只能内部网络访问(10.*.*.*)``192.168.*.*
  • 如果有一个网卡, 并接入了网络, 就有了一个本机地址, 和ip地址, 可以使用这个ip地址接入网络
  • 如果有两个网卡就可以分别接入不同的网络, 例如路由器和交换机
  • 如果两台计算机位于同一个网络, 那么他们可以直接通信, 因为ip地址的前段是相同的, 也就是网络号是相同的.
  • 网络号: ip地址通过子网掩码过滤后得到的.
IP = 101.202.99.2
Mask = 255.255.255.0
Network = IP & Mask = 101.202.99.0
  • 网络号相同, 证明在同一个网路, 可以直接通信
  • 网络号不同, 需要路由器/交换机进行通讯. 也就是网关
  • 网关的作用是链接多个网络
  • 路由: 负责把一个网络的数据包发送到另一个网络

域名

  • 域名解析服务器DNS负责把域名翻译成对应的IP, 客户端再根据IP访问
  • nslookup [url]
  • nsloopkup www.baidu.com
Server:  192.168.16.1
Address: 192.168.16.1#53

Non-authoritative answer:
www.baidu.com canonical name = www.a.shifen.com.
Name: www.a.shifen.com
Address: 61.135.169.121
Name: www.a.shifen.com
Address: 61.135.169.125

网络模型

  • OSI网络模型为了简化网络各层操作, 提供标准接口便于实现和维护
  • 模型从上到下:
    • 应用层: 提供应用程序之间的通讯
    • 表示层: 处理数据格式, 加解密等
    • 会话层: 负责建立和维护会话
    • 传输层: 提供端到端的可靠传输
    • 网络层: 负责根据目标地址选择路由来传输数据
    • 链路层和物理层: 负责把数据进行分片并真正通过物理网络传输
  • TCP协议:
    • 应用层; 传输层; IP层; 网络接口层;

常用协议

  • IP协议: 一个分组协议, 不保证可靠传输

  • TCP协议: 建立在IP协议之上, 传输控制协议, 面向链接的协议, 支持可靠传输和双向通信

  • ip协议只负责发送数据包, 不保证顺序和正确性;

  • tcp协议负责控制数据包传输, 在传输数据之前要先建立连接, 建立链接后才能传输数据, 传输完成后, 断开链接

  • tcp通过接受确认, 超时重传等机制保证数据的可靠传输

  • tcp允许双向通信, 即通信的双方可以同时发送和接受数据

  • tcp协议是应用最广的协议, http和smtp都是建立在tcp协议之上

  • udp协议是一种数据报文协议, 无连接协议, 不保证可靠传输. 在通讯前不需要建立连接, 因此传输效率比tcp高, 并比tcp协议简单; 传输的数据, 需要能够忍受丢失.

TCP编程

  • 一个应用程序通过一个Socket建立一个远程连接, Socket内部通过TCP/IP协议把数据传输到网络
  • Socket, TCP, 和部分IP的功能由操作系统提供, 不同的编程语言只是提供了对操作系统的简单封装.
  • 操作系统抽出Socket接口, 每个应用程序对应不同的Socket, 数据包才能正确地发到对应的应用程序
  • socket: ip + 端口号. 小于1024的属于特权端口
  • 使用Socket进行网络编程的本质, 是两个进程之间的网络通信.
    • 一个进程充当服务器, 主动监听某个指定的端口
    • 一个进程充当客户端, 主动链接服务器的ip地址和指定端口
    • 连接成功后, 服务器和客户端成功建立一个TCP连接, 双方后续就可以随时发送和接受数据
  • Socket成功建立后:
    • 服务端socket是指定的ipd地址和指定的端口号
    • 客户端的socket是它所在的计算机的ip地址和一个由操作系统分配的随机端口号.

案例运行顺序

  1. 启动服务器, 监听端口, 一直查看有没有其他socket访问这个端口的.
  2. 有, 便开启一个新的线程 | 客户端链接这个socket
  3. 获取socket的输入和输出 | 客户端获取socket的输入和输出
  4. 服务器端向socket写入'hello'
  5. 客户端读取socket中数据 [server]: hello
  6. 客户端获取屏幕输入的数据, 并写入socket中
  7. 服务器端接受到socket中数据, 修改后, 再次写入socket中
  8. 客户端收到socket中数据, 并打印在屏幕上: ok: ...

UDP编程

  • udp编程没有创建连接, 数据包一次收发一个, 没有流的概念
  • udp编程使用socket, 指定ip和端口号. 但是和tcp是两套完全独立的端口.

HTTP编程

  • 超文本传输协议, 基于HTTP协议之上的一种请求-响应协议.

  • 客户端和服务器端首先建立TCP链接, 服务器总是使用80和安全的443端口

  • 然后客户端向服务器发送一个HTTP请求, 服务器端接受后, 返回一个HTTP响应, 包含HTML数据

  • 客户端解析HTML后展示给用户.

  • HTTP协议是固定的, 由header和body组成

  • Header:

    • 第一行总是: 请求方法 路径 HTTP版本号
    • 后面总是: Header: value格式
    • Host: 表示请求的域名, 因为一台服务器上可能含有多个网站, 因此有必要依靠Host进行区分
    • User-Agent: 客户端自身标识
    • Accept: 表示客户端可以处理的HTTP响应格式
    • Accept-Language: 表示客户端接受的语言.
  • Get请求, 只有header没有body.

  • POST请求通常要设置Content-Type表示body类型. Content-Length表示内容长度.

  • POST请求中代用body, 用一个空行分割

  • GET请求的参数必须放到url上, 有长度限制

  • 响应的第一行总是: HTTP版本 响应代码 响应说明

    • 1**: 提示行响应, eg: 101: 切换协议, 常见WebSocket协议
    • 2**: 表示成功: 200表示成功, 206表示发送了部分内容
    • 3**: 表示重定向: eg: 301表示永久重定向, 303表示客户端应该按照指定路径重新发送请求
    • 4**: 表示因为客户端问题导致的错误
    • 5**: 表示服务器端错误
  • 请求图片的时候, 图片会把二进制内容发送给客户端

  • 服务器总是被动的接受客户端的请求, 并并响应他

  • http1.1允许在一个tcp请求中反复发送-响应

  • http2.0允许同时发送多个请求, 返回的时候, 不一定按照顺序返回

java的Http编程

  • 只讨论客户端的http编程
  • 早期JDK使用HttpURLConnection进行实现
URL url = new URL("http://www.baidu.com");
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    conn.setUseCaches(false);
    conn.setConnectTimeout(5000);
    // 设置http请求头
    conn.setRequestProperty("Accept", "*/*");
    conn.setRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 11; Windows NT 5.1)");
    // 发送http请求
    conn.connect();
//    if (conn.getResponseCode() != 200) {
//      throw new RuntimeException();
//    }
    // 获取所有的响应
    Map> map = conn.getHeaderFields();
    for (String key: map.keySet()) {
      System.out.println(key+": "+map.get(key));
    }
    // 获取响应内容
    InputStream input = conn.getInputStream();
public class Client {
  static HttpClient httpClient = HttpClient.newBuilder().build(); // 创建全局实例, 内部使用线程池优化多个http连接
  public static void main(String[] args) throws IOException, URISyntaxException, InterruptedException {
    String url = "https://www.sina.com.cn";
    HttpRequest request = HttpRequest.newBuilder(new URI(url))
        .header("User-Agent",  "Java HttpClient")
        .header("Accept", "*/*")
        .timeout(Duration.ofSeconds(5))
        .version(Version.HTTP_2)
        .build();
    HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
    // Http允许重复的header, 因此一个header可对应多个value
    Map> headers = response.headers().map();
    for(String header: headers.keySet()) {
      System.out.println(header + ": " + headers.get(header).get(0));
    }
    System.out.println(response.body().substring(0, 1024) + "...");
  }
}
  • 获取图片, 使用HttpResponse.BodyHandlers.ofByteArray()进行转换
  • 响应内容很大的时候, 可以使用HttpResponse.BodyHandlers.ofInputStream()获取一个数据流进行加载
  • 使用POST请求, 正确设置Content-type即可

发送邮件

  • 各个过程:
    • MUA: Mail User Agent
    • MTA: Mail Transfer Agent
    • MDA: Mail Delivery Agent
  • mua给mta发送邮件的协议是SMTP协议 simple mail transfer protocal协议, 使用25端口, 或者加密端口465或587
  • stmp建议在tcp协议之上
  • 一个Multipart可以添加若开个bodypart, 第一个是正文, 后面是附件

接受邮件

  • 接受邮件使用POP3/IMAP协议

RMI远程调用

  • 一个jvm可以通过网络实现远程调用另一个jvm的方法
  • remote method invocation
  • 实现RMI服务端和客户端共享一个接口
  • 此接口必定派生自java.rmi.Remote, 并在每个方法中声明RemoteException
public class Server {
  public static void main(String[] args) throws RemoteException {
    System.out.println("create World clock remote service...");
    // 实例化一个WorldClock:
    WorldClock worldClock = new WorldClockService();
    // 将此服务转换为远程服务接口:
    WorldClock skeleton = (WorldClock) UnicastRemoteObject.exportObject(worldClock, 0);
    // 将RMI端口注册到1099端口
    Registry registry = LocateRegistry.createRegistry(1099);
    // 注册此服务, 服务为"WorldClock"
    registry.rebind("WorldClock", skeleton);
  }
}

// 客户端调用方法, 通过这个实现类返回结果
public class WorldClockService implements WorldClock {
  @Override
  public LocalDateTime getLocalDateTime(String zoneId) throws RemoteException{
    return LocalDateTime.now(ZoneId.of(zoneId)).withNano(0);
  }
}

public interface WorldClock extends Remote {
  LocalDateTime getLocalDateTime(String zoneId) throws RemoteException;
}

// 客户端只有接口, 并没有实现类, 使用的是服务端实现类获得数据
public class Client {
  public static void main(String[] args) throws RemoteException, NotBoundException {
    // 连接到服务器, 端口1099
    Registry registry = LocateRegistry.getRegistry("localhost", 1099);
    // 查找服务, 并进行强制转换
    WorldClock worldClock = (WorldClock) registry.lookup("WorldClock");
    // 正常调用接口方法
    LocalDateTime now = worldClock.getLocalDateTime("Asia/Shanghai");
    System.out.println(now);
  }
}
  • 客户端对应的WorldClock对应了一个实现类, 由Registry动态生成, 并把方法调用通过网络传递到服务端.
  • 服务器接受到的网络调用的服务并不是我们自己编写的WorldClockService, 而是Registry自动生成的代码, 我们把客户端的实现类称为stub, 而服务端的网络服务类称为skeleton, 会真正调用worldClockService, 获得结果.
  • rmi严重依赖序列化和反序列化, 这种情况下会造成漏洞, 因此必须是双方信任的内网机器

你可能感兴趣的:(Java笔记15 - 网络编程)