본문 바로가기
프로그래밍/Redis

Redis를 이용하여 구조체형식의 메시지 주고받기

by 남생 namsaeng 2022. 3. 19.
반응형

두 호스트(device or PC) 간에 Ethernet을 이용하여 데이터를 주고받는 경우가 있다. 때때로 받은 내용을 디스크에 저장하지 않고 곧바로 클라이언트 상에서 사용하는데, 데이터의 빠른 액세스를 위해 구조체 형식의 메시지를 사용한다.

 

Windows MFC 응용 프로그램에서 Redis Client를 사용하는 방법은 아래의 글을 참고한다.

https://namsaenga.tistory.com/25

 

Redis Client를 Windows MFC 상에서 이용하기

데이터 수집, 가공, 처리와 관련된 응용프로그램을 개발할 때 C++ 언어를 사용하는 윈도 응용프로그램 개발 프레임워크인 MFC(Microsoft Foundatin Class Library)를 이용할 경우가 있다. 여러 이유가 있겠

namsaenga.tistory.com

 

* 운영환경

  • Server OS: Red Hat Enterprise Linux release 8.2 (Ootpa)
  • Server Compiler: g++ (GCC) 8.5.2 20210514 (Red Hat 8.5.0-4)
  • Client OS: Windows 10
  • Client Compiler: ISO C++17 표준

 

1. Server 데이터 입력 코드

 

 

#include "header.h" // 여러 std 라이브러리 포함
#include <cpp_redis-master/includes/cpp_redis/cpp_redis>
#include <iostream>
using namespace std;
using cpp_redis::client;
char genBuff[110000];

struct ANIMAL
{
	char name[5];
	char gender;
	char color[5];
};

struct ANIMAL dog;
struct ANIMAL cat;

static int CreateAnimal() {
  
  dog.name[0] = 'd'; dog.name[1] = 'o'; dog.name[2] = 'g'; dog.name[3] = 'g'; dog.name[4] = 'y';
  dog.gender = 'M';
  dog.color[0] = 'b'; dog.color[1] = 'l'; dog.color[2] = 'a'; dog.color[3] = 'c'; dog.color[4] = 'k';
  cat.name[0] = 'k'; cat.name[1] = 'i'; cat.name[2] = 't'; cat.name[3] = 't'; cat.name[4] = 'y';
  cat.gender = 'F';
  cat.color[0] = 'w'; cat.color[1] = 'h'; cat.color[2] = 'i'; cat.color[3] = 't'; cat.color[4] = 'e';
  
  for (int i = 0; i < 9999; i++) {
    memmove(genBuff + (i * sizeof(dog)), (char*) &dog, sizeof(dog));
  }
  memmove(genBuff + ((9999) * sizeof(cat)), (char*) &cat, sizeof(cat));
  
  client client;
  client.connect("192.168.1.2", 16379, [](const std::string& host, std::size_t port, cpp_redis::client::connect_state status) {
    if (status == cpp_redis::client::connect_state::dropped) {
      std::cout << "client disconnected from " << host << ":" << port << std::endl;
    }
  });
  client.sync_commit();
  ///
  client.send({"flushdb"}, [](cpp_redis::reply& reply) {
    std::cout << "set redis memory " << reply << std::endl;
  });
  client.sync_commit();
  client.send({"config", "set", "maxmemory", "2gb"}, [](cpp_redis::reply& reply) {
    std::cout << "set redis memory " << reply << std::endl;
  });
  client.sync_commit();
  client.send({"config", "set", "maxmemory-policy", "allkeys-lru"}, [](cpp_redis::reply& reply) {
    std::cout << "set redis memory " << reply << std::endl;
  });
  client.sync_commit();
  while (1) {
    client.send({"xadd", "ANIMAL", "*", "welcome to zoo", (char*) &genBuff}, [](cpp_redis::reply& reply) {
      if (reply.is_string()) {
        cout << reply.as_string() << "   " << genBuff << endl;
      }
    });
    client.sync_commit();
  }
  cout << "end!" << endl;
  return 0;
}

int main() {
  return CreateAnimal();
}

 

2. Server redis-client 화면

 

$ redis-cli –h 192.168.1.2 –p 16379
192.168.1.2:16379> xread block 1000 streams ANIMAL $
1)  1)  “ANIMAL”
    2)  1) “welcome to zoo”
        2) “doggyMblackdoggyMblack ... doggyMblackkittyFwhite”

 

 

3. Client 데이터 쿼리 코드

 

struct ANIMAL {
	char name[5];
	char gender;
	char color[5];
};

void RedisTest::DoRedisTest() {

	
	cpp_redis::client client;
	client.connect("192.168.1.2", 16379);
	client.sync_commit();

	char animal_buff[110000];
	ANIMAL animal_array[10000];

	while(1) {
		client.send({ "xread", "block", "1000", "streams", "ANIMAL", "$" }, [ &animal_buff](cpp_redis::reply& reply) {

			if (reply.is_array()) {
				for (auto key : reply.as_array()) {
					if (key.is_array()) {
						for (auto key2 : key.as_array()) {
							if (key2.is_array()) {
								for (auto key3 : key2.as_array()) {
									if (key3.is_array()) {
										for (auto key4 : key3.as_array()) {
											if (key4.is_array()) {
												int cnt = 0;
												for (auto key5 : key4.as_array()) {
													if (key5.is_string()) {
														
														if (cnt == 1) {
																									memmove(animal_buff, key5.as_string().c_str(), sizeof(animal_buff));   // memcpy 해도 되긴 된다.
														}
														cnt++;
													}
												}
											}
											else {
												cout << key4.as_string() << " ";
											}
										}
									}
									else
									{
									}
								}
							}
							else
							{
								cout << key2.as_string() << " ";
							}
						}
					}
				}
			}
			});
		client.sync_commit();

		for (int i = 0; i < 10000; i++) {
			memmove((char *)&animal_array[i], animal_buff + (i * sizeof(ANIMAL)), sizeof(ANIMAL));
		}
		for (int i = 0; i < 5; i++) {
			cout << animal_array[0].name[i];
		}
		cout << "   ";
		for (int i = 0; i < 5; i++) {
			cout << animal_array[9999].name[i];
		}
		cout << endl;
		
	}
}

 

 

4. Client 실행결과 화면

 

ANIMAL 1647588148350-0 doggy   kitty
ANIMAL 1647588148477-0 doggy   kitty
ANIMAL 1647588148607-0 doggy   kitty
ANIMAL 1647588148737-0 doggy   kitty
...

 

 

* 다음은 Redis에 넣을 수 구조체 멤버 변수 목록이다.

 

구분 자료형
가능 bit field (e.g. unsigned int a : 16)
char, signed char, unsigned char
char array (no end with '\0')
float
불가능 string
int, unsigned int, signed int, long long int

 

* memmove vs memcpy

 

1) 위의 Client 쿼리 코드에서 아래와 같이 배열을 생성했을 것이다.

 

char animal_buff[110000];
ANIMAL animal_array[10000];

 

2) Client에서 쿼리를 날렸을 때, Server 측에서 ANIMAL을 10000개 분량을 다 안 던져주고 100개만 줬다고 가정

 

  • a) : 쿼리에서 memcpy로 받는 부분이 에러가 난다. 즉, animal_buff의 크기만큼 "key5.as_string(). c_str()" 이 내용을 리턴해야 된다.
  • b) : a)에서 쿼리에서 받는 부분을 memcpy를 안 쓰고 memmove를 사용하면 에러가 안 날 수 있는데, 이때 memcpy로 100개의 ANIMAL을 animal_array [i]에 복사(0~99 인덱스)하려고 하면 에러가 난다.
  • c) : memmove는 b)의 과정에서 에러가 나지 않는다.

 

 

  • 일반 정수형 및 문자열이 아닌 low-level의 데이터를 주고받는 것은 정말 많은 것을 신경 써야 된다는 것을 느꼈다.

 

RedisDB

반응형

댓글