-
[리눅스 네트워크] - 2 : 채팅 서버 만들기 (feat. multi thread)프로그래밍/컴퓨터 네트워크 2024. 4. 5. 02:59
https://ballbigdiary.tistory.com/2
↑
- 이전에는 1:1 통신으로, 한 클라이언트에서 보낸 메시지를 다시 해당 클라이언트로 보내주는 echo 서버를 제작하였다.
- 이번 시간에는 multi thread를 이용해 1:N 통신을 구현하고,
- 한 클라이언트가 보낸 메시지를 연결된 N개의 모든 클라이언트에게 전송하는 채팅 서버를 만들어 볼 것이다.
💡주의할점!!
본 코드의 경우, thread를 생성하는 pthread_create()함수가 포함되어 있기 때문에,
gcc를 이용한 빌드시, -lpthread 옵션을 추가해 주어야 한다!!!!!server 코드 분석
//chat_server.c #include <stdio.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #include <pthread.h> #define MAX_CLIENT_CNT 500 char PORT[6]; //포트번호 저장. int server_sock; //서버소켓 int client_sock[MAX_CLIENT_CNT] = { 0 }; //서버와 연결될 client_socket의 각 id별 사용 여부 저장 struct sockaddr_in client_addr[MAX_CLIENT_CNT] = { 0 }; //client들의 socket 구조체 저장. pthread_t tid[MAX_CLIENT_CNT]; //각 client를 관리할 각각의 thread int exitFlag[MAX_CLIENT_CNT]; //각 client들의 thread가 종료되었는지 관리 pthread_mutex_t mlock; //context switching에 의한 오류를 막기 위한 mutex변수 void interrupt(int arg) { //서버 종료 Ctrl + C printf("\nYou typed Ctrl + C\n"); printf("Bye\n"); //서버 종료시, 연결된 client를 관리하는 thread 모두 종료시킴. for (int i = 0; i < MAX_CLIENT_CNT; i++) { if (client_sock[i] != 0) { pthread_cancel(tid[i]); pthread_join(tid[i], 0); close(client_sock[i]); //socket도 종료 } } //client정리 완료 후 서버 소켓도 종료 close(server_sock); exit(1); } void removeEnterChar(char* buf) { //개행문자를 널문자로 변환해줌 int len = strlen(buf); for (int i = len - 1; i >= 0; i--) { if (buf[i] == '\n') { buf[i] = '\0'; break; } } } int getClientID() { //새로운 client에게 사용중이지 않은 ID를 부여해줌. for (int i = 0; i < MAX_CLIENT_CNT; i++) { if (client_sock[i] == 0) //사용중이지 않다면, 해당 ID를 부여한다. return i; } return -1; //이미 다 사용중이다. == 할당 가능한 ID가 없다. } //서버의 각 thread가 해줄 동작 = client가 보낸 메시지를 연결된 모든 client에게 전달 void* client_handler(void* arg) { int id = *(int*)arg;//클라이언트의 아이디 char client_IP[100]; strcpy(client_IP, inet_ntoa(client_addr[id].sin_addr)); //네트워크 바이트 오더의 32bit 값을 XXX.XXX.XXX.XXX 형식으로 변경 printf("INFO :: Connect new Client (ID : %d, IP : %s)\n", id, client_IP); char buf[MAX_CLIENT_CNT] = { 0 }; while (1) { memset(buf, 0, MAX_CLIENT_CNT); int len = read(client_sock[id], buf, MAX_CLIENT_CNT); //각 client가 보낸 메시지를 읽어 buffer에 저장. if (len == 0) { //해당 클라이언트와 연결이 끊겼다. printf("INFO :: Disconnect with client.. BYE\n"); exitFlag[id] = 1; break; } if (!strcmp("exit", buf)) {//만약 클라이언트가 보낸 메시지가 exit라면, printf("INFO :: Client want close.. BYE\n"); exitFlag[id] = 1; //종료로 설정 break; } //연결이 유지되있고, exit도 아니라면 removeEnterChar(buf);//개행문자를 널 문자로 변환 pthread_mutex_lock(&mlock); //mutex lock걸기 //해당 클라이언트가 보낸 메시지를 연결된 모든 클라이언트에게 전송 for (int i = 0; i < MAX_CLIENT_CNT; i++) { if (client_sock[i] != 0) { //연결되어 있는 클라이언트에게만 전송 write(client_sock[i], buf, strlen(buf)); } } pthread_mutex_unlock(&mlock);//작업이 끝난 후, mutex 풀기 } //클라이언트가 종료되었다면, 해당 소켓을 닫음 close(client_sock[id]); } int main(int argc, char* argv[]) { if (argc < 2) { //Port번호를 받을 수 있게함. printf("ERROR Input Port Num\n"); exit(1); } //포트번호 저장 strcpy(PORT, argv[1]); //SIGINT신호를 받으면, interrupt함수를 실행 signal(SIGINT, interrupt); //mutex 초기화 pthread_mutex_init(&mlock, NULL); //서버 소켓 생성 server_sock = socket(AF_INET, SOCK_STREAM, 0); if (server_sock == -1) {//서버 소켓 생성 실패 printf("ERROR :: 1_Socket Create Error\n"); exit(1); } //서버 소켓 생성 성공 printf("Server On..\n"); //서버 소켓 구조체 설정 int optval = 1; setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, (void*)&optval, sizeof(optval)); struct sockaddr_in server_addr = { 0 }; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(atoi(PORT)); //bind()함수를 이용해 server 소켓에 주소 할당 if (bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { printf("ERROR :: 2_bind Error\n"); exit(1); } printf("Bind Success\n"); //클라이언트의 연결 요청을 수신할 수 있도록 소켓을 대기상태로 둠 if (listen(server_sock, 5) == -1) { printf("ERROR :: 3_listen Error"); exit(1); } printf("Wait Client...\n"); //서버와 연결될 클라이언트 소켓 설정 socklen_t client_addr_len = sizeof(struct sockaddr_in); int id_table[MAX_CLIENT_CNT]; while (1) { int id = getClientID();//사용중이지 않은 유효한 id를 받음. id_table[id] = id; if (id == -1) { //유효한 id가 없는 경우, 1초 기다리고 다시 id 받기를 시도함 printf("WARNING :: Client FULL\n"); sleep(1); continue; } memset(&client_addr[id], 0, sizeof(struct sockaddr_in)); //클라이언트의 연결 요청을 수락하고, 클라이언트와 통신하기 위한 새로운 클라이언트 소켓을 생성 client_sock[id] = accept(server_sock, (struct sockaddr*)&client_addr[id], &client_addr_len); if (client_sock[id] == -1) { printf("ERROR :: 4_accept Error\n"); break; } //해당 클라이언트에 대한 동작을 수행해줄 thread 생성 //해당 thread는 client_handler 함수를 수행함. pthread_create(&tid[id], NULL, client_handler, (void*)&id_table[id]); //&tid[id] : 생성된 스레드의 식별자를 저장할 변수의 주소 //NULL : 기본적인 스레드 속성을 사용 //client_handler : 새로운 스레드에서 실행될 함수의 포인터 //(void*)&id_table[id] : 함수에 전달할 매개변수를 전달. // 여기서는 해당 클라이언트의 id를 client_handler함수에게 전달함. //이때, void* 형태로 전달하며, 함수 내부에서는 해당 포인터를 다시 적절한 형으로 변환하여 사용 //종료된 client가 있다면, exitFlag를 초기화해주고, //pthread_join을 통해 해당 client를 관리하는 thread가 종료될때까지 기다리고, //종료되었다면 socket을 반환해줌 for (int i = 0; i < MAX_CLIENT_CNT; i++) { if (exitFlag[i] == 1) { exitFlag[i] = 0; pthread_join(tid[i], 0); client_sock[i] = 0; } } } //서버 동작끝. //mutext를 삭제하고, 서버 소켓 종료. pthread_mutex_destroy(&mlock); close(server_sock); return 0; }
이전 echo 서버와 달라진 점을 위주로 분석해보면 다음과 같다.
💡 서버가 하나인 것은 동일하기 때문에, 서버 소켓의 설정은 이전 echo 서버와 동일하다...!
수정된/추가된 변수 목록
- 다수의 client 연결을 위해 client 소켓 디스크립터(client_sock), 구조체 (client_addr)를 저장하는 변수를 모두 배열형태로 선언해 주었다.
- int client_sock[MAX_CLIENT_CNT] = { 0 };
- struct sockaddr_in client_addr[MAX_CLIENT_CNT] = { 0 };
- 각 client를 관리할 thread의 id를 저장해주기 위한 배열이 추가됨
- pthread_t tid[MAX_CLIENT_CNT];
- multi thread로 동작하므로, 한 thread가 종료되었음을 다른 thread에게 알려야 한다. 이를 위한 exitFlag를 각 client마다 가진다. 이때, 해당 변수는 각 client의 thread가 공유할 수 있도록 data영역에 저장하기 위해 전역 변수로 선언해주어야 한다!
- int exitFlag[MAX_CLIENT_CNT];
추가된 함수
- getClientID()
- 새로운 client와 연결할 때, 사용중이지 않은 client_sock을 얻기 위한 함수이다.
- client_sock 배열을 탐색하면서 값이 0인 = 사용중이지 않은 배열 인덱스를 반환한다.
- 만약, 모든 sock을 사용하고 있다면, -1을 반환하여 할당 가능한 sock이 없음을 알린다.
int getClientID() { //새로운 client에게 사용중이지 않은 ID를 부여해줌. for (int i = 0; i < MAX_CLIENT_CNT; i++) { if (client_sock[i] == 0) //사용중이지 않다면, 해당 ID를 부여한다. return i; } return -1; //이미 다 사용중이다. == 할당 가능한 ID가 없다. }
- client_handler()
- 각 client에 대해 서버의 각 thread가 해줄 동작을 정의한다.
- 이때, 함수는 클라이언트의 id를 전달받기 때문에 각 client 별로 동작을 정의해줄 수 있다.
- (client 관련 변수들이 배열이므로, 어떤 client에 대해 동작할지는 id를 인덱스로 하여 접근해준다!)
- 서버가 해주는 역할은 이전 echo서버와 유사하다.
- 다만, 클라이언트가 보낸 메시지를 수신하고 (read),
- 해당 메시지를 모든 클라이언트에게 송신하기 위해(write) for문을 이용해 연결된 (client_sock != 0) 모든 클라이언트에게 보내는 코드로 수정되었을 뿐이다!
- => 중간에 context switching이 일어나 buf가 수정되거나 client 목록이 바뀌거나 하는 등의 불상사가 발생하지 않도록, write() 코드는 mutext_lock & mutex_unlock으로 감싸주어야 한다!
void* client_handler(void* arg) { int id = *(int*)arg;//클라이언트의 아이디 char client_IP[100]; strcpy(client_IP, inet_ntoa(client_addr[id].sin_addr)); //네트워크 바이트 오더의 32bit 값을 XXX.XXX.XXX.XXX 형식으로 변경 printf("INFO :: Connect new Client (ID : %d, IP : %s)\n", id, client_IP); char buf[MAX_CLIENT_CNT] = { 0 }; while (1) { memset(buf, 0, MAX_CLIENT_CNT); int len = read(client_sock[id], buf, MAX_CLIENT_CNT); //해당 id의 client가 보낸 메시지를 읽어 buffer에 저장. if (len == 0) { //해당 클라이언트와 연결이 끊겼다. printf("INFO :: Disconnect with client.. BYE\n"); exitFlag[id] = 1; break; } if (!strcmp("exit", buf)) {//만약 클라이언트가 보낸 메시지가 exit라면, printf("INFO :: Client want close.. BYE\n"); exitFlag[id] = 1; //종료로 설정 break; } //연결이 유지되있고, exit도 아니라면 removeEnterChar(buf);//개행문자를 널 문자로 변환 pthread_mutex_lock(&mlock); //mutex lock걸기 //해당 클라이언트가 보낸 메시지를 연결된 모든 클라이언트에게 전송 for (int i = 0; i < MAX_CLIENT_CNT; i++) { if (client_sock[i] != 0) { //연결되어 있는 클라이언트에게만 전송 write(client_sock[i], buf, strlen(buf)); } } pthread_mutex_unlock(&mlock);//작업이 끝난 후, mutex 풀기 } //클라이언트가 종료되었다면, 해당 소켓을 닫음 close(client_sock[id]); }
thread 생성
- main함수에서 바로 client에 대한 동작을 수행해 주지 않고, 각 client를 담당하는 thread를 생성하여 처리해줌
- 해당 thread는 전달받은 client_handler 함수를 수행함.
pthread_create(&tid[id], NULL, client_handler, (void*)&id_table[id]);
- 기능 : thread 생성
- 입력값
- &tid[id] : 생성된 스레드의 식별자를 저장할 변수의 주소
- NULL : 기본적인 스레드 속성을 사용
- client_handler : 새로운 스레드에서 실행될 함수의 포인터
- (void*)&id_table[id] : 함수에 전달할 매개변수를 전달. (여기서는 해당 클라이언트의 id(=index)를 client_handler함수에게 전달함. 이때, void* 형태로 전달하며, 함수 내부에서 다시 적절한 형으로 변환하여 사용.할 수 있게 함.)
exitFlag 초기화
- 종료된 client에 대한 theread가 있다면, exitFlag를 0으로 초기화 시켜주고 interrupt를 통해 완전히 종료되기 기다렸다가 (pthread_join()) 종료 되었다면 해당 id를 유효한 상태로(0) 전환해줌.
for (int i = 0; i < MAX_CLIENT_CNT; i++) { if (exitFlag[i] == 1) { exitFlag[i] = 0; pthread_join(tid[i], 0); client_sock[i] = 0; } }
interrupt 수정
- 서버에서 interrupt가 발생했을 때 socket만 close() 시킬 것이 아니라, 연결된 client를 담당하는 모든 thread들도 cancle시킬 수 있도록 코드를 수정!
void interrupt(int arg) { //서버 종료 Ctrl + C printf("\nYou typed Ctrl + C\n"); printf("Bye\n"); //서버 종료시, 연결된 client를 관리하는 thread 모두 종료시킴. for (int i = 0; i < MAX_CLIENT_CNT; i++) { if (client_sock[i] != 0) { pthread_cancel(tid[i]); pthread_join(tid[i], 0); close(client_sock[i]); //socket도 종료 } } //client정리 완료 후 서버 소켓도 종료 close(server_sock); exit(1); }
Client 코드 분석
//chat_client.c #include <stdio.h> //표준 입력 및 출력 함수 (printf, fgets 등)를 사용하기 위한 헤더 #include <unistd.h> //Unix 표준 파일 함수 (close, read, write 등)를 사용하기 위한 헤더 #include <stdlib.h> //(exit, atoi 등)를 사용하기 위한 헤더 #include <signal.h> //시그널 처리 함수 (signal, kill 등)를 사용하기 위한 헤더 #include <sys/types.h> //시스템 자료형 및 상수를 사용하기 위해 #include <sys/socket.h> //소켓 프로그래밍을 위한 함수와 자료형을 사용하기 위해 #include <netinet/in.h> //인터넷 프로토콜을 위한 자료형 및 상수를 사용하기 위해 #include <arpa/inet.h> //인터넷 주소 변환 함수 (inet_addr, inet_ntoa 등)를 사용하기 위해 #include <string.h> //문자열 처리 함수 (memset, strcmp, strcpy 등)를 사용하기 위해 #include <pthread.h> //POSIX 스레드 라이브러리 함수를 사용하기 위해 포함 #define NAME_SIZE 20 // client 이름 최대 크기 #define MSG_SIZE 100 // 메시지 최대 크기 char name[NAME_SIZE]; // client 이름 저장 char msg[MSG_SIZE]; // 메시지 저장 pthread_t send_tid; // 메시지 송신용 pthread_t receive_tid; // 메시지 수신용 int exitFlag; //thread가 종료되었는지 판별하는 전역 변수 // data 영역에 저장되어 있기 때문에 모든 thread가 공유한다. //*data, file은 각 thread가 공유. //register, stack은 각 thread가 개별적으로 가짐. char SERVER_IP[20]; //연결할 Server의 IP저장 char SERVER_PORT[6]; //연결할 Server의 PORT저장 int client_sock; //client의 socket void interrupt(int arg) { //Ctrl + C를 통해 종료 printf("\nYou typped Ctrl + C\n"); printf("Bye\n"); //해당 client의 송/수신 thread 종료 pthread_cancel(send_tid); pthread_cancel(receive_tid); //각 thread가 종료되기를 기다림. //완전히 종료될 때까지 blcok되어 코드가 진행되지 않음. pthread_join(send_tid, 0); pthread_join(receive_tid, 0); //모든 thread 종료 완료 후 소켓을 닫음 close(client_sock); exit(1); } void* sendMsg() { //사용자로부터 메시지를 받고, server에게 송신하는 thread char buf[NAME_SIZE + MSG_SIZE + 1]; //클라이언트 정보 + 메시지를 저장할 buffer while (!exitFlag) { //종료된 thread가 있는지 확인 memset(buf, 0, NAME_SIZE + MSG_SIZE); //buffer초기화 fgets(msg, MSG_SIZE, stdin); //사용자가 입력을 하고 있는 중간에도, 메시지를 수신 받을 수 있도록 //scanf()함수 대신 fgets()함수를 사용함! //thrad종료 //1. 본인이 종료됨 if (!strcmp(msg, "exit\n")) { //만약 사용자가 입력한 문자열이 exit라면, exitFlag = 1; //thread 종료로 설정 write(client_sock, msg, strlen(msg)); //client 소켓에 작성하여 서버에도 exit사실을 알림 break; //while문 종료 } //2. 다른 thread가 종료됨 if (exitFlag) break; //종료된 thread가 있다면, while문 종료 //thread 종료 상태가 아니라면, 사용자가 입력한 값을 //sprinf() 표준 함수를 이용하여 name, msg을 buf에 저장 sprintf(buf, "%s %s", name, msg); //client 소켓에 buf 내용을 작성 write(client_sock, buf, strlen(buf)); } } void* receiveMsg() {//서버에게 메시지를 받고, 해당 메시지를 출력 char buf[NAME_SIZE + MSG_SIZE]; //클라이언트 정보 + 메시지를 저장할 buffer while (!exitFlag) {//종료된 thread가 있는지 확인 //종료된 thread가 없다면 memset(buf, 0, NAME_SIZE + MSG_SIZE); int len = read(client_sock, buf, NAME_SIZE + MSG_SIZE - 1); //client 소켓으로부터 메시지를 읽어 buf에 저장함 if (len == 0) {//read()함수의 반환값이 0이다 = 연결이 종료되었다. printf("INFO :: Server Disconnected\n"); kill(0, SIGINT); //SIGINT 시그널을 보내 client를 kill시킴 //0 : 현재 프로세스 그룹을 의미. 속한 모든 스레드 및 프로세스에게 전달 // SIGINT : 인터럽트 시그널을 의미. exitFlag = 1; //thread도 종료 상태로 전환. break; } //연결이 유효하다면, buf의 내용을 출력 printf("%s\n", buf); } } int main(int argc, char* argv[]) { if (argc < 4) { //서버 IP, Port번호 및 클라이언트 이름 3가지를 모두 받게 함. printf("ERROR Input [IP Addr] [Port Num] [User Name]\n"); exit(1); } strcpy(SERVER_IP, argv[1]); //서버 IP저장 strcpy(SERVER_PORT, argv[2]); //서버 PORT저장 sprintf(name, "[%s]", argv[3]); //서버 name저장. [name]형식으로! signal(SIGINT, interrupt); //SIGINT시그널을 받는다면, interrupt 함수가 동작하여 client 종료시킴 client_sock = socket(AF_INET, SOCK_STREAM, 0); //client 소켓 생성 if (client_sock == -1) { //socket생성 실패 printf("ERROR :: 1_Socket Create Error\n"); exit(1); } //printf("Socket Create!\n"); //연결할 server 구조체 정의 struct sockaddr_in server_addr = { 0 }; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); server_addr.sin_port = htons(atoi(SERVER_PORT)); socklen_t server_addr_len = sizeof(server_addr); //서버와의 연결 시도 //실패했다면 if (connect(client_sock, (struct sockaddr*)&server_addr, server_addr_len) == -1) { printf("ERROR :: 2_Connect Error\n"); exit(1); } //printf("Connect Success!\n"); //------서버와 연결 성공------ //멀티 스레드 생성. 송/수신용 총 2개의 스레드 생성 pthread_create(&send_tid, NULL, sendMsg, NULL); //send용 스레드 pthread_create(&receive_tid, NULL, receiveMsg, NULL); //recieve용 스레드 //thread 종료를 기다림 //각 thread는 각각의 함수(sendMsg, recieveMsg)를 수행하고 있음. pthread_join(send_tid, 0); pthread_join(receive_tid, 0); close(client_sock); return 0; }
client 단에서도, 메시지 송 + 수신을 동시에 해야하므로, 각각을 담당하는 2개의 thread를 생성해주어야 한다!
추가된 변수
- 메시지 송신용 thread : pthread_t send_tid;
- 메시지 수신용 thread : pthread_t receive_tid;
- thread가 닫혔는지 나타내는 변수 : int exitFlag;
추가된 함수
- sendMsg() : 메시지 송신용 함수
- fgets()로 사용자입력을 받고, write()함수를 통해 메시지를 송신함.
- 이때, 메시지 입력 중에도 메시지를 수신 받을 수 있도록 scanf()함수 대신 fgets()함수를 이용한다.
- exitFlag == 1이라면, 다른 thread가 종료되었다는 뜻이므로 현재 thread도 더이상 진행하지 않는다.
void* sendMsg() { //사용자로부터 메시지를 받고, server에게 송신하는 thread char buf[NAME_SIZE + MSG_SIZE + 1]; //클라이언트 정보 + 메시지를 저장할 buffer while (!exitFlag) { //종료된 thread가 있는지 확인 memset(buf, 0, NAME_SIZE + MSG_SIZE); //buffer초기화 fgets(msg, MSG_SIZE, stdin); //사용자가 입력을 하고 있는 중간에도, 메시지를 수신 받을 수 있도록 //scanf()함수 대신 fgets()함수를 사용함! //thrad종료 //1. 본인이 종료됨 if (!strcmp(msg, "exit\n")) { //만약 사용자가 입력한 문자열이 exit라면, exitFlag = 1; //thread 종료로 설정 write(client_sock, msg, strlen(msg)); //client 소켓에 작성하여 서버에도 exit사실을 알림 break; //while문 종료 } //2. 다른 thread가 종료됨 if (exitFlag) break; //종료된 thread가 있다면, while문 종료 //thread 종료 상태가 아니라면, 사용자가 입력한 값을 //sprinf() 표준 함수를 이용하여 name, msg을 buf에 저장 sprintf(buf, "%s %s", name, msg); //client 소켓에 buf 내용을 작성 write(client_sock, buf, strlen(buf)); } }
- receiveMsg() : 메시지 수신용 함수
- read() 함수를 통해 소켓통신으로 메시지가 올 때까지 대기함.
- len == 0이다 = 연결이 끊어졌다 이므로 kill()함수를 이용해 SIGINT 시그널을 보냄
- 연결이 유효하다면, 수신한 메시지를 출력함 printf()
void* receiveMsg() {//서버에게 메시지를 받고, 해당 메시지를 출력 char buf[NAME_SIZE + MSG_SIZE]; //클라이언트 정보 + 메시지를 저장할 buffer while (!exitFlag) {//종료된 thread가 있는지 확인 //종료된 thread가 없다면 memset(buf, 0, NAME_SIZE + MSG_SIZE); int len = read(client_sock, buf, NAME_SIZE + MSG_SIZE - 1); //client 소켓으로부터 메시지를 읽어 buf에 저장함 if (len == 0) {//read()함수의 반환값이 0이다 = 연결이 종료되었다. printf("INFO :: Server Disconnected\n"); kill(0, SIGINT); //SIGINT 시그널을 보내 client를 kill시킴 //0 : 현재 프로세스 그룹을 의미. 속한 모든 스레드 및 프로세스에게 전달 // SIGINT : 인터럽트 시그널을 의미. exitFlag = 1; //thread도 종료 상태로 전환. break; } //연결이 유효하다면, buf의 내용을 출력 printf("%s\n", buf); } }
수정된 함수
- interrupt()
- 이전에는, 단순히 sock만 close() 시켰다면 multi thread를 이용한 경우, socket을 종료시키기 전 thread를 먼저 cancle 시켜준 뒤, cancle이 완료 된 후 socket을 close() 시킴!
void interrupt(int arg) { //Ctrl + C를 통해 종료 printf("\nYou typped Ctrl + C\n"); printf("Bye\n"); //해당 client의 송/수신 thread 종료 pthread_cancel(send_tid); pthread_cancel(receive_tid); //각 thread가 종료되기를 기다림. //완전히 종료될 때까지 blcok되어 코드가 진행되지 않음. pthread_join(send_tid, 0); pthread_join(receive_tid, 0); //모든 thread 종료 완료 후 소켓을 닫음 close(client_sock); exit(1); }
'프로그래밍 > 컴퓨터 네트워크' 카테고리의 다른 글
[Linux] Time #date, hwclock, gettimeofday, time, localtime, clock (0) 2024.04.05 [리눅스 네트워크] - 1 : echo 서버 만들기 (0) 2024.04.05