Database/OSSCA2024
[OSSCA2024] 과제 4 해설
[dev] hiro
2024. 5. 8. 18:47
제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
과제 4 설명
echo2 abc라고 보내면 abc가 응답이 아닌 echo2_abc가 응답으로 오도록
- bulk 단위
- Respv2 기준 하나의 명령을 나타내는 block
- ex) $4\r\npong\r\n
- echo abc 라고 명령을 입력했을 때
- c → argc = 2 =>명령어의 길이
- c → argv[0] = “echo” => 첫번째 명령어
- c → argb[1] = “abc” => 두번째 명령어
robj(server object)
- type: 4bit
- 2^4 ⇒ 16개의 타입 가능
- encoding: 4bit
- lru: 24bit
- lru, lfu 쓸때 모두 사용.
- type + encoding + lru 총 32bit/4byte
- refcount: 똑같은거 시키면 +1, 0이 되면 지워짐
- void * ptr ⇒ 8byte
- 총 16byte의 메모리 공간을 할당 받게됨.
argc, argv가 만들어지는 곳
- processMultibulk…에서 생성
- createStringObject() 또는 createObject()에서 만듦
SDS
이전 포스팅에서도 작성했지만 한번 더 작성.
- 서버 시스템에서는 메모리를 아끼기 위해.
- 동적으로 길이를 사용하기에 변수에 따라서 스트링의 길이를 조절하기 위해.
- redis에서 사용하고 있는 구조체
- sds의 사이즈를 가변적으로 가르키기위해.
c에서의 메모리의 값을 읽는 스킬
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct memory_header {
unsigned long size;
char ptr[];
}memheader;
char *STRING = "hello, OSSCA!";
void test(char *ptr) {
printf("%s\n", ptr);
memheader *mh = (memheader *)(ptr-sizeof(memheader));
printf("size: %d\n", mh->size);
}
int main(int argc, char *argv[]) {
printf("%d\n", sizeof(memheader));
memheader *mh = (memheader *)malloc(sizeof(memheader) + 16);
mh->size = strlen(STRING);
memcpy(mh->ptr, STRING, mh->size);
mh->ptr[mh->size] = 0;
test(mh->ptr);
return 0;
}
8
hello, OSSCA!
size: 13
- 뒷 부분만 전달했음에도 size를 읽어올 수 있음.
- 메모리를 다루는 언어인 C라서 가능한 스킬.
- sdsHdrSize()는 실제 메모리의 사이즈를 알려주는데, 이를 통해서 값을 가져올 수 있음.
과제 4 수행
실제 과제 풀이 해답은 여러개가 있을 수 있다.
- robj를 변경하는 방법
- addreplyBulkSds()를 사용하는 방법
- addReplySds()를 사용하는 방법
이 있는데 2, 3번에 대해서만 보이면
addReplySds()를 사용
void echo2Command(client *c) {
sds reply = sdscatfmt(sdsempty(), "echo2_%s", c->argv[1]->ptr);
addReplyLongLongWithPrefix(c, sdslen(reply), '$');
addReplySds(c, reply);
addReplyProto(c, "\r\n", 2);
}
- client 객체로부터 받은 명령어를 sdscatfmt()를 통해 새로운 문자열을 생성.
- sdsempty()
- sds 타입의 새로운 빈 문자열을 생성하는 함수.
- sdsnewlen()을 통해 sds를 새로 생성
- sdscatfmt()
- sds 타입의 변수에 서식 지정된 문자열을 추가하는 함수.
- 형식 지정자(format specifier)를 사용하여 문자열을 생성하고, 해당 문자열을 SDS에 추가합니다. 서식 지정자는 %s, %d, %f 등과 같이 다양한 형태로 사용될 수 있으며, 이를 통해 다양한 타입의 데이터를 문자열로 변환하여 SDS에 추가 가능.
- addReplyLongLongWithPrefix()
- Redis 클라이언트에게 정수 값을 포함하는 응답을 보내느 함수.
- 주어진 정수 값(sds의 길이)이 작을 경우 프로토콜에서 사용되는 header를 공유 객체로 사용.
- 이렇게 함으로써 메모리 절약.
- 공유된 헤더를 사용함으로써 응답을 생성.
- 생성된 응답을 클라이언트에게 전달하기 위해 addReplyProto() 함수를 호출
- addReplySds()
- Redis 클라이언트의 버퍼에 s를 추가하는 함수.
- PrepareClientToWrite()
- 클라이언트가 쓰기 작업을 준비할 수 있는 상태인지 확인.
- _addReplyToBufferOrList()
- flag와 type를 통해 client와의 상태를 확인.
- _addReplyToBuffer() 함수를 호출하여 클라이언트의 응답 버퍼(client->buf)에 응답을 추가.
- 이후 sds는 더이상 사용되지 않으므로 메모리를 해제하여 메모리 leak을 방지
- addReplyProto()
- Redis 클라이언트에게 프로토콜 형식의 응답을 보내는 역할. s와 len을 이용하여 클라이언트의 버퍼에 응답을 추가.
- 여기서는 명령어의 종료 역할.
- PrepareClientToWrite() 실행.
- _addReplyToBufferOrList() 실행.
- sdsempty()
다른 방법
/**
* sdsnewlen을 통해 생성하는 방법
*/
sds reply = sdsnewlen("echo2_", 6); // "echo2_" 문자열을 포함한 SDS 생성
reply = sdscatfmt(reply, "%s", c->argv[1]->ptr); // 뒤에 인자 추가
/**
* sdssetlen을 통해 생성하는 방법
*/
sds reply = sdsnewlen(NULL, 6); // 초기 크기가 6인 빈 SDS 생성
memcpy(reply, "echo2_", 6); // "echo2_" 문자열 복사
sdssetlen(reply, 6); // SDS 길이 설정
reply = sdscatfmt(reply, "%s", c->argv[1]->ptr); // 뒤에 인자 추가
이외의 다른 방법으로도 가능하다.
addReplyBulkSds()를 사용
void echo2Command(client *c) {
sds reply = sdscatfmt(sdsempty(), "echo2_%s", c->argv[1]->ptr);
addReplyBulkSds(c, reply);
}
- 그 이후 생성된 새로운 문자열을 클라이언트에게 반환하게 위해 addReplyBulkSds()를 사용.
addReplyBulkSds()
/* Add sds to reply (takes ownership of sds and frees it) */
void addReplyBulkSds(client *c, sds s) {
addReplyLongLongWithPrefix(c,sdslen(s),'$');
addReplySds(c,s);
addReplyProto(c,"\r\n",2);
}
- 실제 위의 구현한 방법과 동일한 flow로 진행된다.
127.0.0.1:6379>
127.0.0.1:6379> echo 123
"123"
127.0.0.1:6379> echo2 123
"echo2_123"
127.0.0.1:6379>
원하는 대로 결과가 잘 출력된 것을 볼 수 있다.