Java基础之网络编程

网络编程

网络编程

  • 网络编程

    • 多主机间的数据通讯操作
  • 网络(核心定义)

    • 有两台以上电脑就称为网络
  • 网络连接的目的

    • 不仅是为了电脑的串联,主要是为了彼此之间的数据交互
    • 所谓网络游戏本质还是网络通信问题
  • 通讯实现

    • 产生一系列处理协议:IP、TCP、UDP 等
  • 网络编程实现的是数据通讯操作

    • 只是通讯操作分为 客户端 和 服务器端
  • java.net 包中 J2SE 的 API 包含有相关类和接口,提供低层次的通信细节

    • 可直接使用这些类和接口,来专注于解决问题,而不用关注通信细节
  • 网络程序开发模型

    • C/S:Client/Server

      • 开发两套程序:客户端和服务器端
      • 服务器端发生改变客户端也应更新处理
      • 可以由开发者自定义传输协议,使用较私密端口
      • 安全性较高,但开发和维护成本较高
    • B/S:Browse/Server

      • 浏览器和服务器,只开发服务器端的程序
      • 利用浏览器作为客户端进行访问
      • 使用公共 HTTP(HTTPS)协议和公共端口 80
      • 安全性较低,开发和维护成本较低
    • 主要使用 C/S 模型完成网络编程,使用两种常见编程协议

      • TCP:可靠的数据连接
      • UDP:不可靠的数据连接

网络通信

网络通信的要素

  • 双方的 IP 地址
  • 端口号
IP
  • 通过 java.net.InetAdress 类使用 IP

    • 唯一定位一台网络上计算机

    • 127.0.0.1 本机 localhost

      • 127.0.0.1 是主机环回地址
      • 主机环回:地址为 127.0.0.1 的任何数据包都不应该离开计算机(主机)
        • 发送它而不是被发送到本地网络或互联网,只是被自己“环回”,并且发送数据包的计算机成为接收者
    • ip 地址分类

      • ipv4:4个字节组成,每个字节 0~255

        • 共 42 亿,2011 年已经用尽
      • ipv6:128位,8个无符号整数

域名
  • 解决IP地址难记的问题,将IP地址映射为域名(HTTP协议)
端口
  • 标识计算机上特定的网络程序

    • 以整数形式表现:0 ~ 65535

    • 其中0 ~ 1024 已被占用

  • 常见端口

    • tomcat:8080
    • mySQL:3306
    • oracle:1521
    • sqlServer:1433

编程协议

针对于传输层的的协议

TCP
  • TCP(Transmission Control Protocol):传输控制协议

    • 面向连接的、可靠的、基于字节流的传输层通信协议
    • 类似于打电话,可以确认对面接听
    • TCP 层位于 IP 层之上,应用层之下的中间层
    • 保障了两个应用程序之间的可靠通信
    • 通常用于互联网协议,被称 TCP/IP
  • TCP 是一个双向的通信协议

    • 因此数据可以通过两个数据流在同一时间发送
  • 使用前必须先建立TCP连接,形成数据传输通道

    • 传输前采用三次握手方式,是可靠的
    • 在连接中可进行大数据量的传输
    • 传输完毕需释放已建立的连接,效率低
  • TCP协议进行通信的两个应用进程:客户端、服务端

UDP
  • UDP(User Datagram Protocol):用户数据报协议
    • 位于 OSI 模型的传输层,无连接的协议
    • 提供应用程序间要发送数据的数据报
    • UDP 缺乏可靠性且属于无连接协议
      • 应用程序通常必须容许一些丢失、错误或重复的数据包
      • 类似于漂流瓶,扔出去不确认对方是否收到
  • 使用时将数据、源、目的封装成数据包,不需要建立连接
    • 每个数据包的大小限制在64K以内
    • 因无需连接,所以是不可靠的
    • 发送数据结束时无需释放资源,速度快

TCP 编程

Scoket
介绍

套接字,在网络应用开发中广泛采用,事实上的标准

  • java.net.Socket :套接字
    • java.net.ServerSocket 为服务器程序提供一种来监听客户端,并建立连接的机制
  • 套接字使用 TCP 提供两台计算机之间的通信机制
    • 客户端程序创建一个套接字,并尝试连接服务器的套接字
    • 当连接建立时,服务器会创建 Socket 对象
      • 客户端和服务器可以通过对 Socket 对象的写入和读取来进行通信
    • 连接建立后,通过使用 I/O 流进行通信
      • 每一个socket都有一个输出流和一个输入流
      • 客户端的输出流连接到服务器端的输入流
      • 客户端的输入流连接到服务器端的输出流
  • 通信两端都要有socket,作为两台机器通信的端点
    • 网络通信实际就是Socket之间的通信
  • Socket允许程序把网络连接当作一个流,数据在两个Socket间通过 IO 传输
  • 一般主动发起通信的应用程序为客户端,等待通信请求为服务端、
ServerSocket
  • 服务端通过 java.net.ServerSocket 类获取一个端口,并侦听客户端请求

  • 四种构造函数

    • private void setImpl() {									// 实际创建对象的方法
          if (factory != null) {                     				// 工厂创建模式
              impl = factory.createSocketImpl();
              checkOldImpl();                         			// 检查是否是新创建的
          } else {
              impl = new SocksSocketImpl();          			 	// 工厂不存在是直接创建
          }
          if (impl != null)
              impl.setServerSocket(this);             			// 创建成功后赋值到属性
      }
      
      public ServerSocket() throws IOException {					// 创建未绑定服务器的套接字
          setImpl();
      }
      
      public ServerSocket(int port) throws IOException {			// 创建绑定到指定端口的服务器套接字
          this(port, 50, null);									// 调用本类构造
      }
      
      public ServerSocket(int port, int backlog) throws IOException {		// 创建绑定到指定端口的服务器套接字,并指定 backlog
          this(port, backlog, null);										// 调用本类构造
      }
      
      /**
      * 创建具有指定端口、侦听积压和要绑定的本地 IP 地址的服务器
      * @param port 端口号,0 时使用自动分配的端口号,必须在 0 ~ 65535 之间
      * @param backlog 请求的传入连接队列的最大长度
      * @param bindAddr  服务器将绑定到的本地 InetAddress
      * 			bindAddr 参数可用于多宿主主机上的 ServerSocket,只接受对其地址之一的连接请求、
      * 			如果 bindAddr 为空,将默认接受任何/所有本地地址上的连接
      * @throws IOException 打开套接字时发生 I/O 错误。
      * @throws  SecurityException 存在安全管理器并且其checkListen方法不允许该操作。
      * @throws IllegalArgumentException 端口参数超出有效端口值的指定范围,介于 0 ~ 65535 之间(包括 0 和 65535)。
      */
      public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
          setImpl();
          if (port < 0 || port > 0xFFFF)
              throw new IllegalArgumentException("Port value out of range: " + port);
          if (backlog < 1)
              backlog = 50;
          try {
              bind(new InetSocketAddress(bindAddr, port), backlog);		// 将ServerSocket绑定到特定地址(IP 地址和端口号)
      		/* 如果地址为null ,那么系统将选择一个临时端口和一个有效的本地地址来绑定套接字 */
          } catch(SecurityException e) {
              close();
              throw e;
          } catch(IOException e) {
              close();
              throw e;
          }
      }
      
  • 常用方法

    • public int getLocalPort(){}							// 返回此套接字在其上侦听的端口
      
      
      public Socket accept() throws IOException{}			// 侦听并接受到此套接字的连接
      
      public void setSoTimeout(int timeout)				// 通过指定超时值 启用/禁用 SO_TIMEOUT,以毫秒为单位
      
      public void bind(SocketAddress host, int backlog)	// 将 ServerSocket 绑定到特定地址(IP 地址和端口号)
      
Socket
  • java.net.Socket:代表客户端和服务器都用来互相沟通的套接字

    • 客户端要获取 Socket 对象通过实例化
    • 服务器获得 Socket 对象通过 accept() 方法
  • 五个构造方法

    • 当 Socket 构造方法返回,并没有简单的实例化了一个 Socket 对象

      • 实际上会尝试连接到指定的服务器和端口
    • void setImpl() {                					// 将 impl 设置为 SocketImpl 的系统默认类型
          if (factory != null) {
              impl = factory.createSocketImpl();          // 工厂创建
              checkOldImpl();                             // 检查是否是旧的 impl
          } else {
              impl = new SocksSocketImpl();               // 工厂不存在直接创建
          }
          if (impl != null)
              impl.setSocket(this);                   	// impl 创建后设置到SocketImpl属性
      }
      
      public Socket() {               					// 创建未连接的套接字,系统默认类型为 SocketImpl。
          setImpl();
      }
      
      public Socket(InetAddress address, int port) 
          throws IOException {}  							// 创建一个流套接字并将其连接到指定 IP 地址的指定端口号
      
      /**
      * 创建一个套接字并将其连接到指定远程端口上的指定远程地址
      * Socket 还将 bind() 到提供的本地地址和端口。
      * @param address 	远程地址
      * @param port  		远程端口
      * @param localAddr  套接字绑定到的本地地址,或者对于anyLocal地址为null
      * 				    指定的本地地址为null ,则相当于将该地址指定为 AnyLocal 地址
      * @param localPort  套接字绑定到的本地端口或系统选择的空闲端口zero
      * @throws IOException         在创建套接字时发生 I/O 错误
      * @throws SecurityException   存在安全管理器且checkConnect方法不允许连接到目标,或checkListen方法不允许绑定到本地端口
      * @throws IllegalArgumentException   端口参数或 localPort 参数超出有效端口值的指定范围,范围[0, 65535]
      * @throws NullPointerException       address为空
      */
      public Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException {}
      
      public Socket(String host, int port) 
          throws UnknownHostException, IOException {}             // 创建一个流套接字并将其连接到指定主机上的指定端口号
      
      /**
      * 创建一个套接字并将其连接到指定远程端口上的指定远程主机
      * Socket 还将 bind() 到提供的本地地址和端口。
      * 如果主机为null,相当于将地址指定为InetAddress.getByName(null):相当于指定了loopback接口的地址。
      * @param host 远程主机的名称,或null表示环回地址
      * @param port 远程端口
      * @param localAddr 套接字绑定到的本地地址,或者对于anyLocal地址为null
      * @param localPort 套接字绑定到的本地端口,或者 0 使系统在bind操作中选择一个空闲端口
      * @throws IOException 创建套接字时发生 I/O 错误
      * @throws SecurityException 存在安全管理器且checkConnect方法不允许连接到目标,或其checkListen方法不允许绑定到本地端口
      * @throws IllegalArgumentException 端口参数或 localPort 参数超出有效端口值的指定范围,范围[0, 65535]
      */
      public Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException {}
      
      public Socket(Proxy proxy) {}               // 创建一个未连接的套接字,指定代理类型,无论其他设置如何,都应使用该类型  
      
  • 常用方法

    • 客户端和服务器端都有 Socket 对象

      • 无论客户端还是服务端都能够调用这些方法
    • public void connect(SocketAddress host, int timeout) throws IOException{}	// 将套接字连接到服务器,并指定超时值
      public InetAddress getInetAddress(){}										// 返回套接字连接的地址
      public int getPort(){}														// 返回套接字连接的远程端口
      public int getLocalPort(){}													// 返回套接字绑定的本地端口
      public SocketAddress getRemoteSocketAddress(){}						// 返回套接字连接的端点地址,未连接返回 null
      public InputStream getInputStream() throws IOException{}			// 返回套接字的输入流
      public OutputStream getOutputStream() throws IOException{}			// 返回套接字的输出流
      public void close() throws IOException{}							// 关闭套接字
      public void shutdownInput() throws IOException{} 					// 将此套接字的输入流放在“流的末尾”
      public void shutdownOutput() throws IOException{}					// 标记输出流数据写入已经结束
      
InetAddress
  • 表示互联网协议(IP)地址

  • static InetAddress getByAddress(byte[] addr){}					// 在给定原始 IP 地址的情况下,返回 InetAddress 对象
    static InetAddress getByAddress(String host, byte[] addr){}		// 根据提供的主机名和 IP 地址获取 InetAddress
    static InetAddress getByName(String host){}						// 在给定主机名的情况下确定主机的 IP 地址
    String getHostAddress(){}										// 返回 IP 地址字符串(以文本表现形式)
    String getHostName(){}											// 获取此 IP 地址的主机名
    static InetAddress getLocalHost(){}		 						// 返回本地主机
    String toString(){}												// 将此 IP 地址转换为 String
    
netstat 指令

在dos窗口使用

  • netstat -an:查看当前主机所有网络情况
    • 包括端口监听和网络连接情况
  • netstat -an | more:分页显示当前主机所有网络情况
    • 空格显示下一页
    • 本地地址:本机IP地址和端口号
    • 外部地址:服务器端IP地址和端口号
    • 状态
      • LISTENING:正在监听
      • ESTABLISHED :已连接
TCP连接

使用套接字建立TCP连接时的步骤

  1. 服务器实例化一个ServerSocket对象,表示通过服务器上端口通信
  2. 服务器调用 ServerSocket 类的 accept() 方法
    • 该方法将一直等待,直到客户端连接到服务器上给定的端口
  3. 服务器在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接
  4. Socket 类的构造函数试图将客户端连接到指定的服务器和端口号
    • 如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信
  5. 服务器端 accept() 方法返回服务器上一个新的 socket 引用
    • 该 socket 连接到客户端的 socket
TCP头部
  • TCP头部的序列号、确认号以及几个标记位(SYN/FIN/ACK/RST)

    • 序列号:初次建立连接时,客户端和服务端都为「本次的连接」随机初始化一个序列号

      • 整个 TCP 流程中,序列号可以用来解决网络包乱序的问题
    • 确认号:表示「接收端」告诉「发送端」对上一个数据包已经成功接收

      • 可以⽤来解决网络包丢失的问题
    • 标记位

      • SYN 为 1 时,表示希望创建连接
      • ACK 为 1 时,确认号字段有效
      • FIN 为 1 时,表示希望断开连接
      • RST 为 1 时,表示 TCP 连接出现异常,需要断开
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tKqt3bxa-1660831112247)(images/image-20220818211455095.png)]

三次握手

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jgPVHMt2-1660831112249)(images/70.png)]

介绍
  • 三次握手:指建立 TCP 连接时,需要客户端和服务器总共发送 3 个包
  • 目的:连接服务器指定端口,建立TCP连接,
    • 并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息
    • socket 编程中,客户端执行 connect() 时触发三次握手
  • 实际上即确认通信双方(客户端和服务端)的序列号

在这里插入图片描述

第一次握手
  1. 最开始客户端和服务端都处于 CLOSE 状态

  2. 服务器主动监听某个端口,处于 LISTEN 状态

  3. 客户端随机生成出序列号,把标志位设置为 SYN(希望连接),把该报文发送给服务端

    • 序列号一般叫做 client_isn
    • 随机序列号
      • 安全性:随机 ISN 能避免非同一网络的攻击
      • 让通信双方能够根据序号将「不属于」本连接的报文段丢弃
    • SYN:同步缩写,为 1 时,表示希望创建连接
  4. 客户端发送完 SYN 报文后,进入 SYN_SEND 状态

在这里插入图片描述

第二次握手
  1. 服务端接收到了客户端的请求后,初始化对应的序列号

  2. 序列号一般叫做 server_isn

  3. 在「确认号」字段里填上 client_isn + 1(告诉客户端已收到序列号),把 SYNACK 标记位都点亮(置为 1 )

    • SYN 为 1 时,表示希望创建连接
    • ACK 为 1 时,确认号字段有效
  4. 把报文发送客户端,服务端的状态变成 SYN-REVD 状态
    在这里插入图片描述

第三次握手
  1. 客户端收到服务端发送的报文,确认服务端接收到发出的的序列号(通过确认号),且接收到了服务端的序列号( server_isn )

  2. 客户端需要通知服务端已接收到发送过来的序列号

    • 在「确认号」字段上填上 server_isn + 1,标记位 ACK 为1

    • ACK = 1:确认号字段有效

  3. 客户端发送报文后,进入 ESTABLISHED 状态

    • 服务端接收到客户端的报文后,也进入 ESTABLISHED 状态

在这里插入图片描述

两次握手

两次握手是否可行

  • 两次握手只能保证客户端的序列号成功被服务端接收
    • 而服务端无法确认自己的序列号是否被客户端成功接收
丢包
  • 前两个包:客户端发送给服务端的 SYN 包、服务端发送的 SYN + ACK 包丢了

    • 即服务端未接收到客户端发出的 SYN 包;故没有返回客户端的包,或服务端返回的包丢了
    • 客户端迟迟收不到服务端的 ACK 包,会周期性超时重传,直到收到服务端的 ACK
  • 第三个包:客户端发送完第三个包后单方面进入 ESTABLISHED 状态,服务端也认为此时连接正常,但第三个包没到达服务端

    • 此时客户端与服务端都还没数据发送

      • 服务端会认为自己发送的 SYN + ACK 包没发送至客户端,会超时重传的 SYN + ACK
    • 这时候客户端已经要发送数据了,服务端接收到了 ACK + Data 数据包

      • 服务端自然切换到 ESTABLISHED 状态下,并且接收客户端的 Data 数据包
    • 此时服务端要发送数据但发送不了,会一直周期性超时重传 SYN + ACK,直到接收到客户端的 ACK

四次挥手
过程
  • 建立完连接后,客户端和服务端都处于 ESTABLISHED 状态
    • 双方都有权利断开连接,以客户端主动断开为例
  1. 客户端打算关闭连接,发 FIN 报文给服务端(点亮 FIN 标志位),客户端发送后进入 FIN_WAIT_1 状态

  2. 服务端收到 FIN 报文,回复 ACK 报文给客户端(表示已收到),服务端发送后进入 CLOSE_WAIT 状态

    • 客户端接收到服务端的 ACK 报文进入 FIN_WAIT_2 状态
  3. 但服务器可能还有数据要发送给客户端,确认没有数据返回客户端后,发送 FIN 报文给客户端,并进入 LAST_ACK 状态

  4. 客户端收到服务端的 FIN 报文后,回应 ACK 报文,并进入 TIME_WAIT 状态

    • 服务端收到客户端的 ACK 报文后进入 CLOSE 状态

    • 客户端在 TIME_WAIT2MSL,也进入 CLOSE 状态

      • TIME_WAIT 状态:
        1. 保证最后的 ACK 报文 「接收方」一定能收到
          • 如果收不到,对方会重发 FIN 报文
        2. 确保创建新连接时,先前网络中残余的数据都丢失
        3. TIME_WAIT 只出现在主动发起关闭连接的一方
          • 危害:占用内存资源和端口

在这里插入图片描述

原因

为什么需要四次

  • 客户端第一次发送 FIN 报文后,只代表着客户端不再发送数据给服务端
    • 但此时客户端还有接收数据的能力
  • 服务端收到 FIN 报文时,可能还有数据要传输给客户端,所以只能先回复 ACK 给客户端
    • 等到服务端不再有数据发送给客户端时,才发送 FIN 报文给客户端,表示可以关闭了
  • 两个来回共四次挥手
TCP 通信编程
注意
  • 核心特点:使用两个类实现数据的交互处理

    • ServerSocket:服务器端
    • Socket:客户端
  • ServerSocket 主要目的是设置服务器监听端口

  • Socket 需要指明连接的服务器地址与端口

  • TCP通信细节

    • 客户端连接到服务端后,实际上两者之间同样通过端口进行通信

    • 端口由 TCP/IP 分配,为随机的端口,并不确定

简单实现
  • 一般使用网络编程都是使用多线程环境
    • 每个 Socket 连接都开辟一个单独的线程
流对象
  • getInputStream()getOutputStream()方法返回流对象

    • 使用IO流在服务器~客户端间传输数据

    • // 获取字节流对象
      socket.getInputStream();
      socket.getOutputStream();
      // 使用 Scanner 接收输入流
      Scanner sc = new Scanner(socket.getInputStream());
      // 使用 PrintStream 接收输出流
      PrintStream printStream = new PrintStream(socket.getOutputStream());
      // 转为字符流对象使用;字符输出流写入后必须刷新或关闭才能真正写入
      new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
      new BufferedReader(new InputStreamReader(socket.getInputStream()));
      
服务端
  • 基本流程

    1. 创建服务器对象,监听端口,例如:8888
      • ServerSocket serverSocket = new ServerSocket(8888);
    2. 等待连接,返回 socket 对象(套接字)
      • Scoket socket = serverSocket.accept();
    3. 获取输入流对象:获取客户端发送的数据
      • InputStream in = socket.getInputStream();
      • 字节流,可转为字符流进行处理
    4. 读取客户端数据:in.read();
      • 自定义对数据进行处理
    5. 获取输出流对象:向客户端返回数据
      • OutputStream out = socket.getOutputStream();
    6. 向客户端写数据:out.write() 方法
      • 数据写入完毕后使用 socket.shutDownOutputStream(); 方法标记结束
        • 否则客户端无法知道是否输入完毕
        • 循环写入时必须使用,单次通讯可不添加
    7. 关闭释放各种资源
      1. 关闭IO流
        • in.close();
        • out.close()
      2. 关闭链接:socket.close();
      3. 关闭服务端:serverSocket.close();
    /* ~~~ GreetingServer 是服务器端应用程序,使用 Socket 来监听一个指定的端口 ~~~ */
    import java.net.*;
    import java.io.*; 
    public class GreetingServer extends Thread{								// 使用线程实现服务端单独连接客户端
        private ServerSocket serverSocket;
    
        public GreetingServer(int port) throws IOException{
            serverSocket = new ServerSocket(port);							// 构造器中创建服务端监听指定端口
            serverSocket.setSoTimeout(10000);								// 设置 read() 阻塞超时,毫秒单位
            /* 超时仍未读取到数据以抛出异常结束方法,在此之前读取数据刷新计时 */
        }
    
        public void run(){
            while(true){
                try{
                    System.out.println("等待远程连接,端口号为:" + serverSocket.getLocalPort() + "...");
                    Socket server = serverSocket.accept();									// 等待客户端连接
                    System.out.println("远程主机地址:" + server.getRemoteSocketAddress());
                    DataInputStream in = new DataInputStream(server.getInputStream());		// 输入流包装为数据输入流
                    System.out.println(in.readUTF());										// 读取数据并输出
                    // 读取数据过程进行超时约束,10000 ms 内未读取到数据抛出异常终止读取
                    DataOutputStream out = new DataOutputStream(server.getOutputStream());	// 输出流包转为数据输出流
                    out.writeUTF("谢谢连接我:" + server.getLocalSocketAddress() + "\nGoodbye!");		// 返回客户端数据
                    server.close();															// 关闭服务端
                }catch(SocketTimeoutException s){
                    System.out.println("Socket timed out!");								// 捕获异常,超时连接
                    break;
                }catch(IOException e){
                    e.printStackTrace();
                    break;
                }
            }
        }
        public static void main(String [] args){
            int port = Integer.parseInt(args[0]);											// 端口号
            try{
                Thread t = new GreetingServer(port);										// 实例化服务器对象
                t.run();																	// 启动线程,启动服务器
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
    
客户端
  • 基本流程

    1. 通过 IP地址或域名,端口号构建 Socket 对象,连接服务器
    • Scoket socket = new Socket(InetAddress.getLocalHost(), 8888);
    1. 获取输出流,向服务器端传输数据
      • OutputStream out = socket.getOutputStream();
    2. 写入数据:out.write();
      • 写入完毕后通过 socket.shutDownOutputStream(); 方法标记结束
        • 否则服务器端无法知晓是否传输完毕
      • 循环写入时必须使用,单次通讯可不添加
    3. 获取输入流,读取服务器返回的数据
      • InputStream in = socket.getInputStream();
    4. 读取数据:in.read() 方法
      • 自定义对读取到的数据进行处理
    5. 关闭释放资源
      1. IO流
        1. 输出流:out.close();
        2. 输入流:in.close();
      2. 关闭连接:socket.close();
  • /* ~~ GreetingClient 是客户端程序,该通过 socket 连接到服务器并发送请求,然后等待响应 ~~~ */
    import java.net.*;
    import java.io.*;
    public class GreetingClient{
       public static void main(String [] args){
          String serverName = args[0];		
          int port = Integer.parseInt(args[1]);
          try{
             System.out.println("连接到主机:" + serverName + " ,端口号:" + port);
             Socket client = new Socket(serverName, port);					// 通过端口号创建 Socket 对象连接到服务器
             System.out.println("远程主机地址:" + client.getRemoteSocketAddress());
             OutputStream outToServer = client.getOutputStream();			// 获取输出流对象
             DataOutputStream out = new DataOutputStream(outToServer);		// 包装为数据输出流
             out.writeUTF("Hello from " + client.getLocalSocketAddress());	// 写入数据
             InputStream inFromServer = client.getInputStream();			// 获取输入流对象
             DataInputStream in = new DataInputStream(inFromServer);		// 包装为数据输入流
             System.out.println("服务器响应: " + in.readUTF());			   // 读取服务器返回数据并输出
             client.close();												// 关闭连接
          }catch(IOException e){
             e.printStackTrace();
          }
       }
    }
    /*
    连接到主机:localhost ,端口号:6066
    远程主机地址:localhost/127.0.0.1:6066
    服务器响应: 谢谢连接我:/127.0.0.1:6066
    Goodbye!
    */
    
Echo模型
介绍

Echo是一个经典的程序开发模型

  • 客户端随意输入信息并且将信息发送给服务器端
  • 服务器端接收后前面加上一个"ECHO"的标记返回
  • 程序设计
    • 需要采用多次输入的形式,所以不能够每次连接后立刻关闭服务端
    • 可以设置一个字符串,输入byebye,才表示结束本次的操作
简单实现
/*  ~~~~~ 服务端 ~~~~~ */
ServerSocket server=new ServerSocket(9999);						// 服务器端实现,监听端口 9999
System.out.println("等待客户端连接=====");
Socket client = server.accept();								// 等待客户端连接
Scanner scanner = new Scanner(client.getInputStream());			// InputStream 输入流获取客户端输入数据
PrintStream out = new PrintStream(client.getOutputStream());	// OutputStream 输出流向客户端输出数据
boolean flag = true;											// 标识位,控制程序结束
while(flag){
    if(scanner.hasNext()){										// 持续获取客户端输入
        String str = scanner.next().trim();						// 得到客户端信息 trim方法去掉输入字符串空格
        if(str.equalsIgnoreCase("byebye")){						// 判断输入信息为 byebye 时控制程序结束	
            out.println("bye~~~~~~~~~~~~~~~");
            flag = false;
        }else{
            out.println("ECHO:" + str);							// 程序结束之前一直对接收到数据进行回应
        }
    }
}
scanner.close();												// 关闭输入流
out.close();													// 关闭输出流
client.close();													// 关闭 Socket 连接
server.close();													// 关闭服务端

/* ~~~~~~ 客户端 ~~~~~~ */
Socket client = new Socket("localhost",9999);					// 通过连接服务器 主机号加端口号
Scanner input = new Scanner(System.in);							// 获取键盘输入对象,用户进行内容编辑
Scanner scan = new Scanner(client.getInputStream());			// 获取 Socket 连接的输入流,读取服务端返回的信息
PrintStream out = new PrintStream(client.getOutputStream());	// 取得输出流,向服务端发送信息
input.useDelimiter("\n");										// 设置分隔符,当遇到 \n 时停止输入
scan.useDelimiter("\n");
boolean flag = true;
while(flag){
    System.out.print("请输入要发送数据");
    if(input.hasNext()){										// 判断用户是否输入
        String str = input.next().trim();						// 获取键盘输入数据,去除前后空格
        out.println(str);										// 键盘输入数据通过客户端直接发送到服务器端
        if(str.equalsIgnoreCase("byebye")){						// 当检测的用户输入 byebye 时控制程序结束,忽略大小写	
            flag = false;
        }
        if(scan.hasNext()){										// 判断服务端是否返回数据
            System.out.println(scan.next());					// 将服务端返回的数据输出
        }
    }
}
input.close();													// 关闭输入流
scan.close();													// 关闭输入流
out.close();													// 关闭输出流
client.close();													// 关闭 Socket 连接

UDP通信编程

基于数据报的网络编程实现

  • 通过两个类实现 UDP 协议网络程序
    • DatagramSocket:网络发送和接收
    • DatagramPacket:数据包/报,数据内容
  • UDP 数据报通过数据报套接字 DatagramSocket 发送和接收
    • 不保证 UDP 数据报一定能安全送到目的地,也不确定何时到达
      • 客户端是否接收到与发送者无关
  • DatagramPacket 对象封装 UDP 数据报
    • 数据报中包含发送端以及接收端的IP地址和端口号
  • UDP 协议中每个数据报都给出完整的地址信息
    • 无需建立发送方和接收方的连接
    • 没有客户端和服务端,根据收发消息为接收端和发送端
  • 操作流程
    • 建立接收端
      • 建立数据报:DatagramPacket对象
      • 调用 DatagramSocket 的接收方法
      • 关闭 DatagramSocket
    • 建立发送端
      • 建立数据报:DatagramPackage对象
        • 包括消息内容、发送端 IP 地址、接收端端口号
      • 调用 DatagramSocket 的发送方法
      • 关闭 DatagramSocket
/* 发送端 */
public class Test {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket();
        String a = "废物";
        DatagramPacket packet = new DatagramPacket(a.getBytes(), 0, a.length(), InetAddress.getLocalHost(),8888);
        socket.send(packet);
        System.out.println("发送成功");
        socket.close();
    }
}
/* 接收端 */
class A {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(8888);
        System.out.println("正在等待接收消息");
        byte[] bytes = new byte[1024];
        DatagramPacket packet = new DatagramPacket(bytes,0,bytes.length);
        socket.receive(packet);
        System.out.println(new String(bytes,0,packet.getLength()));
        socket.close();
    }
}

URL

  • URL(Uniform Resource Locator):统一资源定位符
    • 俗称为网页地址:表示为互联网上的资源
    • 如网页或者 FTP
  • java.net.URL 类对 url 进行操作

解析

  • 必须组成部分:protocol://host:port/path?query#fragment
    1. protocol:协议,可以是 HTTP、HTTPS、FTP 和 File
    2. port:端口号
    3. path:文件路径及文件名
  • 例如:http://www.runoob.com/index.html?language=cn#j2se
    1. 协议(protocol):http
    2. 主机(host:):www.runoob.com,ip 地址映射域名
    3. 端口号(port): 80 ,未指定端口,HTTP 协议默认的端口号为 80
    4. 文件路径(path):/index.html
    5. 请求参数(query):language=cn
    6. 定位位置(fragment):j2se,定位到网页中 id 属性为 j2se 的 HTML 元素位置

使用

构建
  • java.net 包中定义 URL 类处理有关 URL 内容

    • URL类的创建和使用 java.net.URL 提供丰富的构建方式
    • 可通过 java.net.URL 获取资源
  • 四种构建方式

    • // 使用指定的协议、主机名、端口、文件名创建URL
      public URL(String protocol,String host, int port, String file) throws MalformedURLException{} 
      
      // 使用指定的协议、主机名、文件名创建URL,端口使用协议的默认端口
      public URL(String protocol, String host, String file) throws MalformedURLException{}
      
      // 通过给定的URL字符串创建URL
      public URL(String url) throws MalformedURLException{}
      
      // 使用基地址和相对URL创建
      public URL(URL context, String url) throws MalformedURLException{}
      
URL方法
  • URL类中包含用于访问URL的各个部分的方法

  • public String getPath(){}				// 返回URL路径部分
    public String getQuery(){}				// 返回URL查询部分
    public String getAuthority(){}			// 获取此 URL 的授权部分
    public int getPort(){}					// 返回URL端口部分
    public int getDefaultPort(){}			// 返回协议的默认端口号
    public String getProtocol(){}			// 返回URL的协议
    public String getHost(){}				// 返回URL的主机
    public String getFile(){}				// 获取此URL的文件名;等价于 getPath() + getQuery()
    public String getRef(){}				// 获取此 URL 的锚点(也称为"引用")
    public URLConnection openConnection() throws IOException{}		// 打开URL连接,并运行客户端访问资源
    
  • 实例

    • import java.net.*;
      import java.io.*;
      
      public class URLDemo{
          public static void main(String [] args){
              try{
                  URL url = new URL("http://www.runoob.com/index.html?language=cn#j2se");
                  System.out.println("URL = :" + url);	// URL =:http://www.runoob.com/index.html?language=cn#j2se
                  System.out.println("协议 = " + url.getProtocol());		  // 协议 = http
                  System.out.println("验证信息 = " + url.getAuthority());		// 验证信息 = www.runoob.com
                  System.out.println("文件名及请求参数 = " + url.getFile());	 // 文件名及请求参数 = /index.html?language=cn
                  System.out.println("主机名 = " + url.getHost());			 // 主机名 = www.runoob.com
                  System.out.println("路径 = " + url.getPath());			  // 路径 = /index.html
                  System.out.println("端口 = " + url.getPort());			  // 端口 = -1
                  System.out.println("默认端口 = " + url.getDefaultPort());	// 默认端口 = 80
                  System.out.println("请求参数 = " + url.getQuery());			// 请求参数 = language=cn
                  System.out.println("定位位置 = " + url.getRef());			// 定位位置 = j2se
              }catch(IOException e){
                  e.printStackTrace();
              }
          }
      }
      

URLConnections

介绍
  • 抽象类 URLConnection :表示应用程序和 URL 间的通信链接的所有类的超类

    • 实例可用于读取和写入 URL 引用的资源
  • 创建到 URL 的连接是多步骤的过程

    • openConnection()
    • connect()
    • 操纵影响与远程资源的连接的参数
      • 与资源交互;查询标题字段和内容
    1. 连接对象通过 URL 调用 openConnection 方法创建
      • 设置参数和一般请求属性被操纵
    2. 与远程对象实际连接使用 connect 方法建立
      • 远程对象变为可用,可访问远程对象的标头字段和内容
  • 请求后对 URLConnectionInputStreamOutputStream 调用 close() 方法释放网络资源

    • 除非特定的协议规范为其指定了不同的行为
获取
  • openConnection() 方法返回 java.net.URLConnection

  • 例如

    • 连接 HTTP 协议的 URL

      • openConnection() 方法返回 HttpURLConnection 对象
    • 连接的 URLJAR 文件

      • openConnection() 方法将返回 JarURLConnection 对象
    • 等等…

方法列表
  • getContentType 方法被 getContent 方法用来判断远程对象的类型
  • 子类会重写 getContentType 方法很方便
  • 可以忽略所有预连接参数和一般请求属性
    • 预连接参数和请求属性默认为合理值
    • 该接口的大多客户端,只有两个有趣的方法
      • getInputStreamgetContent ,通过便利方法镜像到URL类中。
方法描述
Object getContent()检索URL链接内容
Object getContent(Class[] classes)检索URL链接内容
String getContentEncoding()返回头部 content-encoding 字段值
int getContentLength()返回头部 content-length字段值
String getContentType()返回头部 content-type 字段值
int getLastModified()返回头部 last-modified 字段值
long getExpiration()返回头部 expires 字段值
long getIfModifiedSince()返回对象的 ifModifiedSince 字段值
public void setDoInput(boolean input)URL 连接可用于输入和/或输出。若使用 URL 连接进行输入,将 DoInput 标志设置为 true;不使用则设置为 false。默认值为 true
public void setDoOutput (boolean output)URL 连接可用于输入和/或输出。若使用 URL 连接进行输出,将 DoOutput 标志设置为 true;不使用则设置为 false。默认值为 false
public InputStream getInputStream () throws IOException返回URL的输入流,用于读取资源
public OutputStream getOutputStream () throws IOException返回URL的输出流, 用于写入资源
public URL getURL()返回 URLConnection 对象连接的URL
实例
  • URL 采用 HTTP 协议

    • openConnection{} 返回 HttpURLConnection 对象
  • import java.net.*;
    import java.io.*;
    public class URLConnDemo{
        public static void main(String [] args){
            try{
                URL url = new URL("http://www.runoob.com");
                URLConnection urlConnection = url.openConnection();			// 获取连接对象
                HttpURLConnection connection = null;
                if(urlConnection instanceof HttpURLConnection){
                    connection = (HttpURLConnection) urlConnection;			// 判断并强转为 http 类型
                }
                else{
                    System.out.println("请输入 URL 地址");
                    return;
                }
                //通过连接对象获取输入流,并转为字符流
                BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String urlString = "";
                String current;
                while((current = in.readLine()) != null){
                    urlString += current;									// 读取连接内容
                }
                System.out.println(urlString);
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
    

下载

  • 下载网络资源

  • class Test{
        public static void main(String[] args) throws IOException {
            URL url = new URL("https://blog.csdn.net/guorui_java/article/details/119299329");	// 指定资源地址
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();			// 通过 url 获取连接
            InputStream in = urlConnection.getInputStream();									// 通过连接得到流对象
            FileOutputStream out = new FileOutputStream("D:\\新建文件夹\\新建文本文档.txt");	   // 创建输出流
            byte[] array = new byte[1024];														// 设置缓冲数组
            int len = 0;
            while ((len = in.read(array)) != 0){
                out.write(array, 0, len);					// 循环读取数据并输出到本地文件
            }
            out.close();									// 关闭流
            in.close();
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值