【Java进阶篇】——网络编程
创始人
2025-05-30 11:52:49
0

一、网络编程概述

  • Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序

  • Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并且 Java 实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境

🌔 1、与网络相关的软件架构包括哪些?

  • C/S架构(Client / Server):指客户端和服务器结构 【QQ、美团等】
    请添加图片描述

  • B/S架构(Browser / Server):指浏览器和服务器结构【IE、谷歌等】
    请添加图片描述

  • 网络编程的作用就是 在一定的协议下,实现两台计算机通信的程序

  • 上面的两种架构都离不开网络的支持

🌔 2、什么是计算机网络?

  • 把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源

🌔 3、网络编程的目的是什么?

  • 直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯

🌔 4、网络编程中有三个主要的问题?

  • 如何准确地定位网络上一台或多台主机
  • 如何定位主机上的特定的应用
  • 找到主机后,如何可靠、高效地进行数据传输

二、网络通信要素

  • 如何实现网络上的主机互相通信呢?
    • 首先,要知道通信双方地址的 Ip 和 端口号
    • 不同的硬件、操作系统之间的通信需要一种特定的网络通信协议

2.1 通信第一要素:IP地址和域名

🌔 1、什么是IP地址呢?

  • 指互联网协议地址(Internate Protocal Address):Ip地址用来给网络中的一台计算机设备做唯一编号

🌔 2、IP地址的第一种分类方式:

  • IPv4:是一个32位的二进制数,通常被分为4个字节,表示成a.b.c.d 的形式,以点分十进制表示,例如192.168.65.100 。其中a、b、c、d都是0~255之间的十进制整数

请添加图片描述
- IP地址 = 网络地址 + 主机地址
- 网络地址:标识计算机或网络设备所在的网段
- 主机地址:标识特定主机或网络设备
请添加图片描述

  • IPv6:采用128位地址长度,共16个字节,写成8个无符号整数,每个整数用四个十六进制位表示,数之间用冒号(:)分开。比如:ABCD:EF01:2345:6789:ABCD:EF01:2345:6789

🌔 3、IP地址的第二种分类方式:

  • 公网地址( 万维网使用)和 私有地址( 局域网使用)。192.168.开头的就是私有地址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用

🌔 4、介绍几个最基础的常用命令:

  • 在 DOS 中(Win + R 输入cmd),通过 ipconfig 指令可以查看本机的 IP 地址
  • 通过 ping IP地址可以检测本机和目标地址之间是否可以连通

🌔 5、有两个特殊的IP地址

  • 本地回环地址(hostAddress):127.0.0.1
  • 主机名(hostName):localhost

🌔 6、在Internet上的主机有两种方式表示地址:

  • 域名(hostName):例如:www.baidu.com
  • IP地址(hostAddress):202.108.35,211

🌔 7、什么是域名解析呢?

  • 当在连接网络时输入一个主机的域名后,域名服务器(DNS,Domain Name System,域名系统)负责将域名转化为IP地址,这样才能和主机建立连接

请添加图片描述

在浏览器里解析一个域名的完整流程

  • 在浏览器中输入www . qq .com 域名,操作系统会先检查自己本地的hosts文件是否有这个网址映射关系,如果有,就先调用这个IP地址映射,完成域名解析。
  • 如果hosts里没有这个域名的映射,则查找本地DNS解析器缓存,是否有这个网址映射关系,如果有,直接返回,完成域名解析。
  • 如果hosts与本地DNS解析器缓存都没有相应的网址映射关系,首先会找TCP/IP参数中设置的首选DNS服务器,在此我们叫它本地DNS服务器,此服务器收到查询时,如果要查询的域名,包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析,此解析具有权威性。
  • 如果要查询的域名,不由本地DNS服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析,此解析不具有权威性。
  • 如果本地DNS服务器本地区域文件与缓存解析都失效,则根据本地DNS服务器的设置(是否设置转发器)进行查询,如果未用转发模式,本地DNS就把请求发至13台根DNS,根DNS服务器收到请求后会判断这个域名(.com)是谁来授权管理,并会返回一个负责该顶级域名服务器的一个IP。本地DNS服务器收到IP信息后,将会联系负责.com域的这台服务器。这台负责.com域的服务器收到请求后,如果自己无法解析,它就会找一个管理.com域的下一级DNS服务器地址(http://qq.com)给本地DNS服务器。当本地DNS服务器收到这个地址后,就会找(http://qq.com)域服务器,重复上面的动作,进行查询,直至找到www.qq.com主机 【类似于问路,自己连续问别人】
  • 如果用的是转发模式,此DNS服务器就会把请求转发至上一级DNS服务器,由上一级服务器进行解析,上一级服务器如果不能解析,或找根DNS或把转请求转至上上级,以此循环。不管是本地DNS服务器用是是转发,还是根提示,最后都是把结果返回给本地DNS服务器,由此DNS服务器再返回给客户机【自己问别人,别人进一步问别人】

2.2 通信第二要素:端口号

  • 网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?

    • 如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)
    • 不同的进程,设置不同的端口号
  • 端口号:用两个字节表示的整数,它的取值范围是0-65535

    • 公认端口:0~1023。被预先定义的服务通信占用 【HTTP(80)、FTP(21)】
    • 注册端口:1024~49151。分配给用户进程或应用程序 【Tomcat(8080)、Oracle(1521)】
    • 动态/私有端口:49152~65535
  • 如果端口号被另一个服务或应用程序所占用,会导致当前程序启动失败

请添加图片描述

2.3 通信第三要素:网络通信协议

  • 网络通信协议:在计算机网络中,这些连接和通信的规则被称为网络通信协议

    • 它对数据的传输格式、传输速率、传输步骤、出错控制等做了统一的规定
    • 通信双方必须同时遵守才能完成数据交换
  • 如何实现如此复杂的网络协议呢?

    • 最常用的复合方式是层次方式
    • 同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系
  • 有两套参考模型:

    • OSI参考模型:过于理想化未得到推广
    • TCP/IP模型(协议):国际标准

请添加图片描述

  • TCP/IP协议:传输控制协议或因特网互联协议,主要包括TCP传输控制协议和IP网络互联协议,实际具有多个具有不同功能且互为关联的协议,是当前互联网最基本、使用最广泛的协议

请添加图片描述

  • TCP/IP协议中的四层都代表什么?
    • 应用层:决定了向用户提供应用服务时通信的活动 【HTTP协议、FTP协议、SNMP(简单网络管理协议)、SMTP(简单邮件传输协议)和POP3】

    • 传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议或UDP协议

      • TCP传输控制协议:是一种面向连接的、可靠的、基于字节流的传输层通信协议
      • UDP用户数据报协议:是一个无连接的传输层协议、提供面向事务的简单不可靠的通信传送服务
    • 网络层:是协议的核心,支持网间互联的数据通信,主要用于将传输的数据进行分组,将分组数据发送到目标计算机或网络

      • IP又称为互联网协议,负责将数据从源传送到目的地 ,它在源地址和目的地址之间传送一种称之为数据包的东西,它还提供对数据大小的重新组装功能,以适应不同网络对包大小的要求
    • 物理+数据链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议 【光纤、网线提供的驱动】

请添加图片描述

三、TCP与UDP协议

  • 通信的协议还是比较复杂的,java.net 包中包含的类和接口,它们提供低层次的通信细节。我们可以直接使用这些类和接口,来专注于网络程序开发,而不用考虑通信的细节
  • java.net 包中提供了两种常见的网络协议的支持:
    • UDP:用户数据报协议【User Datagram Protocal】
    • TCP:传输控制协议(Transmission Control Protocol)

🌔 1、我们需要知道这两个协议分别代表什么?

  • TCP协议:

    • TCP协议进行通信的两个应用进程:客户端、服务端

    • 使用TCP协议之前,需要先建立TCP连接,形成基于字节流的数据传输通道

    • 传输前,采用“三次握手”方式,点对点通信,是可靠的传输

      • TCP协议使用重发机制,当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体的确认信息;如果没收到,就会重复发送刚才的信息
    • 在连接中可进行 大数据量的传输

    • 传输完毕,需要释放已建立的连接,效率较低 【打电话】

  • UDP协议:

    • UDP协议进行通信的两个应用进程:发送端和接收端
    • 将数据、源、目的封装成数据报(传输的基本单位),不需要建立连接
    • 发送不管对方是否准备好,也不能保证数据的完整性,是不可靠的传输
    • 每个数据报的大小限制在64K内
    • 发送数据结束时,无需释放资源,开销小,通信效率高
    • 适用于音频、视频和普通数据的传输 【发短信·】

🌔 2、上面提及的TCP三次握手指的是什么?

  • TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
    • 第一次握手: 客户端向服务器端发起 TCP 连接请求
    • 第二次握手: 服务器端发送针对客户端TCP连接请求的确认
    • 第三次握手: 客户端发送确认的确认

请添加图片描述

1、客户端会随机一个初始序列号seq=x,设置SYN=1 ,表示这是SYN握手报文。然后就可以把这个 SYN 报文发送给服务端了,表示向服务端发起连接,之后客户端处于同步已发送状态。

2、服务端收到客户端的 SYN 报文后,也随机一个初始序列号(seq=y),设置ack=x+1,表示收到了客户端的x之前的数据,希望客户端下次发送的数据从x+1开始。
设置 SYN=1 和 ACK=1。表示这是一个SYN握手和ACK确认应答报文。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于同步已接收状态。

3、客户端收到服务端报文后,还要向服务端回应最后一个应答报文,将ACK置为 1 ,表示这是一个应答报文
ack=y+1 ,表示收到了服务器的y之前的数据,希望服务器下次发送的数据从y+1开始。
最后把报文发送给服务端,这次报文可以携带数据,之后客户端处于 连接已建立 状态。服务器收到客户端的应答报文后,也进入连接已建立状态。

  • 完成三次握手,连接建立后,客户端和服务器就可以开始进行输出传输了。
  • 基于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如:下载文件、浏览网页等

🌔 3、TCP的四次握手代表什么呢?

  • TCP协议,在发送数据结束后,释放连接时需要经过四次挥手
    • 第一次挥手:客户端向服务器端提出结束连接,让服务器做好最后的准备。【客户端处于半关闭状态,不再向服务器发送数据,但可以接收数据】
    • 第二次挥手,服务器接收到客户端释放连接的请求后,会将最后的数据发给客户端,并告知上层的应用进程不再接受数据
    • 第三次挥手,服务器发送完数据后,会给客户端发送一个释放连接的报文。客户端接收到这个报文后,就可以正式释放连接了
    • 第四次挥手,客户端接收到服务器最后的释放连接报文后,要回复一个彻底断开的报文。服务器收到后才会彻底释放连接
      • 这里客户端,发送完最后的报文后,会等待2MSL,因为有可能服务器没有收到最后的报文,那么服务器迟迟没收到,就会再次给客户端发送释放连接的报文,此时客户端在等待时间范围内接收到,会重新发送最后的报文,并重新计时。如果等待2MSL后,没有收到,那么彻底断开。

请添加图片描述

1、客户端打算断开连接,向服务器发送FIN报文(FIN标记位被设置为1,1表示为FIN,0表示不是),FIN报文中会指定一个序列号,之后客户端进入FIN_WAIT_1状态。也就是客户端发出连接释放报文段(FIN报文),指定序列号seq = u,主动关闭TCP连接,等待服务器的确认。

2、服务器收到连接释放报文段(FIN报文)后,就向客户端发送ACK应答报文,以客户端的FIN报文的序列号 seq+1 作为ACK应答报文段的确认序列号ack = seq+1 = u + 1。接着服务器进入CLOSE_WAIT(等待关闭)状态,此时的TCP处于半关闭状态(下面会说什么是半关闭状态),客户端到服务器的连接释放。客户端收到来自服务器的ACK应答报文段后,进入FIN_WAIT_2状态。

3、服务器也打算断开连接,向客户端发送连接释放(FIN)报文段,之后服务器进入LASK_ACK(最后确认)状态,等待客户端的确认。服务器的连接释放(FIN)报文段的FIN=1,ACK=1,序列号seq=m,确认序列号ack=u+1。

4、客户端收到来自服务器的连接释放(FIN)报文段后,会向服务器发送一个ACK应答报文段,以连接释放(FIN)报文段的确认序号 ack 作为ACK应答报文段的序列号 seq,以连接释放(FIN)报文段的序列号 seq+1作为确认序号ack。
之后客户端进入TIME_WAIT(时间等待)状态,服务器收到ACK应答报文段后,服务器就进入CLOSE(关闭)状态,到此服务器的连接已经完成关闭。客户端处于TIME_WAIT状态时,此时的TCP还未释放掉,需要等待2MSL后,客户端才进入CLOSE状态。


四、网络编程API

🌔 1、InetAddress类

  • 该类表示IP地址,有两个子类:Inet4Address、Inet6Address

  • 该类没有提供公共的构造器,而是提供了几个静态方法来获取 InetAddress 实例

    • public static InetAddress getLocalHost()
    • public static InetAddress getByName(String host)
    • public static InetAddress getByAddress(byte [] addr)
  • 该类提供了几个常用的方法:

    • public String getHostAddress(): 返回IP地址字符串
    • public String getHostName():获取此IP地址的主机名
    • public boolean isReachable(int timeout): 测试是否到达该地址

我们通过代码来演示如何通过这几个静态方法获取到InetAddress实例

package com.zwh.shangguigu.netcoding_;import java.net.InetAddress;
import java.net.UnknownHostException;/*** @author Bonbons* @version 1.0* 演示获取InetAddress实例的几种方式*/
public class InetAddressTest {public static void main(String[] args) {try{//1、获取本地主机InetAddress localHost = InetAddress.getLocalHost();System.out.println("本地主机: " +  localHost);//2、根据域名获取InetAddress baidu = InetAddress.getByName("www.baidu.com");System.out.println("baidu: " + baidu);//3、根据地址获取InetAddress baidu2 = InetAddress.getByAddress(new byte[]{(byte)202, 108, 22, 5});System.out.println("baidu2: " + baidu2);}catch (UnknownHostException e){e.printStackTrace();}}
}

在这里插入图片描述
请添加图片描述

🌔 2、Socket类

  • 网络上具有唯一标识的IP地址和端口号组合在一起构成唯一能识别的标识符 >> 套接字 (Socket)
  • 套接字在开发网络应用程序中被广泛使用,网络通信就是Socket之间的通信
  • 通信的两端都要有Socket,是两台机器间通信的端点
  • Socket 允许应用程序把网络连接当成一个流,数据在两个Socket间通过IO传输
  • 一般主动发起通信的应用程序是客户端等待通信请求的为服务端
  • Socket 又分为两类:
    • 流套接字(steam socket):使用TCP提供可以来的字节流服务

      • ServerSocket:此类实现TCP服务器套接字,服务器套接字等待请求通过网络传入
      • Socket:此类实现客户端套接字,套接字是两台机器间通信的端点【有时直接把客户端套接字简称为套接字】
    • 数据报套接(datagram socket):使用UDP提供尽力而为的数据报服务

      • DatagramSocket:此类表示用来发送和接收UDP数据报包套接字

🌔 3、服务器套接字的相关API

  • 构造方法:ServerSocket(int port) 创建绑定到特定端口的服务器套接字
  • 常用方法:Socket accept(): 侦听并接受到此套接字的连接

🌔 4、Socket类的相关API 【客户端套接字】

(1)Socket类的常用构造方法:【区别在于一个利用IP地址,一个利用域名】

  • public Socket(InetAddress address, int port): 创建一个流套接字并将其连接到指定IP地址的指定端口号
  • public Socket(String host, int port): 创建一个流套接字并将其连接到指定主机的指定端口号上

(2)Socket 类的常用方法

  • public InputStream getInputStream(): 返回此套接字的输入流,可以用于接收消息
  • public OutputStream getOutputStream(): 返回此套接字的输出流,可以用于发送消息
  • public InetAddress getInetAddress(): 此套接字连接到的远程IP地址【处于未连接状态就返回 null】
  • public InetAddress getLocalAddress(): 获取套接字绑定的本地地址
  • public int getPort(): 此套接字连接到的远程端口号 【未连接套接字返回0】
  • public int getLocalPort(): 返回此套接字绑定到的本地端口【未连接套接字返回-1】
  • public void close(): 关闭此套接字,也会关闭该套接字的 InputStream、OutputStream
    • 关闭后不可以在网络中使用(重新连接/重新绑定),需要创建新的套接字对象
    • public void shutdownInput(): 不能在此套接字的输入流中接受任何数据【调用后会从输入流接收一个EOF(文件结束符)】
    • public void shutdownOutput(): 禁用此套接字的输出流,不能通过此套接字的输出流发送任何数据
      • 对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列

我们需要知道:

  • 先后调用Socket的shutdownInput()和shutdownOutput()方法,仅仅关闭了输入流和输出流,并不等于调用Socket的close()方法
  • 在通信结束后,仍然要调用Scoket的close()方法,因为只有该方法才会释放Socket占用的资源,比如占用的本地端口号等

🌔 4、DatagramSocket类的常用方法

  • public DatagramSocket(int port):创建数据报套接字并将其绑定到本地主机上的指定端口

    • 套接字将被绑定到通配符地址,IP地址由内核来选择
  • public DatagramSocket(int port, InetAddress laddr):创建数据报套接字,将其绑定到指定的本地地址

    • 本地端口必须在 0 到 65535 之间(包括两者)。如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地址,IP 地址由内核选择
  • public void close(): 关闭此数据报套接字

  • public void sent(DatagramPacket p):从此套接字发送数据报包

    • 该数据报包中包含:将要发送的数据、其长度、远程主机的IP地址和远程主机的端口号
  • public void receive(DatagramPacket p): 从套接字接受数据报包,当此方法返回时,DatagramPacket 的缓冲区填充了接受的数据

    • 数据报包也包含发送方的IP地址和机器上的端口号
    • 此方法在接受到数据报前一直阻塞
    • 数据报包对象的length字段包含所接受信息的长度
    • 如果信息比包长度长,该信息被截断【一部分信息丢失】
  • public InetAddress getLocalAddress(): 获取套接字绑定的本地地址

  • public int getLocalPort(): 返回此套接字绑定的本地主机上的端口号

  • public InetAddress getInetAddress(): 返回此套接字连接的地址【未连接返回 null】

  • public int getPort(): 返回此套接字的端口,未连接返回-1

🌔 5、DatagramPacket类的常用方法

  • public DatagramPacket(byte [] buf, int length): 构造DatagramPacket,用来接受长度为length的数据报【length <= buf.length】
  • public DatagramPacket(byte [] buf, int length, InetAddress address, int port): 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号 【长度规则和上一个构造方法一致】
  • public InetAddress getAddress(): 返回某台机器的IP地址,此数据报将要发往该机器或者是从该机器收到的
  • public int getPort(): 返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的
  • public byte [] getData(): 返回数据缓冲区,将接受到的或将要发送的数据从缓冲区中的编译量 offset 处开始,持续length长度
  • public int getLength(): 返回将要发送或接受到的数据的长度

五、TCP网络编程

🌔 1、首先先介绍一下TCP编程的通信模型:

  • Java语言的基于套接字TCP编程分为服务端编程和客户端编程,其通信模型如图所示:

请添加图片描述
🌔 2、客户端程序和服务器程序的开发步骤:

(1)客户端程序的开发步骤:

  • 创建Socket:根据指定服务端的IP地址或端口号构造Socket类对象。
    • 若服务器端相应,则建立客户端到服务器的通信线路;连接失败则出现异常
  • 打开链接到Socket的输入/输出流:使用getInputStream()方法获得输入流;使用getOutputStream()方法获得输出流,进行输出传输
  • 按照一定的协议对Socket进行读/写操作:通过输入流读取服务器放入线路的信息(不能读取自己放入线路的信息),通过输出流将信息写入线路
  • 关闭Socket:断开客户端到服务器的连接,释放线路

(2)服务器程序的开发步骤:

  • 调用 ServerSocket(int port):创建一个服务器套接字,并绑定到指定端口上,用于监听客户端的请求
  • 调用accept(): 监听连接请求,如果客户端请求连接,则接受链接,返回通信套接字对象
  • 调用该Socket类对象的getOutputStream()和getInputStream(): 获取输入流和输出流,开始网络数据的发送和接受
  • 关闭Socket对象:客户端访问结束,关闭通信套接字

🌔 3、案例一:演示单个客户端与服务器单次通信

需求:客户端连接服务器,连接成功后给服务器发送"lalala",服务器收到消息后,给客户端返回"欢迎登录",客户端收到消息后,断开链接

package com.zwh.shangguigu.netcoding_;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;/*** @author Bonbons* @version 1.0* 单个客户端与服务器单次通信演示*/
//服务器端代码
public class Server {public static void main(String[] args) {try{//1、创建ServerSocket对象,绑定到8888端口ServerSocket server = new ServerSocket(8888);System.out.println("等待连接...");//2、在8888端口监听客户端的连接请求,该方法是个阻塞方法,没有客户端连接一直处于阻塞状态Socket socket = server.accept();InetAddress inetAddress = socket.getInetAddress();System.out.println(inetAddress.getHostAddress() + "客户端连接成功!");//3、获取输入流,用来接受客户端发送给服务器的数据InputStream input = socket.getInputStream();//字节数组暂存数据byte [] data = new byte[1024];StringBuilder s = new StringBuilder();int len;while((len = input.read(data)) != -1){s.append(new String(data, 0, len));}System.out.println(inetAddress.getHostAddress() + "客户端发送的消息是: " + s);//4、获取输出流,用来发送数据给该客户端OutputStream out = socket.getOutputStream();//发送数据[转换成字节,因为用的是字节输出流]out.write("欢迎登录".getBytes());//刷新缓存out.flush();//5、关闭socket,不再与客户端通信,socket的输入、输出流也一起关闭了socket.close();//6、如果不再接受任何客户端通信,可以关闭服务器端程序server.close();}catch (IOException e){e.printStackTrace();}}
}//客户端代码
class Client{public static void main(String[] args) {try{//1、准备Socket,连接服务器,需要指定服务器的IP地址和端口号Socket socket = new Socket("127.0.0.1", 8888);//2、获取输出流,用来发送数据给服务器OutputStream out = socket.getOutputStream();//发送数据out.write("lalala".getBytes());//要在流末尾写一个 "流的末尾" 的标记,对方才能读到-1,否则对方的读取方法会一直阻塞socket.shutdownOutput();//3、获取输入流,用来接收服务器发送给客户端的数据InputStream input = socket.getInputStream();//接收数据byte [] data = new byte[1024];StringBuilder s = new StringBuilder();int len;while((len = input.read(data)) != -1){s.append(new String(data, 0, len));}System.out.println("服务器返回的消息是: " + s);//4、关闭socket,不再与服务器通信socket.close();}catch (IOException e){e.printStackTrace();}}
}

🌔 4、演示多个客户端与服务器之间的多次通信

  • 通常情况下,服务器不应该只接受一个客户端请求,而应该不断地接受来自客户端的所有请求,所以Java程序通常会通过循环,不断地调用ServerSocketaccept()方法

  • 如果服务器端要“同时”处理多个客户端的请求,就要为每个客户端单独分配一个进程来处理,否则无法实现“同时”

  • 装饰者设计模式:不管底层IO流是怎样的节点流(文件流、网路Socket产生的流等),程序都可以将其包装成处理流,甚至可以多层包装,从而提供更多方便的处理

案例需求:多个客户端连接服务器,并进行多次通信

- 每一个客户端连接成功后,从键盘输入英文单词或中国成语,并发送给服务器
- 服务器收到客户端的消息后,把词语“反转”后给客户端
- 客户端接收服务器返回的"词语",打印显示
- 当客户端输入 "stop" 时断开与服务器的连接
- 多个客户端可以同时给服务器发送“词语”,服务器可以“同时”处理多个客户端的请求

请添加图片描述

package com.zwh.shangguigu.netcoding_;import java.lang.String;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;/*** @author Bonbons* @version 1.0* 演示多个客户端与服务器之间的多次通信*/
public class Server2 {public static void main(String[] args) throws IOException {//1、准备ServerSocketServerSocket server = new ServerSocket(8888);System.out.println("等待连接...");int count = 0;while(true){//2、监听一个客户端的连接Socket socket = server.accept();System.out.println("第" + ++count + "个客户端" + socket.getInetAddress().getHostAddress() + "连接成功!");ClientHandlerThread ct = new ClientHandlerThread(socket);ct.start();}//永远监听是否有客户端的请求}static class ClientHandlerThread extends Thread{private Socket socket;private String ip;public ClientHandlerThread(Socket socket){super();this.socket = socket;ip = socket.getInetAddress().getHostAddress();}@Overridepublic void run() {try{//(1)获取输入流,用来接受该客户端发送给服务器的数据BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));//(2)获取输出流,用来发送数据给该客户端PrintStream ps = new PrintStream(socket.getOutputStream());//(3)接收数据String str;while((str = br.readLine()) != null){//反转服务器得到的"成语"StringBuilder word = new StringBuilder(str);word.reverse();//返回给客户端ps.println(word);}System.out.println("客户端" + ip + "正常退出");}catch (IOException e){System.out.println("客户端" + ip + "意外退出");
//                e.printStackTrace();}finally {try{//(6)断开连接释放资源socket.close();}catch (IOException e){e.printStackTrace();}}}}
}
//客户端程序
class Client2{public static void main(String[] args) throws IOException{//1、准备Socket,连接服务器,需要指定服务器的IP地址和端口号Socket socket = new Socket("127.0.0.1", 8888);//2、获取输出流,用来发送数据给服务器OutputStream out = socket.getOutputStream();PrintStream ps = new PrintStream(out);//3、获取输入流,用来接受服务器发送给客户端的数据InputStream input = socket.getInputStream();BufferedReader br;if(args != null && args.length > 0){String encoding = args[0];//下面encoding这里报错,无法解析,但是我换成指定的字符集就没问题 >> 解决了,是我原来导入的String不对br = new BufferedReader(new InputStreamReader(input, encoding));}else{br = new BufferedReader(new InputStreamReader(input));}Scanner scanner = new Scanner(System.in);while(true){System.out.println("输入发送给服务器的单词或成语: ");String message = scanner.nextLine();if(message.equals("stop")){socket.shutdownOutput();break;}//4、发送数据ps.println(message);//接收数据String feedback = br.readLine();System.out.println("从服务器收到的反馈是: " + feedback);}//5、关闭socket,断开与服务器的连接scanner.close();socket.close();}
}

🌔 5、TCP网络编程经典案例:聊天室

package com.zwh.shangguigu.netcoding_;import com.sun.xml.internal.ws.api.handler.MessageHandler;import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Scanner;/*** @author Bonbons* @version 1.0*/
public class TestChatServer {//存储所有在线的客户端static ArrayList online = new ArrayList<>();public static void main(String[] args) throws IOException {//1、启动服务器,绑定端口号ServerSocket server = new ServerSocket(8989);//2、接收多个客户端同时连接while(true){Socket accept = server.accept();//把新连接的客户端添加到online列表中online.add(accept);//自定义的线程,用来给每个客户端分配一个线程,为了同时处理请求MessageHandler mh = new MessageHandler(accept);mh.start();}}static class MessageHandler extends Thread{private Socket socket;private String ip;public MessageHandler(Socket socket){super();this.socket = socket;}@Overridepublic void run() {try{//获取客户端的ipip = socket.getInetAddress().getHostAddress();//给其他客户转发"我上线了"sendToOther(ip + "上线了");//接收客户端发送的消息InputStream input = socket.getInputStream();//字符流到字节流的桥梁InputStreamReader reader = new InputStreamReader(input);BufferedReader br = new BufferedReader(reader);String str;while((str = br.readLine()) != null){sendToOther(ip + ":" + str);}sendToOther(ip + "下线了");}catch (IOException e){try{sendToOther(ip + "掉线了");}catch (IOException e){e.printStackTrace();}}finally {online.remove(socket);}}//我们封装一个方法用于给给其他客户端转发xxx信息public void sendToOther(String message) throws IOException{//遍历所有在线的客户端,一一转发for(Socket on : online){OutputStream every = on.getOutputStream();//使用PrintStream的目的是为了使用它的println方法按行打印PrintStream ps = new PrintStream(every);ps.println(message);}}}
}//客户端
class TestChatClient{public static void main(String [] args) throws Exception{//1、连接服务器Socket socket = new Socket("127.0.0.1", 8989);//2、开启两个线程//(1)线程1负责接收转发的消息[xxx上线、掉线、下线]Receive receive = new Receive(socket);receive.start();//(2)线程2负责发送自己的消息Send send = new Send(socket);send.start();//等待我发送线程结束了才会结束整个程序 [join() 等待结束]send.join();socket.close();}
}//接收消息
class Receive extends Thread{private Socket socket;public Receive(Socket socket){super();this.socket = socket;}@Overridepublic void run() {try{InputStream inputStream = socket.getInputStream();Scanner input = new Scanner(inputStream);while(input.hasNextLine()){String line = input.nextLine();System.out.println(line);}}catch (IOException e){e.printStackTrace();}}
}//发送消息
class Send extends Thread{private Socket socket;public Send(Socket socket){super();this.socket = socket;}@Overridepublic void run() {try{OutputStream outputStream = socket.getOutputStream();PrintStream ps = new PrintStream(outputStream);Scanner input = new Scanner(System.in);while(true){System.out.println("自己的话: ");String str = input.nextLine();if("bye".equals(str)){break;}ps.println(str);}input.close();}catch (IOException e){e.printStackTrace();}}
}
  • 对于客户端:可以是自定义的或浏览器
  • 对于服务端:可以是自定义的或Tomcat服务器

六、UDP网络编程

  • UDP(User Datagram Protocol,用户数据报协议):是一个无连接的传输层协议,提供面向事务的简单不可靠的信息传送服务,类似于短信

🌔 1、UDP网络编程的通信模型是什么?

  • UDP协议是一种面向非连接的协议,指的是在正式通信前不比与对方先建立连接,不管对方状态就直接发送,也不在乎对方是否接收到了完整的数据,UDP协议无法控制,是一种不可靠的协议

    • 优点就是:快,省内存空间和流量;UDP会尽最大努力交付数据,但不保证可靠交付
    • 没有TCP的确认机制、重传机制,因为网络原因没传送到对端,也不会给应用层返回错误信息
  • UDP协议是面向数据报文的信息传送服务,在发送端没有缓冲区 。对于应用层交付下来的报文在添加了首部之后就直接交付于ip层,不会进行合并,也不会进行拆分,而是一次交付一个完整的报文

    • 我们要发送100个字节的报文,我们调用一次send()方法就会发送100字节,接收方也需要用receive()方法一次性接收100字节,不能使用循环每次获取10个字节,获取十次这样的做法【一次性交付】
  • UDP协议没有拥塞控制,所以当网络出现的拥塞不会导致主机发送数据的速率降低。虽然UDP的接收端有缓冲区,但是这个缓冲区只负责接收,并不会保证UDP报文的到达顺序是否和发送的顺序一致。因为网络传输的时候,由于网络拥塞的存在是很大的可能导致先发的报文比后发的报文晚到达。如果此时缓冲区满了,后面到达的报文将直接被丢弃

    • 因此UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境,数据报大小限制在64K以下

请添加图片描述

  • DatagramSocketDatagramPacket 实现了基于UDP协议网络程序
    • UDP数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证 UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达
    • DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号
    • UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接

🌔 2、UDP网络编程的开发步骤:

(1)发送端程序的开发步骤

  • 创建DatagramSocket:默认使用系统随机分配的端口号
  • 创建DatagramPacket:将要发送的数据用字节数组表示,并指定要发送的数据长度,接收方的IP地址和端口号
  • 调用该DatagramSocket类对象的send方法:发送数据报DatagramPacket对象
  • 关闭DatagramSocket对象:发送端程序结束,关闭通信套接字

(2)接收端程序的开发步骤:

  • 创建DatagramSocket: 指定监听的端口号
  • 创建DatagramPacket:指定接收数据用的字节数组,起到临时数据缓冲区的效果,并指定最大可以接收的数据长度
  • 调用该DatagramSocket类对象的receive方法:接收该数据报DatagramPacket对象
  • 关闭DatagramSocket:接收端程序结束,关闭通信套接字

🌔 3、演示使用UDP网络编程发送和接收消息

基于UDP协议的网络编程仍然需要在通信实例的两端各建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送、接收数据报的对象,Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送、接收的数据报

(1)最基础的发送端和接收端核心代码

  • 发送端:
DatagramSocket ds = null;
try{ds = new DatagramSocket();byte [] by = "hello,www.baidu.com".getBytes();DatagramPacket dp = new Datagram(by, 0, by.length, InetAddress.getByName("127.0.0.1"), 10000);ds.send(dp);
}catch(Exception e){e.printStackTrace();
}finally{if(ds != null){ds.close();}
}
  • 接收端
DatagramSocket ds = null;
try{ds = new DatagramSocket(10000); byte [] by = new byte[1024 * 64];DatagramPacket dp = new DatagramPacket(dp.getData(), 0, dp.getLength());System.out.println(str + "--" + dp.getAddress());
}catch(Exception e){e.printStackTrace();
}finally{if(ds != null){ds.close();}
}

(2)案例二:完整代码

  • 发送端:
package com.atguigu.udp;import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;public class Send {public static void main(String[] args)throws Exception {
//		1、建立发送端的DatagramSocketDatagramSocket ds = new DatagramSocket();//要发送的数据ArrayList all = new ArrayList();all.add("尚硅谷让天下没有难学的技术!");all.add("学高端前沿的IT技术来尚硅谷!");all.add("尚硅谷让你的梦想变得更具体!");all.add("尚硅谷让你的努力更有价值!");//接收方的IP地址InetAddress ip = InetAddress.getByName("127.0.0.1");//接收方的监听端口号int port = 9999;//发送多个数据报for (int i = 0; i < all.size(); i++) {
//			2、建立数据包DatagramPacketbyte[] data = all.get(i).getBytes();DatagramPacket dp = new DatagramPacket(data, 0, data.length, ip, port);
//			3、调用Socket的发送方法ds.send(dp);}//		4、关闭Socketds.close();}
}
  • 接收端:
package com.atguigu.udp;import java.net.DatagramPacket;
import java.net.DatagramSocket;public class Receive {public static void main(String[] args) throws Exception {
//		1、建立接收端的DatagramSocket,需要指定本端的监听端口号DatagramSocket ds = new DatagramSocket(9999);//一直监听数据while(true){//2、建立数据包DatagramPacketbyte[] buffer = new byte[1024*64];DatagramPacket dp = new DatagramPacket(buffer,buffer.length);//3、调用Socket的接收方法ds.receive(dp);//4、拆封数据String str = new String(dp.getData(),0,dp.getLength());System.out.println(str);}//        ds.close();}
}

七、URL编程

  • URL(Uniform Resource Locator):统一资源定位符,表示 Internet 上某一资源的地址

  • 通过URL我们可以访问互联网上的各种资源【www, ftp站点】

    • 浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源
  • URL 的基本结构由5部分组成:

    • 传输协议

    • 主机名

    • 端口号

    • 文件名

    • 片段名?参数列表

      <传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表

  • 例如:http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123

传输协议为:http
主机名为:192.168.1.100
端口号:8080
文件名:helloworld/index.jsp
片段名:a
参数列表:username=shkstart&password=123 【参数名=参数值】

  • Java中 java.net 中实现了类URL,可以通过构造器初始化URL对象

    • public URL(String spec): 通过一个表示URL地址的字符串构造一个URL对象
      Url url = new Url("http://www.baidu.com")
    • public URL(URL context, String spec):通过基URL和相对URL构建一个URL对象
      URL downloadUrl = new URL(url, "download.html");
    • public URL(String protocol, String host, String file):指定了传输协议,主机域名和文件
      URL url = new URL("http", "www.atguigu.com", “download. html");
    • public URL(String protocol, String host, int port, String file):指定了协议,主机,端口号,文件
      URL gamelan = new URL("http", "www.atguigu.com", 80, “download.html");
  • URL类的构造器都声明抛出非运行时异常,必须要对这一异常进行处理,通常是用 try-catch 语句进行捕获。

🌔 1、URL类的常用方法:

  • 一个URL对象生成后,其属性是不能被改变的,但可以通过它给定的方法来获取这些属性:
    • public String getProtocol(): 获取该URL的协议名
    • public String getHost(): 获取该URL的主机名
    • public String getPort(): 获取该URL的端口号
    • public String getPath(): 获取该URL的文件路径
    • public String getFile(): 获取该URL的文件名
    • public String getQuery(): 获取该URL的查询名

🌔 2、针对HTTP协议的URLConnection类

  • URL的方法 openStream():能从网络上读取数据

  • 若希望输出数据,例如向服务器端的 CGI (公共网关接口-Common Gateway Interface-的简称,是用户浏览器和服务器端的应用程序进行连接的接口)程序发送一些数据,则必须先与URL建立连接,然后才能对其进行读写,此时需要使用 URLConnection

  • URLConnection:表示到URL所引用的远程对象的连接。当与一个URL建立连接时,首先要在一个 URL 对象上通过方法 openConnection() 生成对应的 URLConnection 对象。如果连接过程失败,将产生IOException.

    • URL netchinaren = new URL (“http://www.atguigu.com/index.shtml”);
    • URLConnectonn u = netchinaren.openConnection( );
  • 通过URLConnection对象获取的输入流和输出流,即可以与现有的CGI程序进行交互。

    • public Object getContent( ) throws IOException
    • public int getContentLength( )
    • public String getContentType( )
    • public long getDate( )
    • public long getLastModified( )
    • public InputStream getInputStream ( ) throws IOException
    • public OutputSteram getOutputStream( )throws IOException

总结:

  • 位于网络中的计算机具有唯一的IP地址,这样不同的主机可以互相区分。

  • 客户端-服务器是一种最常见的网络应用程序模型。服务器是一个为其客户端提供某种特定服务的硬件或软件。客户机是一个用户应用程序,用于访问某台服务器提供的服务。端口号是对一个服务的访问场所,它用于区分同一物理计算机上的多个服务。套接字用于连接客户端和服务器,客户端和服务器之间的每个通信会话使用一个不同的套接字。TCP协议用于实现面向连接的会话。

  • Java 中有关网络方面的功能都定义在 java.net 程序包中。Java 用 InetAddress 对象表示 IP 地址,该对象里有两个字段:主机名(String) 和 IP 地址(int)。

  • 类 Socket 和 ServerSocket 实现了基于TCP协议的客户端-服务器程序。Socket是客户端和服务器之间的一个连接,连接创建的细节被隐藏了。这个连接提供了一个安全的数据传输通道,这是因为 TCP 协议可以解决数据在传送过程中的丢失、损坏、重复、乱序以及网络拥挤等问题,它保证数据可靠的传送。

  • 类 URL 和 URLConnection 提供了最高级网络应用。URL 的网络资源的位置来同一表示 Internet 上各种网络资源。通过URL对象可以创建当前应用程序和 URL 表示的网络资源之间的连接,这样当前程序就可以读取网络资源数据,或者把自己的数据传送到网络上去

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...