네트웍 서버를 개발하는데.. 궁금한점이 생겼습니다.

gugudan의 이미지

지금 방식은
2개의 스레드가 돌고 있는데 하나는 receive thread가 있고
나머지 하나는 send thread가 있습니다.

send thread는 queue에 패킷이 들어오면
패킷을 보내는 방식으로 되어 있습니다..

문제는 패킷을 broadcast할때 문제가 생기네요..
만일 10명이 접속을 하면 10명한테
broadcast를 해야 하는데..
그렇다면 queue에 패킷을 10개를 집어 넣어야 한다는 문제가
생기네요..

다른 좋은 방법은 없는지요?

익명 사용자의 이미지

broadcast, multicast를 구현하는 방법중 하나가, reference count라는
것을 이용하는 방법이 있습니다.
Layer 4, 특히, tcp에서 이를 구현한다면 레퍼런스 카운트를 가지고 루프도는 방법이 있겠지요.

UDP의 경우라면, IP Multicast나 Broadcast를 사용하는 방법이 있습니다.
또는 브로드캐스트 도메인내에 있다면(단일 세그먼트라면) 이더넷레벨의 브로드캐스트 멀티개스트를 사용하는 방법도 있습니다.

큐에 10개 넣는것도 그리 나빠보이지 않습니다.

rasungboy의 이미지

gugudan wrote:

문제는 패킷을 broadcast할때 문제가 생기네요..
만일 10명이 접속을 하면 10명한테
broadcast를 해야 하는데..
그렇다면 queue에 패킷을 10개를 집어 넣어야 한다는 문제가
생기네요..

다른 좋은 방법은 없는지요?

왜 이걸로 문제가 생기는지요?
당연히 10명한테 보낼려면 10개의 패킷을 만들어서
Send 스레드에 넣어야 할텐데..

gugudan의 이미지

gugudan wrote:

왜 이걸로 문제가 생기는지요?
당연히 10명한테 보낼려면 10개의 패킷을 만들어서
Send 스레드에 넣어야 할텐데..

너무 당연한걸 물어봤나요?
전에 개발을 할때는 한 패킷 가지고 전체의 사용자들한테 보내는
구조로 개발을 했었는데.
이번에는 그런 구조가 아니다 보닌까
꼭 머가 잘못된거 같아서요..

ssehoony의 이미지

각 사용자마다 send 쓰레드가 하나씩 생기는 방식은 아닌 듯 하군요.
그렇다면 send 용 데이터 큐도 사용자 마다 있을 필요가 없는 듯 하니, send 쓰레드 용 큐 하나만 생성하고 그 큐에 데이터가 하나 들어오면 그 데이터를 접속된 모든 사용자에게 loop 를 돌며 send 한다면 원하시는 효과가 나올 듯 하군요.

하지만 한쓰레드가 loop 를 돌며 전체 사용자에게 전송하는 방식은 중간에 특정사용자의 네트웍이 느리다면 다른 사용자에게 영향을 준다는 문제점을 고려하셔야 겠네요. non block 모드로 쏘면 데이터가 잘리는 수가 생길 수도 있지요. 좋은 방법은 select 함수의 write 이벤트를 이용하는 방법이지만 프로그램이 복잡해 지더군요.

일단 저 같은 경우는 non block 로 셋팅하고 소켓 write 버퍼를 상당히 크게 잡은 후 그냥 한 쓰레드가 loop 를 돌며 송신하는 방식을 사용하고 있는데, 한 사용자당 한 쓰레드나 select 함수의 write 이벤트를 이용하는 방법으로 수정을 하고 싶은 상태 입니다.
(지금 하고 있는 방식으로도 잘 작동하고 있긴합니다.)

이 외에 좋은 구조가 있을까 궁금하네요.
일단 현재 제 지식으로는 위의 방식 이외는 모르겠네요.
고수님들 계시면 힌트라고 주고 가시면 감사하겠습니다. ^^

익명 사용자의 이미지

devilhero wrote:

...
(지금 하고 있는 방식으로도 잘 작동하고 ....)
...

이 방법이 그 누구에게라도 추천하는 The best입니다.
문제가 있거나, 아니면 처음으로 시도하는 경우가 아니라면 말입니다.

devilhero wrote:

이 외에 좋은 구조가 있을까 궁금하네요.
일단 현재 제 지식으로는 위의 방식 이외는 모르겠네요.

IP Multicast에 대해 공부하시면, 보다 진보될 수 있겠군요.
먼(?) 얘기지만, IPv6에서는 Multicast가 디폴트입니다.
(Broadcast는 IPv4에서만 디폴트였지요.)

더 진보적으로 간다면, Reliable Multicast를 ....

익명 사용자의 이미지

즉, 잘~ 되면 된것입니다. 방법이야 널렸으나.....

rasungboy의 이미지

gugudan wrote:

너무 당연한걸 물어봤나요?
전에 개발을 할때는 한 패킷 가지고 전체의 사용자들한테 보내는
구조로 개발을 했었는데.
이번에는 그런 구조가 아니다 보닌까
꼭 머가 잘못된거 같아서요..

즉 하나의 패킷데이타를 사용자들한테 send 시 각 유저들마다

패킷데이타 사본을 만들지 않고 보내고 싶다 이거시군요.

사실 저도 이거때문에 좀 고민좀 했었는데 마땅히 적당한 아이디

어가 떠올르지 않더군요...(논스레드인데도..)

혹 님과 같은경우는 send 스레드에 패킷을 넣을때 하나의 패킷

만 넣고 그 패킷을 받아야 하는 소켓리스트를 같이 넣으면

어떨까 합니다..

struct Packet {
    WORD size;
    char* pPacket;
    list<SOCKET> dest;

};

큐에 들어갈 아이템은 이런식이 되지 않을까요..

gugudan의 이미지

struct Packet { 
    WORD size; 
    char* pPacket; 
    list<SOCKET> dest; 

};

흠..위의 방식도 괜찮아 보이네요..

Quote:

각 사용자마다 send 쓰레드가 하나씩 생기는 방식은 아닌 듯 하군요.
그렇다면 send 용 데이터 큐도 사용자 마다 있을 필요가 없는 듯 하니, send 쓰레드 용 큐 하나만 생성하고 그 큐에 데이터가 하나 들어오면 그 데이터를 접속된 모든 사용자에게 loop 를 돌며 send 한다면 원하시는 효과가 나올 듯 하군요.

하지만 한쓰레드가 loop 를 돌며 전체 사용자에게 전송하는 방식은 중간에 특정사용자의 네트웍이 느리다면 다른 사용자에게 영향을 준다는 문제점을 고려하셔야 겠네요. non block 모드로 쏘면 데이터가 잘리는 수가 생길 수도 있지요. 좋은 방법은 select 함수의 write 이벤트를 이용하는 방법이지만 프로그램이 복잡해 지더군요.

일단 저 같은 경우는 non block 로 셋팅하고 소켓 write 버퍼를 상당히 크게 잡은 후 그냥 한 쓰레드가 loop 를 돌며 송신하는 방식을 사용하고 있는데, 한 사용자당 한 쓰레드나 select 함수의 write 이벤트를 이용하는 방법으로 수정을 하고 싶은 상태 입니다.
(지금 하고 있는 방식으로도 잘 작동하고 있긴합니다.)

저도 지금 non block로 세팅하고 data를 보내는 방식을 하고 있는데
왜 write버퍼를 크게 잡아야 하는지요?

ssehoony의 이미지

write 버퍼를 크게 잡아야 하는 이유.

논블럭으로 데이터를 송신할 경우
send 버퍼가 가령 10K 인데 제가 이쪽으로 4K 를 1초에 한번 씩 10번 쏘면 총합 40K 겠죠?
이걸 클라이언트에서 네트웍이 느려서 10초동안에 20K 만 받았다면 서버측에서 40K중 20K는 클라이언트로 전송되고 10K 는 소켓 send 버퍼에 남고 10K 는 그냥 사라져 버립니다.
이건 write 함수의 return 값을 갖고 확인해 볼 수 있습니다.
이런 현상의 유무를 알고 싶다면 write 함수의 return 값과 송신한 데이터 사이즈의 값을 비교해서 서로 다르다면 송신에 문제가 있는 거지요.

사실 이럴 경우 버퍼를 크게 늘리는 것 보다 select 함수의 write 이벤트를 통해 client 가 다시 데이터를 받을 준비가 됐을때 송신하지 못한 부분을 송신하면 되는데, 이건 프로그램 구조를 많이 변경해야 할 수도 있져.(제 프로그램이 그런 경우입니다.) 그래서 임시 방편으로 버퍼를 늘리는 거지요.
역시나 단점을 메모리 사용량이 늘어난다는 것이고 버퍼 사이즈는
클라이언트에 얼마나 많은 데이터를 보내냐와 클라이언트와의 네트워크 속력에 달렸겠죠.
저 같은 경우는 클라이언트 네트워크 사정이 서로들 많이 다르고 2~300바이트의 작은 패킷을 짧은 시간에 많이 보내기 때문에 100K로 잡았습니다.
ftp 같은 대용량을 전송할 때는 select 나 block 모드를 사용해야겠지요

익명 사용자의 이미지

곧, QUEUE 개념과 미들웨어 개념이 나오겠군요 ^^

gugudan의 이미지

devilhero wrote:
write 버퍼를 크게 잡아야 하는 이유.

논블럭으로 데이터를 송신할 경우
send 버퍼가 가령 10K 인데 제가 이쪽으로 4K 를 1초에 한번 씩 10번 쏘면 총합 40K 겠죠?
이걸 클라이언트에서 네트웍이 느려서 10초동안에 20K 만 받았다면 서버측에서 40K중 20K는 클라이언트로 전송되고 10K 는 소켓 send 버퍼에 남고 10K 는 그냥 사라져 버립니다.
이건 write 함수의 return 값을 갖고 확인해 볼 수 있습니다.
이런 현상의 유무를 알고 싶다면 write 함수의 return 값과 송신한 데이터 사이즈의 값을 비교해서 서로 다르다면 송신에 문제가 있는 거지요.

왜 이런 현상이 발생하는거죠?

Quote:

곧, QUEUE 개념과 미들웨어 개념이 나오겠군요 ^^

위의 글과 무슨 연관이 있나요?

익명 사용자의 이미지

위에서는 send buffer의 경우만을 고려했습니다만, 수신도 같이 고려하게되고,
이를 일반화(generalize)하는 과정을 거치게됩니다.

거대/대량 메시지, 거대사용자, 트랜잭션, 신뢰성을 추구하다 보면, 이를 하나의 독립적
콤포넌트로 고려하게됩니다.

이러한 노력은 큐개념의 구현과 결국 미들웨어로 가게됩니다.
* 큐란, 자료구조에서 나오는 큐와 기본적으로 유사하지만, 비동기적인 처리와,
아주 거대한 용량(메시지사이즈, 메시지개수, 사용자수, 트랙잭션...)을 같이 고려하는 것을 의미합니다.
* 위叩걋?경우에는 다양한 미들웨어들 중에서, 메시지 패싱 미들웨어가 적절하겠습니다.
* 구지 미들웨어를 사용할 필요는 없습니다. 구현할 필요도, 잘~ 돈다면 말이지요.

gugudan의 이미지

Quote:
구현할 필요도, 잘~ 돈다면 말이지요

잘 도는지 아직 테스트가 이루어 지지 않아서..
ㅎㅎㅎㅎㅎㅎ

답변 달아 주신분들 많은 도움 되었습니다.
감사합니다.

ssehoony의 이미지

gugudan wrote:
devilhero wrote:
write 버퍼를 크게 잡아야 하는 이유.

논블럭으로 데이터를 송신할 경우
send 버퍼가 가령 10K 인데 제가 이쪽으로 4K 를 1초에 한번 씩 10번 쏘면 총합 40K 겠죠?
이걸 클라이언트에서 네트웍이 느려서 10초동안에 20K 만 받았다면 서버측에서 40K중 20K는 클라이언트로 전송되고 10K 는 소켓 send 버퍼에 남고 10K 는 그냥 사라져 버립니다.
이건 write 함수의 return 값을 갖고 확인해 볼 수 있습니다.
이런 현상의 유무를 알고 싶다면 write 함수의 return 값과 송신한 데이터 사이즈의 값을 비교해서 서로 다르다면 송신에 문제가 있는 거지요.

왜 이런 현상이 발생하는거죠?

이건 버클리 소켓에서 write 함수를 사용할 때 non block 와 block 의 의미를 확실히 이해할 필요가 있는데요
non block 는 데이터를 client 직접 쏘고 송신 결과를 return 해주는 상태가 아닙니다.
그냥 단순 send 버퍼에 담고, 그 결과를 return 해주는거지요.
실제 데이터는 send 버퍼있는 것을 background 로 전송을 해줍니다. 이런 background 작업중에 에러가 발생했을 경우 어떻게 피드백 해주는지는 잘 모르겠습니다. (select 함수의 read, write 이외 하나 더 있는데 혹시 그게 아닐까 추측을 해봅니다.)

정말 background 로 작동하는지 못 믿으시겠다고요?
음 그렇다면 서버에서 send 버퍼를 20메가 정도 잡고 10메가 데이터를 non block 로 client 에 쏘세요. 그리고 그때까지 소요 시간을 로그로 남기세요.
역시 클라언트도 데이터를 받기 시작해서 끝까지 받았을때 까지 시작을 로그로 남겨 보세요.

제가 말씀 드린데로라면 send 버퍼가 실제 데이터 보다 크기 때문에 서버는 쏘자 마자 완료했다고 return 을 하겠지요.
하지만 클라이언트는 한참 있다가 완료하게 됩니다.
이건 서버, 클라이언트가 네트워크 속도에 따라 더욱더 시간차가 클겁니다.
혹시 해보시게 되면 테스트 결과 좀 올려주세요 ^^

익명 사용자의 이미지

Quote:

이건 버클리 소켓에서 write 함수를 사용할 때 non block 와 block 의 의미를 확실히 이해할 필요가 있는데요
non block 는 데이터를 client 직접 쏘고 송신 결과를 return 해주는 상태가 아닙니다.
그냥 단순 send 버퍼에 담고, 그 결과를 return 해주는거지요.
실제 데이터는 send 버퍼있는 것을 background 로 전송을 해줍니다. 이런 background 작업중에 에러가 발생했을 경우 어떻게 피드백 해주는지는 잘 모르겠습니다. (select 함수의 read, write 이외 하나 더 있는데 혹시 그게 아닐까 추측을 해봅니다.)

정말 background 로 작동하는지 못 믿으시겠다고요?
음 그렇다면 서버에서 send 버퍼를 20메가 정도 잡고 10메가 데이터를 non block 로 client 에 쏘세요. 그리고 그때까지 소요 시간을 로그로 남기세요.
역시 클라언트도 데이터를 받기 시작해서 끝까지 받았을때 까지 시작을 로그로 남겨 보세요.

제가 말씀 드린데로라면 send 버퍼가 실제 데이터 보다 크기 때문에 서버는 쏘자 마자 완료했다고 return 을 하겠지요.
하지만 클라이언트는 한참 있다가 완료하게 됩니다.
이건 서버, 클라이언트가 네트워크 속도에 따라 더욱더 시간차가 클겁니다.
혹시 해보시게 되면 테스트 결과 좀 올려주세요 ^^

위의 말대로 background로 작동하는건 이해가 가는데
그래도 패킷이 손실되는건 이해가 가지 않네요..
TCP라는 프로토콜 자체가 연결지향이기 때문에
패킷이 손실되지 않는 구조 아닌가요?

ssehoony의 이미지

gugudan2 wrote:
위의 말대로 background로 작동하는건 이해가 가는데
그래도 패킷이 손실되는건 이해가 가지 않네요..
TCP라는 프로토콜 자체가 연결지향이기 때문에
패킷이 손실되지 않는 구조 아닌가요?

오래전 글이 위로 올라왔군요.
네 TCP 는 일단 송신하면 수신측에서 정상적으로 수신이 되는 걸 보장해 줍니다. (물론 하드웨어 적인 문제로 접속이 끊겼을 때는 불가~)
그런데 이런 특징은 TCP 프로토콜의 스펙이고 이를 구현한건 커널일텐데, 커널은 소켓의 send 버퍼에 들어 있는 것에 대해서만 송신을 보장 해 주죠.
제가 말씀 드린 상황은 send 버퍼가 가득차서 send 버퍼에 데이터가 들어가지 못했을 때 문제가 생긴다는 것이었습니다. write 함수의 리턴 값을 통해 send 버퍼에 몇바이트가 입력됐는지 확인이 가능하고요.
그래서 가급적 send 버퍼에 데이터가 가득 차서 더이상 입력이 안되는 것을 막기 위해 차선으로 send 버퍼 사이즈를 늘리는거죠.

gugudan의 이미지

커널 send 버퍼의 크기가 정해져 있는데
네트워크가 느린 관계로 아직 전송을 하지 않은 상태에서
어플에서 계속적으로 커널의 send 버퍼에 데이타를 넣다 보면..
커널의 send 버퍼 사이즈가 부족하기 떄문에 데이타가 솔실된다는
얘기이지요?

좀 이제 이해가 가네요..

지금 문제가 write를 할때 패킷의 크기를 정확하게 패킷의 길이만큼 하면
클라이언트가 이상한 증상을 보이고 있습니다.
그런데 패킷길이를 + 60 정도 하닌까 정상적으로 수행이 됩니다.

그래서 혹시 이 문제가 아닌가 해서..
다시 읽어보닌까 좀 이해가 가지 않아서 물어보게 된겁니다.

        if(getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvBufferSize, &sockOptSize) < 0)
        {
                printf("getsockopt() failedn");
                return -1;
        }
        printf("rcvBufferSize  :%d\n",rcvBufferSize );



        sockOptSize = sizeof(sndBufferSize);
        if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sndBufferSize, &sockOptSize) < 0)
        {
                printf("getsockopt() failedn");
                return -1;
        }
        printf("sndBufferSize  :%d\n",sndBufferSize );

위의 코드를 수행하닌까.
rcvBufferSize :87380
sndBufferSize :16384

sndBufferSize의 값이 너무 적은거 같아서
sndBufferSize의 값을 setsockopt함수를 이용해서
변경 해 보았지만 여전히 같은 현상이 보이네요..

근데 아무리 생각해봐도 저희쪽 패킷의양은 많은 편이 아닌데..
기껏해야 많아야.. write를 가장 많이 했을때 한 200byte
도 되지 않을텐데..
그렇다면 커널쪽이 아닌 어플의 서버쪽 문제가 아닌가 하네요..

역시 쉬운게 없네요..

그럼 수고하십시요.

ssehoony의 이미지

ASCII 데이터로 통신하신다면 수신측에서 데이터를 수신해서 찍어보면 디버깅이 용이 할 듯 한데 아마 binary 통신일 듯 하군요.
서버 클라이언트간 플랫폼이 다를때 binary 통신을 하시려면 구조체 팩킹 이라고 하나요?? memory alignment 에 의해 구조체 사이즈가 C의 소스상으로 같은 구조라고 실제 메모리는 다르게 구성될 수 있는데.
이 문제가 아닐까 하는 생각도 드네요.
일단 제일 먼저 송신측 사이즈와 수신측 사이즈가 같은지 확인해 보시면 좋을 듯 하군요.

익명 사용자의 이미지

Quote:

커널 send 버퍼의 크기가 정해져 있는데
네트워크가 느린 관계로 아직 전송을 하지 않은 상태에서
어플에서 계속적으로 커널의 send 버퍼에 데이타를 넣다 보면..
커널의 send 버퍼 사이즈가 부족하기 떄문에 데이타가 솔실된다는
얘기이지요?

위의 얘기대로 하면 이해는 가는데..

Quote:

가급적 send 버퍼에 데이터가 가득 차서 더이상 입력이 안되는 것을 막기 위해 차선으로 send 버퍼 사이즈를 늘리는거죠.

이렇게 늘리면 왜 send 버퍼 사이즈가 입력이 되는걸까요?
thisnome의 이미지

Quote:

커널 send 버퍼의 크기가 정해져 있는데
네트워크가 느린 관계로 아직 전송을 하지 않은 상태에서
어플에서 계속적으로 커널의 send 버퍼에 데이타를 넣다 보면..
커널의 send 버퍼 사이즈가 부족하기 떄문에 데이타가 솔실된다는
얘기이지요?

send 버퍼 사이즈가 꽉 찬 상태라면 커널 버퍼의 데이타가 손실된다기 보다는 write 에서 에러를 리턴하지 않나요?
application 에서 에러처러된 데이타를 버리지만 않는다면, 데이타 손실은 없을것 같은데요..

ssehoony의 이미지

Anonymous wrote:

이렇게 늘리면 왜 send 버퍼 사이즈가 입력이 되는걸까요?

이 질문은 send 버퍼 사이즈를 지정 할 수 있는 옵션이 있냐는 의미인가요? vector 처럼 사이즈를 자동으로 늘리지 않나? 하는 건가요?

그 이유는 고정 사이즈 버퍼에 비해 vector 가 갖는 단점과 동일 할 듯 합니다. 버퍼가 자동으로 증가할 때 새로운 버퍼를 만들고 이전 버퍼에 있는 데이터를 복사해 오는 오버헤드가 문제일 듯 하네요.
send buf 사이즈를 제어할 필요가 있는 이유는 네트워크 상황에 따라 버퍼를 크게 해야 최상의 통신 속도를 얻을 수 있는데 너무 크케 잡으면 메모리의 낭비가 발생하는 단점이 있어서 네크워크 상태에서 따라 개발자가 적절하게 제어할 수 있도록 하기 위함 일 듯 합니다. 보통 send 버퍼의 사이즈는 rtt 시간과 mss 을 고려해서 사이즈 결정하는게 되는데, 인생사가 수학처럼 딱딱 떨어지는 것이 아니라서 rtt는 근거리 통신이 아닌 이상 때에 따라 값의 변동이 심하다는 것이 문제이고, "rtt 가 일정하다" 하더라도 client 에 따라 rtt 값이 서로 다르니 그것또한 문제이지요. 그나마 요즘은 서버 메모리가 많이 커져서 send buf 를 크게 잡는데 크게 무리가 없다는게 그나마 위안이 되는군요.

thisnome wrote:

send 버퍼 사이즈가 꽉 찬 상태라면 커널 버퍼의 데이타가 손실된다기 보다는 write 에서 에러를 리턴하지 않나요?
application 에서 에러처러된 데이타를 버리지만 않는다면, 데이타 손실은 없을것 같은데요..

네 write 함수의 리턴 값으로 알 수 있고, app 에서 이를 감지하여 보내지 못 한 부분은 별도로 보관하고 있다가 select() 의 write 이벤트를 감지하여 write 가 가능할때 이전에 보관해 두었던 데이터를 송신하는게 최선입니다.
하지만 이건의 이미 구현되어진 덩치큰 데몬의 구조상 이를 적용하기 힘들때 차선책으로 버퍼사이즈를 늘리는 선택을 하는 것도 좋다. 라는 것이지요.
처음부터 데몬을 만드신다면 write 이벤트를 이용하여 구현하는게 좋지요. 시스템콜 횟수도 감소하고 소켓 버퍼에 의해 불필요하게 소모되는 메모리량도 줄일 수 있고, 에러 핸들링도 app 개발자가 제어 할 수 있는 여러가지 좋은 점이 있습니다. (물론 처음 네크워크 서버를 만드시는 분이라는 약간의 복잡도가 증가한다는 것이 부담으로 다가올 수 있지만요.)

이전에 "ACE의 장점이 뭐냐?" 에 대한 쓰레드가 있었는데, ACE의 장점중에 하나가 ACE의 프레임워크 규칙에 맞춰 코딩을 하면 위와 같은 문제가 다 해결이 되는 구조로 작성이 된다는 것 일듯 하군요. (하지만 전 ACE 보다 libevent 를 더 좋아합니다. :) )

댓글 달기

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