프로그래밍/컴퓨터 네트워크
[리눅스 네트워크] - 2 : 채팅 서버 만들기 (feat. multi thread)
KimuGamJa
2024. 4. 5. 02:59
https://ballbigdiary.tistory.com/2
[리눅스 네트워크] - 1 : echo 서버 만들기
네트워크 프로그래밍이란? 네트워크 프로그래밍은 시스템 프로그래밍의 연장선이다! 컴퓨터의 경우, 랜선이라 부르는 이더넷 케이블을 통해 Data가 송/수신 되고 있다. 이때, 케이블은 H/W 장치이
ballbigdiary.tistory.com
↑
- 이전에는 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);
}