- 일방적인 연결 종료의 문제점
- close/ closesocket: 데이터 송수신 불가능한 종료 상황
- 호스트1이 마지막 데이터를 전송하고 연결을 종료했을 시 호스트1은 호스트2가 전송하는 데이터를 수신하는 함수를 호출할 수 없음
→ 데이터의 송수신에 사용되는 stream의 일부만 종료하여 송신만 닫거나 수신만 닫음: Half-close

- stream은 한 방향으로만 데이터의 이동이 가능하므로 양방향 통신을 위해 두 개의 stream 필요
- 상호간의 데이터의 송수신이 가능한 상태: stream이 형성된 상태
- 두 호스트에 socket이 연결되면 호스트마다 입력 stream과 출력 stream이 하나씩 형성
(출력 stream은 입력 stream으로, 입력 stream은 출력 stream으로 연결)

- shutdown함수: 하나의 stream만 끊는 함수
int shutdown(
[in] SOCKET s, // 종료할 socket의 file descriptor
[in] int how // 종료 방법에 대한 정보 전달
);
- 매개변수 how의 종류
인자(Linux/ window) | 의미 |
SHUT_RD/SD_RECEIVE | 입력 스트림 종료 |
SHUT_WR/SD_SEND | 출력 스트림 종료 (출력 버퍼에 전송되지 못한 데이터가 존재하면 전송) |
SHUT_RDWR/SD_BOTH | 입출력 스트림 종료 |
- shutdown함수를 사용하는 이유
서버가 클라이언트에게 파일을 전송하고 클라이언트는 파일을 모두 받으면 thank you라는 메시지를 송신할 경우
- 클라이언트는 입력함수를 얼마나 많이 호출하여 파일 데이터를 모두 수신해야 할지 알 수 없고 계속 입력함수를 호출한다면 블로킹 상태(호출된 함수가 반환하지 않는 상태)에 빠짐
- 클라이언트는 파일의 끝을 의미하는 EOF를 수신해 확인이 가능하지만 서버에서 출력 stream을 종료해야지만 EOF 전송가능 → 서버가 입출력 stream을 모두 닫을 경우 클라이언트가 송신하는 thank you라는 메시지를 수신받지 못하므로 출력 stream만 닫는 shutdown함수 호출
- half-close 기반의 파일전송 프로그램

//server
// half close
#define BUF_SZ 30
int main(int argc, char* argv[])
{
SOCKET sock, cln_soc;
SOCKADDR_IN serv_adr, clnt_adr;
int clnt_sz, str_len;
WSADATA wsaData;
char message[BUF_SZ];
FILE* fd;
const char* name = "E:\\repos_n\\TCPex\\copy.txt";
const char* mode = "r";
if (argc != 2)
{
printf("usage: %s <port>\n", argv[0]);
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
err_handling("startup() error");
fd = fopen(name, mode);
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
err_handling("socket() error");
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(ADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));
if (bind(sock, &serv_adr, sizeof(serv_adr)) == SOCKET_ERROR)
err_handling("bind() error");
if(listen(sock, 5)==SOCKET_ERROR)
err_handling("listen() error");
clnt_sz = sizeof(clnt_adr);
cln_soc = accept(sock, &clnt_adr, &clnt_sz);
if (cln_soc == INVALID_SOCKET)
err_handling("accept() error");
while (feof(fd) == 0)
{
str_len = fread(message, 1, BUF_SZ, fd);
send(cln_soc, message, strlen(message), 0);
}
shutdown(cln_soc, SD_SEND); // wr half close -> send eof to client
recv(cln_soc, message, BUF_SZ, 0);
printf("msg: %s\n", message);
fclose(fd);
closesocket(sock);
WSACleanup();
return 0;
}
// client
// half close
#define BUF_SZ 30
int main(int argc, char* argv[])
{
FILE* fd;
SOCKET sock;
int str_len = 0;
SOCKADDR_IN serv_adr, clnt_adr;
WSADATA wsaData;
char msg[BUF_SZ] = { 0 };
const char* name = "data.txt";
const char* mode = "wt";
if (argc != 3)
{
printf("usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
err_handling("startup() error");
fd = fopen(name, mode);
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
err_handling("socket() eror");
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]));
if(connect(sock, &serv_adr, sizeof(serv_adr))==SOCKET_ERROR)
err_handling("connect() eror");
while ((str_len = recv(sock, msg, BUF_SZ - 1, 0)) != 0)
{
msg[str_len] = 0;
fwrite(msg, 1, str_len, fd);
}
puts("done");
send(sock, "thank you", 10, 0);
fclose(fd);
closesocket(sock);
WSACleanup();
return 0;
}
'프로그래밍의 기초 > TCP | IP' 카테고리의 다른 글
IP주소와 도메인 이름 사이의 변환 (0) | 2022.01.13 |
---|---|
DNS (Domain Name System) (0) | 2022.01.13 |
UDP의 데이터 송수신 특성과 UDP에서의 connect 함수 호출 (0) | 2022.01.12 |
UDP 기반 서버/ 클라이언트의 구현 (0) | 2022.01.12 |
UDP에 대한 이해 (0) | 2022.01.12 |