【随记】记一次项目中用到的技术和问题

1、概述

【需求背景】 服务端由C语言开发,功能为一个APIServer(gateway),根据客户端发来的请求报文头类型,来分发对应的具体服务,具体服务,不限具体的语言,可能是python,可能是java,也可能是C++。  本文针对java端和ApiServer对接,进行描述。

APIServer和其他服务,并不是跨机器,跨网络的,而是放在一个硬件设备里。

客户端和ApiServer 采用SSLSocket连接。

【解析】无论是微服务架构,还是如上述的C/S模型架构的内容,本质上都是客户进程和服务进程之间的 进程间通信 (IPC)。

习惯javaWeb微服务开发的人,面向的是应用层开发,用到的协议是Http。B/S架构并不关注 传输层或操作系统级的概念、逻辑,往往都被封装进一些中间件中了。(其实所谓的RPC 就是Remote  Process Community,远程进程通信,传统实现方式,是基于Socket,可以参考《UNIX网络编程》)

但是习惯C/S开发,特别是熟悉Linux环境开发,对进程通信的方式一定深有体会,管道、socket、消息队列、信号,共享内存。这些都是常用的手段。

对于跨主机的进程通信,最常用的就是socket,从传输层来解决进程通信问题。socket本质上是一个文件,对于程序员在使用它的时候,Socket暴露给我们的仅仅是一个文件描述符,一个文件句柄。

Linux上的7种文件

dclb-sp  

d:文件夹文件

c:字符设备 

b:块设备文件

l:连接文件

-:普通文件

s: socket文件

p:管道文件 

由于本项目要求,进程间通信是本地的,并且不允许暴露端口,于是协商之下,我的模块和ApiServer之间采用了域套接字。

整个网络走向是:

客户端 ----------SSLSocket -------》Linux ApiServer  ------UDSocket-----》服务端内部服务模块

绿色双向箭头: 表示模块之间有socket连接

蓝色箭头,程序进程和OS之间的系统调用。

其中,server 和APIServer 是两个独立的进程,运行在同一个物理环境中,二者之间并不采用Web的Http协议,或普通进程间通信(比如有名管道,消息队列)。而是采用域套接字。

2、什么是域套接字?

学名叫 Unix domain socket(UDS),用于实现同一主机进程间通信的一种机制。(inter-process communication 进程间通信)

 socket 原本是为网络通讯设计的,但后来在 网络socket 上发展出一种 IPC 机制,就是 UNIX domain socket。虽然网络 socket 也可用于同一台主机的进程间通讯(通过 loopback 地址 127.0.0.1),但是 UNIX domain socket 用于 IPC 更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC 机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。另外一个特点就是,它相对于网络Socket更安全,不需要暴露端口和ip地址。

3、java中该如何使用unix domain socket?

jdk1.8是不支持域套接字的,从16开始支持uds,本次项目中,我升级到了 jdk17.0.3.1

服务端伪代码基本如下:

private void SocketServer(ServerSocketChannel serverSocketChannel) {

        //1、启动之前,先删除sock文件,如果在bind前,文件存在,会报错
       FileUtil.deleteFile(SocketServerConfigs.SOCK_FILE_PARTH);
      
        
       
 
        ByteBuffer buf = ByteBuffer.allocate(1024);
        SocketChannel client = null;
        try {
           //2、初始化域套接字,绑定文件/tmp/my.sock
            Path paths = Paths.get(SocketServerConfigs.SOCK_FILE_PARTH;);
            UnixDomainSocketAddress ud = UnixDomainSocketAddress.of(paths);   
            serverSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
            serverSocketChannel.bind(ud);
            }catch(Exception e){
            //处理异常
            }

        while(true){

        //3、阻塞式等待客户端接入,当有客户端接入时,解除阻塞,继续运行后续代码
         client = serverSocketChannel.accept();


        //读操作
         int bytesRead = client.read(buf); 
         while (bytesRead > 0) { //处理消息
           buf.flip();
            
            //TODO:处理读到的内容        
                   
           bytesRead = client.read(buf);
         }
        buf.clear();

        //写操作 
        //ret_buf是一个回执的ByteBuff报文
           while (ret_buf.hasRemaining()) {
               client.write(ret_buf);
            }

        }
 }


 

客户端如下:

private void ClientServer(ServerSocketChannel serverSocketChannel) {


 
        ByteBuffer buf = ByteBuffer.allocate(1024);
        SocketChannel client = null;
        try {
           //1、初始化域套接字,绑定文件/tmp/my.sock
            Path paths = Paths.get(SocketServerConfigs.SOCK_FILE_PARTH;);
            UnixDomainSocketAddress ud = UnixDomainSocketAddress.of(paths);   
            client = SocketChannel.open(StandardProtocolFamily.UNIX);
            client.bind(ud);
            }catch(Exception e){
            //处理异常
            }

       
            //2、连接服务端
           boolean re = client.connect();
            if(re){
            //读操作
         int bytesRead = client.read(buf); 
             while (bytesRead > 0) { //处理消息
               buf.flip();
            
                //TODO:处理读到的内容        
                   
               bytesRead = client.read(buf);
             }
                buf.clear();


                
        //写操作 
        //ret_buf是一个回执的ByteBuff报文,此处省略组织报文的代码
           while (ret_buf.hasRemaining()) {
               client.write(ret_buf);
            }

          }else{

        //连接失败的操作        
    }

     }

服务端或客户端的执行流程都是两大步骤:

1、初始化套接字选项(绑定域套接字文件,选择协议)。

2、客户端 执行套接字connect,服务端执行套接字accept

【bug】遇到以下问题

1、由于jdk从1.8升级到17,maven和ideal项目原本的一些配置,会在每次项目启动的时候,发生一些冲突。

如果发现jdk依赖包没有变化,代码中提示jdk各种包缺失之类的,请检查以下配置:

配置完上述内容后,项目的External Libraries中的jdk也从1.8 变成了 17

2、每次编译过程,都爆出maven错误,请检查以下配置:

3、如果已经改了上述内容,编译后还是会报maven错误,这些配置又重置回1.8,检查如下配置:

要配置maven的setting.xml,让maven默认Profiles指向 jdk17

<profiles>
 
     <profile>
              <id>jdk-1.8</id>
              <activation>
                <activeByDefault>false</activeByDefault>
                <jdk>1.8</jdk>
              </activation>
              <properties>
                <maven.compiler.source>1.8</maven.compiler.source>
                <maven.compiler.target>1.8</maven.compiler.target>
                <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
              </properties>
         </profile>
		 
		  <profile>
              <id>jdk-17</id>
              <activation>
                <activeByDefault>true</activeByDefault>
                <jdk>17</jdk>
              </activation>
              <properties>
                <maven.compiler.source>17</maven.compiler.source>
                <maven.compiler.target>17</maven.compiler.target>
                <maven.compiler.compilerVersion>17</maven.compiler.compilerVersion>
              </properties>
         </profile>

之所以要配置这个,有个重要的原因,本项目中需要用到sun.font 这个包,但是JDK17中,没有这个包,需要添加一个编译参数:

-add-exports java.desktop/sun.font=ALL-UNNAMED

如果不进行maven修改,每次启动环境都回到1.8,会报错如下:

java 目标8 不允许选项, -add-exports

4、编译参数冲突问题:

为什么需要 -add-exports

JDK17中,没有sun.font包

-add-exports java.desktop/sun.font=ALL-UNNAMED

通过此编译参数进行导入

release 和 -add-exports 是不可以同时使用的。

参考官网:JDK17 Tool javac --release

Note: When using --release to specify a release that supports the Java Platform Module System, the --add-exports option cannot be used to enlarge the set of packages exported by the Java SE, JDK, and standard modules in the specified release.

使用Maven编译时,配置如下:

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <!--javac release和add-exports冲突-->
<!--                    <release>17</release>-->
                    <compilerArgs>
                        <arg>--add-exports</arg>
                        <arg>java.desktop/sun.font=ALL-UNNAMED</arg>
                    </compilerArgs>
                </configuration>

                <executions>
                    <execution>
                        <id>make-compiler</id> 
                        <phase>compile</phase> 
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

5、使用shade和assembly打包

当项目中用到spring时,最终jar包中存在  spring.schemas,spring.handlers,spring.toolings

这三个文件,这三个文件中包含的内容,是spring各个组件的xsd路径。jar包启动后,spring的各个组件会根据这三个文件的内容去找相应组件的命名空间,如果本地可以找到,则直接使用本地的,如果找不到,就要去网页上寻找。

如果使用assembly打包,会直接替换前面spring依赖写入的xsd路径,导致最终jar包内的xsd不完整,出现的状况就是,jar包启动,联网可以运行,一旦离线,项目不能运行,提示的错误如下:

org.xml.sax.SAXParseException: schema_reference.4: Failed to read schema document 'http://springframework.org/schema/beans/spring-beans-2.0.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not .

shade打包,可以实现增量式修改这三个文件,新的spring组件的xsd路径,会继续在这三个文件中追加,而不是覆盖。

<!-- 增量修改 spring.handler-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version> 1.7.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.handlers</resource>
                                </transformer>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.schemas</resource>
                                </transformer>
<transformer
                                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.tooling</resource>
                                </transformer>

                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.alex.demo.MySocketServer</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

同时别忘了配置主启动类。

4、NIO

//TODO

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值