우분투 리눅스 채팅 프로그램

guk1234의 이미지

안녕하세요 최근에 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: 
첨부파일 크기
Image icon 캡처.PNG359.96 KB
익명 사용자의 이미지

네트워크 소켓을 공부한 사람들이 간단한 메시지 송수신 같은 것을 성공적으로 구현하고 나면, 자연스레 이런 종류의 인스턴트 메신저 프로그램을 만들고 싶어하더군요.

그런 프로그램을 만들기 위해서 정말 번거로운 부분이 네트워크 프로그래밍이 아니라, 콘솔 입출력을 원하는 대로 제어하는 데 있다는 사실을 깨닫는 데 보통 시간이 그닥 오래 걸리지 않습니다.

현실적으로, 터미널이 표준 모드(canonical mode)로 동작하면서, 프로그램이 (scanf나 gets 따위로) 표준 입력을 받기 위해 대기하면서도 비동기적인 출력을 적절히 내보낼 수 있게 구현하는 게 가능한지 잘 모르겠습니다.

감히 아주 불가능하다고 단정짓지는 못하겠습니다만, 이걸 제대로 해 보려고 시도한 몇몇 사람들은 정말 엄청 삽질하던데요.

보통 그 노력을 하느니 별도의 터미널 제어 라이브러리를 활용하거나, 아예 GUI로 가는 게 오히려 더 간단할 것 같습니다. 채팅창과 채팅 메시지 입력 칸을 분리해서 관리하는 게 훨씬 편하니까요.

guk1234의 이미지

나중에 GUI로 도전 해보겠습니다.

익명 사용자의 이미지

옛날 90년대에 터미널에서 채팅하는게 가능했습니다.
write 라는 명령어도 있고 ytalk 이라는 것도 있고,
사설 BBS에서 채팅하던게 생각나네요.
터미널에 입출력하다보면 먹통 현상이 발생할텐데 그걸 어떻게 제어해서 먹통현상이 발생되지 않게 하느냐가 관건일 듯 합니다.

익명 사용자의 이미지

요점은 터미널이 "canonical mode"인 것입니다.

본문의 프로그램과 같이 표준 입력이 블럭당할 수 있는 상황에서, 사용자 입력의 반향(echo)과 네트워크에서 날라온 메시지의 출력이 콘솔에서 서로 뒤섞이는 일을 어떻게 막을 수 있을까요? 그런 일이 일어난다고 기능상으로 문제가 있는 것은 아니지만, 별로 보기 좋은 UI는 아니니까요.

방법이 전혀 없다고까지는 말씀을 못 드리겠지만, 제가 모르는 어떤 깔끔한 방식이 있지 않는 한 그냥 noncanonical 모드로 후퇴하는 게 나을 것 같습니다. 그러면 어떻게든 프로그램이 복잡해지겠지요. canonical mode가 제공했던 라인 단위 입력 편집 기능을 직접 제공해야 할 테니까요.

라스코니의 이미지

어떤 출력 형식을 원하시는지 잘 모르겠네요. 좀 예를 들어주시면 좋을 것 같은데.

우선, 메시지 처리 구조체가 안보이네요. 간단한 1:1 메시지 처리가 아니라 1:N 메시지 처리를 하려면 우선 관련 구조체부터 고려하심이 좋을 듯 합니다.

가령 메시지는
sender id, sender name (optional), message tex (max 256), received time
으로 구성될 수 있겠죠.

이 메시지들을 링크 리스트로도 연결할 수 있고, 그냥 단순 버퍼를 할당해서 할 수도 있고요(이게 더 핸들링하기 어려울 듯). 일단 구조체로 보관할 수 있게 되면 화면에서 표시하는 것은 의외로 간단할 수도 있습니다.

그냥 bufall[]에 저장하면 혼란이....

guk1234의 이미지

오류 출력 화면 이미지 첨부 하겠습니다.

댓글 첨부 파일: 
첨부파일 크기
Image icon 캡처.PNG359.96 KB
라스코니의 이미지

이건 서버쪽 코드를 봐야 겠네요.
가끔 서버가 이상한 형태로 채팅 메시지 문자열 패킷을 클라이언트에게 보낸다고 보여지네요.

guk1234의 이미지

다른 오픈 소스 서버로 열어서도 해봤는데 클라이언트에서 같은 문제가 발생 했고
다른 오픈 소스 클라이언트와 제가 만든 서버로 하니깐 사진과 같은 오류는 없었습니다.

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<netinet/in.h>
#include<pthread.h>
 
#define COUNT 10//최대 접속 인원 수
#define MAX_LINE 530//최대 채팅길이
#define MAX_LIST 100//저장 가능한 메시지갯수
 
int clnt_number = 0; //접속자 인원 수
int clnt_sock_arr[COUNT]; //접속자를 각 소켓에 저장하기 위한 정수형 배열
int sockfd;//소켓
int list_count;//저장된 메시지 갯수
unsigned long int acc_sock;//연결된 소켓
char admin[MAX_LINE];//관리자 메시지
char add_admin[MAX_LINE];//관리자 명 + 메시지
char list[MAX_LIST][MAX_LINE];//메시지 내용
 
void* chat_input(void* a);//메시지를 읽는 쓰레드
void* admin_chat(void* b);//관리자 기능 및 안내 쓰레드
void exitchat(int i);//사용자 종료 명령어 확인 함수
void write_msg(char* msg, int len);//서버 --> 클라이언트 메시지 송신 함수
void input_list(void);//메시지 최대 갯수 초과 시 정렬 함수
void output_list(int number);//저장된 메시지 출력 함수
 
int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr, cliaddr;
	socklen_t addrlen = sizeof(cliaddr); // 서버 구조체 선언
 
	pthread_t a_thread;//쓰레드 선언
	pthread_t b_thread;//쓰레드 선언
 
	if(argc != 2) // 메인함수에 들어온 매개변수에 따라 잘못 입력 시 사용법 출력 후 종료
	{
		printf("Usage : %s port \n", argv[0]);
		return -1;
	}
 
	if((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0) // 소켓 함수로 소켓 생성, 오류 시 오류 출력
	       	// make socket and error cheaking
	{
		perror("Socket failed!");
		return -1;
	}
 
	/* 구조체를 이용하여 서버의 아이피 주소와 포트 그리고 프로토콜을 설정, 관리 */
	servaddr.sin_family = AF_INET; // 통신 프로토콜 설정  -> ipv4
	servaddr.sin_port = htons(atoi(argv[1])); // 입력된 포트 번호 컴퓨터 인식 가능하도록 처리 (htons 함수 참조)
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // ip 주소 컴퓨터 인식 가능하도록 처리 (htonl 함수 참조)
 
	if(bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) // 바인드 함수, 오류 시 오류 출력
	{
		perror("Bind failed !");
		return -1;
 
	}
 
	listen(sockfd, COUNT); // COUNT에서 설정한 인원 까지의 접속자 허용 / 리슨함수로 클라이언트 접속요청 대기
 
	while(1)
	{
		puts("lintening . . .");
		acc_sock = accept(sockfd, (struct sockaddr*)&cliaddr, &addrlen); // 클라이언트 접속 요청 들어올 시 액셉트함수로 연결 수락함
 
		if(acc_sock < 0) // 마찬가지로 오류 발생 시 오류출력, 종료
		{
			perror("Accept failed !");
			return -1;
		}
		puts("Connected client"); // 연결 성공 시 메세지 출력
 
		clnt_sock_arr[clnt_number++] = acc_sock;//새로운 사용자가 입장할 때마다 소켓번호 부여
                printf("새로운 연결, Client IP : %s\n", inet_ntoa(cliaddr.sin_addr));
		pthread_create(&a_thread, NULL, chat_input, (void*)acc_sock);//쓰레드 생성
		pthread_create(&b_thread, NULL, admin_chat, (void*)acc_sock);//쓰레드 생성
	}
 
	pthread_join(a_thread, NULL);//쓰레드 종료 대기
	pthread_join(b_thread, NULL);//쓰레드 종료 대기
 
	return 0;
}
 
void* chat_input(void* a)
{
	char temp[MAX_LINE];//받을 메시지
	int str_len;//메시지 길이
	list_count = 0;//저장된 메시지 갯수 초기화
 
	while(1)
	{
		str_len = (read((unsigned long int)a, temp, sizeof(temp)));//클라이언트 -> 서버로 수신
 
		input_list();//메시지 최대 갯수 초과시 정렬 함수 호출
		strcpy(list[_count], temp);//수신된 메시지를 저장
		list_count++;//저장된 메시지 갯수 증가
 
		write_msg(temp, str_len);//메시지 송신 함수 호출
 
		temp[str_len] = 0;
		printf("%s", temp);
		strcpy(temp,"");//다음 받을 메시지를 위해 초기화
 
		continue;
	}
}
 
void exitchat(int i)
{
	close(clnt_sock_arr[i]);//메시지를 보낸 사용자의 소켓 종료
	if(i != clnt_number - 1)//마지막 사용자가 아닌 경우
	{
		clnt_sock_arr[i] = clnt_sock_arr[clnt_number - 1];//메시지를 보낸 사용자의 소켓을 채움(소켓정렬)
		clnt_number--;
	}
 
}
 
void write_msg(char* msg, int len)
{
	int i;
 
	for(i=0; i < clnt_number; i++)
        {
		if(strstr(msg, ": /exit") != NULL)
		{
   			exitchat(i);//사용자 종료 명령어 확인 함수 호출
		}
 
		write(clnt_sock_arr[i], msg, len);//서버 --> 클라이언트로 송신
	}
 
}
 
void* admin_chat(void* b)
{
	int list_q;//확인할 메시지 갯수
	int i;
 
	while(getchar() == '\n')
	{
		printf("관리자모드가 활성화 되었습니다.\n");
		while(1)
		{
			fgets(admin, sizeof(admin), stdin);//안내 메시지 및 명령어 입력
			sprintf(add_admin, "admin : %s", admin);
 
 
 
			if(strstr(add_admin, ": /close"))
			{
                                for(i = 0; i < clnt_number; i++)
				write(clnt_sock_arr[i], add_admin, sizeof(add_admin));//메시지 송신 함수 호출(모든 사용자)
 
				strcpy(admin, "서버를 종료합니다.....3\n");//서버 종료안내
				sprintf(add_admin, "admin : %s", admin);
				write_msg(add_admin, sizeof(add_admin));//메시지 송신 함수 호출(모든 사용자)
				printf("%s", admin);
				sleep(1);
				strcpy(admin, "서버를 종료합니다.....2\n");//서버 종료안내
				sprintf(add_admin, "admin : %s", admin);
				write_msg(add_admin, sizeof(add_admin));//메시지 송신 함수 호출(모든 사용자)
				printf("%s", admin);
				sleep(1);
				strcpy(admin, "서버를 종료합니다.....1\n");//서버 종료안내
				sprintf(add_admin, "admin : %s", admin);
				write_msg(add_admin, sizeof(add_admin));//메시지 송신 함수 호출(모든 사용자)
				printf("%s", admin);
				sleep(1);
				strcpy(admin, "프로그램이 종료 되었습니다.\n");//서버 종료안내
				sprintf(add_admin, "admin : %s", admin);
				write_msg(add_admin, sizeof(add_admin));//메시지 송신 함수 호출(모든 사용자)
				printf("%s", admin);
				exit(0);//서버 강제 종료(모든 사용자 강제 종료)
			}
 
			else if(strstr(add_admin, ": /list"))
			{
				do{
				printf("원하는 갯수를 입력하시오. : ");
				scanf("%d", &list_q);//확인할 메시지 갯수 입력
 
				output_list(list_q);//저장된 메시지 출력 함수 호출
				}while(list_q > list_count);//입력 받은 메시지 > 저장된 메시지 갯수 시 반복
			}
		}
	}
}
 
void input_list(void)
{
	int j;
 
	if(list_count >= MAX_LIST)
	{
		for (j = 0; j < 99; j++) {
			strcpy(list[j], list[j + 1]);//메시지 저장 갯수 초과 시 메시지 정렬(오래된 메시지 삭제)
		}
		list_count = MAX_LIST - 1;//다음에 받을 메시지 번호를 마지막 저장위치로 고정
	}
}
 
void output_list(int number)
{	
	int i;
 
	if(number > list_count)//입력 받은 메시지 > 저장된 메시지 갯수 시 안내문 출력
		printf("갯수를 초과하였습니다.\n");
 
	else
	{
		printf("--------채팅 내역(%d개)--------\n", number);
		for(i = 0; i < number; i++)
	       	{
			printf("%s", list[i]);//저장된 메시지 출력
		}
		printf("--------------------------------\n");
	}
}
[/]
라스코니의 이미지

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); // 입력 후 버퍼를 비워준다

클라에서 이렇게 해보셨나요?

guk1234의 이미지

넵 저렇게 해봤는데 저렇게하면 사진과 같은 오류및
2번째 클라이언트에서 체팅 입렵 전까지 1번째 클라이언트 메시지가 출력이 되지 않습니다.

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.