侧边栏壁纸
  • 累计撰写 57 篇文章
  • 累计创建 10 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

4、网络

yilee
2023-04-04 / 0 评论 / 0 点赞 / 142 阅读 / 0 字 / 正在检测是否收录...
温馨提示:
本文最后更新于2024-05-31,若内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

四、网络

4.1 连接到服务器

  1. 使用telnet(P191)

  2. 用Java 连接到服务器

    • 连接到某个端口并打印出它所找到的信息

      // 打开一个套接字,负责启动该程序内部和外部之间的通信。
      Socket s = new Socket("yilee.top", 80);
      // 获取套接字输入流,该对象可以像其他任何流对象一样使用。
      Scanner in = new Scanner(s.getInputStream(), "UTF-8");
      
  3. 套接字超时

    • 从套接字读取信息时,在有数据可供访问之前,读操作将会被阻塞。如果此时主机不可达,那么应用将要等待很长的时间,并且因为受底层操作系统的限制而最终会导致超时。

    • 读写超时

      • 对于不同的应用,应该确定合理的超时值。然后调用setSoTimeout 方法设置这个超时值(单位:毫秒) 。如果已经为套接字设置了超时值,并且之后的读操作和写操作在没有完成之前就超过了时间限制,那么这些操作就会抛出SocketTimeoutException 异常。
      // 设置读写超时
      Socket s = new Socket(...);
      s.setSoTimeout(10000); // time out after 10 seconds
      
    • 连接超时

      // 以下构造器不设置超时时间会一直阻塞,直到建立连接
      Socket(String host, int port)
      // 使用先构建一个无连接的套接字,然后再使用一个超时来进行连接的方式解决
      Socket s = new Socket();
      s.connect(new InetSocketAddress(host, port), timeout);
      
  4. 因特网地址

    • InetAddress 对象实现主机名和因特网地址之间进行转换

      // 静态的getByName 方法可以返回代表某个主机的InetAddress 对象。
      InetAddress address = InetAddress.getByName("time-a.nist.gov");
      // 一些访问量较大的主机名通常会对应于多个因特网地址,以实现负载均衡。
      // 调用getAllByName 方法来获得所有主机
      InetAddress[] address = InetAddress.getAllByName("time-a.nist.gov");
      // 静态的getlocalHost 方法来得到本地主机
      InetAddress address = InetAddress.getlocalHost();
      

4.2 实现服务器

  1. 服务器套接字

    • ServerSocket 类用于建立套接字。

      // 建立一个负责监控端口8198的服务器。
      ServerSocket s = new ServerSocket(8189);
      // 告诉程序不停地等待, 直到有客户端连接到这个端口
      Socket incoming = s.accept();
      // 你可以使用这个对象来得到输入流和输出流,
      InputStream inStream = incoming.getinputStream();
      OutputStream outStream = incoming.getOutputStream();
      // 关闭了连接进来的套接
      incoming.close();
      
  2. 为多个客户端服务

  • 服务器程序

    import java.io.*;
    import java.net.*;
    import java.nio.charset.StandardCharsets;
    import java.util.*;
    
    class SocketRunnable implements Runnable {
        Socket socket;
    
        public SocketRunnable(Socket socket) {
            this.socket = socket;
        }
    
        @Override
        public void run() {
            try (InputStream inStream = socket.getInputStream();
                    OutputStream outStream = socket.getOutputStream()) {
                Scanner inScanner = new Scanner(inStream, "UTF-8");
                // 读取数据
                while (inScanner.hasNextLine()) {
                    String str = inScanner.nextLine();
                    // 响应数据
                    outStream.write(String.format("收到数据:%s\r\n", str).getBytes(StandardCharsets.UTF_8));
                    System.out.println(str);
                    if ("BYE".equals(str)) {
                        System.out.println("客户端断开连接。");
                        break;
                    }
                }
            } catch (Exception err) {
                System.out.println(err.getMessage());
            }
        }
    }
    
    public class SocketTest {
        public static void main(String[] args) throws IOException {
            try (ServerSocket serverSocket = new ServerSocket(8189)) {
                while (true) {
                    Socket socket = serverSocket.accept();
                    new Thread(new SocketRunnable(socket)).start();
                }
            }
        }
    }
    
  • 客户端程序

    import java.io.*;
    import java.net.*;
    import java.nio.charset.StandardCharsets;
    import java.util.*;
    
    public class SocketTest {
        public static void main(String[] args) throws IOException {
            Socket socket = new Socket("127.0.0.1", 8189);
            try (InputStream inStream = socket.getInputStream();
                 OutputStream outStream = socket.getOutputStream()) {
                Scanner inScanner = new Scanner(inStream, "UTF-8");
                Scanner systemIn = new Scanner(System.in);
                while (systemIn.hasNextLine()) {
                    String str = systemIn.nextLine();
                    outStream.write(String.format("%s\r\n", str).getBytes(StandardCharsets.UTF_8));
                    if ("BYE".equals(str)) {
                        System.out.println("断开连接。");
                        break;
                    }
                    System.out.println(inScanner.nextLine());
                }
            } catch (Exception err) {
                System.out.println(err.getMessage());
            }
        }
    }
    
  1. 半关闭
  • 半关闭(half-close) 提供了这样一种能力:套接字连接的一端可以终止其输出,同时仍旧可以接收来自另一端的数据。在客户端使用半关闭方法:

    // 服务器端将读取输入信息,直至到达输入流的结尾,然后它再发送响应。
    try (Socket socket = new Socket(host, port)) {
        Scanner in = new Scanner(socket.getInputStream(), "UTF-8");
        PrintWriter writer = new PrintWriter(socket.getOutputStream());
        // send request data
        writer.print(...);
        writer.flush();
        socket.shutdownOutput();
        // now socket is half-closed
        // read response data
        while (in.hasNextLine()) {
            String line = in.nextLine();
            ...
        }
    }
    

    该协议只适用于一站式(one-shot) 的服务,例如HTTP 服务,在这种服务中,客户端连接服务器,发送一个请求,捕获响应信息,然后断开连接。

4.3 可中断套接字

  1. 当连接到一个套接字时,当前线程将会被阻塞直到建立连接或产生超时为止。当通过套接字读写数据时,当前线程也会被阻塞直到操作成功或产生超时为止。

    • 使用SocketChannel 类,中断套接字操作

      SocketChannel channel = SocketChannel.open(new InetSocketAddress(host, port));
      

      通道(channel ) 并没有与之相关联的流,它所拥有的read 和write 方法都是通过使用Buffer 对象来实现的。ReadableByteChannel 接口和WritableByteChannel 接口都声明了这两个方法。

    • 不处理缓冲区

      // 使用Scanner 类从SocketChannel 中读取信息
      Scanner in = new Scanner (channel, "UTF-8 ");
      // 调用静态方法Channels.newOutputStream , 可以将通道转换成输出流。
      OutputStream outStream = Channels.newOutputStream(channel);
      

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

4.4 获取Web 数

  1. URL 和URI

    • URL 和URLConnection 类封装了大量复杂的实现细节, 这些细节涉及如何从远程站点获取信息。

    • URL 的使用示例

      URL url = new URL(urlString);
      // 获得该资源的内容,使用URL 类中的open Stream 方法
      InputStream inStream = url.openStream();
      Scanner in = new Scanner(inStream, "UTF-8");
      
    • URL 和URI的区别

      • URI 是个纯粹的语法结构,包含用来指定Web 资源的字符串的各种组成部分。URI 类并不包含任何用于访问资源的方法,它的唯一作用就是解析。
      • URL 是 URI 的一个特例,它包含了用千定位Web 资源的足够信息。URL 类可以打开一个到达资源的流。URL 类只能作用于那些Java 类库知道该如何处理的模式,例如http: 、https : 、ftp: 、本地文件系统( file: ) 和JAR 文件( jar : ) 。
    • 一个URI

      [scheme:]schemeSpecificPart[#fragment]
      
      • […]表示可选部,并且:和#可以被包含在标识符

      • 包含scheme: 部分的URI 称为绝对URI 。否则,称为相对URI 。

      • 如果绝对URI 的schemeSpecificPart 不是以/开头的,我们就称它是不透明的。

        mailto:cay@.horstmann.com
        
      • 所有绝对的透明URI 和所有相对URI 都是分层的(hierarchical) 。

        http://horstmann.com/index.html
        ../../java/net/Socket.html#Socket()
        
      • 一个分层URI 的schemeSpecificPart 具有结构

        [//authority][path][?query]
        // [...]表示可选的部分。
        
      • 基千服务器的URI, authority 部分具有结构

        [user-info@]host[:port]
        // port 必须是一个整数。
        
    • URI 类的作用之一是解析标识符并将它分解成各种不同的组成部分。另一个作用是处理绝对标识符和相对标识符。与此相反的过程称为相对化(relativization) 。

      // URI各个部分获取
      getScheme、getSchemeSpecificPart、getAuthority、getUserInfo
      getHost、getPort、getPath、getQuery、getFragment
      
      
      • 绝对化 combined = base.resolve(relative);
      绝对URI
      http://docs.mycompany.com/api/java/net/ServerSocket.html
      相对URI
      ../../java/net/Socket.html#Socket()
      绝对化之后的URI
      http://docs.mycompany.com/api/java/net/Socket.html#Socket()
      
      • 相对化 relative = base.relativize(combined);
      基本URI
      http://docs.mycompany.com/api
      另一个URI
      http://docs.mycompany.com/api/java/1ang/String.html
      相对化之后的URI
      ./java/1ang/String.html
      
  2. 使用URLConnection 获取信息

    • 使用URLConnection 类从某个Web 资源获取更多信

      • 调用URL 类中的openConnection 方法获得URLConnection 对象

        URLConnection connection = url.openConnection();
        
      • 使用以下方法来设置任意的请求属性

        setDoinput
        setDoOutput
        setifModifiedSinee
        setUseCaches
        setAllowUserinteraction
        setRequestProperty
        setConnectTimeout
        setReadTimeout
        

        建立的连接只产生从服务器读取信息的输入流,并不产生任何执行写操作的输出流。如果想获得输出流,那么你需要调用setDoInput方法

        connection.setDoOutput(true) ;
        

        设置某些请求头( request header )

        • setlfModifiedSince 方法用于告诉连接你只对自某个特定日期以来被修改过的数据感兴趣。
        • setUseCaches 和 setAllowUserInteraction 这两个方法只作用千Applet(不重要)。
        • setUseCaches 方法用千命令浏览器首先检查它的缓存。
        • setAllowUserInteraction方法则用千在访问有密码保护的资源时弹出对话框,以便查询用户名和口令。
        • setRequestProperty , 它可以用来设置对特定协议起作用的任何“名-值( name/value ) 对“ 。
      • 调用connect 方法连接远程资源,除了与服务器建立套接字连接外,该方法还可用千向服务器查询头信息

        connection.connect();
        
      • 与服务器建立连接后,你可以查询头信息。getHeaderFieldKey 和 getHeaderField 这两个方法枚举了消息头的所有字段。getHeaderFields 方法返回一个包含了消息头中所有字段的标准Map 对象。

        • 调用 getHeaderFieldKey() 可以获得响应头的第n 个键,其中n 从1 开始!如果n 为0 或大千消息头的字段总数,该方法将返回null 值。没有哪种方法可以返回字段的数量,你必须反复调用getHeaderFieldKey 方法直到返回null 为止。

          String key = connection.getHeaderFieldKey(n);
          
        • 调用 getHeaderField() 可以得到第n 个值。

          String key = connection.getHeaderField(n);
          
        • 调用 getHeaderFields 方法可以返回一个封装了响应头字段的Map 对象。

          Map<String,List<String>> headerFields = connection.getHeaderFields();
          
        • 为了简便起见, Java 提供了6 个方法用以访问最常用的消息头类型的值,并在需要的时候将它们转换成数字类型

          130712a9aed44033aba8e574f10caf15

      • 访问资源数据。使用getlnputStream 方法获取一个输入流用以读取信息(这个输入流与URL 类中的open Stream 方法所返回的流相同)。另一个方法getContent 在实际操作中并不是很有用。

  3. 提交表单数据

    • 使用GET 命令时,只需将参数附在URL 的结尾处即可,每个参数都具有“名字=值”的形,而这些参数之间用&字符分隔开。

      http://host/path?query
      

      参数的值将遵循下面的规则

      • 保留字符A 到Z 、a 到z 、0 到9 , 以及.- ~ _。
      • 用+字符替换所有的空格。
      • 将其他所有字符编码为UTF-8, 并将每个字节都编码为%后面紧跟一个两位的十六进制数字。
    • POST 请求经常用来处理具有大量数据的表单。不在URL 上附着参数,而是从URLConnection 中获得输出流,并将名/ 值对写入到该输出流中。我们仍旧需要对这些值进行URL 编码,并用&字符将它们隔开。

      // 创建一个 URLConnection 对象
      URL url = new URL("http://host/path");
      URLConnection connection = url.openConnection();
      // 调用setDoOutput 方法建立一个用千输出的连接。
      connection.setDoOutput(true);
      // 调用getOutputStream 方法获得一个流,向服务器发送文本信息可以将流包装在PrintWriter中
      PrintWriter out = new PrintWriter(connection.getOutputStream(), "UTF-8");
      // 向服务器发送数据
      out.print(namel + "=" + URLEncoder.encode(valuel, "UTF-8") + "&");
      out.print(name2 + "=" + URLEncoder.encode(value2, "UTF-8"));
      // 关闭输出流。
      out.close();
      // 调用 getInputStream 方法读取服务器的响应。
      

      如果cookie 需要在重定向中从一个站点发送给另一个站点,那么你可以像下面这样配置一个全局的cookie 处理器, cookie 就可以被正确地包含在重定向请求中了。

      CookieHandler.setDefau1t(new CookieManager(null, CookiePo1icy.ACCEPTALL));
      

API注释

4.1.2 用Java 连接到服务器

java.net.Socket 1.0

Socket(String host, int port)
// 构建一个套接字,用来连接给定的主机和端口。
InputStream getlnputStream()
OutputStream getOutputStream()
// 获取可以从套接字中读取数据的流,以及可以向套接字写出数据的流。

4.1.3 套接字超时

java.net.Socket 1.0

Socket() 1.1
// 创建一个还未被连接的套接字。
void connect(SocketAddress address) 1. 4
// 将该套接字连接到给定的地址。
void connect(SocketAddress address, int timeoutInMilliseconds) 1. 4
// 将套接字连接到给定的地址。如果在给定的时间内没有响应,则返回。
void setSoTimeout(int timeoutInMilliseconds) 1.1
// 设置该套接字上读请求的阻塞时间。如果超出给定时间,则抛出一个InterruptedIOException异常。
boolean isConnected() 1. 4
// 如果该套接字已被连接,则返回true 。
boolean isClosed() 1. 4
// 如果套接字已经被关闭,则返回true 。

4.1.4 因特网地址

java.net.InetAddress 1.0

static InetAddress getByName(String host)
static InetAddress[] getAllByName(String host)
// 为给定的主机名创建一个InetAddress 对象,或者一个包含了该主机名所对应的所有因特网地址的数组。
static InetAddress getlocalHost()
// 为本地主机创建一个InetAddress 对象。
byte[] getAddress()
// 返回一个包含数字型地址的字节数组。
String getHostAddress()
// 返回一个由十进制数组成的字符串,各数字间用圆点符号隔开,例如, “ 129.6 . I 5 . 28 ” 。
String getHostName()
// 返回主机名。

4 .2.1 服务器套接字

java.net.ServerSocket 1.0

ServerSocket(int port)
// 创建一个监听端口的服务器套接字。
Socket accept()
// 等待连接。该方法阻塞(即,使之空闲)当前线程直到建立连接为止。该方法返回一个Socket 对象,程序可以通过这个对象与连接中的客户端进行通信。
void close()
// 关闭服务器套接字。

4.2.3 半关闭

java.net.Socket 1.0

void shutdownOutput() 1.3
// 将输出流设为“流结束"
void shutdownInput() 1.3
// 将输入流设为“流结束" 。
boolean isOutputShutdown () 1.4
// 如果输出已被关闭, 则返回true 。
boolean isInputShutdown() 1.4
// 如果输入已被关闭, 则返回true 。

4.3 可中断套接字

java.net.InetSocketAddress 1.4

InetSocketAddress (String hostname, int port)
// 用给定的主机和端口参数创建一个地址对象,并在创建过程中解析主机名。如果主机名不能被解析,那么该地址对象的unresolved 属性将被设为true 。
boolean isUnresolved()
// 如果不能解析该地址对象,则返回true 。

java.nio.channels.SocketChannel 1.4

static SocketChannel open(SocketAddress address)
// 打开一个套接字通道,并将其连接到远程地址。

java.nio.channels.Channels 1.4

static InputStream newInputStream(ReadableByteChannel channel)
// 创建一个输入流,用以从指定的通道读取数据。
static OutputStream newOutputStream(WritableByteChannel channel)
// 创建一个输出流,用以向指定的通道写入数据。

4.4.2 使用URLConnection 获取信息

java.net.URL 1.0

InputStream openStream()
// 打开一个用千读取资源数据的输入流。
URLConnection openConnection();
// 返回一个URLConnection 对象,该对象负责管理与资源之间的连接。

java.net.URLConnection 1.0

void setDoInput(boolean doInput)
boolean getDoInput()
// 如果doInput 为true, 那么用户可以接收来自该URLConnection 的输入。
void setDoOutput(boolean doOutput)
boolean getDoOutput ()
// 如果doOutput 为true , 那么用户可以将输出发送到该URLConnection 。
void setlfModifiedSince(long time)
long getlfModifiedSince()
// 属性ifModifiedSince 用千配置该URLConnection 对象,使它只获取那些自从某个给定时间以来被修改过的数据。调用方法时需要传入的time 参数指的是从格林尼治时间1970 年1 月1 日午夜开始计算的秒数。
void setUseCaches(boolean useCaches)
boolean getUseCaches()
// 如果useCaches 为true , 那么数据可以从本地缓存中得到。请注意, URLConnection本身并不维护这样一个缓存,缓存必须由浏览器之类的外部程序提供。
void setAllowUserInteraction(boolean allowUserInteraction)
boolean getAllowUserInteraction()
// 如果allowUserInteraction 为true , 那么可以查询用户的口令。请注意, URLConnection本身并不提供这种查询功能。查询必须由浏览器或浏览器插件之类的外部程序实现。
void setConnectTimeout(int timeout) 5.0
int getConnectTimeout() 5.0
// 设置或得到连接超时时限(单位: 毫秒) 。如果在连接建立之前就已经达到了超时的时限,那么相关联的输入流的connect 方法就会抛出一个SocketTimeoutException 异常。
void setReadTimeout(int timeout) 5.0
int getReadTimeout() 5.0
// 设置读取数据的超时时限(单位:毫秒) 。如果在一个读操作成功之前就已经达到了超时的时限,那么read 方法就会抛出一个SocketTimeoutException 异常。
void setRequestProperty(String key, String value)
// 设置请求头的一个字段。
Map<String, List<String>> getRequestProperties() 1.4
// 返回请求头属性的一个映射表。相同的键对应的所有值被放置在同一个列表中。
void connect()
// 连接远程资源并获取响应头信息。
Map<String, List<String>> getHeaderFields() 1.4
// 返回响应的一个映射表。相同的键对应的所有值被放置在同一个列表中。
String getHeaderFieldKey(int n)
// 得到响应头第n 个字段的键。如果n 小千等于0 或大于响应头字段的总数,则该方法返回null 值。
String getHeaderField(int n)
// 得到响应头第n 个字段的值。如果n 小于等千0 或大千响应头字段的总数,则该方法返回null 值。
int getContentlength()
// 如果内容长度可获得,则返回该长度值,否则返回- l 。
String getContentType()
// 获取内容的类型,比如text/plain 或i mage/gif 。
String getContentEncoding()
// 获取内容的编码机制,比如gzip 。这个值不太常用,因为默认的identity 编码机制并不是用Content - Encoding 头来设定的。
long getDate()
long getExpiration()
long getLastModified()
// 获取创建日期、过期日以及最后一次被修改的日期。这些日期指的是从格林尼治时间1970 年1 月1 日午夜开始计算的秒数。
InputStream getlnputStream()
OutputStream getOutputStream()
// 返回从资源读取信息或向资源写入信息的流。
Object getContent()
// 选择适当的内容处理器,以便读取资源数据并将它转换成对象。该方法对于读取诸如text/plain 或i mage/gif 之类的标准内容类型并没有什么用处,除非你安装了自己的内容处理器。

4.4.3 提交表单数据

java.net HttpURLConnection 1.0

InputStream getErrorStream()
// 返回一个流,通过这个流可以读取Web 服务器的错误信息。

java.net.URLEncoder 1.0

static String encode(String s, String encoding) 1.4
// 采用指定的字符编码模式(推荐使用“ UTF- 8") 对字符串s 进行编码,并返回它的URL 编码形式。 在 U肛 编码中, ' A'-'Z' , 'a'-'z' , '0' - '9 ', '-' ,,' _' , ' . ' 和'* '等字符保持不变,空格被编码成'+',所有其他字符被编码成“%XY” 形式的字节序列,其中OxXY 为该字节十六进制数。

java.net.URLDecoder 1.2

static string decode(String s, String encoding) 1.4
// 采用指定编码模式对已编码字符串s 进行解码,并返回结果。
0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区