반응형
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- TiDB
- SQLite
- 컴퓨터 강좌
- 함수포인터
- Preprocessor
- 한빛미디어
- newSQL
- 구조와 원리
- getopts
- Symbol
- kernel
- 긴옵션
- Windows via c/c++
- 포인터변수
- FreeBSD
- UNIX
- Golang
- 전처리기
- OS 커널
- 포인터
- DBMS
- TiKV
- Programming
- 약어
- UNIX Internals
- Pointer
- DBMS 개발
- 커널
- go
- bash
Archives
- Today
- Total
sonumb
Golang 서버와 C 클라이언트 간 통신 본문
1. 개요
C로 개발된 애플리케이션과 go로 작성된 애플리케이션이 TCP 통신해야 할 필요가 있다.
대개 C로 작성된 기존 서버에 새롭게 작성된 go 애플리케이션이 접속하는 형태의 프로젝트가 많을 것이다.
허나 아래에 제안될 코드는 C 클라이언트가 Go로 작성된 서버로 접속하는 형태다.
C 클라이언트는 연결정보 (pid, 실행인자 등등)을 구성하여 접속한 go 서버에 전송, go 서버는 수신된 연결정보를 출력하는 코드다.
(바이트 오더링이 생략될 수 있도록 Handshake 프로토콜을 주고 받았다고 가정한다.)
2. 코드
Golang 서버
아래 코드는, 메시지를 읽어 헤더를 파싱하고 헤더 타입에 따라 로직을 실행한다.
현재 MSG_OPCODE_CONNECT
만 있으므로 이를 처리한다.ConnHandler1()
함수는 전송한 패킷 전체를 읽은 뒤, 헤더/바디를 분리하여 처리하고,ConnHandler2()
함수는 헤더 크기 만큼 소켓버퍼에서 읽고, 헤더에 기입된 바디크기만큼 다시 읽은 뒤, 내용을 처리하는 루틴이다.
(ioutil.ReadAll()
함수 내부적으로 버퍼를 할당하며, 성능 하락포인트라고 여겨진다.)
// TCP Server
package main
/*
#include <string.h>
#include <stdint.h>
#define MSG_OPCODE_CONNECT 1
typedef struct _MsgHeader {
uint8_t OpCode;
uint8_t reserved1[3];
uint32_t seqno;
int32_t body_len;
} MsgHeader;
typedef struct _MsgBodyConnect {
int32_t pid;
int8_t proc_type;
int8_t reserved1;
int8_t do_relaunch;
int32_t cmd_line_len;
char cmd_line[1];
} MsgBodyConnect;
char get_cmd_line( MsgBodyConnect * connect, int offset )
{
return connect->cmd_line[offset];
}
typedef union _MsgBody {
MsgBodyConnect connect;
// 다른 메시지들 선언
} MsgBody;
typedef struct _Msg {
MsgHeader hdr;
MsgBody body;
} Msg;
const int32_t MsgHeaderSize = sizeof( MsgHeader );
const int32_t MsgBodySize = sizeof( MsgBody );
const int32_t MsgSize = sizeof( Msg );
Msg msg;
*/
import "C"
import (
"io"
"log"
"net"
"fmt"
_ "unsafe"
"strings"
"runtime"
"io/ioutil"
"bufio"
"unsafe"
)
func ConnHandler1(conn net.Conn) {
recvBuf := make([]byte, 8096)
// 전체 읽기
for {
n, err := conn.Read(recvBuf)
if nil != err {
if io.EOF == err {
log.Println(err)
return
}
log.Println(err)
return
}
if n > 0 {
// 바이트 버퍼에서 구조체로 컨버팅
goUnsafePtr := C.CBytes(recvBuf)
var msg *C.struct__Msg = (*C.struct__Msg)(goUnsafePtr)
// 메시지 타입에 따라 처리
if msg.hdr.OpCode == C.MSG_OPCODE_CONNECT {
var body * C.struct__MsgBodyConnect = (* C.struct__MsgBodyConnect)(unsafe.Pointer(&msg.body))
// get cmd line
cmdLineLen := int(body.cmd_line_len)
cmdLine := make([]byte, body.cmd_line_len )
for i := 0 ; i < cmdLineLen ; i++ {
ch := C.get_cmd_line(body, C.int(i))
cmdLine[i] = byte(ch)
}
delim := "\n"
cmdLineStr := string(cmdLine)
argv := strings.Split(cmdLineStr, delim)
fmt.Println( ">> pid:", body.pid )
fmt.Println( ">> cmd line(len:",body.cmd_line_len, "):", argv )
fmt.Println( body )
// 수신한 argv 를 통해 재기동하는 루틴
//go func() {
// sttyArgs := syscall.ProcAttr{
// "",
// []string{},
// []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
// nil,
// }
// syscall.ForkExec(argv[0], argv, &sttyArgs )
//}()
}
}
}
}
func ConnHandler2(conn net.Conn) {
recvHeaderBuffer := make([]byte, C.MsgHeaderSize)
var sockReader *bufio.Reader = bufio.NewReader(conn)
// 헤더 읽기
for {
n, err := conn.Read(recvHeaderBuffer)
if nil != err {
if io.EOF == err {
log.Println(err)
return
}
log.Println(err)
return
}
if n > 0 {
// 바이트 버퍼에서 헤더로 컨버팅
goUnsafePtr := C.CBytes(recvHeaderBuffer) // = C.struct__Msg(recvBuf)
var msgHdr *C.struct__MsgHeader = (*C.struct__MsgHeader)(goUnsafePtr) // = C.struct__Msg(recvBuf)
// 헤더에 기입된 바디크기만큼 읽기
bodyReadBuf, err := ioutil.ReadAll(io.LimitReader(sockReader, int64(msgHdr.body_len)))
if err != nil {
log.Println(err)
return
}
// 메시지 타입에 따라 처리
if msgHdr.OpCode == C.MSG_OPCODE_CONNECT {
bodyUnsafePtr := C.CBytes(bodyReadBuf)
var body * C.struct__MsgBodyConnect = (*C.struct__MsgBodyConnect)(bodyUnsafePtr)
// get cmd line
cmdLineLen := int(body.cmd_line_len)
cmdLine := make([]byte, body.cmd_line_len )
for i := 0 ; i < cmdLineLen ; i++ {
ch := C.get_cmd_line(body, C.int(i))
cmdLine[i] = byte(ch)
}
delim := "\n"
cmdLineStr := string(cmdLine)
argv := strings.Split(cmdLineStr, delim)
fmt.Println( ">> pid:", body.pid )
fmt.Println( ">> cmd line(len:",body.cmd_line_len, "):", argv )
fmt.Println( body )
// 수신한 argv 를 통해 재기동하는 루틴
//go func() {
// sttyArgs := syscall.ProcAttr{
// "",
// []string{},
// []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
// nil,
// }
// syscall.ForkExec(argv[0], argv, &sttyArgs )
//}()
}
}
}
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
l, err := net.Listen("tcp", ":5032")
if nil != err {
log.Fatalf("fail to bind address to 5032; err: %v", err)
}
defer l.Close()
for {
conn, err := l.Accept()
if nil != err {
log.Printf("fail to accept; err: %v", err)
continue
}
go ConnHandler2(conn)
}
}
C로 작성된 클라이언트
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <string.h>
#include <stdint.h>
#include <string.h>
#include <stdint.h>
#define MSG_OPCODE_CONNECT 1
typedef struct _MsgHeader {
uint8_t OpCode;
uint8_t reserved1[3];
uint32_t seqno;
int32_t body_len;
} MsgHeader;
typedef struct _MsgBodyConnect {
int32_t pid;
int8_t proc_type;
int8_t reserved1;
int8_t do_relaunch;
uint8_t failover_retry_max_count;
int32_t cmd_line_len;
char cmd_line[1];
} MsgBodyConnect;
char get_cmd_line( MsgBodyConnect * connect, int offset )
{
return connect->cmd_line[offset];
}
typedef union _MsgBody {
MsgBodyConnect connect;
// 다른 바디 선언
} MsgBody;
const int32_t MsgBodySize = sizeof( MsgBody );
typedef struct _Msg {
MsgHeader hdr;
MsgBody body;
} Msg;
const int32_t MsgHeaderSize = sizeof( MsgHeader );
const int32_t MsgSize = sizeof( Msg );
char msg_buf[1024*16];
Msg * msg = (Msg *)msg_buf;
#define offsetof(s,m) (size_t)&(((s *)0)->m)
int main( int argc, char ** argv )
{
int ret;
struct sockaddr_in client_sockaddr;
int client_sockfd;
int writelen;
int readlen;
int i = 0;
char delim[2] = "\n";
char cmdline[1024] = {};
for ( i = 0 ; i < argc ; i++ )
{
strcat( cmdline, argv[i] );
strcat( cmdline, delim);
}
sleep(1);
/* create client socket */
client_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (client_sockfd == -1) {
perror("[C] socket");
return -1;
}
printf("[C] socket\n");
/* set client_sockaddr */
memset(&client_sockaddr, 0, sizeof(struct sockaddr_in));
client_sockaddr.sin_family = AF_INET;
client_sockaddr.sin_port = htons(5032);
inet_pton(AF_INET, "127.0.0.1", &client_sockaddr.sin_addr.s_addr);
/* connect */
printf("[C] connect\n");
ret = connect(client_sockfd, (struct sockaddr *)&client_sockaddr, sizeof(struct sockaddr_in));
if (ret == -1) {
perror("[C] connect");
return -1;
}
/* send */
printf("[C] send\n");
msg->hdr.OpCode = MSG_OPCODE_CONNECT;
msg->body.connect.pid = getpid();
msg->body.connect.proc_type = 2;
msg->body.connect.cmd_line_len = strlen(cmdline);
printf("cmdline(%d):%s\n", msg->body.connect.cmd_line_len, cmdline);
memcpy( msg->body.connect.cmd_line,
cmdline,
msg->body.connect.cmd_line_len );
msg->hdr.body_len =
msg->body.connect.cmd_line_len + offsetof(MsgBodyConnect, cmd_line);
writelen = send( client_sockfd,
msg,
MsgHeaderSize + msg->hdr.body_len,
0 );
// send() 호출 결과 생략
close(client_sockfd);
return 0;
}
결과
테스트 순서는 go 서버 실행 후 C 애플리케이션 실행이다.
>>> Golang 서버 실행 및 결과
$ go run server.go
2021/10/12 11:26:40 &{1 [0 0 0] 0 59}
>> pid: 3555
>> cmd line(len: 47 ): [./client --arg1 1234 --arg2 client description ]
&{3555 2 0 0 0 47 [46] [47 99 108]}
2021/10/12 11:26:40 EOF
>>> C 애플리케이션 실행 및 결과
$ gcc -g -lc client.c -o client
$ ./client --arg1 1234 --arg2 "client description"
[C] socket
[C] connect
[C] send
cmdline(47):./client
--arg1
1234
--arg2
client description
반응형