미완 채팅 프로그램 손봐주세요.
아무래도 소스 코드를 직접 올리다 보니 보기 힘든 부분인 있네요 쩝
참고로 Linux gcc에서 했습니다.
좀 더 편하게 보시려면 소스를 복사 하셔서 실행 파일을 만드신 후 보시는것이..
아직 많이 부족한지라 올리는것도 미숙하네요 ㅜㅜ
// chatting_server
#include "string.h" // 문자열 처리를 위한 헤더파일
#include "stdlib.h" //
#include "stdio.h" // 입.출력을 위한 헤더파일
#include "unistd.h" //
#include "sys/types.h" //
#include "netinet/in.h" //
#include "sys/stat.h" //
#include "sys/socket.h" // socket 사용을 위한 헤더파일
#include "arpa/inet.h" // internet 사용을 위한 헤더파일
#include "pthread.h" // thread를 정의하는 헤더파일
#define BUFSIZE 100 // 메세지 길이.
void *client_connection(void *arg); //
void send_message(char *message, int len); // 입력 받은 메세지를 접속된 모든 클라이언트에게 보내는 함수.
int client_number = 0; // 접속되어 있는 클라이언트 수를 기억한다.
int client_socks[10]; // 클라이언트 접속자 수를 제한한다.(맞나? 10명 이상 접속이 되던데 ㅡㅡ;)
pthread_mutex_t mutex; // 접근을 제어 하고자 하는 전역 변수
int main(int argc, char **argv) // 문자열 갯수, 문자열 포인터 배열(공백 기준)
{
int server_sock, client_sock; // 서버 소켓, 클라이언트 소켓
int client_addr_size; // 클라이언트 주소 길이
struct sockaddr_in server_addr, client_addr; // 서버와 클라이언트의 구조체(통신방법, 주소, 포트번호)
pthread_t thread; // thread함수 사용.
if(argc != 2) // 인자가 2개 필요하다.
{
printf("사용방법 : %s \n", argv[0]); // 원하는 인자 갯수와 각 유형을 보여준다.
exit(1);
}
if(pthread_mutex_init(&mutex, NULL)) // mutex 객체를 초기화 한다.(기본성질(NULL) : fast)
{
perror("mutex error : ");
exit(1);
}
if((server_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) // 서버 소켓 생성
{
perror("socket error : ");
exit(1);
}
////////////////////////////////// 주소 구조체에 주소 지정 ////////////////////////////////////////
memset(&server_addr, 0, sizeof(server_addr)); // server_addr 구조체를 0으로 초기화 한다.
server_addr.sin_family = AF_INET; // 인터넷 사용 공간 확보.
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 서버 주소(내 자신의 주소를 불러온다.)
server_addr.sin_port = htons(atoi(argv[1])); // 사용 할 포트(두번째 인자로 들어온 값은 포트 번호이다.)
///////////////////////////////////////////////////////////////////////////////////////////////////
if(bind(server_sock, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) // 서버 소켓 주소 설정
{
perror("bind error : ");
exit(1);
}
if(listen(server_sock, 5) < 0) // 클라이언트 접속을 기다린다.(5의 의미는?)
{
perror("listen error : ");
exit(1);
}
/////////////////////////////////////// 서버 실행 /////////////////////////////////////////////////
while(1)
{
client_addr_size = sizeof(client_addr);
// 클라이언트 주소 길이. (어떻게 접속하기전에 클라이언트 길이를 구할 수 있는가?)
client_sock = accept(server_sock, (struct sockaddr *) &client_addr, &client_addr_size);
// 클라이언트가 접속하면 접속을 허용하고 클라이언트 소켓을 생성한다.
////////////////////////////////////// 동시 접근 방지 /////////////////////////////////////////////
// 접속자의 숫자 제한이 있기 때문에 동시에 클라이언트가 접속해도 순차적으로 접속을 허용하여 제한//
// 인원이 넘어가지 않도록 조절한다. ///////////////////////////////////////////////////////////////
pthread_mutex_lock(&mutex);
client_socks[++client_number] = client_sock; // 클라이언트가 접속하면 접속자 수를 증가 시킨다.
pthread_mutex_unlock(&mutex);
///////////////////////////////////////////////////////////////////////////////////////////////////
pthread_create(&thread, NULL, client_connection, (void *)client_sock);
// 새로운 클라이언트가 접속해서 소켓을 생성하면 새로운 스레드를 생성해서 처리한다.
printf("새로운 연결, 클라이언트 IP : %s \n", inet_ntoa(client_addr.sin_addr));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
return 0;
}
void *client_connection(void *arg)
{
int client_sock = (int)arg; //
int str_len = 0; // 클라이언트의 메세지 문자열 길이.
char message[BUFSIZE]; // 메세지 최대 길이
int i;
while((str_len = read(client_sock, message, sizeof(message))) != 0)
// 클라이언트로부터 메세지를 읽어 들인다. 메세지 길이가 0이면 읽어 들이는 것을 멈춘다.
{
send_message(message, str_len); // 받은 메세지를 접속된 모든 클라이언트에 보낸다.
}
//////////////////////////////////// 동시 접근 방지 ///////////////////////////////////////////////
// 클라이언트로부터 읽어들인 메세지가 0이면 접속 종료로 판단하고 처리한다. 이때 동시 접속 종료가 //
// 되더라도 순차적으로 종료 처리를 한다. //////////////////////////////////////////////////////////
pthread_mutex_lock(&mutex);
for(i = 0; i < client_number; ++i) // 접속자 수만큼 반복
{
if(client_sock == client_socks[i]) // 종료된 클라이언트를 찾는다.
{
--client_number; // 클라이언트 수를 감소 시킨다.
for( ; i < client_number; ++i) // 종료된 클라이언트 index위치부터 접속된 인원수 숫자까지 반복.
{
client_socks[i] = client_socks[i+1];
/* 종료된 클라이언트 뒤에 접속 되었던 클라이언트들의 자리를 앞으로 한 칸씩 이동 시킨다. (index자리 이동)
* 그러면 이후에 접속하는 클라이언트가 순차적으로 위치한다.
* 그렇지 않으면 클라이언트가 종료 할 때마다 빠져나간 위치을 따로 stack (또는 queue)에 저장해 두었다가
* 새로운 클라이언트가 접속하면 저장되어 있는 빈 위치에 가도록 해 줘야 한다.
*/
}
break;
}
}
pthread_mutex_unlock(&mutex);
///////////////////////////////////////////////////////////////////////////////////////////////////
close(client_sock); // 클라이언트 소켓을 닫는다.
return 0;
}
void send_message(char *message, int len) // 클라이언트에서 서버로 온 메세지를 다시 클라이언트에 보낸다.
{
int i;
///////////////////////////////////////// 동시 접근 방지 //////////////////////////////////////////
// 임의의 클라이언트에게서 받은 메세지를 접속된 모든 클라이언트에게 보낼때 또 다른 임의의 클라이 //
// 언트가 메세지를 보낸다면 그 메세지는 이전 메세지를 다 보낼 때까지 대기 한다. ///////////////////
pthread_mutex_lock(&mutex);
for(i = 0; i <= client_number; ++i) // 접속된 클라이언트 수 만큼 메세지를 보낸다.
{
write(client_socks[i], message, len); // 접속된 클라이언트 전원에게 받은 메세지를 보내 준다.
}
pthread_mutex_unlock(&mutex);
///////////////////////////////////////////////////////////////////////////////////////////////////
}
// ******************************************************************************************************* //
// chatting_client
#include "string.h" // 문자열 처리를 위한 헤더파일
#include "stdlib.h" //
#include "stdio.h" // 입.출력을 위한 헤더파일
#include "unistd.h" //
#include "sys/stat.h" //
#include "sys/types.h" //
#include "sys/socket.h" // socket 사용을 위한 헤더파일
#include "arpa/inet.h" // internet 사용을 위한 헤더파일
#include "pthread.h" // thread를 정의하는 헤더파일
#define BUFSIZE 100 // 메세지 길이
#define NAMESIZE 20 // 접속자(클라이언트) 별명 길이
void *send_message(void *arg); // 메세지를 입력 받아 서버로 보내는 함수
void *recv_message(void *arg); // 서버로부터 받은 메세지를 출력하는 함수
char name[NAMESIZE]; // 이름 저장 배열
char message[BUFSIZE]; // 메세지 저장 배열
int main(int argc, char **argv)
{
int client_sock; // 서버와 통신 할 클라이언트 소켓
struct sockaddr_in client_addr; // 클라이언트 주소
pthread_t send_thread, recv_thread; // 메세지를 보내고 받을 thread 함수
void *thread_result; // thread의 return값을 받는다.
if(argc != 4) // 인자가 4개 필요하다.
{
printf("사용방법 : %s \n", argv[0]); // 원하는 인자 갯수와 각 유형을 보여준다.
exit(1);
}
sprintf(name, "[%s]", argv[3]); // name배열에 main의 세번째 인자값(별명)을 저장한다.
if((client_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) // 클라이언트 소켓 생성
{
perror("socket error : ");
exit(1);
}
memset(&client_addr, 0, sizeof(client_addr)); // client_addr 구조체를 0으로 초기화 한다.
client_addr.sin_family = AF_INET; // 인터넷 사용공간 확보
client_addr.sin_addr.s_addr = inet_addr(argv[1]); // 접속할 서버 주소(IP Address)
client_addr.sin_port = htons(atoi(argv[2])); // 접속할 포트
if(connect(client_sock, (struct sockaddr *) &client_addr, sizeof(client_addr)) < 0)
// 지정한 주소(IP)로 접속
{
perror("connect error : ");
exit(1);
}
pthread_create(&send_thread, NULL, send_message, (void *)client_sock);
// 메세지를 입력받아 server에 전달 할 thread생성
pthread_create(&recv_thread, NULL, recv_message, (void *)client_sock);
// server로부터 메세지를 받아 출력 할 thread 생성
pthread_join(send_thread, &thread_result); // 사용할 필요가 없어진 메세지 보내기 thread종료
pthread_join(recv_thread, &thread_result); // 사용할 필요가 없어진 메세지 받기 thread종료
close(client_sock);
return 0;
}
void *send_message(void *arg)
{
int client_sock = (int)arg; //
char name_message[NAMESIZE + BUFSIZE]; // 별명과 메세지의 합 만큼 길이를 지정한다.
while(1)
{
fgets(message, BUFSIZE, stdin); // 클라이언트에서 메세지를 입력 받는다.
if(!strcmp(message, "q\n")) // 메세지에 q\n가 들어오면
{
close(client_sock); // 클라이언트를 종료 시킨다.
exit(0);
}
sprintf(name_message, "%s %s", name, message);
// name_message 배열에 name과 message 저장
write(client_sock, name_message, strlen(name_message)); // name_message을 server에 보낸다.
}
}
void *recv_message(void *arg)
{
int client_sock = (int)arg; //
char name_message[NAMESIZE + BUFSIZE]; // 별명과 메세지의 합 만큼 길이를 지정한다.
int str_len; //
while(1)
{
if((str_len = read(client_sock, name_message, NAMESIZE + BUFSIZE - 1)) < 0)
// server로 부터 메세지를 읽어 온다.
{
perror("read error : ");
exit(1);
}
name_message[str_len] = 0; //
fputs(name_message, stdout); // server에서 받은 메세지를 출력한다.
}
}
// ******************************************************************************************************* //
위와 같이 thread를 이용하여 server - clinet chatting 프로그램을 작성 했습니다.
정상적으로 실행은 되는데 정확히 이해를 못하는 부분도 있어서 혼자 주석을 달아봤는데 역시나 다 달지 못했네요 ㅜㅜ
// 이 빈칸인 부분 주석 좀 달아주세요.(설명 좀 해주세요.)
그리고 혹시나 주석이 잘 못 달린 부분이 있으면 수정 바랍니다.
Q1)두개의 client 실행시
client1화면..................................client2화면
hi(enter)...................................[client1]hi
[client1]hi.................................hello(enter)
[client2]hello............................[client2]hello
이 때 client1에서 글을 입력 도중에 client2에서 메세지를 보내면
how about...................................my name tom(enter)
how about[client2]my name tom...............[client2]my name tom << 문제의 라인
you?(enter).................................[client1]how about you?
[client1]how about you?
이렇게 창에 메세지 입력중 다른 client의 메세지가 뜨게 됩니다.
이렇게 read되는 메세지 출력 부분과 관계없이 write할 메세지를 작성 할 수는 없나요?
how about...................................my name tom(enter)
[client2]my name tom.................[client2]my name tom
you?(enter).................................[client1]how about you?
[client1]how about you?
이 정도만 되도 될 것 같은데.. 아니면
[client2]my name is tom......................[client2]my name is tom
[client1]how about you?.....................[client1]how about you?
how about you?(enter).......................my name is tom(enter)
이렇게 완전 입력 부분과 출력부분인 분리 되게 할 수 있나요?
Q2)process를 이용한 채팅방 구현과 채팅중(채팅방 소속 상관없이) 1:1채팅(쪽지) 기능을 추가 해 보고 싶은데 조언 부탁드립니다.
댓글 달기