• Java> Java核心卷读书笔记


    连接到服务器

    telnet手动连接

    对于Windows,要想使用telnet工具,需要先启动telnet服务。
    启动方法:控制面板->程序->启用或关闭Windows功能->选择并启用Telnet Client(Telnet客户端)
    连接示例,命令行输入

    telnet time-a.nist.gov 13
    

    得到类似下图:

    命令行命令请求与远程服务器time-a.nist.gov 进行通讯,端口号13。

    Java连接服务器

    socket连接失败,会抛出异常UnknowHostException;如果是其他文件,抛出IOException。
    Java连接服务器简单示例

    /**
    * Socket连接远程地址, 获取流之后, 直接打印每一行. 一直持续到流发送完毕且服务器断开连接
    */
    try(Socket socket = new Socket("time-a.nist.gov", 13);  // 新建套接字, 负责启动该进程内部和外部之间的通信. 远程地址: "time-a.nist.gov"; 13: 端口号
    Scanner in = new Scanner(socket.getInputStream(), "UTF-8")) {  
          while(in.hasNext()) {
                String line = in.next();
                System.out.println(line);
          }
    }
    

    套接字超时

    1. 没有数据导致超时
      从套接字读取信息,读操作可能会阻塞,直到有数据可读。
      可以根据需要,设置合理的超时值,来解决这个问题
    Socket s = new Socket();
    s.setSoTimeout(1000); // 10秒后超时
    
    1. 读写操作超时
      如果已经为套接字设置超时值,不过之后的读/写操作没有完成之前就超过时间限制,那么这些操作会抛出SocketTimeOutException异常。可以捕获异常,并对超时做出反应。
    try {
          InputStream in = s.getInputStream();
          ...
    }
    catch (InterruptedIOException e) {
          // react to timeout
    }
    
    1. 连接超时
      构造器Socket(String host, int port)会无限期阻塞下去,直到与主机建立初始连接。
      可以先构建一个无连接的套接字,然后使用超时来进行连接的方式解决该问题
    Socket s = new Socket();
    s.connect(new InetSocketAddress(host, port), timeout);
    
    1. Socket 常用API
      java.net.Socket 1.0
    函数原型 加入JDK版本 描述
    Socket() 1.1 创建一个还没有被连接的套接字
    void connect(SocketAddress address) 1.4 将该套接字连接到给定地址
    void connect(SocketAddress address, int timeInMilliseconds) 1.4 将套接字连接到给定地址。如果给定时间内没有响应,则返回
    void setSoTimeout(int timeoutInMilliseconds) 1.1 设置套接字上读请求的阻塞时间。如果超出给定时间,则抛出InterruptedIOException异常
    boolean isConnected() 1.4 如果套接字已经被连接,返回true
    boolean isClosed() 1.4 如果套接字已被关闭,返回true

    因特网地址(IP地址)

    IP地址是一连串数字表示的主机地址,分为IPv4和IPv6两类。IPv4:IP地址4byte,如192.168.0.1;IPv6:IP地址16byte。
    InetAddress类:需要在主机和因特网之间转换时使用

    InetAddress功能

    1. 获取主机IP地址
    InetAddress address = InetAddress.getByName("time-a.nist.gov"); // getByName返回代表某个主机的InetAddress对象, 该对象封装了IP地址
    byte[] b = address.getAddress(); // 获取IP地址(4byte数据形式)
    
    1. 获取多个主机IP地址
      同一个域名主机,可能对应多个IP地址,因为主机为了实现负载均衡,包含多个IP地址。访问主机时,随机返回其中一个。
      获取所有主机IP地址
    InetAddress[] addresses = InetAddress.getAllByName(host); // host是主机名
    
    1. 获取本机地址
      本地回环地址,对因特网上其他主机无用
    InetAddress address = InetAddressgetByName("localhost"); // localhost代表本地回环地址, 127.0.0.1
    
    InetAddress.getLoopbackAddress(); // 第二种获取本地回环地址方式
    

    本地主机地址

    InetAddress address = InetAddress.getLocalHost();
    

    InetAddress示例

        public static void main(String[] args) throws UnknownHostException {
            String host = "www.horstmann.com";
            InetAddress[] addresses = InetAddress.getAllByName(host);
            for (InetAddress a : addresses) {
                System.out.println(a);
            }
    
            System.out.println("local address = " + InetAddress.getLocalHost());
            System.out.println("loop back address = " + InetAddress.getLoopbackAddress());
        }
    

    示例运行结果

    www.horstmann.com/204.44.192.29
    local address = DESKTOP-H0C09BT/172.16.6.131
    loop back address = localhost/127.0.0.1
    

    服务器

    服务器套接字

    服务器启动后,会等待某个客户端连接到它的端口。ServerSocket类用于创建服务器套接字。

    服务器套接字ServerSocket跟前面提到的Socket套接字,是什么关系?
    ServerSocket是用于服务器的,Socket是用于客户端的。服务器是等待客户端连接,客户端是通过网络请求连接远程服务器。

    1. 创建服务器套接字
    ServerSocket s = new ServerSockt(8189); // 建立负责监控8189端口的服务器, 选择8189是因为没有别的程序/服务在使用该端口
    
    1. 等待客户端连接
    Socket incoming = s.accept(); // 告诉程序不停等待, 直到有客户端连接监听的8189端口
    
    1. 服务器通过输入输出流与客户端通信
      服务器发送给服务器输出流的所有信息,都会成为客户端的输入;
      来自客户端的输出流,都会成为服务器端的输入;
    // 获取服务器输入流
    InputStream inStream = incoming.getInputStream();
    // 获取服务器输出流
    OutputStream outStream = incoming.getInputStream();
    
    // 将流包装成扫描器和写入器, 通过套接字发送文本
    Scanner in = new Scanner(inStream, "UTF-8");
    PrintWriter out = new PrintWriter(new OutputStreamWriter(outStream, "UTF-8"), true /* auto flush */);
    
    // 向客户端发送一条信息
    out.println("hello! Enter BYE to exit.");
    
    // 关闭连接进来的套接字
    incoming.close();
    

    服务器程序框架

    每个服务器,都会不间断执行下面的循环:

    1. 通过输入数据流从客户端接收一个命令;
    2. 解码客户端命令;
    3. 收集客户端请求信息;
    4. 通过输出数据流发送信息给客户端;

    服务器示例

    例子演示服务器程序框架,如何创建ServerSocket,等待连接,以及如何向客户端发送消息。

    try(ServerSocket s = new ServerSocket(8189)) {
          try (Socket incoming = s.accept()) { // 阻塞, 直到有客户端连接到服务器
                InputStream inStream = incoming.getInputStream();
                OutputStream outStream = incoming.getOutputStream();
                
                // 用Scanner包装输入流, PrintWriter包装输出流
                try (Scanner in = new Scanner(inStream, "UTF-8")) {
                      PrintWriter out = new PrintWriter(new OutputStreamWriter(outStream, "UTF-8"));
                      out.println("Hello! Enter BYE to exit.");
                }
    
                // 响应客户端输入
                boolean done = false;
                while(!done && in.hasNextLine()) {
                      String line = in.nextLine();
                      out.println("Ehco: " + line);
                      if (line.equals("BYE")) done = true;
                }
          }
    }
    

    运行方式:本地命令行模式下输入telnet localhost 8189;远程运行需要本机IP地址或者域名,提到本地回环地址。

    为多个客户端服务

    前面的简单服务器的例子只能支持一个服务器,如果要支持多个客户端,要怎么办?
    可以使用多线程,每当程序建立一个新套接字时(s.accept()成功返回一个Socket),启动一个新的线程来处理服务器和该客户端之间的连接,主程序立即返回并等待下一个连接。
    示例

    ServerSocket s = new ServeSocket(8189);
    
    // 服务器为每个新建Socket新建一个线程, 处理与具体客户端之间连接问题. 主程序仍然继续等待下一个连接请求
    while(true) {
          Socket incoming = s.accept();
          Runnable r = new ThreadedEchoHanlder(incoming);
          
          // 新建线程
          Thread t = new Thread(r);
          // 启动线程
          t.start();
    }
    
    public class ThreadedEchoHandler implements Runnable {
          private Socket incoming;
          public ThreadedEchoHandler(Socket incoming) {
                this.incoming = incoming;
          }
    
          @override
          public void run() {
                try (InputStream inStream = incoming.getInputStream();
                OutputStream outStream = incoming.getOutputStream();) {
                      // 处理输入输出响应
                      // e.g. Scanner包装InputStream, PrintWriter包装OutputStream
                      Scanner in = new Scanner(inStream, "UTF-8");
                      PrintWriter out = new PrintWriter(new OutputStreamWriter(outStream), "UTF-8");
                      
                      // 向客户端发送消息
                      out.println("Hello! Enter BYE to exit.");
                      // 响应客户端输入, 以收到客户端"BYE"字符串为结束
                      boolean done = false;
                      while (!done && in.hasNextLine()) {
                            String line = in.nextLine();
                            out.println("Echo: " + line);
                            if (line.trim().equals("BYE")) done = true;
                      }
                } catch (IOException e) {
                      e.printTraceStack();
                }
          }
    }
    

    半关闭

    半关闭(half close),简单说,就是只收不发,或者只发不收。
    典型应用场景:向服务器传输数据,一开始不知道要传多少; 传文件,向文件写数据写完后即可关闭。
    如果不这样做,要么关闭套接字,这样与服务器连接断开;要么保持套接字打开,不过这样仍然占用着输出流资源。

    客户端使用半关闭方法示例:

    String host = "127.0.0.1";
    long port = 8189;
    
    try (Socket socket = new Socket(host, port)) {
          Scanner in = new Scanner(socket.getInputStream(), "UTF-8");
          PrintWriter writer = new PrintWriter(socket.getOutputStream());
          // 向服务器发送请求数据
          writer.print("last data");
          writer.flush();
    
          socket.shutdownOutput(); // 关闭输出, 这之后客户端(套接字)进入半关闭状态
          // 读取服务器数据
          while (in.hasNextLine()) {
                String line = in.nextLine();
                // 处理line
          }
    }
    

    可中断套接字

    两种常件阻塞情况

    • 当连接到一个套接字时,当前线程会阻塞,直到连接建立或超时;
    • 通过套接字读写数据时,当前线程阻塞,直到操作成功或者超时;

    如果先网络读取速度过慢,发生阻塞的时候,如果用户想取消怎么办?
    这就需要中断套接字 -- 套接字通道SocketChannel类。

    使用SocketChannel

    1. 打开SocketChannel
    String host = "localhost"; // 示例, 本地主机
    long port = 8189; // 示例, 选择一个未被占用的端口号
    SocketChannel channel = SocketChannel.open(new InetSocketAddress(host, port));
    
    1. 从SocketChannel读取信息
      用Scanner类从SocketChannel读取信息,可以避免处理缓冲区,不过这不是强制性的。
    Scanner in = newScanner(channel, "UTF-8");
    

    3.将通道转换成输出流

    OutputStream outStream =Channels.newOutputStream(channel);
    

    当线程正在执行打开、读取或写入操作时,如果发生中断,那么这些操作将不会阻塞,而是抛出异常结束。

    *** 注解:看到这里,还是不明白,中断套接字到底有什么用?怎么用? ***

    获取Web数

    URL , URI

    URL: Uniform Resource Locator, 统一资源定位符,包含了用于定位Web资源的足够信息,是URI的特例;
    URI: Uniform Resource Identifier,统一资源标识符,是纯粹的语法结构,用来指定Web资源的字符串的各种组成部分;
    URN: mailto:cay@horstmann.com属于URI,但不属于URL,因为根据该标识符无法定位任何数据。这样的URI称为URN(Uniform Resource Name),统一资源名称。

    Java URI类不包含任何处理资源的方法,唯一作用是解析。Java URL类可以打开一个到资源的流,只能作用于:http, https, ftp, 本地文件系统(file:)和JAR文件(jar:)。

    1. URI类解析标识符
      解析标识符,并分解成各种不同的部分
    getScheme
    getSchemeSpecificPart
    getAuthority
    getUserInfo
    getHost
    getPort
    getPath
    getQuery
    getFragment
    
    1. URI类处理绝对标识符和相对标识符
    • 解析相对URI
      比如将绝对URI http://docs.mycompany.com/api/java/net/ServerSocket.html和相对URI../../java/net/Socket.html#Socket(),组合成绝对URIhttp://docs.mycompany.com/api/java/net/Socket.htm#Socket()
    • 相对化relativization
      比如一个URIhttp://docs.mycompany.com/api和另外一个URIhttp://docs.mycompany.com/api/java/lang/String.html相对化之后,得到新URIjava/lang/String.html

    使用URLConnection获取信息

    利用URLConnection类,能比URL类获取更多Web资源信息和控制功能。

    • URLConnection使用步骤:
    1. 调用URL类的openConnection() 获得URLConnection对象
    URLConnection connection = url.openConnection();
    
    1. 设置请求属性
    setDoInput
    setDoOutput
    setIfModifiedSince
    setUseCaches
    setAllowUserInteraction
    setRequestProperty
    setConnectTimeout
    setReadTimeout
    
    1. 调用connect连接远程资源
    connection.connect(); // 除与服务器建立连接套接字外, 该方法还可以用于向服务器查询头信息(header information)
    
    1. 与服务器建立连接后,可以查询头信息
      getHeaderFieldKey和getHeaderField枚举了消息头的所有字段,getHeaderFields包含了消息头中所有字段的标准Map对象。
    getContentType
    getContentLength
    getContentEncoding
    getDate
    getExpiration
    getLastModified
    

    getHeaderFieldKey和getHeaderField,以及getHeaderFields查询消息头信息的用法示例

    // 必要步骤: 创建URL并连接服务器
    String urlName = "http://www.163.com";
    URL url = new URL(urlName);
    URLConnection connection = url.openConnection();
    connection.connect();
    
    // 打印消息头信息
    /* 方式一: 使用getHeaderFieldKey和getHeaderField 获取消息头信息, 多个需要循环获取, 直到key = null */
    int n = 0;
    String key = connection.getHeaderFieldKey(n);
    String value = connection.getHeaderField(n);
    
    /*方式二: 使用getHeaderFields来获取消息头 */
    Map<String, List<String>> headers= connection.getHeaderFields();
    headers.forEach((k ,v)->{
          for (String s : v) {
                System.out.println(k + ": " + s);
          }
    });
    
    // 打印消息头具体字段信息
    System.out.println("---------");
    System.out.println("getContentType: " + connection.getContentType());
    System.out.println("getContentLength: " + connection.getContentLength());
    System.out.println("getContentEncoding: " + connection.getContentEncoding());
    System.out.println("getDate: " + connection.getDate());
    System.out.println("getExpiration: " + connection.getExpiration());
    System.out.println("getLastModified: " + connection.getLastModified());
    System.out.println("---------");
    
    String encoding = connection.getContentEncoding();
    if (encoding == null) encoding = "UTF-8";
    try (Scanner in = new Scanner(connection.getInputStream(), encoding)) {
          // 打印内容前10行
          for (int i = 1; in.hasNextLine() && i <= 10; i++) {
                System.out.println(in.nextLine());
          }
          if(in.hasNextLine()) System.out.println("...");
    }
    catch(IOException e) {
          e.printStackTrace();
    }
    

    5.访问资源数据
    getInputStream获取一个输入流以读取信息,该方法与URL类的openstream方法返回流相同。另外一个方法,getContent实际操作中不是很有用。
    注意:URLConnection的getInputStream和getOutputStream方法,与Socket中这些方法并不相同,特别是在处理请求和响应消息头时。

    connection.getInputStream() 等价于 url.openStream()
    
    connection.getInputStream() 不等于 socket.getInputStream()
    
    • URLConnection一些重要方法
      默认情况下,建立连接只产生从服务器读取信息的输入流,并不产生任何执行写操作的输出流。如果想获得输出流(如向Web服务器提交数据),需要调用:
    connection.setDoOutput(true);
    
    // 其他重要方法
    setIfModifiedSince // 告诉连接, 只对某个自特定日期以来被修改过的数据感兴趣
    
    setRequestProperty // 用来设置对特定协议起作用的任何“名-值”对(name/value)
    
    /* 只作用于Applet */
    setUserCaches // 用于命令浏览器首先检查它的缓存
    setAllowUserInteraction // 用于在访问有密码保护的资源时弹出对话框, 以便查询用户名和口令
    

    访问一个带有密码保护Web页的例子,步骤:
    1)将用户名、冒号和密码以字符串形式连接在一起

    String input = username + ":" + password;
    

    2)计算上一步字符串Base64编码,用于将字节序列编码成可打印的ASCII字符序列。

    Base64.Encoder encoder = Base64.getEncoder();
    String encoding = encoder.encodeToString(input.getBytes(StandardCharsets.UTF-8));
    

    3)用"Authorization"这个名字和"Basic" + encoding的值调用setRequestProperty方法

    connection.setRequestProperty("Authorization", "Basic" + encoding);
    

    带有密码保护Web页步骤框架:

    String url = "http://horstmann.com";
    URL url = new URL(urlName);
    URLConnection connection = url.openConnection();
    
    String userName = "username"; // 用户名
    String password = "password"; // 密码
    String input = username + ":" + password;
    Base64.Encoder encoder = Base64.getEncoder();
    String encoding = encoder.encodeToString(input.getBytes(StandardCharsets.UTF_8));
    connection.setRequestProperty(""Authorization", "Basic " + encoding);
    
    conection.connect(); // 连接服务器
    
    //...
    

    错误处理

    如果服务器出现错误,调用connection.getInputStream会抛出异常FileNotFoundException。此时,服务器仍然会向浏览器返回一个错误页面,为了捕捉这个错误页,可以调用getErrorStream,调用方法InputStream err = connection.getErrorStream()

    重定向redirect

    将POST数据发送给服务器时,服务器端程序产生的响应可能是redirect,后面跟着完全不同URL。
    HttpURLConnection类通常可以用来处理这种重定向。当要处理的URL是htpp://或者https://开头,那么URLConnection对象可以强制转换为其子类HttpURLConnection对象。

  • 相关阅读:
    POJ 1330:Nearest Common Ancestors【lca】
    图论中一类问题的总结 :必须边(点) 可行边(点)
    POJ 1486 Sorting Slides【二分图匹配】
    POJ 2375 Cow Ski Area【tarjan】
    Unity打开AppStore进行评论
    Unity3D UGUI不规则图片点击事件处理
    Unity3D之聊天框怎么跟随内容大小而变换
    Unity3D之小物体层消隐技术
    Unity3D之新手引导(责任链模式)
    Unity3D之FSM有限状态机
  • 原文地址:https://www.cnblogs.com/fortunely/p/14055801.html
Copyright © 2020-2023  润新知