Java网络编程及常用组件分析
一 、网络编程底层原理
后端服务用到网络请求非常常见,如何在技术不断升级的过程中,规范化管理网络请求、保持高性能调用是个持久话题
知其然,仍需知其所以然。先搞明白底层原理,了解最基础的网编过程。
1 网络编程三要素
A:IP地址 — 都懂,网络世界的唯一标识。
B:端口号 — 应用程序中某个进程所占用的逻辑端口。
C:传输层协议 — UDP协议与TCP协议
做web类经常会听到各种协议,UDP、TCP、IP、HTTP等各种协议很头疼,这里提供一张图,帮你看懂这些协议。
右图是7层模型,又称OSI(Open System Interconnection,开放式系统互联)7层模型。
http、https均在应用层。UDP、TCP处在传输层、IP协议处在网络层,配合左图适当发挥想象空间理解其含义吧。
2 传输层UDP协议与TCP协议区别
UDP (User Datagram Protocol) :用户数据包协议。
TCP (Transmission Control Protocol):传输控制协议。
传输层协议是通信的规则,UDP与TCP是2种不同的传输层协议,各自根据其自身特性均有代表性的应用场景。
UDP协议,将数据源和目的地封装成数据包,不需要建立连接;每个数据包的大小限制在64K;因为不建立连接,是不可靠协议;但传输速度快。
TCP协议,建立连接,形成数据传输的通道;在连接中进行大数据量传输;通过三次握手完成连接,是可靠协议;必须建立连接,传输效率稍差。
UDP: TCP:
把数据打包 建立连接通道
数据有限制(64K) 数据无限制
不建立连接 三次握手建立连接
速度快 速度慢
不可靠(易丢包) 可靠
实际应用中,更倾向于将2中协议混合使用,将不注重可靠性的广播类数据通过UDP协议传输,而对于需要文件稳定上传、下载的场景通过TCP协议传输。
3 UDP协议代码实例
UDP协议使用底层java.net.DatagramPacket;java.net.DatagramSocket;进行数据报封装、套接字对象请求发送和接收。
消息发送:
/**
*
* 发送UDP数据
* @author Administrator
* DatagramSocket 表示用来发送和接收数据报包的套接字。
*
* 1 创建对象
* 2 构造数据报包
* 3 发送请求
* 4 释放连接
*/
public class UdpSendDemo {
public static void main(String[] args) throws IOException {
//创建客户端socket对象
DatagramSocket ds = new DatagramSocket();
//构造数据报包 包含文件内容 长度 端口号 协议
//DatagramPacket(byte[] buf, int length, InetAddress address, int port)
//构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
byte[] bys ="hello socket".getBytes();
//定义发送目标的地址、端口号
DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("localhost"),8448);
//发送socket请求
ds.send(dp);
//释放连接
ds.close();
}
}
消息接收:
/**
* 接受UDP数据
* @author Administrator
*
* 1 创建接收Socket对象
* 2 创建数据报(接收)
* 3 调用sockrt接收方法,接受对象
* 4 解析socket套接字包
* 5 释放资源
*/
public class UdpReceiveDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(8448);
//DatagramPacket(byte[] buf, int length)
//构造 DatagramPacket,用来接收长度为 length 的数据包。
byte[] bys =new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
ds.receive(dp);
//解析数据中的端口号及ip信息
InetAddress inetAddress = dp.getAddress();
//解析数据包
//创建获取数据的缓冲区
byte[] bys2 = dp.getData();
String s = new String(bys2,0, bys2.length);
System.out.println("来者地址:"+ inetAddress.getHostAddress() + "来者说:" + s);
ds.close();
}
}
4 TCP协议代码实例
原理分析:服务端监听端口,客服端发送Socket套接字连接,建立连接后即建立字节流通道,客服端写字节、服务端读字节、响应给客服端字节流再读,完成交互。
服务端监听:
/**
* TCP协议:接收数据
* A:创建接收端的socket对象
* B:监听客户端 端口,返回一个对应的socket对象
* C:获取输入流
* D:释放资源
*/
public class ServerTcpSocket {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);
//监听客户端 返回对应的docket对象
//阻塞式方法
Socket s =ss.accept();
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);
String str =new String(bys,0,len);
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+":"+str);
//获取输出流
OutputStream os =s.getOutputStream();
os.write("数据已经收到了".getBytes());
//服务器不关闭 客户端对应的socket关闭 ss.close
s.close();
}
}
客户端:
/**
* Tcp协议发送数据:
* A:创建发送端的Socket对象,成功则表示建立连接
* B:获取输出流,写数据
* C:释放资源
*
* 注意:TCP协议需要先保证接收端开放 否则:Connection refused: connect
*/
public class ClientTcpSocket {
public static void main(String[] args) throws IOException {
//创建发送端的Socket对象
//Socket(String host,int port)
Socket s =new Socket("localhost",8888);
OutputStream os = s.getOutputStream();
os.write("hello TCP".getBytes());
InputStream is =s.getInputStream();
byte[] bys = new byte[1024];
//阻塞
int len =is.read(bys);
String server = new String(bys,0,len);
System.out.println("server:" + server);
s.close();
}
}
二、常用组件技术选型
在 Java 生态中,虽然有数不清的 HTTP client lib 组件库,但是大体可以分为这三类:
1、JDK 自带的 HttpURLConnection 标准库;
2、 Apache HttpComponents HttpClient, 以及基于该库的 wrapper, 如 Unirest. 非基于 Apache HttpComponents HttpClient,
3.1、大量重写应用层代码的 HTTP client 组件库,典型代表是 OkHttp.
3.2、基于OkHttp发展出来的Retrofit
1.JDK自带的网络组件 HttpURLConnection
1.1 HttpURLConnection 缺点:缺乏连接池管理、请求控制等、底层基于socket、阻塞式通道流传输
1.2 从oracle官网上对HttpURLConnection的介绍看,该方法底层是基于socket实现的。JDK从1.1就开始使用的HttpURLConnection。包含http请求及ssl(Secure Sockets Layer 安全套接字协议)协议的https请求。
1.3 传输流默认大小4096字节
1.4 底层文件传输沿用socket的通道字节流
方法:
public static void main(String[] args) throws IOException {
String urlString = "https://httpbin.org/post";
String bodyString = "password=e10adc3949ba59abbe56e057f20f883e&username=test3";
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
OutputStream os = conn.getOutputStream();
os.write(bodyString.getBytes("utf-8"));
os.flush();
os.close();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream is = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
System.out.println("rsp:" + sb.toString());
} else {
System.out.println("rsp code:" + conn.getResponseCode());
}
}
总结来说:JDk1.1一直沿用的HttpURLConnection方法底层依旧是socket,阻塞式交互完全无法满足现在的需求,通道内字节流传输性能也略显逊色。
2 commons-httpclient.
2.1 commons-httpclient Apache老版本的http组件(2.x版本均移植至3.1),目前已停止更新,官网介绍如下。
2.2 commons-httpclient3.1版本的httpclient基于jdk1.1的HttpURLConnection方法也着实有不错改进,至少在连接管理上有不少提升,因为已经停更了,这里不做过多赘述,。
2.3 国际惯例,先导包
<!--网络请求-->
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
2.4 代码示例:
public static String sendGet(String urlParam){
String result=null;
try {
// 创建httpClient实例对象
HttpClient httpClient = new HttpClient();
// 设置httpClient连接主机服务器超时时间:15000毫秒
httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(15000);
// 创建GET请求方法实例对象
GetMethod getMethod = new GetMethod(urlParam);
// 设置post请求超时时间
getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 60000);
getMethod.addRequestHeader("Content-Type", "application/json");
httpClient.executeMethod(getMethod);
result = getMethod.getResponseBodyAsString();
getMethod.releaseConnection();
}catch (Exception e){
log.error("发送网络请求失败 {}",urlParam,e);
}
return result;
}
3 Apache HttpComponents
3.1 HttpComponents继承自Commons HttpClient 3.x,是apache目前在维护的,市面上最常用的HTTP协议网络传输Java组件。
3.2 稳定的HttpComponents版本 4.1、4.5(该版本是较稳定、国内最常用的版本,本文着重讲4.5版本)、5.0
3.3 HttpCore是一组轻量级HTTP传输组件。HttpCore支持两种I / O模型:基于经典的阻塞式Java I / O模型和基于Java NIO的非阻塞事件驱动的I / O模型。阻塞模型更适合数据密集型低延迟方案,而非阻塞模型更适合于高延迟方案。在原始数据吞吐量中,原始数据吞吐量的重要性不如处理数千个同时HTTP连接的能力。资源高效的方式。
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
3.4 或许说了那么多你还不知道我在说什么,干脆提人吧,CloseableHttpClient 看到它你应该或许明白了。
3.5 目前市场主流的java网编工具,国内主流大厂都在用,下面解读百度云、腾讯云、华为云的SDK版本中网络请求中使用httpcomponents的源码,学习别人的实际用法。
3.5.1 百度
<!-- 百度云SDK-->
<dependency>
<groupId>com.baidubce</groupId>
<artifactId>bce-java-sdk</artifactId>
<version>0.10.86</version>
</dependency>
上面是百度云SDK的maven地址,百度云SDK用法就不聊了,直接看看1、配置 2、发送 3、接收
1、连接配置
//首先设置连接配置,代码中设置ak/sk、局点,这3个百度云业务中用到的配置,直接忽略
BceClientConfiguration config = new BceClientConfiguration()
.withCredentials(new DefaultBceCredentials(accessKeyId, secretAccessKy))
.withEndpoint(endPoint);
//重点看BceClientConfiguration这个类
这里设置了连接超时时间50s、最大连接数50个、应用层网络协议等等,并且挨个提供了set方法供使用者自定义连接配置。【开发标准】
//继续看如何发送请求的 这里是官网提供的生成客户端的方法
CdnClient cdnClient = new CdnClient(config);
//cdnClient 继承了abstractBceClient方法,并且业务场景中很多方法都用到了父类中的invokeHttpClient(T,T)方法,继续追寻原委
可以看到实际源码中,httpclient还是使用的CloseableHttpclient方法,实际SDK中还有异步的方法 CloseableHttpAsyncClient,具体的业务用法这里就不再赘述。
4 okhttp
okhttp也是目前比较主流的网络请求的主要工具,后面有机会再好好解读。这里同样拿腾讯云的SDK来学习okhttp的具体使用方法。
4.1 腾讯云
<!-- 腾讯云SDK-->
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<!-- go to https://search.maven.org/search?q=tencentcloud-sdk-java and get the latest version. -->
<!-- 请到 https://search.maven.org/search?q=tencentcloud-sdk-java 查询最新版本 -->
<version>3.1.87</version>
</dependency>
同样从配置和发送请求来分析
3.1.1 实例化client对象
// 实例化要请求产品(以 cvm 为例)的 client 对象
CvmClient client = new CvmClient(cred, "ap-guangzhou");
从上图能看出来,实际的连接管理是在profile中设置的(包括连接超时时间、协议、读写超时时间等)
而这里的HttpConnection实际用的就是OkHttpClient