tcp, udp를 이용해 귓속말 기능을 추가한 멀티채팅
글쓴이: jhyeup / 작성시간: 월, 2015/09/21 - 12:11오후
tcp를 이용해 브로드캐스트를 하고 udp를 이용해서 귓속말 하는 채팅 프로그램을 구현하는 중에
막혀서 질문하려고 합니다.
서버에서 멀티스레드를 사용해서 한쪽 스레드에서는 tcp를 통한 브로드캐스팅만 진행하고
다른쪽 스레드에서는 udp를 통해서 귓속말을 전달해주려고 합니다.
문제가 되는 부분은 서버와 클라이언트의 while문입니다.
한개의 서버를 실행시키고 두개의 클라이언트를 실행시켰을때
한개의 클라이언트에서 귓속말을 받게될 클라이언트의 이름(whisper_name)과 클라이언트 본인의 이름(name)을 sendto 하게되면 서버는 recvfrom(whisper_name)이 두번일어나게 됩니다.
제 생각으로는 서버에서 클라이언트갯수만큼 스레드가 생성되어 두개의 스레드에서 각각 recvfrom(whisper_name)을 실행하는 것이 아닐까 생각됩니다.
두번째 문제가 되는 부분은
서버의 for문안에서 귓속말 대상의 이름을 찾은 후 그 대상에게 귓속말을 전달해주는 부분입니다.
서버에서는 sendto를 하는데 클라이언트에서는 recvfrom을 하지 못합니다.
네트워크 과목을 처음수강하는지라 tcp,udp에 관해서 지식이 전무합니다.
검색을 열심히 해봤지만 정확한 답변을 찾기가 어렵네요.
답변 부탁합니다.
코드 전문입니다.
서버 코드
/* * chat_server.c * Written by SW. YOON */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <pthread.h> #define BUFSIZE 100 #define NAMESIZE 20 #define SOCKSIZE 20 void * clnt_connection(void *arg); void * whisper_connection(); void send_message(char * message, int len); void error_handling(char *message); int clnt_number=0; int clnt_socks[10]; pthread_mutex_t mutx; int serv_sock_tcp; //tcp 소켓 int serv_sock_udp; //udp 소켓 char serv_sock_udp_char[SOCKSIZE]; int clnt_sock; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; int clnt_addr_size; pthread_t thread; //tcp 스레드 pthread_t thread2; //udp 스레드 struct client { int tcp_sock; int udp_sock; char name[NAMESIZE]; struct sockaddr_in addr; }; struct client clnt[10]; int main(int argc, char **argv) { if(argc!=2) { printf("Usage : %s <port>\n", argv[0]); exit(1); } if(pthread_mutex_init(&mutx, NULL)) error_handling("mutex init error"); serv_sock_tcp=socket(PF_INET, SOCK_STREAM, 0); serv_sock_udp=socket(PF_INET, SOCK_DGRAM, 0); if(serv_sock_tcp == -1) error_handling("TCP 소켓 생성 오류"); if(serv_sock_udp == -1) error_handling("UDP 소켓 생성 오류"); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); serv_addr.sin_port=htons(atoi(argv[1])); if(bind(serv_sock_tcp, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1) error_handling("bind() error"); if(bind(serv_sock_udp, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1) error_handling("bind() error"); printf("서버의 udp번호 :%d\n", serv_sock_udp); if(listen(serv_sock_tcp, 5)==-1) error_handling("listen() error"); while(1){ clnt_addr_size=sizeof(clnt_addr); clnt_sock=accept(serv_sock_tcp, (struct sockaddr*)&clnt_addr,&clnt_addr_size); //tcp 연결 read(clnt_sock, serv_sock_udp_char, sizeof(serv_sock_udp_char)); //udp 포트번호 전송받아서 구조체에 입력 clnt[clnt_number].udp_sock = atoi(serv_sock_udp_char); printf("%d udp 포트 번호 수신\n", clnt[clnt_number].udp_sock); read(clnt_sock, clnt[clnt_number].name, sizeof(clnt[clnt_number].name)); //이름 전송받아서 구조체에 입력 printf("%s 이름 전송받음\n"); clnt[clnt_number].tcp_sock = clnt_sock; //tcp 포트번호 구조체에 입력 printf("%d tcp 포트번호 저장\n", clnt[clnt_number].tcp_sock); clnt[clnt_number].addr = clnt_addr; pthread_mutex_lock(&mutx); clnt_socks[clnt_number++]=clnt_sock; pthread_mutex_unlock(&mutx); printf("새로운 연결, 클라이언트 IP : %s \n", inet_ntoa(clnt_addr.sin_addr)); pthread_create(&thread, NULL, clnt_connection, (void*)clnt_sock); pthread_create(&thread2, NULL, whisper_connection, NULL); } return 0; } void * clnt_connection(void *arg) { int clnt_sock= (int)arg; int str_len=0; char message[BUFSIZE]; int i; while( (str_len=read(clnt_sock, message, sizeof(message))) != 0) send_message(message, str_len); pthread_mutex_lock(&mutx); for(i=0; i<clnt_number; i++){ /* 클라이언트 연결 종료 시 */ if(clnt_sock == clnt_socks[i]){ for( ; i<clnt_number-1; i++) clnt_socks[i]=clnt_socks[i+1]; break; } } clnt_number--; pthread_mutex_unlock(&mutx); close(clnt_sock); return 0; } void * whisper_connection() { char recv_name[NAMESIZE]; char send_name[NAMESIZE]; char temp_name[NAMESIZE]; char message[BUFSIZE]; int i=0; while(1) { printf("while loop 시작\n"); int j; recv_name[0] = '\0'; send_name[0] = '\0'; recvfrom(serv_sock_udp, recv_name, NAMESIZE, 0, (struct sockaddr*)&clnt_addr, &clnt_addr_size); //귓속말 받을 상대방 이름 전송받음 printf("%s 받을 사람 이름 전송받음\n", recv_name); recvfrom(serv_sock_udp, send_name, NAMESIZE, 0, (struct sockaddr*)&clnt_addr, &clnt_addr_size); //귓속말 보낸 이름 전송받음 printf("%s 보낸 사람 이름 정송받음\n", send_name); recvfrom(serv_sock_udp, message, BUFSIZE, 0, (struct sockaddr*)&clnt_addr, &clnt_addr_size); //귓속말 내용 전송받음 printf("%s 귓속말 내용 전송받음\n", message); for(i=0; i<clnt_number; i++) { if(strcmp(recv_name, clnt[i].name)==0) { printf("귓속말 대상 이름 찾음\n"); sendto(serv_sock_udp, send_name, strlen(send_name), 0, (struct sockaddr*)&clnt[i].addr, sizeof(clnt[i].addr)); //귓속말 전송한 사람 이름 전송 sendto(serv_sock_udp, message, strlen(message), 0, (struct sockaddr*)&clnt[i].addr, sizeof(clnt[i].addr)); //귓속말 내용 전송 } } printf("귓속말 대상 이름 못찾음\n"); printf("\n"); } } void send_message(char * message, int len) { int i; pthread_mutex_lock(&mutx); for(i=0; i<clnt_number; i++) write(clnt_socks[i], message, len); pthread_mutex_unlock(&mutx); } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
클라이언트 코드
/* * chat_client.c * Written by SW. YOON */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <pthread.h> #define BUFSIZE 100 #define NAMESIZE 20 #define SOCKSIZE 20 void * send_message(void *arg); void * recv_message(void *arg); void * udp_send_message(void *arg); void * udp_recv_message(void *arg); void error_handling(char *message); char name[NAMESIZE]="[Default]"; char message[BUFSIZE]; int sock_tcp; //tcp소켓 int sock_udp; //udp소켓 char sock_udp_char[SOCKSIZE]; struct sockaddr_in serv_addr; int serv_addr_size; pthread_t snd_thread, rcv_thread; pthread_t udp_snd_thread, udp_rcv_thread; void * thread_result; struct sock { int tcp_num; int udp_num; }; struct sock sockets; int main(int argc, char **argv) { if(argc!=4){ printf("Usage : %s <IP> <port> <name>\n", argv[0]); exit(1); } sprintf(name, "[%s]", argv[3]); sock_tcp=socket(PF_INET, SOCK_STREAM, 0); if(sock_tcp==-1) error_handling("tcp_socket() error"); sock_udp=socket(PF_INET, SOCK_DGRAM, 0); if(sock_udp==-1) error_handling("udp_socket() error"); sockets.tcp_num = sock_tcp; sockets.udp_num = sock_udp; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=inet_addr(argv[1]); //serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); serv_addr.sin_port=htons(atoi(argv[2])); if(connect(sock_tcp, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) //tcp연결 error_handling("connect() error"); sprintf(sock_udp_char, "%d", sock_udp); write(sock_tcp, sock_udp_char, strlen(sock_udp_char)); //udp 포트번호 전송 printf("udp 포트번호 전송\n"); write(sock_tcp, argv[3], strlen(argv[3])); //사용자 이름전송 printf("사용자 이름 전송\n"); pthread_create(&snd_thread, NULL, send_message, (void*)&sockets); pthread_create(&rcv_thread, NULL, recv_message, (void*)&sockets); //pthread_create(&udp_snd_thread, NULL, udp_send_message, (void*)sock_udp); pthread_create(&udp_rcv_thread, NULL, udp_recv_message, (void*)sock_udp); pthread_join(snd_thread, &thread_result); pthread_join(rcv_thread, &thread_result); //pthread_join(udp_snd_thread, &thread_result); pthread_join(udp_rcv_thread, &thread_result); close(sock_tcp); close(sock_udp); return 0; } void * send_message(void *arg) /* 메시지 전송 쓰레드 실행 함수 */ { struct sock* sockets = (struct sock*)arg; int sock_tcp = sockets->tcp_num; int sock_udp = sockets->udp_num; char whisper_name[NAMESIZE]; char name_message[NAMESIZE+BUFSIZE]; while(1) { fgets(message, BUFSIZE, stdin); if(!strcmp(message,"q\n")) { /* 'q' 입력 시 종료 */ close(sock_tcp); exit(0); } else if(strcmp(message, "/w\n") == 0) // /w입력시 귓속말 모드 { printf("귓속말 할 상대를 입력하세요 : "); fgets(whisper_name, NAMESIZE, stdin); //귓속말 상대 이름 입력 //sendto(sock_udp, " ", strlen(" "), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); sendto(sock_udp, whisper_name, strlen(whisper_name)-1, 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); printf("귓속말 상대 이름 전송\n"); //귓속말 상대 이름 전송 sendto(sock_udp, name+1, strlen(name)-2, 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); printf("내 이름 전송\n"); //귓속말 보낸 이름 전송 printf("귓속말 내용 : "); fgets(message, BUFSIZE, stdin); // 귓속말 내용 입력받음 sendto(sock_udp, message, strlen(message)-1, 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); //귓속말 내용 전송 } else { sprintf(name_message,"%s %s", name, message); write(sock_tcp, name_message, strlen(name_message)); } } } void * recv_message(void *arg) /* 메시지 수신 쓰레드 실행 함수 */ { struct sock* sockets = (struct sock*)arg; int sock_tcp = sockets->tcp_num; int sock_udp = sockets->udp_num; char name_message[NAMESIZE+BUFSIZE]; int str_len; while(1){ str_len = read(sock_tcp, name_message, NAMESIZE+BUFSIZE-1); if(str_len==-1) return 1; name_message[str_len]=0; fputs(name_message, stdout); } } void * udp_recv_message(void *arg) //귓속말 수신 스레드 실행 함수 { int sock_udp = (int)arg; char name[NAMESIZE]; char message[BUFSIZE]; while(1) { printf("while loop 시작\n"); serv_addr_size = sizeof(serv_addr); recvfrom(sock_udp, name, NAMESIZE, 0, (struct sockaddr*)&serv_addr, &serv_addr_size); printf("귓속말 보낸 사람 : %s\n", name); recvfrom(sock_udp, message, BUFSIZE, 0, (struct sockaddr*)&serv_addr, &serv_addr_size); printf("귓속말 내용 : %s\n", message); } } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
Forums:
mongoose
오픈소스중에 mongoose 웹 서버가 있습니다.
그거도 mutex 사용합니다. ㅇ_ㅇ;;
----------------------------------------------------------------------------
젊음'은 모든것을 가능하게 만든다.
매일 1억명이 사용하는 프로그램을 함께 만들어보고 싶습니다.
정규 근로 시간을 지키는. 야근 없는 회사와 거래합니다.
각 분야별. 좋은 책'이나 사이트' 블로그' 링크 소개 받습니다. shintx@naver.com
recvfrom 함수 시용시 데이터를 수신했는지
recvfrom 함수 시용시 데이터를 수신했는지 확인하셔야 합니다.
그리고 UDP는 패킷이 유실될 수 있다는 가능성을 두고 설계하셔야 합니다.
댓글 달기