前言
Unix domain socket
又叫 IPC(inter-process communication 进程间通信)socket
,用于实现同一主机上的进程间通信。
socket 原本是为网络通讯设计的,但后来在 socket 的框架上发展出一种 IPC 机制,就是 UNIX domain socket。虽然网络 socket 也可用于同一台主机的进程间通讯(通过 loopback 地址 127.0.0.1),但是 UNIX domain socket 用于 IPC 更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等
,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC 机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。
Unix 套接字:Unix 套接字、 UNIX 域套接字、 Unix 域协议族AF_UNIX、POSIX 本地进程间通信套接字、POSIX 本地 IPC 套接字、、、、、、——所有这些术语或多或少指的是同一个概念——主机内部协议和寻址该方案或多或少类似于 Internet 上计算机之间的套接字通信。通常,但并非总是如此,Unix 套接字由文件系统上的路径名引用。PF_UNIXPF_LOCALAF_FILEPF_FILE
Unix 套接字可用于多种平台(包括 macOS、Windows 和 Linux),它们的工作方式都略有不同。
Unix域套接字
有兴趣的同学可以百度查找相关资料,这里不再赘述;
由上可知,unix套接字的通讯效率
是高于socket通讯的,在同一台服务器
上使用unix domian socket进行进程间的通讯,也能提高我们的通讯效率,例如同一台服务java server与java server
、java server与mysql
、java server与postgresql
、java server与docker
等通讯,可以使用unix domain socket进行数据交互,可以提高执行效率,同时也也避免了服务对外暴露端口,提高的安全性
;
这篇文章主要实现java通过unix domain socket
技术对mysql数据库的应用,主要涉及步骤讲解、代码示例;后续也会提供java基于unix domain socket实现Socket
、rmi
、postgresql
应用,尤其java通过unix domain socket技术调用postgresql数据库,我们工作中涉及到的应用最多,后续会着重讲解;
注:为了方便大家使用,本文介绍两种
unix domain socket连接mysql
数据库的方式:java + jdbc
和 spring boot + mybatis
mysql.sock简介:
“mysql.sock”是mysql的套接字文件,是mysql的主机和客户机在同一host
上的时候,使用unix socket做为通讯协议的载体。在UNIX系列系统下本地连接MySQL可以采用TCP连接
和UNIX域套接字
两种方式;其中UNIX域套接字方式需要一个套接字文件
,可用“show variables like ‘socket’\G;”命令来查看本地socket文件位置。
MySQL
中我们常用的有两种连接数据库
的方式,分别是:
mysql -uyouruser -pyourpassword -P3306 -h127.0.0.1
mysql -uyouruser -pyourpassword -P3306 -h192.168.199.198
mysql -uyouruser -pyourpassword --socket=/tmp/mysql.sock
mysql -uyouruser -pyourpassword -hlocalhost
mysql -uyouruser -pyourpassword
以上三种方式是等价的,默认不填时,默认--host=localhost
,--socket=/tmp/mysql.sock
mysql使用 Unix 域套接字连接:
Connector/J
本身不支持使用 Unix 域套接字连接到 MySQL 服务器。但是,可以使用通过可插入套接字工厂提供功能的第 3 方库。这样的自定义工厂应该实现 Connector/J 的 com.mysql.cj.protocol.SocketFactory
接口或遗留 com.mysql.jdbc.SocketFactory
接口。当您为 Unix 套接字使用此类自定义套接字工厂时,请遵循以下要求:
1)MySQL 服务器必须配置系统变量 --socket
(对于使用 JDBC API 的本机协议连接)或 --mysqlx-socket
(对于使用 X DevAPI 的 X 协议连接),它必须包含 Unix 套接字文件的文件路径。
2)自定义工厂的完全限定类名应该通过连接属性传递给 Connector/J socketFactory
。例如,对于 junixsocket
库,设置:
socketFactory=org.newsclub.net.mysql.AFUNIXDatabaseSocketFactory
您可能还需要将其他参数作为连接属性传递给自定义工厂
。例如,对于 junixsocket
库,提供具有以下属性的套接字文件的文件路径junixsocket.file
:
junixsocket.file=path_to_socket_file
3)Fore release 8.0.21 及更早版本:使用 X 协议
时,设置连接属性 xdevapi.useAsyncProtocol=false(
这是 Connector/J 8.0.12 及更高版本的默认设置)。异步套接字通道不支持 Unix 套接字。当 时xdevapi.useAsyncProtocol=true
,该 socketFactory
属性将被忽略(连接属性 xdevapi.useAsyncProtocol
自 8.0.22 版以来已被弃用)。
注:可以参考MySQL Connector/J 8.0 开发人员指南
创建数据库表student
:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` varchar(10) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`sex` char(6) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`pwd` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`email` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = DYNAMIC;
SET FOREIGN_KEY_CHECKS = 1;
连接socket
,创建AFUNIXDatabaseSocketFactoryCJ
类:
package com.config;
import com.mysql.cj.conf.PropertySet;
import com.mysql.cj.conf.RuntimeProperty;
import com.mysql.cj.protocol.ServerSession;
import com.mysql.cj.protocol.SocketConnection;
import com.mysql.cj.protocol.SocketFactory;
import org.newsclub.net.unix.AFUNIXSocket;
import org.newsclub.net.unix.AFUNIXSocketAddress;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.Socket;
public class AFUNIXDatabaseSocketFactoryCJ implements SocketFactory {
private AFUNIXSocket rawSocket;
private Socket sslSocket;
@Override
public <T extends Closeable> T connect(String s, int i, PropertySet propertySet, int i1) throws IOException {
RuntimeProperty<String> prop = propertySet.getStringProperty("junixsocket.file");
String sock;
if (prop != null && !prop.isExplicitlySet()) {
sock = prop.getStringValue();
} else {
sock = "/var/lib/mysql/mysql.sock";
}
final File socketFile = new File(sock);
this.rawSocket = AFUNIXSocket.connectTo(AFUNIXSocketAddress.of(socketFile));
this.sslSocket = rawSocket;
return (T) rawSocket;
}
@Override
public <T extends Closeable> T performTlsHandshake(SocketConnection socketConnection, ServerSession serverSession) throws IOException {
return null;
}
}
建立数据库Connection
连接:
package com.sk.jdbc;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class MysqlDemo {
//建立connection连接
public static Connection getConn() throws Exception{
Driver driver = new com.mysql.cj.jdbc.Driver();
String url = "jdbc:mysql://localhost/test";
//将用户名和密码封装在Properties中
Properties info = new Properties();
info.setProperty("socketFactory","com.config.AFUNIXDatabaseSocketFactoryCJ");
info.setProperty("user","root");
info.setProperty("password","%Dsh0406");
info.setProperty("junixsocket.file","/var/lib/mysql/mysql.sock");
info.setProperty("sslMode","DISABLED");
Connection conn = driver.connect(url,info);
System.out.println(conn);
return conn;
}
//通过conn操作数据库,主要用于jdbc实现
public static List<String> run() throws Exception{
Connection conn = getConn();
Statement stmt = conn.createStatement();
String sql = "select * from student";
ResultSet rs = stmt.executeQuery(sql);
//处理结果集
List<String> res = new ArrayList<>();
while(rs.next()) {
String str = rs.getString("name");
res.add(str);
}
rs.close();
System.out.println("结果集关闭成功!");
stmt.close();
System.out.println("语句通道关闭成功!");
conn.close();
System.out.println("关闭连接成功!");
//遍历输出
if(res!=null){
for(String out:res) {
System.out.println(out);
}
}
return res;
}
}
jdbc
实现对数据库调用,结合上一步的run()
package com.controller;
import com.bean.Student;
import com.mapper.StudentMapper;
import com.sk.jdbc.MysqlDemo;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.sql.SQLException;
import java.util.List;
@Log4j2
@RestController
public class TestAction {
@Resource
private StudentMapper studentMapper;
/**
* 验证jdbc调用
* @return
*/
@GetMapping("/test/local")
private Object get(){
try {
return MysqlDemo.run();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
jdbc
调用执行结果:
日志信息
接口调用:
集合springboot+mybatis
使用
创建mapper
package com.mapper;
import com.bean.Student;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
@Mapper
@Repository
public interface StudentMapper {
@Select("select * from student")
List<Student> selectAll();
@Select("select * from student where id = #{id}")
Student selectById(@Param("id") int id);
@Select("select * from student where name = #{name}")
Map<String, Object> selectByName1(@Param("name") String name);
@Select("select * from student where name = #{name}")
Student selectByName2(@Param("name") String name);
@Select("select * from student where name = #{name} and pwd = #{pwd}")
Student selectByNameAndPwd(String name, String pwd);
@Delete("delete from student where id = #{id}")
boolean deleteById(int id);
@Insert("insert into student values (null,#{name},#{sex},#{pwd},#{email})")
boolean insertUser(String name, String sex, String pwd, String email);
@Update("update student set name = #{name} where id = #{id}")
boolean updateById(String name, int id);
}
重写DataSource
类MyDataSource.java
package com.config;
import com.sk.jdbc.MysqlDemo;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
public class MyDataSource implements DataSource {
@Override
public Connection getConnection() {
try {
return MysqlDemo.getConn();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public Connection getConnection(String username, String password) {
try {
return MysqlDemo.getConn();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
手动初始化数据库连接DruidConfig.java
package com.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class DruidConfig {
@Bean
public DataSource druid(){
return new MyDataSource();
}
}
创建验证接口
package com.controller;
import com.bean.Student;
import com.mapper.StudentMapper;
import com.sk.jdbc.MysqlDemo;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.sql.SQLException;
import java.util.List;
@Log4j2
@RestController
public class TestAction {
@Resource
private StudentMapper studentMapper;
/**
* 验证mybatis
* @return
*/
@GetMapping("/test")
private Object get2(){
List<Student> studentList = studentMapper.selectAll();
log.info(studentList);
return studentList;
}
}
执行结果:
参考资料:
https://github.com/kohlschutter/junixsocket
https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-unix-socket.html
https://mp.weixin.qq.com/s/h3jOYAD05QfHSRXLnQtPYw