最近接到一任务,需要在Android上实现跨语言接口调用Java->Go->C,直接用C库岂不是最好的(就是要重构C库,老代码无人能维护了)?首席架构师提出了上述的技术方向,但是呢,gRPC不让用,因为需要服务什么的巴拉巴拉。。。好吧,领导让干咱就干,下面我们就用UNIX socket来一试究竟吧:
Go服务:
func main() {
os.Remove("/home/duan/unixsockdemo.sock")
unixAddr, err := net.ResolveUnixAddr("unix", "/home/duan/unixsockdemo.sock")
if err != nil {
fmt.Println("failed to open socket", err)
return
}
lis, err := net.ListenUnix("unix", unixAddr)
if err != nil {
fmt.Println("failed to listen unix socket", err)
return
}
for {
conn, err := lis.Accept()
if err != nil {
fmt.Println("failed to accept unix socket", err)
return
}
fmt.Println("unix socket connected", conn.LocalAddr().String())
closeCh := make(chan struct{})
go handler(context.TODO(), conn, closeCh)
go readLoop(conn, closeCh)
}
}
var readBufChan = make(chan []byte)
func handler(ctx context.Context, conn net.Conn, closeCh chan struct{}) {
for {
select {
case <-ctx.Done():
conn.Close()
return
case <-closeCh:
fmt.Println("remote closed", conn.LocalAddr().String())
conn.Close()
return
case readBuf := <-readBufChan:
sayHelloReq := &api.SayHelloRequest{}
err := proto.Unmarshal(readBuf, sayHelloReq)
if err != nil {
fmt.Println("failed to unmarshal read buffer to SayHelloRequest", err)
continue
}
fmt.Println("receive unix socket", conn.LocalAddr().String(), sayHelloReq.Name)
sendBuf, err := proto.Marshal(&api.SayHelloResponse{
Message: "Hello " + sayHelloReq.Name,
})
if err != nil {
fmt.Println("failed to marshal SayHelloResponse to buffer", err)
continue
}
_, err = conn.Write(sendBuf)
if err != nil {
fmt.Println("failed to send to socket,maybe closed", err)
return
}
}
}
}
func readLoop(conn net.Conn, closeCh chan struct{}) {
readBuf := make([]byte, 1024)
for {
readN, err := conn.Read(readBuf)
if err != nil {
fmt.Println("failed to read unix socket", err)
close(closeCh)
return
}
readBufChan <- readBuf[:readN]
}
对了,为了方便消息传递,用protobuf,proto如下
syntax = "proto3";
option go_package = "server/api";
option java_multiple_files = true;
option java_package = "org.example.unixsocket.protos";
option java_outer_classname = "ChatterProtos";
package example;
service Chatter {
rpc SayHello(SayHelloRequest) returns (SayHelloResponse) {}
}
message SayHelloRequest {
string Name = 1;
}
message SayHelloResponse {
string message = 1;
}
ok,接下来搞一搞Java调用吧,上代码
File sockFile=new File("/home/duan/unixsockdemo.sock");
UnixSocketAddress address=new UnixSocketAddress(sockFile);
try {
UnixSocketChannel channel=UnixSocketChannel.open(address);
UnixSocket unixSocket=new UnixSocket(channel);
OutputStream outputStream =unixSocket.getOutputStream();
InputStream inputStream=unixSocket.getInputStream();
long beginT=System.currentTimeMillis();
SayHelloRequest request=SayHelloRequest.newBuilder().setName("Java").build();
System.out.println(String.format("marshal spend %dms",System.currentTimeMillis()-beginT));
request.writeTo(outputStream);
System.out.println(String.format("send spend %dms",System.currentTimeMillis()-beginT));
byte[] bytesRead=new byte[1024];
int nRead=inputStream.read(bytesRead);
byte[] bytesResp=new byte[nRead];
System.arraycopy(bytesRead,0,bytesResp,0,nRead);
System.out.println(String.format("receive spend %dms",System.currentTimeMillis()-beginT));
SayHelloResponse response=SayHelloResponse.parseFrom(bytesResp);
System.out.println(String.format("unmarshal spend %dms",System.currentTimeMillis()-beginT));
long elapsedMillis=System.currentTimeMillis()-beginT;
System.out.println(String.format("%s total spend %dms",response.getMessage(),elapsedMillis));
} catch (IOException e) {
throw new RuntimeException(e);
}
为什么里面有耗时计算?因为我发现耗时有点高,所以就想看看哪一点高了,
marshal spend 20ms
send spend 49ms
receive spend 49ms
unmarshal spend 57ms
Hello Java total spend 57ms
我们用go调用试试耗时情况呢,
func main() {
conn, err := net.Dial("unix", "/home/duan/unixsockdemo.sock")
if err != nil {
fmt.Println("failed to connect to unix socket", err)
return
}
defer conn.Close()
beginT := time.Now()
sendBuf, err := proto.Marshal(&api.SayHelloRequest{Name: "Golang"})
if err != nil {
fmt.Println("failed to marshal SayHelloRequest to buffer", err)
return
}
_, err = conn.Write(sendBuf)
if err != nil {
fmt.Println("failed to send to unix socket", err)
return
}
recvBuf := make([]byte, 1024)
recvN, err := conn.Read(recvBuf)
if err != nil {
fmt.Println("failed to read unix socket", err)
return
}
sayHelloResp := &api.SayHelloResponse{}
err = proto.Unmarshal(recvBuf[:recvN], sayHelloResp)
if err != nil {
fmt.Println("failed to unmarshal buffer to SayHelloResponse", err)
return
}
microSecDuration := time.Since(beginT).Microseconds()
fmt.Println(sayHelloResp.Message, microSecDuration)
}
Hello Golang 127
0.1ms。。。我们换C试试?
int main()
{
int sock = 0;
int ret = 0;
struct sockaddr_un sock_addr;
unsigned char send_buf[1024];
unsigned char recv_buf[1024];
kms::SayHelloRequest request;
kms::SayHelloResponse response;
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1)
{
printf("failed to new unix socket,%d", errno);
return -1;
}
sock_addr.sun_family = AF_UNIX;
strcpy(sock_addr.sun_path, "/home/duan/unixsockdemo.sock");
ret = connect(sock, (const sockaddr *)&sock_addr, sizeof(struct sockaddr_un));
if (ret == -1)
{
printf("failed to connect to unix socket,%d", errno);
return -1;
}
auto start_time = system_clock::now();
request.set_name("CPP");
if (!request.SerializeToArray(send_buf, 1024))
{
printf("failed to serialize SayHelloRequest");
return -3;
}
ssize_t size_sended = write(sock, send_buf, request.ByteSizeLong());
if (size_sended == -1)
{
printf("failed to send to unix socket,%d", errno);
return -4;
}
ssize_t size_recved = recv(sock, recv_buf, 1024, 0);
if (size_recved == -1)
{
printf("failed to receive unix socket,%d", errno);
return -5;
}
if (!response.ParseFromArray(recv_buf, size_recved))
{
printf("failed to parse received SayHelloResponse");
return -6;
}
printf("receive %s\n", response.message().c_str());
duration<double> diff = system_clock::now() - start_time;
printf("total spend %fs\n", diff.count());
return 0;
}
total spend 0.000177s
和go差不多。。。嗯,就测试到这里吧,Java这个耗时情况,估计方案要玄乎了。