리눅스의 close 함수호출과 윈도우의 closesocket 함수호출은 완전종료를 의미한다. 완전종료라는 것은 데이터를 전송하는 것은 물론이거니와 수신하는 것 조차 더 이상 불가능한 상황을 의미한다.
위와 같은 경우 호스트 A는 호스트 B가 전송하는 데이터를 수신하지 못하며 데이터 수신과 관련된 함수의 호출 자체가 불가능해진다. 이러한 문제의 해결을 막기 위해 스트림의 일부만 종료 하는 방법이 제공되고 있다.
소켓을 통해서 두 호스트가 연결되면 그 다음부터는 상호간에 데이터의 송수신이 가능한 상태가 된다. 이러한 상태를 가리켜 '스트림이 형성된 상태'라고 한다.
두 호스트간에 소켓이 연결되면, 각 호스트별로 입력 스트림과 출력 스트림이 형성된다. 소켓의 우아한 종료라는 것은 한번에 이 두 스트림을 모두 끊어버리는 것이 아니라, 이 중 하나의 스트림만 끊는 것이다.
#include <sys/socket.h>
int shutdown(int sock, int howto);
두 번째 매개변수에 전달될 수 있는 인자의 종류
SHUT_RD의 경우 데이터 수신 불가, SHUT_WR은 데이터 전송 불가, SHUT_RDWR은 모두불가.
'클라이언트가 서버에 접속하면 서버는 약속된 파일을 클라이언트에게 전송하고, 클라이언트는 파일을 잘 수신했다는 의미로 문자열 "Thank you'를 서버에 전송한다."
이를 구현하기 위해 클라이언트는 EOF의 수신을 함수의 반환 값을 통해서 확인이 가능하게 만들고, 이는 출력 스트림을 종료하여 상대 호스트로 EOF가 전송되게 한다. 이러한 경우 close함수호출을 하게되면 스트림이 종료되어 버리므로 half-close를 이용한다.
file_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char* message);
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);
}
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;
}
}
void error_handling(char * message)
{
fputs(message, stderr);
fputc("\n", stderr);
exit(1);
}
while문 부분에서 파일을 다 보내주고나서 shutdown을 통해 출력스트림을 제한한다. 그 후에 입력 스트림을 남겨두어 클라이언트로 부터 메세지를 받을 준비를 한다.
file_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char * message);
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));
while((read_cnt = read(sd, buf, BUF_SIZE)) != 0)
fwrite((void*)buf, 1, read_cnt, fp);
puts("Received file data");
write(sd, "Thank you", 10);
fclose(fp);
close(sd);
return 0;
}
void error_handling(char* message)
{
fputs(message, stderr);
fputc("\n", stderr);
exit(1);
}
파일을 다 받은 후에 서버로 인사 메세지를 전송하고 있고, 서버의 입력스트림이 닫히지 않았다면 이 메시지를 수신할 수 있다.
#include <winsock2.h>
int shutdown(SOCKET sock, int howto);
file_server_win.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <WinSock2.h>
#define BUF_SIZE 30
void ErrorHandling(char* message);
int main(int argc, char* argv[])
{
WSADATA wsaData;
SOCKET hServSock, hClntSock;
FILE* fp;
char buf[BUF_SIZE];
int readCnt;
SOCKADDR_IN servAdr, clntAdr;
int clntAdrSz;
if (argc != 2)
{
printf("Usage: %s <port> \n", argv[0]);
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
fp = fopen("file_server_win.c", "rb");
hServSock = socket(PF_INET, SOCK_STREAM, 0);
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = AF_INET;
servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
servAdr.sin_port = htons(atoi(argv[1]));
bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr));
listen(hServSock, 5);
clntAdrSz = sizeof(clntAdr);
hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, clntAdrSz);
while (1)
{
readCnt = fread((void*)buf, 1, BUF_SIZE, fp);
if (readCnt < BUF_SIZE)
{
send(hClntSock, (char*)&buf, readCnt, 0);
break;
}
send(hClntSock, (char*)&buf, BUF_SIZE, 0);
}
shutdown(hClntSock, SD_SEND);
recv(hClntSock, (char*)buf, BUF_SIZE, 0);
printf("Messsage from client: %s \n", buf);
fclose(fp);
closesocket(hClntSock); closesocket(hServSock);
WSACleanup();
return 0;
}
void ErrorHandling(char* message)
{
fputs(message, stderr);
fputc("\n", stderr);
exit(1);
}
file_client_win.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#define BUF_SIZE 30
void ErrorHandling(char* message);
int main(int argc, char* argv[])
{
WSADATA wsaData;
SOCKET hSocket;
FILE* fp;
char buf[BUF_SIZE];
int readCnt;
SOCKADDR_IN servAdr;
if (argc != 3)
{
printf("Usage: %s <IP> <port>", argv[0]);
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
fp = fopen("receive.dat", "wb");
hSocket = socket(PF_INET, SOCK_STREAM, 0);
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = AF_INET;
servAdr.sin_addr.s_addr = inet_addr(argv[1]);
servAdr.sin_port = htons(atoi(argv[2]));
connect(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr));
while ((readCnt = recv(hSocket, buf, BUF_SIZE, 0)) != 0)
fwrite((void*)buf, 1, readCnt, fp);
puts("Received file data");
send(hSocket, "Thank you", 10, 0);
fclose(fp);
closesocket(hSocket);
WSACleanup();
return 0;
}
void ErrorHandling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}