Golang与C语言之间的二进制协议通信

二进制协议

Posted by hughnian on December 19, 2019

Golang与C语言之间的二进制协议通信

文本协议与二进制协议

在服务器程序的开发中,各个服务端与客户端都需要进行通信,才能传递数据信息,同时不同的服务端与客户端之间都有彼此各自的特点与独有信息,所以在通信时我们需要指定协议,以达到相应的服务端与客户端可以知道对方都发了什么消息。 一般来说,协议主要包括二进制协议和文本协议。

文本协议

顾名思义就是基于文本形式的协议,如最常用的json和xml形式的协议。特别是web端的http请求中json是最常用的。现在也出了很多类似json的通信文本如messagepack, like json, but fast & small。 优点:

  • 方便修改,需要增加条件或者减少,只要添加或减少key和value就可以了。
  • 方便跨平台,目前json,xml,messagepack都是可以支持多平台多种语言。 缺点:
  • 带宽消耗大,传输速度上慢于二进制通信速度。
  • 不方便加密,同时加密后通信速度上也会减慢。

二进制协议

也是顾名思义,二进制的协议内容都是基于二进制的,大部分的tcp直连都是采用二进制协议方式,协议的类型都是统一采用消息头(消息类型+消息长度)+消息体(消息具体内容)的方式,当然你也可以设计自己独具自己特点的消息。 优点:

  • 节约内存带宽,传输速度快,二进制协议的具体内容都是二进制的01串,体积上小于文本协议。
  • 方便加密,本身二进制协议就是01串,不是文本,只能计算机懂,如果不知道具体的协议格式,人是无法知道消息是啥意思。

大端序与小端序

简单点说,就是字节的存储顺序,如果数据都是单字节的,那怎么存储无所谓了,但是对于多字节数据,比如int,double等,就要考虑存储的顺序了。注意字节序是硬件层面的东西,对于软件来说通常是透明的。再说白一点,字节序通常只和你使用的处理器架构有关,而和编程语言无关,比如常见的Intel x86系列就是小端序。 一般服务端与客户端通过网络通信的话都是用大端序(网络字节序)通信,等本地接收到的消息需要转化成小端序(主机字节序)再进行消息解析,处理。

  • 大端序:数据的高位字节存放在地址的低端,低位字节存放在地址高端,也是网络字节序
  • 小端序:数据的高位字节存放在地址的高端,低位字节存放在地址低端,也是主机字节序

Golang与C与语言二进制协议通信

C语言中的二进制通信我们一般采用结构体定义协议。结构体中可以定义消息头和消息体。消息的长度和数据都可以对应结构体中的不同数据类型变量。 Golang中也是可以使用结构体的,不过Golang的数据类型要比C语言更丰富更方便使用。我们可以使用Golang中的encoding/binary包进行二进制协议的封装。

C语言服务端

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/unistd.h>
#include <ev.h>
#include <pthread.h>

#define PORT 9806
#define BACKLOG 5
#define HEADER_SIZE 12

typedef struct Server {
    int sfd;
    struct ev_loop *loop;
    struct ev_io watcher;
} Server;

typedef struct Conn {
    int cfd;
    struct ev_loop *loop;
    struct ev_io watcher;
} Conn;

/**
 * 二进制协议头
 *
 */
typedef struct Header {
    uint32_t clientType;
    uint32_t type;
    uint32_t len;
} Header;

void rpc_set_non_block(int fd) {
    int flags;
    flags = fcntl(fd, F_GETFL);
    flags |= O_NONBLOCK;
    fcntl(fd, F_SETFL, flags);
}

static void read_handler(struct ev_loop *el, struct ev_io *watcher, int revents);
static void write_handler(struct ev_loop *el, struct ev_io *watcher, int revents);
static void accept_handler(struct ev_loop *el, struct ev_io *watcher, int revents);

void *
io_run(void *arg) {
    Conn *c = arg;

    c->loop = ev_loop_new(0);
    c->watcher.data = c;

    ev_io_init(&c->watcher, read_handler, c->cfd, EV_READ);
    ev_io_start(c->loop, &c->watcher);
    ev_run(c->loop, 0);

    return NULL;
}

static void
accept_handler(struct ev_loop *el, struct ev_io *watcher, int revents) {
    Server *s = watcher->data;

    if(revents == EV_ERROR) {
        return;
    }

    Conn *c;
    c = (Conn *)malloc(sizeof(Conn));
    if(NULL == c) {
        return;
    }
    memset(c, 0, sizeof(Conn));

    int cfd;
    struct sockaddr_in clientaddr;
    int addrlen = sizeof(struct sockaddr_in);
    cfd = accept(s->sfd, (struct sockaddr *)&clientaddr, (socklen_t *)&addrlen);
    if(cfd == -1) {
        printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
        return;
    }
    rpc_set_non_block(cfd);

    c->cfd = cfd;
    c->loop = ev_loop_new(0);
    c->watcher.data = c;

    ev_io_init(&c->watcher, read_handler, c->cfd, EV_READ);
    ev_io_start(c->loop, &c->watcher);
    ev_run(c->loop, 0);

    //pthread_t pid;
    //pthread_create(&pid, NULL, io_run, (void *)c);
}

static void
read_handler(struct ev_loop *el, struct ev_io *watcher, int revents) {
    Conn *c = watcher->data;

    if(revents == EV_ERROR) {
        return;
    }

    //char *data;
    //data = (char *)malloc(sizeof(char));
    //char data[HEADER_SIZE];
    Header *h = (Header *)malloc(sizeof(Header));
    if(NULL == h) {
        return;
    }

    if(revents & EV_READ) {
        int rnum;
        //rnum = read(c->cfd, data, HEADER_SIZE);
        rnum = read(c->cfd, h, sizeof(Header));
        if(rnum == 0) {
            close(c->cfd);
            return;
        } else if(rnum == -1) {
            if(errno != EAGAIN || errno != EWOULDBLOCK) {
                printf("code: %d, error: %s\n", errno, strerror(errno));
                return;
            }
        }

        //Header *h = (Header *)data;
        printf("client type:%d\n", ntohl(h->clientType)); //这里大端序转小端序
        printf("type:%d\n", ntohl(h->type));
        printf("len:%d\n", ntohl(h->len));
        
        ev_io_stop(el, &c->watcher);
        //ev_io_init(&s->watcher, write_handler, s->sfd, EV_WRITE);
        //ev_io_start(el, &s->watcher);
    }
}

static void
write_handler(struct ev_loop *el, struct ev_io *watcher, int revents) {
    Conn *c = watcher->data;

    if(revents == EV_ERROR) {
        return;
    }

    if(revents & EV_WRITE) {
        int wnum;
        wnum = write(c->cfd, "xxx", HEADER_SIZE);
        return;

        ev_io_stop(el, &c->watcher);
        ev_io_init(&c->watcher, read_handler, c->cfd, EV_READ);
        ev_io_start(el, &c->watcher);
    }
}

int
main(int argc, char **argv) {
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(PORT);

    int sfd = socket(AF_INET, SOCK_STREAM, 0);

    int flag = 1;
    setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, &flag, sizeof(flag));
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));

    if(bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("bind");
        return 0;
    }

    if(listen(sfd, BACKLOG) == -1) {
        perror("accept");
        return 0;
    }

    rpc_set_non_block(sfd);

    Server *s = (Server *)malloc(sizeof(Server));
    if(NULL == s) {
        return 0;
    }
    s->sfd  = sfd;
    s->loop = ev_loop_new(0);
    s->watcher.data = s;
    ev_io_init(&s->watcher, accept_handler, sfd, EV_READ);
    ev_io_start(s->loop, &s->watcher);
    ev_run(s->loop, 0);
}

Golang客户端

package main

import (
	"net"
	"encoding/binary"
	"fmt"
)

func main() {
	conn, err := net.Dial("tcp", "192.168.1.176:9806")
	if err != nil {
		fmt.Errorf("connect error")
		return
	}

	var data []byte
	data = make([]byte, 12)

	binary.BigEndian.PutUint32(data[:4], 16666)   //这里小端序转大端序,用户golang的binary.BigEndian
	binary.BigEndian.PutUint32(data[4:8], 2)
	binary.BigEndian.PutUint32(data[8:12], 15879)
	//data := []byte(`hahaha`)
	wnum, err := conn.Write(data)
	if wnum == 0 || err != nil {
		fmt.Errorf("write error")
		return
	}

	fmt.Println("write ok")
}