[4주차] echo 명령어 업그레이드
과제
echo2 abc라고 보내면 응답이 abc가 아니라 echo2_abc의 형태로 응답이 오도록 합니다.
힌트
기존에 사용하던 client *의 내부구조를 살펴보아야 합니다.
다음 구조체와 함수들을 참고하세요.
- sds
- sdscatfmt
- sdsempty
- addReplyBultSds
실습
Redis의 Client 구조체
접속한 클라이언트의 정보를 가지고 있습니다.
- argc는 입력 받은 파라미터의 개수
- argv는 입력 받은 파라미터 각각을 저장한 배열
typedef struct client {
...
int argc; /* Num of arguments of current command. */
robj **argv; /* Arguments of current command. */
int argv_len; /* Size of argv array (may be more than argc) */
...
} client;
여기서 입력 받는 파라미터의 값을 변경해야 하므로 argv의 값을 찾아 변경해야 합니다.
argv는 robj 타입으로 선언되어 있습니다.
robj
redisObject와 이름만 다르고 내용은 같습니다.
argv의 값은 ptr의 참조값을 참조하고 있겠네요.
echo Command 디버깅
$ echo 123
c->argv[0] ->ptr 에 echo
c->argv[1]->ptr 에 123
networking.c
[리드미 참조]
networking.c는 클라이언트, 마스터 및 복제본의 I/O를 정의합니다.
redis 프로토콜에 따라 클라이언트 쿼리 버퍼를 파싱하기 위한 진입점이 processInputBuffer()입니다.
networking.c에서 processInputBuffer() 내부 로직에 따라 processMultibulkBuffer()를 호출합니다.
processMultibulkBuffer()가 사용자의 명령을 받아서 커맨드를 완성합니다.
createStringObject()함수를 호출합니다.
querybuffer내에서의 위치와 bulk의 길이를 전달하는 것 같습니다.
sds querybuf; /* Buffer we use to accumulate client queries. */
size_t qb_pos; /* The position we have read in querybuf. */
long bulklen; /* Length of bulk argument in multi bulk request. */
createRawStringObject() 함수를 호출합니다.
sdsnewlen(ptr, len)함수를 호출하여 objstring을 만들어줍니다.
sds(Simple Dynamic Strings) 구조체
Redis에서 사용하는 String을 처리하는 역할을 합니다.
[sds 사용 이유]
문자열을 단순히 char*만 사용해서 저장하면 문자열 길이를 확인하기 위해 항상 저장된 메모리 크기를 확인해야 합니다.
속도가 생명인 레디스에게 큰 단점이 될 수 있습니다.
이를 극복하기 위해 sds 구조체를 사용합니다.
sds의 타입 자체는 char* 형입니다.
sds의 사이즈를 가변적으로 가리키기 위해 사이즈별로 나누어져 있습니다.
저장된 문자열의 길이를 확인함에 있어 시간복잡도가 O(1)입니다.
sds의 최대 크기(redis 아이템 하나의 최대 크기)는 512mb입니다.
실제 sds가 할당되면 type 정보의 크기 때문에 앞에 hdrlen+1만큼의 메모리가 더 할당됩니다.
sdscatfmt
sds를 printf처럼 만들어내는 함수입니다.
빠른 구현을 위해서 특정 포맷만 지원합니다.
* %s - C String
* %S - SDS string
* %i - signed int
* %I - 64 bit signed integer (long long, int64_t)
* %u - unsigned int
* %U - 64 bit unsigned integer (unsigned long long, uint64_t)
* %% - Verbatim "%" character.
모든 포맷을 지원하고 싶다면 sdscatprintf를 사용해야 합니다.
sdscatfmt는 첫 파라미터로 받은 sds를 확장해서 원하는 값을 넣어줍니다.
즉, 이 값을 이용해서 원하는 포맷으로 값을 만들 수 있습니다.
sdsempty
비어 있는 sds를 만드는 함수입니다.
echoCommand 살펴보기
echoCommand는 addReplyBulk() 함수를 이용합니다.
addReply()를 호출합니다.
기본적인 addReply는 robj를 입력 받습니다.
addReplySds
sds를 파라미터로 받고, 사용하면 메모리를 해제합니다. 즉, 넘긴 파라미터는 사용할 수 없게 됩니다.
Redis 응답 동작
client 안에 reply라는 응답을 담는 구조체가 있고 add Reply 함수는 궁극적으로 응답 포맷에 맞춰서 항목들을 해당 버퍼에 넣어주는 역할입니다.
최종적으로 event에 맞춰서 writeToClient 함수에 의해 전달됩니다.
멘토님이 sdscatfmt(sdsempty(), "echoSummer_%s", c->argv[1]->ptr) 부분은 주셨습니다.
생각해보면 답을 다 주셨습니다.(함수구현부를 주셨으니까)
1. echo 명령어는 echo 뒤에 입력받은 문자를 그대로 반환
2. addReplyBulk()의 인자로 c->argv[1] 넘김
3. argv[1]은 echo 뒤에 입력으로 들어오는 문자열임
그렇다면 이 argv[1]을 변형시키면 됩니다.
argv[1]에서 참조하고 있는 값을 변형시켜주면 addReplyBulk()함수에서 argv[1]을 알아서 갖다 반환해서 출력할 것입니다.
처음 디버깅 결과에 c->argv[1]->ptr의 값이 123인 것으로 보아 ptr에 문자열이 있을거라고 짐작할 수 있습니다.
그러면 c->argv[1]->ptr에 내가 원하는 값을 넣으면 됩니다. addReplyBulk가 알아서 값을 가져올테니까요.
결과
고찰
사실,, 다른 스터디가,,, 마무리 단계였어서 그거 하다가 미뤄버렸습니다,,,
다음,, 과제는,, 미루지,, 않겠습니다,,
하면서 다 이해하고 싶었는데 c언어도 오랫만에 보고 레디스는 처음이라 용어도 생소해서 이해를 다 하지는 못했는데 말하다 보니 변명 같네요;
잘 모르겠으면 시간을 더 들였어야했는데 맨먼스 측정 실패...
자료구조 시간에 시간 계산하는거 정말 싫어했는데 지금보니 너무나도 중요한 내용이었고 그 외에도 공부해야할 것들이 너무 많이 보여서 행복합니다.
내일도 파이팅 ♪(^∇^*)
참고
http://www.redisgate.com/redis/configuration/internal_string.php
https://djlee118.tistory.com/109
https://charsyam.wordpress.com/2013/02/01/%EC%9E%85-%EA%B0%9C%EB%B0%9C-redis-internals-sds/