[C][소켓] 메인스레드가 recv 하는 동안 fd가 close되면 어떻게 될까?
2024. 9. 4. 23:49
결론 => recv 함수에서 에러 (0)를 반환하게 된다. recv 함수 반환값에 대한 처리에 따라 달려있다.
(즉, recv 함수에 대한 반환값이 0보다 작거나 같을 때에 대한 처리를 어떻게 하냐에 달려있다.)
배경 설명
두 프로세스가 있다.
하나의 프로세스는 데이터를 송신하는 프로세스이고,
또 다른 하나는 데이이터를 수신하는 프로세스이다.
여기서, 수신하는 프로세스의 파일 디스크립터(fd)를 닫아버리면 어떻게될까?
먼저 예제 코드를 보자.
클라이언트 쪽 예제코드는, 클라이언트가 1초마다 3번 서버 쪽에게 메시지를 전송하는 코드이고
서버 쪽 코드는, 클라이언트의 메시지를 계속 수신하돼, 5초 후에 다른 스레드에서 수신 FD를 close하는 예제이다.
■ 클라이언트 소스
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char buffer[BUFFER_SIZE] = {0};
char *hello = "Hello from client";
// 소켓 생성
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("\n Socket creation error \n");
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// IP 주소 변환
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
printf("\nInvalid address/ Address not supported \n");
return -1;
}
// 서버에 연결
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("\nConnection Failed \n");
return -1;
}
for (int i = 0; i < 3; i++) {
// 서버로 메시지 전송
send(sock, hello, strlen(hello), 0);
printf("Hello message sent\n");
sleep(1);
}
// 소켓 종료
close(sock);
return 0;
}
gcc -o client client.c
./client
■ 서버 소스
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int server_fd;
void* close_fd_after_5sec(void* arg) {
sleep(5);
close(server_fd);
}
int main() {
int new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
char *hello = "Hello from server";
pthread_t th;
// 소켓 생성
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 주소 구조체 초기화
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 소켓에 주소를 바인딩
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 연결 대기 (백로그 큐 크기: 3)
if (listen(server_fd, 3) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server is waiting for connections...\n");
// 클라이언트 연결 수락
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {
perror("accept failed");
close(server_fd);
exit(EXIT_FAILURE);
}
if (pthread_create(&th, NULL, close_fd_after_5sec, (void*)NULL)) {
printf("Error creating thread 1\n");
return 1;
}
while (1) {
// 클라이언트로부터 메시지 수신
int valread = read(new_socket, buffer, BUFFER_SIZE);
if (valread > 0) {
printf("Received from client: %s\n", buffer);
} else {
printf("Invalid valread: %d\n", valread);
break;
}
}
// 소켓 종료
close(new_socket);
close(server_fd);
return 0;
}
gcc -o server server.c -lpthread
./server
결과화면
1) 클라이언트 측
$ ./client
Hello message sent
Hello message sent
Hello message sent
2) 서버 측
$ ./server
Server is waiting for connections...
Received from client: Hello from client
Received from client: Hello from client
Received from client: Hello from client
Invalid valread: 0
고찰
여기서 알 수 있는 것은, 다른 스레드에서 File descriptor를 close해버리면, recv 함수가 불리울 때, 0을 반환한다는 것이다. 여기서 주의할 점이 몇가지 있다.
(1) recv 함수로 인해 block이 된 상황에서, fd를 닫아버릴 때, 0을 반환하는 것이다.
(2) 저 쪽 서버쪽 코드에는 recv함수가 0과 작거나 같을 때, while문을 break하게 되어있다. 이 작업을 하지 않으면 닫혀진 fd를 통해 계속 recv 시도를 하게 될 것이고, 이 recv 함수는 계속 0과 작거나 같은 값만 반환하게 될 것이다.
'C > C' 카테고리의 다른 글
[C/C++] __attribute__((packed))에 대한 이해 (1) | 2024.03.28 |
---|---|
[C] 파일입출력 예제 (0) | 2023.03.31 |
[C/C++] 실행시간 측정하기 time(), clock() (0) | 2023.03.30 |
[C] C언어 알아두기 (Main) (0) | 2022.11.16 |
아스키코드표 (ASCII Code table) (0) | 2022.07.18 |