2014년 12월 10일 수요일

TCP/IP 소켓 프로그래밍 7강

지금까지는 소켓 연결을 그냥 끊어버렸다.
하지만 이번 강에서는 우아하게(?) 끊는 방법을 알아보고자 한다.
두 호스트 간에 소켓이 연결되면 입력 스트림과 출력 스트림이 형성된다.
우아한 종료는 이 둘 중 하나만 끊고, 일을 처리한 다음 나머지 한개도 끊는 것이다.
이걸 Half-close 라고 한다. 이때 사용하는 함수는 다음과 같다.

#include <sys/socket.h>
int shutdown(int sock, int howto);
-sock : 종료할 소켓의 파일 디스크립터 전달
-howto : 종료방법에 대한 정보 전달

이 중 howto는 3가지가 있다.
-SHUT_RD 입력 스트림 종료
-SHUT_WR 출력 스트림 종료
-SHUT_RDWR 입출력 스트림 종료

함수를 알아봤는데 이 함수를 언제 써야 할지를 잘 모르겠다.
이 함수가 필요한 이유를 보자.
만약 a와 b가 통신을 하는데.
a ->b에게 데이터 전송하고 일방적으로 종료를 해버리면
b->a 에게 메시지를 전달할 수 없다.
예를 들면 b가 전송 완료 되면 전송 완료 되었다는 메세지를 a에게 보내야 한다면
a는 그 메세지를 받을 수 없다.
그래서 전송 종료가 되어서 송신은 끊어도 수신하는 문은 열어 놓는 것이다.
그리고 모든 것이 완료가 되어야 다 끊는 방식이 필요한 것이다.

말로 표현하니 좀 이상한데 그림으로 표현하면 다음과 같다
(출처 : 윤성우 열혈 TCP/IP 소켓 프로그래밍)



서버 측 소스를 보자.(일부분만 볼꺼다.)

int main(int argc, char *argv[])
{
int serv_sd, clnt_sd;
FILE * fp;
char buf[BUF_SIZE];
int read_cnt;

struct sockaddr_in serv_adr, clnt_adr;
socklen_t clnt_adr_sz;

if(argc!=2) {
printf("Usage: %s <port>\n", argv[0]);
exit(1);
}
//서버의 소스 파일인 file_server.c를 클라이언트에게 전송하기 위해 파일을 연다
fp=fopen("file_server.c", "rb"); 
serv_sd=socket(PF_INET, SOCK_STREAM, 0);  

memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[1]));

bind(serv_sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
listen(serv_sd, 5);

clnt_adr_sz=sizeof(clnt_adr);  
        //클라이언트가 연결요청하면 수락하고 반복으로 데이터를 전송한다
clnt_sd=accept(serv_sd, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);

while(1)
{
read_cnt=fread((void*)buf, 1, BUF_SIZE, fp);
if(read_cnt<BUF_SIZE)
{
write(clnt_sd, buf, read_cnt);
break;
}
write(clnt_sd, buf, BUF_SIZE);
}
//출력 스트림만 종료한다.
shutdown(clnt_sd, SHUT_WR);
        //입력 스트림은 살아있다.
read(clnt_sd, buf, BUF_SIZE);
printf("Message from client: %s \n", buf);
//파일을 닫는다
fclose(fp);
        //클라이언트, 서버 둘다 종료한다.
close(clnt_sd); close(serv_sd);
return 0;
}

이번엔 클라이언트 측 소스를 보자(역시 main만 볼꺼다)
int main(int argc, char *argv[])
{
int sd;
FILE *fp;

char buf[BUF_SIZE];
int read_cnt;
struct sockaddr_in serv_adr;
if(argc!=3) {
printf("Usage: %s <IP> <port>\n", argv[0]);
exit(1);
}
//서버가 전송한 파일을 저장하기 위해 파일을 생성하고 열었다
fp=fopen("receive.dat", "wb");
sd=socket(PF_INET, SOCK_STREAM, 0);  

memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
serv_adr.sin_port=htons(atoi(argv[2]));
        //연결 요청
connect(sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
//서버가 EOF를 보낼 때까지 파일을 쓴다(저장)
while((read_cnt=read(sd, buf, BUF_SIZE ))!=0)
fwrite((void*)buf, 1, read_cnt, fp);

puts("Received file data");
//다 받았으면 서버에게 Thank you를 전송한다
        write(sd, "Thank you", 10);
//파일 닫기
        fclose(fp);
        //소켓 종료
close(sd);
return 0;
}

윈도우는 함수 자체는 동일하지만 매개변수가 약간 다르다.
#include <winsock2.h>
int shutdown(SOCKET sock, int howto);
-sock : 종료할 소켓의 핸들
-howto : 종료방법에 대한 정보 전달

howto에 관한 인자
-SD_RECEIVE 입력 스트림 종료
-SD_SEND 출력 스트림 종료
-SD_BOTH 입출력 스트림 종료

이번 강은 비교적 양이 적어서 금방 공부했다.
역시 소스코드를 받아서 직접 해보는 것이 좋다.

다음 강은 도메인 이름과 인터넷 주소에 대해 알아보겠다.

댓글 없음:

댓글 쓰기