우분투 리눅스 채팅 프로그램
글쓴이: guk1234 / 작성시간: 금, 2019/05/31 - 1:46오전
안녕하세요 최근에 c언어로 소켓프로그래밍을 이용한 채팅 프로그램을 만들었습니다.
그런데 문제는 클라이언트쪽에 문제가 생겼습니다.
원래는 클라이언트에 [현재시간 닉네임:(메시지 입력)] 이런식으로 출력 되고 메시지를 입력 하는데 2개 이상의 클라이언트가 연결 해서 1번 클라에 채팅을 하려면 2번 클라에는 1번 클라의 메시지가 잘 출력 되다가 이제 2번 클라에서 메시지를 입력하면 1번 클라에 처음 출력물이[현재시간 1번클라닉네임 (입력받은 메시지) : 현재시간 2번클라닉네임 (입력받은 메시지) : ]이런 순으로 출력하는데.. 해결 방법이 안떠오릅니다..
제가 공부중이라 코드는 많이 난잡 할 수 있어요..
오류 화면도 올려봅니다.
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<arpa/inet.h> #include<sys/socket.h> #include<stdio_ext.h> #include<pthread.h> #include <time.h> #define MAX_LINE 500 // 전송할 수 있는 메세지의 최고 길이 #define NAME_LEN 30 // 설정할 수 있는 이름의 최고 길이 unsigned long int sock; char bufname[NAME_LEN]; // 이름을 저장하는 문자열 char bufmsg[MAX_LINE]; // 메세지를 저장하는 문자열 char bufall[NAME_LEN + MAX_LINE]; // 이름과 메세지를 동시에 저장할 문자열 -> 서버로 보낼 때 이 문자열 사용 char* exit_order = "/exit"; int printm(void); // 클라이언트에게 메뉴를 출력하는 함수 void input_name(char *name); // 이름을 설정하는 함수 void exitchat(char *str); // 클라이언트가 입력한 메세지에 /exit가 포함되었을 경우 인식하여 프로그램을 종료하는 함수 void* user_chat(void* a);//채팅 송신 쓰레드 void* read_chat(void* b);//채팅 수신 쓰레드 int main(int argc, char **argv) { int select_menu; struct sockaddr_in servaddr; // 서버 구조체 선언 pthread_t a_thread;//쓰레드 선언 pthread_t b_thread;//쓰레드 선언 sock = socket(PF_INET, SOCK_STREAM, 0); if(argc < 3) // 프로그램 잘못 실행 시 사용법 출력 { printf("Usage: %s ip_addres port\n", argv[0]); return -1; } if(sock < 0) // 소켓 생성, 오류 시 에러문 출력과 종료 { perror("socket failed !"); return -1; } memset(&servaddr, 0, sizeof(servaddr)); // servaddr 안의 데이터를 0으로 초기화 시킴 (FD_SET 함수도 사용 가능) servaddr.sin_family = AF_INET; inet_pton(AF_INET, argv[1], &servaddr.sin_addr); servaddr.sin_port = htons(atoi(argv[2])); // 포트번호, 아이피 주소, 프로토콜 체계 설정 if(connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) // 컨넥트 함수를 통해서 서버에 연결 요청을 보냄 { perror("Connection failed !"); return -1; } puts("Connection Success !"); input_name(bufname); // 최초 1회 사용자 이름 입력 do{ select_menu = printm(); // 메뉴 출력함 -> 함수 반환값 이용 switch(select_menu){ case 1: input_name(bufname); // 1번을 선택 시 이름 바꿈 break; case 2: printf("채팅방에 입장하셨습니다.\n"); // 2번을 선택 시 채팅방 바로 입장 break; } }while(select_menu == 1); pthread_create(&a_thread, NULL, user_chat, (void*)sock);//쓰레드 생성 pthread_create(&b_thread, NULL, read_chat, (void*)sock);//쓰레드 생성 pthread_join(a_thread, NULL);//쓰레드 종료대기 pthread_join(b_thread, NULL);//쓰레드 종료대기 return 0; } int printm(void){ // 메뉴를 출력하고 선택한 값을 반환한다 int selectnum; printf("* * *채팅 프로그램* * *\n"); printf("1. 닉네임 변경.\n"); printf("2. 채팅방 입장.\n"); while(1){ scanf("%d", &selectnum); if(selectnum > 0 && selectnum < 3){ return selectnum; break; } else{ printf("wrong number. input again.\n"); break; } } } void input_name(char *name){ // 이름을 바꾸는 함수 printf("닉네임을 입력해주세요.(최대 30자) : "); scanf("%s", name);//닉네임 설정 } void exitchat(char *str){ // 메세지에서 /exit 문자열을 찾아낼 시 프로그램을 종료하도록 하는 함수 if(strstr(str, exit_order) != NULL){ printf("프로그램을 종료합니다.\n"); close(sock);//소켓종료 exit(0);//프로그램종료 } } void* user_chat(void* a) { struct tm tm; time_t ct; while(1) { ct = time(NULL); //현재 시간을 받아옴 tm = *localtime(&ct); __fpurge(stdin); // 입력 전 버퍼를 비워준다 write(1, "\033[0G", 4); printf("%s : ", bufname); printf("[%02d:%02d:%02d] %s : ", tm.tm_hour, tm.tm_min, tm.tm_sec, bufname); fgets(bufmsg, sizeof(bufmsg), stdin); // bufmsg 배열에 키보드로 값을 입력받는다 __fpurge(stdin); // 입력 후 버퍼를 비워준다 fprintf(stderr, "\033[1A"); sprintf(bufall, "[%02d:%02d:%02d] %s : %s", tm.tm_hour, tm.tm_min, tm.tm_sec, bufname, bufmsg); // bufall 배열에 각각 이름과 입력받은 메세지를 저장한다 write(sock, bufall, strlen(bufall)); // write 함수를 이용하여 서버에 메세지를 전송한다 exitchat(bufall); // 보내는 메세지에 /exit가 포함된 경우 프로그램을 종료한다. } } void* read_chat(void* b) { char temp[MAX_LINE + NAME_LEN]; int str_len; while(1) { str_len = (read((unsigned long int)b, temp, sizeof(temp)));//서버 --> 클라이언트 메시지 수신 temp[str_len] = 0; if(strstr(temp, bufall) == NULL)//본인 메시지 본인 수신 차단 { printf("%s", temp);//받은 메시지 출력 strcpy(temp,"");//초기화 } } }
File attachments:
첨부 | 파일 크기 |
---|---|
![]() | 359.96 KB |
Forums:
네트워크 소켓을 공부한 사람들이 간단한 메시지 송수신
네트워크 소켓을 공부한 사람들이 간단한 메시지 송수신 같은 것을 성공적으로 구현하고 나면, 자연스레 이런 종류의 인스턴트 메신저 프로그램을 만들고 싶어하더군요.
그런 프로그램을 만들기 위해서 정말 번거로운 부분이 네트워크 프로그래밍이 아니라, 콘솔 입출력을 원하는 대로 제어하는 데 있다는 사실을 깨닫는 데 보통 시간이 그닥 오래 걸리지 않습니다.
현실적으로, 터미널이 표준 모드(canonical mode)로 동작하면서, 프로그램이 (scanf나 gets 따위로) 표준 입력을 받기 위해 대기하면서도 비동기적인 출력을 적절히 내보낼 수 있게 구현하는 게 가능한지 잘 모르겠습니다.
감히 아주 불가능하다고 단정짓지는 못하겠습니다만, 이걸 제대로 해 보려고 시도한 몇몇 사람들은 정말 엄청 삽질하던데요.
보통 그 노력을 하느니 별도의 터미널 제어 라이브러리를 활용하거나, 아예 GUI로 가는 게 오히려 더 간단할 것 같습니다. 채팅창과 채팅 메시지 입력 칸을 분리해서 관리하는 게 훨씬 편하니까요.
감사합니다.
나중에 GUI로 도전 해보겠습니다.
옛날 90년대에 터미널에서 채팅하는게 가능했습니다.
옛날 90년대에 터미널에서 채팅하는게 가능했습니다.
write 라는 명령어도 있고 ytalk 이라는 것도 있고,
사설 BBS에서 채팅하던게 생각나네요.
터미널에 입출력하다보면 먹통 현상이 발생할텐데 그걸 어떻게 제어해서 먹통현상이 발생되지 않게 하느냐가 관건일 듯 합니다.
요점은 터미널이 "canonical mode"인
요점은 터미널이 "canonical mode"인 것입니다.
본문의 프로그램과 같이 표준 입력이 블럭당할 수 있는 상황에서, 사용자 입력의 반향(echo)과 네트워크에서 날라온 메시지의 출력이 콘솔에서 서로 뒤섞이는 일을 어떻게 막을 수 있을까요? 그런 일이 일어난다고 기능상으로 문제가 있는 것은 아니지만, 별로 보기 좋은 UI는 아니니까요.
방법이 전혀 없다고까지는 말씀을 못 드리겠지만, 제가 모르는 어떤 깔끔한 방식이 있지 않는 한 그냥 noncanonical 모드로 후퇴하는 게 나을 것 같습니다. 그러면 어떻게든 프로그램이 복잡해지겠지요. canonical mode가 제공했던 라인 단위 입력 편집 기능을 직접 제공해야 할 테니까요.
어떤 출력 형식을 원하시는지 잘 모르겠네요. 좀 예를
어떤 출력 형식을 원하시는지 잘 모르겠네요. 좀 예를 들어주시면 좋을 것 같은데.
우선, 메시지 처리 구조체가 안보이네요. 간단한 1:1 메시지 처리가 아니라 1:N 메시지 처리를 하려면 우선 관련 구조체부터 고려하심이 좋을 듯 합니다.
가령 메시지는
sender id, sender name (optional), message tex (max 256), received time
으로 구성될 수 있겠죠.
이 메시지들을 링크 리스트로도 연결할 수 있고, 그냥 단순 버퍼를 할당해서 할 수도 있고요(이게 더 핸들링하기 어려울 듯). 일단 구조체로 보관할 수 있게 되면 화면에서 표시하는 것은 의외로 간단할 수도 있습니다.
그냥 bufall[]에 저장하면 혼란이....
감사합니다.
오류 출력 화면 이미지 첨부 하겠습니다.
이건 서버쪽 코드를 봐야 겠네요.
이건 서버쪽 코드를 봐야 겠네요.
가끔 서버가 이상한 형태로 채팅 메시지 문자열 패킷을 클라이언트에게 보낸다고 보여지네요.
서버측 코드 입니다.
다른 오픈 소스 서버로 열어서도 해봤는데 클라이언트에서 같은 문제가 발생 했고
다른 오픈 소스 클라이언트와 제가 만든 서버로 하니깐 사진과 같은 오류는 없었습니다.
printf("[%02d:%02d:%02d] %s :
printf("[%02d:%02d:%02d] %s : ", tm.tm_hour, tm.tm_min, tm.tm_sec, bufname);
__fpurge(stdin); // 입력 전 버퍼를 비워준다
fgets(bufmsg, sizeof(bufmsg), stdin); // bufmsg 배열에 키보드로 값을 입력받는다
__fpurge(stdin); // 입력 후 버퍼를 비워준다
클라에서 이렇게 해보셨나요?
넵
넵 저렇게 해봤는데 저렇게하면 사진과 같은 오류및
2번째 클라이언트에서 체팅 입렵 전까지 1번째 클라이언트 메시지가 출력이 되지 않습니다.
댓글 달기