socket 배열

익명 사용자의 이미지

server에서 epoll 기반 다중 접속으로 되어 있는 데 시그널 처리하는 함수에서 y 입력하면 소켓 종료되게 하는 부분에서 여러 클라이언트라 배열로 변경하려고 합니다.

server에서 client 소켓 배열로 만들고 시그널 함수에서 수정할 부분이 있는지 알려주시면 감사하겠습니다.

server

#include "libDefine.h"
 
void signalHandlerServer(int nSignal);
 
int setNonblockSocket(int nFileDescriptor);
 
int g_nServerSocket = -1;								/* server socket file descriptor */
int g_nClientSocket[MAXCLIENT] = {0, };
int nClientCount = 0;
//int g_nClientSocket = -1;								/* client socket file descriptor */
 
int main(int argc, char *argv[])
{
	int nPort = -1;
	char szMsg[SIZEBUFFER + 1] = {0,};			/* 수신용 버퍼 */
	int nReadLength = 0;								/* 받은 메시지 길이 저장 변수 */
	struct sockaddr_in stClientAddr = {0,};	/* 클라이언트 주소 저장할 구조체 */
	unsigned int uClientAddrSize = sizeof(stClientAddr);			/* 클라이언트 주소 구조체의 크기 */
	int nWriteLength = 0;
	struct epoll_event stEvent = {0, };
	struct epoll_event *pstEpollEvent[MAXCLIENT] = &stEvent;
	int nEpollFileDescriptor = -1;
	int nEventCnt = 0;
	int nIndex = 0;
 
	/* signal 처리 */
	signal(SIGINT, signalHandlerServer);
 
	if (argc != 2)
	{
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
 
	/* port 예외 처리 port 범위 */
	if(atoi(argv[1]) < 1024 || atoi(argv[1]) > 49151)
	{
		errHandle(errno, "port 사용하지 않는 포트입니다.\n");
		exit(1);
	}
	else
	{
		nPort = atoi(argv[1]);
	}
 
	printf("서버 연결 중입니다.\n");
	g_nServerSocket = serverSocket(nPort);
 
	/* server socket 예외 처리 */
	if(g_nServerSocket < 0)
	{
		errHandle(errno, "socket() error \n");
		exit(1);
	}
 
	nEpollFileDescriptor = epoll_create(EPOLLSIZE);
 
	if(nEpollFileDescriptor < 0)
	{
		errHandle(errno, "epoll file error \n");
		close(g_nServerSocket);
		g_nServerSocket = -1;
		exit(1);
	}
 
	/* calloc() 메모리 할당 및 초기화 */
	if((pstEpollEvent = calloc(EPOLLSIZE, sizeof(struct epoll_event))) == NULL)
	{
		errHandle(errno, "할당되지 않았습니다. \n");
		close(nEpollFileDescriptor);
		nEpollFileDescriptor = -1;
		close(g_nServerSocket);
		g_nServerSocket = -1;
		exit(1);
	}
 
	setNonblockSocket(g_nServerSocket);						/* server socket 에서 비동기 IO 설정 */
	stEvent.events = EPOLLIN | EPOLLRDHUP | EPOLLET;	/* event 발생 시 알림 */
	stEvent.data.fd = g_nServerSocket;						/* server socket 추가 */
 
	/* server socket 상태 변화를 nEpollFileDescriptor 통해 알 수 있다. */
	/* 예외 처리 추가 */
	if(epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_ADD, g_nServerSocket, &stEvent) < 0)
	{
		errHandle(errno, "server socket 상태 변화를 nEpollFileDescriptor 통해 관찰할 수 없습니다.");
		epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_DEL, g_nServerSocket, &stEvent);
		close(nEpollFileDescriptor);
		nEpollFileDescriptor = -1;
		close(g_nServerSocket);
		g_nServerSocket = -1;
		exit(1);
	}
 
	while(1)
	{
		/* nEpollFileDescriptor 의 사건 발생 시 pstEpollEvent 에 fd 채운다. */
		nEventCnt = epoll_wait(nEpollFileDescriptor, pstEpollEvent, EPOLLSIZE, -1);
 
		/* neventCnt 예외 처리 */
		if(nEventCnt < 0)
		{
			errHandle(errno, "epoll wait error");
			break;
		}
		else if(nEventCnt == 0)
		{
			break;
		}
 
		/* event 발생 횟수 출력 */
		printf("return epoll 발생 \n");
 
		for(nIndex = 0; nIndex < nEventCnt; nIndex++)
		{
			if(pstEpollEvent[nIndex].data.fd == g_nServerSocket)
			{
				g_nClientSocket = accept(g_nServerSocket, (struct sockaddr*) &stClientAddr, &uClientAddrSize);
 
				g_nClientSocket[nClientCount++] = g_nClientSocket;
 
				/* client socket 예외 처리 */
				if(g_nClientSocket < 0)
				{
					errHandle(errno, "accept error");
					continue;
				}
				else
				{
					printf("클라이언트 접속 IP : %s\n", inet_ntoa(stClientAddr.sin_addr));
					printf("accept() client 접속 요청 수락");
				}
 
				setNonblockSocket(g_nClientSocket);						/* client 소켓 넌블로킹으로 */
				stEvent.events = EPOLLIN | EPOLLRDHUP | EPOLLET;	/* event */
				//stEvent.events = EPOLLIN | EPOLLET;
				stEvent.data.fd = g_nClientSocket;						/* client socket */
				/* epoll_ctl() 로 pstEpollEvent 상태 변화를 nEpollFileDescriptor로 확인 */
				if(epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_ADD, g_nClientSocket, &stEvent) < 0)
				{
					errHandle(errno, "client socket 상태 변화가 되지 않았습니다.");
					epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_DEL, g_nClientSocket, &stEvent);
					close(nEpollFileDescriptor);
					nEpollFileDescriptor = -1;
					close(g_nClientSocket);
					g_nClientSocket = -1;
					break;
				}
				printf("연결된 클라이언트 : %d\n", g_nClientSocket);
			}
			else
			{
				nReadLength = read(pstEpollEvent[nIndex].data.fd, szMsg, SIZEBUFFER);
 
				printf("받은 메시지 %s\n", szMsg);
 
				if(nReadLength == 0)
				{
					//epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_DEL, pstEpollEvent[nIndex].data.fd, &stEvent);
					epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_DEL, pstEpollEvent[nIndex].data.fd, &stEvent);
					//close(nEpollFileDescriptor);
					//nEpollFileDescriptor = -1;
					close(pstEpollEvent[nIndex].data.fd);
					printf("연결 종료된 클라이언트 %d\n", pstEpollEvent[nIndex].data.fd);
					break;
				}
				else if(nReadLength < 0)
				{
					errHandle(errno, "read error \n");
 
					/* 에러 예외 처리 추가 error */
					if(errno == EAGAIN || errno == EINVAL || errno == EBADF || errno == EEXIST || errno == ELOOP || 
						errno == ENOENT || errno == ENOMEM || errno == ENOSPC || errno == EPERM)	/* error 예외 처리 */
					{
						errHandle(errno, "error \n");
						epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_DEL, pstEpollEvent[nIndex].data.fd, &stEvent);
						//epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_DEL, pstEpollEvent[nIndex].data.fd, &stEvent);
						close(nEpollFileDescriptor);
						nEpollFileDescriptor = -1;
						close(pstEpollEvent[nIndex].data.fd);
						break;
					}
				}
				else
				{
					for(nIndex=0; nIndex < nClientCount; nIndex++)//클라이언트 개수만큼
					{
						nWriteLength = write(pstEpollEvent[nIndex].data.fd, szMsg, nReadLength); //클라이언트들에게 메시지를 전달한다.
					}
 
					if(nWriteLength < 0)
					{
						errHandle(errno, "write error \n");
						epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_DEL, pstEpollEvent[nIndex].data.fd, &stEvent);
						close(nEpollFileDescriptor);
						nEpollFileDescriptor = -1;
						close(pstEpollEvent[nIndex].data.fd);
						break;
					}
 
					memset(szMsg, 0, sizeof(szMsg));
				}
			}
 
			for(nIndex = 0; nIndex < nClientCount; nIndex++)	//배열의 갯수만큼
			{
				if(g_nClientSocket == g_nClientSocket[nIndex])	//만약 현재 clientSock값이 배열의 값과 같다면
				{
					while(nIndex++ < nClientCount - 1)				//클라이언트 개수 만큼
					{
						g_nClientSocket[nIndex] = g_nClientSocket[nIndex + 1];	//앞으로 땡긴다.
					}
					break;
				}
			}
		}
	}
 
	free(pstEpollEvent);
	pstEpollEvent = NULL;
	close(nEpollFileDescriptor);
	nEpollFileDescriptor = -1;
	nClientCount--;				//클라이언트 개수 하나 감소
	close(g_nClientSocket);
	g_nClientSocket = -1;
	close(g_nServerSocket);
	g_nServerSocket = -1;
	printf("연결이 끊어졌습니다. \n");
	return 0;
}
 
void signalHandlerServer(int nSignal)
{
	char szMessage[SIZEBUFFER] = {0,};
 
	if (nSignal == SIGINT)
	{
		fputs("종료하시겠습니까?(y 입력) : ", stdout);
		fgets(szMessage, SIZEBUFFER, stdin);
		if(!strcasecmp(szMessage, "y\n"))
		{
			if(g_nClientSocket >= 0)
			{
				//close(g_nClientSocket);
				close(g_nClientSocket[MAXCLIENT]);
				g_nClientSocket = -1;
				printf("client socket 종료되었습니다.\n");
			}
 
			if(g_nServerSocket >= 0)
			{
				close(g_nServerSocket);
				g_nServerSocket = -1;
				printf("server socket 종료되었습니다.\n");
			}
			exit(0);
		}
	}
}
 
int setNonblockSocket(int nFileDescriptor)
{
	int nFlag = fcntl(nFileDescriptor, F_GETFL, 0);
 
	/* nFlag 예외 처리 */
	if(nFlag < 0)
	{
		errHandle(errno, "flag error");
		exit(1);
	}
 
	fcntl(nFileDescriptor, F_SETFL, nFlag | O_NONBLOCK);
 
	return 0;
}

client

#include "libDefine.h"
 
void signalHandlerClient(int nSignal);
 
int g_nClientSocket = -1;
int g_nClientSocket[EPOLLSIZE] = {0, };
 
int main(int argc, char *argv[])
{
	int nPort = -1;
	char *pszAddr = NULL;					/* ip 문자 입력받을 포인터 변수 */
	char szMsg[SIZEBUFFER + 1] = {0,};	/* 서버에 보낼 메시지를 저장할 문자열 버퍼 */
	int nReadLength = 0;						/* 송수신 메시지의 문자열 길이 */
	int nWriteLength = 0;
 
	signal(SIGINT, signalHandlerClient);
 
	/* port */
	if (argc != 3)
	{
		printf("usage : %s <IP> <port> \n", argv[0]);
		exit(1);
	}
 
	pszAddr = argv[1];
 
	/* port 사용 범위 예외 처리 */
	if(atoi(argv[2]) < 1024 || atoi(argv[2]) > 49151)
	{
		errHandle(errno, "port incorrect \n");
		exit(1);
	}
	else
	{
		nPort = atoi(argv[2]);
	}
 
	g_nClientSocket = clientSocket(nPort, pszAddr);
 
	if(g_nClientSocket < 0)
	{
		errHandle(errno, "socket() error \n");
		exit(1);
	}
 
	while (1)
	{
		memset(szMsg, 0, sizeof(szMsg));
 
		fputs("입력 메시지(Q는 종료, 버퍼(1024)까지 입력할 수 있습니다.) : ", stdout);
		fgets(szMsg, sizeof(szMsg), stdin);
		//szMsg[strlen(szMsg) - 1] = '\0';
 
		/*
		if(fgets(szMsg, sizeof(szMsg), stdin) == NULL)
		{
			fputs("입력 메시지 ", stdout);
			fgets(szMsg, sizeof(szMsg), stdin);
			fputs("입력 ", stdout);
		}
		*/
 
		if(!strcasecmp(szMsg, "q\n"))
		{
			close(g_nClientSocket);
			g_nClientSocket = -1;
			break;
		}
 
		if((nWriteLength = write(g_nClientSocket, szMsg, strlen(szMsg))) < 0)
		{
			errHandle(errno, "write error \n");
			close(g_nClientSocket);
			g_nClientSocket = -1;
			exit(1);
		}
 
		memset(szMsg, 0, sizeof(szMsg));
 
		if((nReadLength = read(g_nClientSocket, szMsg, nWriteLength)) < 0)
		{
			errHandle(errno, "read error \n");
			close(g_nClientSocket);
			g_nClientSocket = -1;
			exit(1);
		}
 
		szMsg[nReadLength] = 0;
		printf("서버로 받은 메시지 : %s\n", szMsg);
	}
	return 0;
}
 
void signalHandlerClient(int nSignal)
{
	char szMessage[SIZEBUFFER] = {0,};
 
	if (nSignal == SIGINT)
	{
		fputs("종료하시겠습니까?(y 입력) : ", stdout);
		fgets(szMessage, SIZEBUFFER, stdin);
		if(!strcasecmp(szMessage, "y\n"))
		{
			if(g_nClientSocket >= 0)
			{
				close(g_nClientSocket);
				g_nClientSocket = -1;
				printf("연결 종료되었습니다.\n");
			}
			exit(0);
		}
	}
}

libDefine.c

#include "libDefine.h"
 
int serverSocket(int nPort)
{
	int nServerSocket = -1;
	struct sockaddr_in stServerAddr = {0,};	/* 서버 주소 저장할 구조체  */
	int nOptval = 0;
 
	if(nPort < 0)
	{
		errHandle(errno, "port error");
		return -1;
	}
 
	/* TCP 통신용 서버 소켓 생성 */
	nServerSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
 
	if (nServerSocket < 0)
	{
		errHandle(errno, "socket() error");
		return -1;
	}
	else
	{
		printf("socket() TCP 통신용 서버 소켓이 생성되었습니다.\n");
	}
 
	if(setsockopt(nServerSocket, SOL_SOCKET, SO_REUSEADDR, (const void *)&nOptval, sizeof(int)) < 0)
	{
		errHandle(errno, "socket option error");
		close(nServerSocket);
		nServerSocket = -1;
		return -1;
	}
 
	memset(&stServerAddr, 0, sizeof(stServerAddr));
	stServerAddr.sin_family = AF_INET;
	stServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	stServerAddr.sin_port = htons(nPort);
 
	/* bind */
	if (bind(nServerSocket, (struct sockaddr*) &stServerAddr, sizeof(stServerAddr)) < 0)
	{
		errHandle(errno, "bind() error %d\n", nPort);
		close(nServerSocket);
		nServerSocket = -1;
		return -1;
	}
	else
	{
		printf("bind() - socket에 주소와 IP 할당되었습니다.\n");
	}
 
	/* 소켓을 서버용으로 사용할 수가 있게 한다. */
	if (listen(nServerSocket, LISTENQ) < 0)
	{
		errHandle(errno, "listen() error %d\n", nPort);
		close(nServerSocket);
		nServerSocket = -1;
		return -1;
	}
	else
	{
		printf("listen() 연결 요청 대기하는 중입니다.\n");
	}
 
	return nServerSocket;
}
 
int clientSocket(int nPort, char *pszAddr)
{
	int nClientSocket = -1;
	struct sockaddr_in stServerAddr = {0,};			/* 접속할 서버의 주소 저장할 구조체 */
	struct epoll_event stEvent[EPOLLSIZE] = {0,};
	int nEpollFileDescriptor = -1;
 
	if(nPort < 0)
	{
		errHandle(errno, "port error");
		return -1;
	}
 
	if(pszAddr == NULL)
	{
		errHandle(errno, "address error");
		return -1;
	}
 
	nClientSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
 
	if(nClientSocket < 0)
	{
		errHandle(errno, "socket() error");
		return -1;
	}
 
	nEpollFileDescriptor = epoll_create(EPOLLSIZE);
 
	/* 서버 쪽 주소 구조체 0 초기화 */
	memset(&stServerAddr, 0, sizeof(stServerAddr));
	stServerAddr.sin_family = AF_INET;								/* 인터넷 주소체계 사용 */
	stServerAddr.sin_addr.s_addr = inet_addr(pszAddr);			/* 서버 IP 구조체에 저장 */
	stServerAddr.sin_port = htons(nPort);							/* 서버 Port 구조체에 저장 */
 
	/* connect */
	if (connect(nClientSocket, (struct sockaddr*) &stServerAddr, sizeof(stServerAddr)) < 0)
	{
		errHandle(errno, "connect() error %s\n", pszAddr);
		close(nClientSocket);
		nClientSocket = -1;
		return -1;
	}
	else
	{
		printf("연결되었습니다.\n");
	}
 
	if(nClientSocket < 0)
	{
		errHandle(errno, "connect error \n");
		close(nClientSocket);
		nClientSocket = -1;
	}
 
	return nClientSocket;
}
 
/* error 처리 handler */
void errHandle(int nErrorCode, char *pszMsg, ...)
{
	va_list argptr;
	char szBuf[MESSAGE] = {0,};
 
	if(nErrorCode)
	{
		printf("error = %s\n", strerror(errno));
	}
 
	if(pszMsg == NULL)
	{
		printf("message error = %s\n", strerror(errno));
	}
 
	va_start(argptr, pszMsg);
	vsnprintf(szBuf, MESSAGE, pszMsg, argptr);		/* 버퍼에 문자열 쓰고 버퍼 길이 지정하며 va_list 얻는다. */
	va_end(argptr);
	puts(szBuf);
}

libDefine.h

#ifndef LIBDEFINE_H_
#define LIBDEFINE_H_
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <stdarg.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/time.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdbool.h>
 
#define SIZEBUFFER	1024
#define LISTENQ		10
#define MESSAGE		1000
#define EPOLLSIZE		100
#define MAXCLIENT		100
 
/* 서버 socket */
int serverSocket(int nPort);
 
/* 클라이언트 */
int clientSocket(int nPort, char *pszAddr);
 
/* 에러 예외 처리 */
void errHandle(int nErrorCode, char *pszMsg, ...);
 
#endif

jick의 이미지

지금 하시려고 하는 게 보기보다 꽤 까다로운 일입니다. Signal handler 안에서 쓸 수 있는 함수는 *대단히* 제한되어 있습니다.

http://man7.org/linux/man-pages/man7/signal-safety.7.html

예컨대 fputs, fget, printf, exit 같은 함수는 다 못씁니다.

그럼에도 불구하고 SIGINT를 잡아서 처리를 구현하고 싶다면, 제 생각에 가장 쉬운 방법은 프로그램이 시작할 때 pipe를 하나 엽니다. 그리고 그 pipe의 읽는 쪽 fd를 epoll의 감시 목록에 추가합니다. 시그널 핸들러에서는 다른 거 할 필요 없고 pipe에 한 바이트를 write하면 됩니다.

그리고 메인 함수에서 pipe에 뭔가 읽을 게 있는지를 검사하면 중간에 놓치는 일 없이 signal 발생 유무를 검사할 수 있습니다.

* 이 이야기가 잘 이해가 안되신다면... 정상입니다. -_- 시그널을 제대로 처리하는 건 대단히 어렵고, 대충 짜면 반드시 빈틈이 생깁니다. 웬만하면 Stevens의 Advanced Programming in the Unix Environment 관련 챕터를 정독하는 걸 권합니다.

jame의 이미지

그러면 signal에서 처리하려면 pipe 시그널을 추가해야 되나요?
원래 클라이언트 소켓을 배열 형태로 만들어서 y 입력하면 소켓 종료되게 하는 것입니다.
여러 클라이언트가 있으면 A가 종료하면 A만 종료되게 하는 것입니다.

jame의 이미지

그리고 client에서 버퍼보다 넘게 보내면 서버에서 나눠서 받는 데 이를 클라이언트에서 버퍼보다 넘으면 못 보내게 하거나 에러 처리로 하려고 합니다.
클라이언트는 fgets() 함수로 버퍼까지 받아서 버퍼보다 넘는 예외 처리를 추가를 어떻게 하면 될까요?

jame의 이미지

그것이 2개의 client 접속 후 차례로 접속 종료하였을때 소켓이 정리되지 않는 버그인 데 그래서 클라이언트 배열로 만들고 처리하려고 합니다.

댓글 달기

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