// 각각의 커넥션에 대해 do_something 하기
while(true){// accept
structsockaddr_inclient_addr={};socklen_tsocklen=sizeof(client_addr);intconnfd=accept(fd,(structsockaddr*)&client_addr,&socklen);if(connfd<0){continue;// error
}do_something(connfd);close(connfd);}
위 do_something 은 아래와 같이 read/write를 한다.
1
2
3
4
5
6
7
8
9
10
11
12
staticvoiddo_something(intconnfd){charrbuf[64]={};ssize_tn=read(connfd,rbuf,sizeof(rbuf)-1);if(n<0){msg("read() error");return;}printf("client says: %s\n",rbuf);// 클라이언트로부터 온 메세지 출력
charwbuf[]="world";write(connfd,wbuf,strlen(wbuf));// 응답으로 world를 보낸다
}
while(true){// accept
structsockaddr_inclient_addr={};socklen_tsocklen=sizeof(client_addr);intconnfd=accept(fd,(structsockaddr*)&client_addr,&socklen);if(connfd<0){continue;// error
}// only serves one client connection at once
while(true){int32_terr=one_request(connfd);if(err){break;}}close(connfd);}
// 실제 request처리할 `one_request` 함수에서 사용할 헬퍼 함수 2개를 먼저 만들어 본다.
staticint32_tread_full(intfd,char*buf,size_tn){// n byte가 될때까지 커널에서 읽기
// 데이터
while(n>0){ssize_trv=read(fd,buf,n);if(rv<=0){return-1;// error, or unexpected EOF
}assert((size_t)rv<=n);n-=(size_t)rv;buf+=rv;}return0;}staticint32_twrite_all(intfd,constchar*buf,size_tn){// 버퍼가 가득차면 부분적으로만 데이터를 성공적으로 쓸 수 있기 때문에
// 우리가 필요한 것보다 적은 bytes를 반환하도록 해준다.
while(n>0){ssize_trv=write(fd,buf,n);if(rv<=0){return-1;// error
}assert((size_t)rv<=n);n-=(size_t)rv;buf+=rv;}return0;}
위 write와 read 헬퍼 함수를 이용하여 다음과 같이 요청 처리 함수 one_request 를 작성할 수 있다.
constsize_tk_max_msg=4096;staticint32_tone_request(intconnfd){// 4 bytes header
charrbuf[4+k_max_msg+1];errno=0;int32_terr=read_full(connfd,rbuf,4);if(err){if(errno0){msg("EOF");}else{msg("read() error");}returnerr;}uint32_tlen=0;memcpy(&len,rbuf,4);// assume little endian
if(len>k_max_msg){msg("too long");return-1;}// request body
err=read_full(connfd,&rbuf[4],len);if(err){msg("read() error");returnerr;}// do something
rbuf[4+len]='\0';printf("client says: %s\n",&rbuf[4]);// reply using the same protocol
constcharreply[]="world";charwbuf[4+sizeof(reply)];len=(uint32_t)strlen(reply);memcpy(wbuf,&len,4);memcpy(&wbuf[4],reply,len);returnwrite_all(connfd,wbuf,4+len);}
line 7과 line 25에서 read 가 두 번 되고 있다. “buffered IO”를 이용하면 syscall을 줄일 수 있다. 한 번의 버퍼에 최대한 많은 양을 읽고 여러 요청을 한번에 파싱하는 방법으로 더 효율적이다.
staticint32_tquery(intfd,constchar*text){uint32_tlen=(uint32_t)strlen(text);if(len>k_max_msg){return-1;}charwbuf[4+k_max_msg];memcpy(wbuf,&len,4);// assume little endian
memcpy(&wbuf[4],text,len);if(int32_terr=write_all(fd,wbuf,4+len)){returnerr;}// 4 bytes header
charrbuf[4+k_max_msg+1];errno=0;int32_terr=read_full(fd,rbuf,4);if(err){if(errno0){msg("EOF");}else{msg("read() error");}returnerr;}memcpy(&len,rbuf,4);// assume little endian
if(len>k_max_msg){msg("too long");return-1;}// reply body
err=read_full(fd,&rbuf[4],len);if(err){msg("read() error");returnerr;}// do something
rbuf[4+len]='\0';printf("server says: %s\n",&rbuf[4]);return0;}
이 장에서는 아주 간단한 len 에 대한 프로토콜만 실습하여보았지만 실제 프로토콜은 훨씬 더 복잡하다. 또 바이너리 대신 텍스트를 사용하는데, 이는 사람이 읽기 쉬운 장점이 있지만, 파씽 부분에서 바이너리보다 더 신경써야 하는 부분이 많다. 또 프로토콜에 따라 구분 부호가 달라지거나 없을 수 있기 때문에 프로토콜 분석은 실습보다 더 어려울 수 있다.