文章目录
网络编程
网络编程
-
网络编程
- 多主机间的数据通讯操作
-
网络(核心定义)
- 有两台以上电脑就称为网络
-
网络连接的目的
- 不仅是为了电脑的串联,主要是为了彼此之间的数据交互
- 所谓网络游戏本质还是网络通信问题
-
通讯实现
- 产生一系列处理协议: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连接时的步骤
- 服务器实例化一个
ServerSocket
对象,表示通过服务器上端口通信 - 服务器调用
ServerSocket
类的 accept() 方法- 该方法将一直等待,直到客户端连接到服务器上给定的端口
- 服务器在等待时,一个客户端实例化一个
Socket
对象,指定服务器名称和端口号来请求连接 Socket
类的构造函数试图将客户端连接到指定的服务器和端口号- 如果通信被建立,则在客户端创建一个
Socket
对象能够与服务器进行通信
- 如果通信被建立,则在客户端创建一个
- 服务器端
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()
时触发三次握手
- 实际上即确认通信双方(客户端和服务端)的序列号
第一次握手
-
最开始客户端和服务端都处于
CLOSE
状态 -
服务器主动监听某个端口,处于 LISTEN 状态
-
客户端随机生成出序列号,把标志位设置为
SYN
(希望连接),把该报文发送给服务端- 序列号一般叫做
client_isn
- 随机序列号
- 安全性:随机
ISN
能避免非同一网络的攻击 - 让通信双方能够根据序号将「不属于」本连接的报文段丢弃
- 安全性:随机
SYN
:同步缩写,为 1 时,表示希望创建连接
- 序列号一般叫做
-
客户端发送完
SYN
报文后,进入SYN_SEND
状态
第二次握手
-
服务端接收到了客户端的请求后,初始化对应的序列号
-
序列号一般叫做 server_isn
-
在「确认号」字段里填上
client_isn + 1
(告诉客户端已收到序列号),把SYN
和ACK
标记位都点亮(置为 1 )SYN
为 1 时,表示希望创建连接ACK
为 1 时,确认号字段有效
-
把报文发送客户端,服务端的状态变成
SYN-REVD
状态
第三次握手
-
客户端收到服务端发送的报文,确认服务端接收到发出的的序列号(通过确认号),且接收到了服务端的序列号(
server_isn
) -
客户端需要通知服务端已接收到发送过来的序列号
-
在「确认号」字段上填上
server_isn + 1
,标记位ACK
为1 -
ACK = 1:确认号字段有效
-
-
客户端发送报文后,进入
ESTABLISHED
状态- 服务端接收到客户端的报文后,也进入
ESTABLISHED
状态
- 服务端接收到客户端的报文后,也进入
两次握手
两次握手是否可行
- 两次握手只能保证客户端的序列号成功被服务端接收
- 而服务端无法确认自己的序列号是否被客户端成功接收
丢包
-
前两个包:客户端发送给服务端的
SYN
包、服务端发送的SYN + ACK
包丢了- 即服务端未接收到客户端发出的
SYN
包;故没有返回客户端的包,或服务端返回的包丢了 - 客户端迟迟收不到服务端的
ACK
包,会周期性超时重传,直到收到服务端的ACK
- 即服务端未接收到客户端发出的
-
第三个包:客户端发送完第三个包后单方面进入
ESTABLISHED
状态,服务端也认为此时连接正常,但第三个包没到达服务端-
此时客户端与服务端都还没数据发送
- 服务端会认为自己发送的
SYN + ACK
包没发送至客户端,会超时重传的SYN + ACK
包
- 服务端会认为自己发送的
-
这时候客户端已经要发送数据了,服务端接收到了
ACK + Data
数据包- 服务端自然切换到
ESTABLISHED
状态下,并且接收客户端的 Data 数据包
- 服务端自然切换到
-
此时服务端要发送数据但发送不了,会一直周期性超时重传
SYN + ACK
,直到接收到客户端的ACK
包
-
四次挥手
过程
- 建立完连接后,客户端和服务端都处于
ESTABLISHED
状态- 双方都有权利断开连接,以客户端主动断开为例
-
客户端打算关闭连接,发
FIN
报文给服务端(点亮FIN
标志位),客户端发送后进入FIN_WAIT_1
状态 -
服务端收到
FIN
报文,回复ACK
报文给客户端(表示已收到),服务端发送后进入CLOSE_WAIT
状态- 客户端接收到服务端的
ACK
报文进入FIN_WAIT_2
状态
- 客户端接收到服务端的
-
但服务器可能还有数据要发送给客户端,确认没有数据返回客户端后,发送
FIN
报文给客户端,并进入LAST_ACK
状态 -
客户端收到服务端的
FIN
报文后,回应ACK
报文,并进入TIME_WAIT
状态-
服务端收到客户端的
ACK
报文后进入CLOSE
状态 -
客户端在
TIME_WAIT
到2MSL
,也进入CLOSE
状态TIME_WAIT
状态:- 保证最后的
ACK
报文 「接收方」一定能收到- 如果收不到,对方会重发
FIN
报文
- 如果收不到,对方会重发
- 确保创建新连接时,先前网络中残余的数据都丢失
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()));
-
服务端
-
基本流程
- 创建服务器对象,监听端口,例如:8888
ServerSocket serverSocket = new ServerSocket(8888);
- 等待连接,返回 socket 对象(套接字)
Scoket socket = serverSocket.accept();
- 获取输入流对象:获取客户端发送的数据
InputStream in = socket.getInputStream();
- 字节流,可转为字符流进行处理
- 读取客户端数据:
in.read();
- 自定义对数据进行处理
- 获取输出流对象:向客户端返回数据
OutputStream out = socket.getOutputStream();
- 向客户端写数据:
out.write()
方法- 数据写入完毕后使用
socket.shutDownOutputStream();
方法标记结束- 否则客户端无法知道是否输入完毕
- 循环写入时必须使用,单次通讯可不添加
- 数据写入完毕后使用
- 关闭释放各种资源
- 关闭IO流
in.close();
out.close()
- 关闭链接:
socket.close();
- 关闭服务端:
serverSocket.close();
- 关闭IO流
/* ~~~ 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(); } } }
- 创建服务器对象,监听端口,例如:8888
客户端
-
基本流程
- 通过
IP
地址或域名,端口号构建Socket
对象,连接服务器
Scoket socket = new Socket(InetAddress.getLocalHost(), 8888);
- 获取输出流,向服务器端传输数据
OutputStream out = socket.getOutputStream();
- 写入数据:
out.write();
- 写入完毕后通过
socket.shutDownOutputStream();
方法标记结束- 否则服务器端无法知晓是否传输完毕
- 循环写入时必须使用,单次通讯可不添加
- 写入完毕后通过
- 获取输入流,读取服务器返回的数据
InputStream in = socket.getInputStream();
- 读取数据:
in.read()
方法- 自定义对读取到的数据进行处理
- 关闭释放资源
- IO流
- 输出流:
out.close();
- 输入流:
in.close();
- 输出流:
- 关闭连接:
socket.close();
- IO流
- 通过
-
/* ~~ 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 数据报一定能安全送到目的地,也不确定何时到达
- 客户端是否接收到与发送者无关
- 不保证 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
protocol
:协议,可以是 HTTP、HTTPS、FTP 和 Fileport
:端口号path
:文件路径及文件名
- 例如:
http://www.runoob.com/index.html?language=cn#j2se
- 协议(protocol):
http
- 主机(host:):www.runoob.com,ip 地址映射域名
- 端口号(port):
80
,未指定端口,HTTP
协议默认的端口号为 80 - 文件路径(path):
/index.html
- 请求参数(query):
language=cn
- 定位位置(fragment):
j2se
,定位到网页中 id 属性为 j2se 的 HTML 元素位置
- 协议(protocol):
使用
构建
-
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()
- 操纵影响与远程资源的连接的参数
- 与资源交互;查询标题字段和内容
- 连接对象通过 URL 调用
openConnection
方法创建- 设置参数和一般请求属性被操纵
- 与远程对象实际连接使用
connect
方法建立- 远程对象变为可用,可访问远程对象的标头字段和内容
-
请求后对
URLConnection
的InputStream
或OutputStream
调用close()
方法释放网络资源- 除非特定的协议规范为其指定了不同的行为
获取
-
openConnection()
方法返回java.net.URLConnection
-
例如
-
连接
HTTP
协议的URL
openConnection()
方法返回HttpURLConnection
对象
-
连接的
URL
为JAR
文件openConnection()
方法将返回JarURLConnection
对象
-
等等…
-
方法列表
getContentType
方法被getContent
方法用来判断远程对象的类型- 子类会重写
getContentType
方法很方便 - 可以忽略所有预连接参数和一般请求属性
- 预连接参数和请求属性默认为合理值
- 该接口的大多客户端,只有两个有趣的方法
getInputStream
和getContent
,通过便利方法镜像到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.youkuaiyun.com/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(); } }