【技术应用】java基于UNIX域套接字unix domain socket连接redis
- 一、前言
- 二、springboot内置tomcat
- 三、tomcat对unix域套接字协议支持分析
- 四、springboot内置tomcat使用unixdomainsocket协议示例
- 五、springboot外置tomcat配置unixdomainsocket
- 六、总结
一、前言
近期总结了不少**UNIX域套接字协议(unix domain socket
)**在工作中的应用,但是有一个工作中使用最多的组件支持使用UNIX DOMAIN SOCKET协议
的应用示例一直没有总结,那就是:tomcat支持UNIX域套接字协议(unix domain socket)的应用示例,今天总结一下;
往期UNIX域套接字协议在组件中的应用示例:
1、【技术应用】java基于UNIX域套接字(unix domain socket)连接redis
2、【技术应用】java基于UNIX域套接字(unix domain socket)连接postgresql数据库
3、【技术应用】java基于UNIX域套接字(unix domain socket)连接mysql数据库
二、springboot内置tomcat
1、springboot支持tomcat内置功能,项目直接可以编译成jar包运行,tomcat可以编译到jar内部,简化了应用,这也是springboot一大特点;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2、springboot内置tomcat的配置项属性通常是在application.yml配置文件
中设置,如下:
server:
tomcat:
accept-count: 80 #挂起的请求队列最大连接数,默认100
max-connections: 2000 #最大连接数,默认10000,tomcat内tcp连接池的大小
max-threads: 200 #最大线程数,默认200,超过加入等待队列,默认是100,当等待队列达到100后,直接拒绝此次请求返回connection refused。连接超时时间默认为20秒
min-spare-threads: 5 #最小工作线程数
connection-timeout: 60000 #server端的socket超时间,默认60s
accesslog:
enabled: true #启动tomcat访问日志
但是,通过分析所有的配置项,并没有发现unixdomainsocket
相关的配置信息,所以不能通过apllication.yml配置文件
实现unixdomainsocket功能;
3、springboot中通过代码配置内置tocmat属性;
1)配置Servlet容器的端口为9000
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import
org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;
@Component
public class MyWebServerFactoryCustomizer implements
WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);
}
}
2)使用ConfigurableServletWebServerFactory
的子类(比如TomcatServletWebServerFactory
)来配置Servlet容器
import java.time.Duration;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
@Component
public class MyTomcatWebServerFactoryCustomizer implements
WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory server) {
server.addConnectorCustomizers((connector) ->
connector.setAsyncTimeout(Duration.ofSeconds(20).toMillis()));
}
}
三、tomcat对unix域套接字协议支持分析
1、tomcat的组成可以分为两部分:连接器
和容器
连接器(connector):专门用于处理与网络连接相关的问题,比如在web开发中的Socket链接,request封装,连接线程池等等。
容器(servlet):用来存放我们编写的网站程序。tomcat一共有四个容器:Engine, Host, Context和Wrapper。一个Wrapper对应一个Servlet, 一个Context对应一个应用(比如,默认情况下webapps/ROOT中存放的为主应用,对应一个站点的根目录),一个Host对应一个站点(例如不同的域名),Engine是引擎。
2、connector里面负责处理请求的协议主要有基于bio,nio,nio2,apr
几种不同的http协议,其中支持NIO协议
的属性设置内是支持unixdomainsocket属性
配置的;
pollerThreadPriority:(int)The priority of the poller threads
selectorTimeout:(int)The time in milliseconds to timeout on a select() for the poller
useSendfile:(bool)Use this attribute to enable or disable sendfile capability
socket.directBuffer:(bool)Boolean value, whether to use direct ByteBuffers or java mapped ByteBuffers
socket.directSslBuffer:(bool)Boolean value, whether to use direct ByteBuffers or java mapped ByteBuffers for the SSL buffers
socket.appReadBufSize:(int)Each connection that is opened up in Tomcat get associated with a read ByteBuffer
socket.appWriteBufSize:(int)Each connection that is opened up in Tomcat get associated with a write ByteBuffer
socket.bufferPool:(int)The NIOx connector uses a class called NioXChannel that holds elements linked to a socket
socket.bufferPoolSize:(int)The NioXChannel pool can also be size based, not used object based
socket.processorCache:(int)Tomcat will cache SocketProcessor objects to reduce garbage collection
socket.eventCache:(int)Tomcat will cache PollerEvent objects to reduce garbage collection
unixDomainSocketPath:Where supported, the path to a Unix Domain Socket that this Connector will create and await incoming connections
unixDomainSocketPathPermissions:Where supported, the posix permissions that will be applied to the to the Unix Domain Socket specified with unixDomainSocketPath above
useInheritedChannel:(bool)Defines if this connector should inherit an inetd/systemd network socket
所以,我们只需要设置unixDomainSocketPath属性就可以了;
四、springboot内置tomcat使用unixdomainsocket协议示例
1、jdk版本
在使用springboot的内置tomcat使用,依赖的jdk最低版本是jdk16
,因为从jdk16开始,才支持Unix套接字;
在2019 Windows Server和Windows 10提供了对Unix套接字的支持,Unix套接字常用语本地进程之间通信,相比于TCP协议,本地进程使用Unix套接字可以更高效安全的通信。JDK16新增了一个适配Unix套接字的新接口java.net.UnixDomainSocketAddress
用于支持这一特性
如果jdk版本低于16,则会报错:
Caused by: java.lang.UnsupportedOperationException: Java Runtime does not support Unix domain sockets. You must use Java 16 to use this feature.
at org.apache.tomcat.util.compat.JreCompat.openUnixDomainServerSocketChannel(JreCompat.java:337) ~[tomcat-embed-core-9.0.70.jar:9.0.70]
at org.apache.tomcat.util.net.NioEndpoint.initServerSocket(NioEndpoint.java:252) ~[tomcat-embed-core-9.0.70.jar:9.0.70]
at org.apache.tomcat.util.net.NioEndpoint.bind(NioEndpoint.java:230) ~[tomcat-embed-core-9.0.70.jar:9.0.70]
at org.apache.tomcat.util.net.AbstractEndpoint.bindWithCleanup(AbstractEndpoint.java:1227) ~[tomcat-embed-core-9.0.70.jar:9.0.70]
at org.apache.tomcat.util.net.AbstractEndpoint.init(AbstractEndpoint.java:1240) ~[tomcat-embed-core-9.0.70.jar:9.0.70]
at org.apache.coyote.AbstractProtocol.init(AbstractProtocol.java:604) ~[tomcat-embed-core-9.0.70.jar:9.0.70]
at org.apache.coyote.http11.AbstractHttp11Protocol.init(AbstractHttp11Protocol.java:76) ~[tomcat-embed-core-9.0.70.jar:9.0.70]
at org.apache.catalina.connector.Connector.initInternal(Connector.java:1047) ~[tomcat-embed-core-9.0.70.jar:9.0.70]
... 21 common frames omitted
所以我们使用jdk19
<properties>
<java.version>19</java.version>
</properties>
2、springboot内置tomcat属性配置
springboot内置tomcat配置unix域套接字协议很简单,只需要设置connector.setProperty("unixDomainSocketPath","D:\\http.sock")
package com.example.tomcat19.config;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyTomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory server) {
server.addConnectorCustomizers((connector) -> {
//connector.setAsyncTimeout(Duration.ofSeconds(20).toMillis());
//connector.setProperty("protocol","org.apache.coyote.http11.Http11NioProtocol");
connector.setProperty("unixDomainSocketPath","D:\\http.sock");
}
);
}
}
3、服务端测试接口
package com.example.tomcat19.action;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class TestAction {
@GetMapping("/test")
public String test(){
log.info("收到请求id:1");
return "哈哈哈";
}
}
服务端项目结构:
4、请求客户端
为了方便验证,我们这里使用socket
基于http.sock
向服务端发送http请求报文
package com.sk.init;
import java.net.StandardProtocolFamily;
import java.net.UnixDomainSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class Test {
public static void main(String[] args) throws Exception{
// 建立 Unix Socket 连接
//File sockFile = new File("D:\\http.sock");
SocketChannel socketChannel = SocketChannel.open(StandardProtocolFamily.UNIX);
UnixDomainSocketAddress of = UnixDomainSocketAddress.of("D:\\http.sock");
//UnixDomainSocketAddress of = UnixDomainSocketAddress.of("D:\\test.sock");
boolean connect = socketChannel.connect(of);
System.out.println(connect);
String newData = "this is domain socket..." + System.currentTimeMillis();
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("GET /test HTTP/1.1").append("\n");
stringBuffer.append("Host: 127.0.0.1").append("\r\n");
stringBuffer.append("Connection: keep-alive").append("\r\n");
stringBuffer.append("Cache-Control: max-age=0").append("\r\n");
stringBuffer.append("sec-ch-ua: \" Not A;Brand\";v=\"99\", \"Chromium\";v=\"100\", \"Google Chrome\";v=\"100\"").append("\r\n");
stringBuffer.append("sec-ch-ua-mobile: ?0").append("\r\n");
stringBuffer.append("sec-ch-ua-platform: \"Windows\"").append("\r\n");
stringBuffer.append("Upgrade-Insecure-Requests: 1").append("\r\n");
stringBuffer.append("User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36").append("\r\n");
stringBuffer.append("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9").append("\r\n");
stringBuffer.append("Sec-Fetch-Site: none").append("\r\n");
stringBuffer.append("Sec-Fetch-Mode: navigate").append("\r\n");
stringBuffer.append("Sec-Fetch-User: ?1").append("\r\n");
stringBuffer.append("Sec-Fetch-Dest: document").append("\r\n");
stringBuffer.append("Accept-Encoding: gzip, deflate, br").append("\r\n");
stringBuffer.append("Accept-Language: zh-CN,zh;q=0.9").append("\r\n");
stringBuffer.append("\r\n");
//stringBuffer.append("Accept: */*").append("\r\n");
ByteBuffer buf = ByteBuffer.allocate(2048);
buf.clear();
buf.put(stringBuffer.toString().getBytes());
//buf.put(newData.getBytes());
buf.flip();
while (buf.hasRemaining()) {
socketChannel.write(buf);
}
socketChannel.close();
}
}
5、请求结果
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.2-SNAPSHOT)
23:06:54.720 [main] INFO org.springframework.boot.StartupInfoLogger.logStarting(StartupInfoLogger.java:51) - Starting Tomcat19Application using Java 19.0.1 with PID 6248 (G:\work3\UDS redis\tomcat19\target\classes started by Administrator in G:\work3\UDS redis\tomcat19)
23:06:54.962 [main] INFO org.springframework.boot.SpringApplication.logStartupProfileInfo(SpringApplication.java:630) - No active profile set, falling back to 1 default profile: "default"
23:06:55.895 [main] INFO org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:108) - Tomcat initialized with port(s): 8086 (http)
23:06:55.905 [main] INFO org.apache.juli.logging.DirectJDKLog.log(DirectJDKLog.java:173) - Initializing ProtocolHandler ["http-nio-D:\\http.sock"]
23:06:55.906 [main] INFO org.apache.juli.logging.DirectJDKLog.log(DirectJDKLog.java:173) - Starting service [Tomcat]
23:06:55.906 [main] INFO org.apache.juli.logging.DirectJDKLog.log(DirectJDKLog.java:173) - Starting Servlet engine: [Apache Tomcat/10.1.4]
23:06:55.996 [main] INFO org.apache.juli.logging.DirectJDKLog.log(DirectJDKLog.java:173) - Initializing Spring embedded WebApplicationContext
23:06:55.996 [main] INFO org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.prepareWebApplicationContext(ServletWebServerApplicationContext.java:291) - Root WebApplicationContext: initialization completed in 991 ms
23:06:56.344 [main] INFO org.apache.juli.logging.DirectJDKLog.log(DirectJDKLog.java:173) - Starting ProtocolHandler ["http-nio-D:\\http.sock"]
23:06:56.364 [main] INFO org.springframework.boot.web.embedded.tomcat.TomcatWebServer.start(TomcatWebServer.java:220) - Tomcat started on port(s): -1 (http) with context path ''
23:06:56.374 [main] INFO org.springframework.boot.StartupInfoLogger.logStarted(StartupInfoLogger.java:57) - Started Tomcat19Application in 2.287 seconds (process running for 4.057)
23:07:12.012 [http-nio-D:\\http.sock-exec-1] INFO org.apache.juli.logging.DirectJDKLog.log(DirectJDKLog.java:173) - Initializing Spring DispatcherServlet 'dispatcherServlet'
23:07:12.032 [http-nio-D:\\http.sock-exec-1] INFO org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:532) - Initializing Servlet 'dispatcherServlet'
23:07:12.033 [http-nio-D:\\http.sock-exec-1] INFO org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:554) - Completed initialization in 1 ms
23:07:12.062 [http-nio-D:\\http.sock-exec-1] INFO com.example.tomcat19.action.TestAction.test(TestAction.java:13) - 收到请求id:1
五、springboot外置tomcat配置unixdomainsocket
<Service name="CatalinaLocal">
<Connector
protocol="org.apache.coyote.http11.Http11AprProtocol"
unixDomainSocketPath="/opt/app-name/http.sock" />
<Engine
defaultHost="localhost"
name="Catalina">
<Host
name="localhost"
appBase="webapps"
unpackWARs="true"
autoDeploy="false"
deployIgnore="(?!.*hawtio).*">
<Valve
className="org.apache.catalina.valves.RemoteIpValve" />
</Host>
</Engine>
</Service>
注:这里的配置没有做验证,有兴趣的可以实测一下
六、总结
unix域套接字协议
还是非常有用,尤其是涉及性能优化时,有很多大厂的微服务架构都支持unix域套接字协议;