tcp, udp를 이용해 귓속말 기능을 추가한 멀티채팅

jhyeup의 이미지

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);
}

shint의 이미지

오픈소스중에 mongoose 웹 서버가 있습니다.

그거도 mutex 사용합니다. ㅇ_ㅇ;;

----------------------------------------------------------------------------
젊음'은 모든것을 가능하게 만든다.

매일 1억명이 사용하는 프로그램을 함께 만들어보고 싶습니다.
정규 근로 시간을 지키는. 야근 없는 회사와 거래합니다.

각 분야별. 좋은 책'이나 사이트' 블로그' 링크 소개 받습니다. shintx@naver.com

Anti-Lock의 이미지

recvfrom 함수 시용시 데이터를 수신했는지 확인하셔야 합니다.
그리고 UDP는 패킷이 유실될 수 있다는 가능성을 두고 설계하셔야 합니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.