2009년 12월 16일

2009년 11월 12일

telnet 으로 메일 보내기


telnet 프로그램은 원하는 서버와 포트로의 tcp 연결을 지원한다.

예를 들어, $ telnet www.naver.com 80 이라고 입력하면 해당서버의 80번 포트로 TCP 연결을 맺는다.

이러한 기능을 이용하여 SMTP(25번) 서버에 접속하여 메일을 보내고,
간단하게 송신자 조작도 해보자. 친구에게 대통령으로부터 메일이 온거 같은 착각을 하도록...

친구의 메일 주소를 확보한다.
mynaverfriend@naver.com 이라고 하자.
naver.com 의 메일서버의 IP 주소를 알아야 한다. nslookup을 이용한다.

-----------------------------------
nslookup  <-- 입력

Default Server:  fns1.hananet.net
Address:  210.94.0.7

> set type=mx <-- 입력
naver.com <-- 입력
Server:  fns1.hananet.net
Address:  210.94.0.7

Non-authoritative answer:
naver.com       MX preference = 10, mail exchanger = mx2.naver.com
naver.com       MX preference = 10, mail exchanger = mx3.naver.com
>
-----------------------
위 정보로부터 @naver.com으로 메일을 발송하면 mx2 or mx3.naver.com으로 들어간다는 것을 알 수 있다.

이제 telnet을 사용할 때가 되었다.
(파란색 부분이 실제 입력하는 부분임)
-----------------------------------------------------------

$ telnet mx3.naver.com 25
Trying 202.131.27.102...
Connected to mx3.naver.com.
Escape character is '^]'.
220 rcvmail6.nm2.naver.com ESMTP Terrace MailWatcher 5.40.2008012515 (for naver.com)
ehlo hi
250-rcvmail6.nm2.naver.com Pleased to meet you
250-SIZE 20480000
250-8BITMIME
250-HELP
250-PIPELINING
250 ENHANCEDSTATUSCODES
mail from: <president@cwd.go.kr>
250 2.1.0 Sender Ok
rcpt to: <mynaverfriend@naver.com>
250 2.1.0 Recipient Ok
data
354 Start mail input; end with "."
From: 대한민국대통령 <president@cwd.go.kr>
To: <mynaverfriend@naver.com>
Subject: 대통령으로부터...
                                             <=== 여기에 반드시 빈줄이 필요하다. 이 이후부터가 메일 내용이 된다.
You are a good man.
Thanks a lot.


.    <=== 여기에 점을 하나 찍었다. 이 점으로 내용의 끝을 알려준다.(점찍고 엔터치고...)
250 2.5.0 Message accepted for delivery
quit   <=== 종료하겠다는 의미로...
221 2.0.0 rcvmail6.nm2.naver.com Service closing transmission channel
Connection closed by foreign host.
-------------------------------------------------------------------------------------

실제로 메일 잘 도착했는지 확인해보자.
아래 그림과 같이 대통령으로부터 메일이 온 것처럼 보인다.









SMTP와 메일의 MIME Type을 좀 공부하면 쉽게 조작 메일을 보낼 수 있다.



2009년 11월 6일

ssh 터널링을 이용하여 외부서버에서 내부서버로 파일 복사하기(자동 백업하기)


목적: 외부 파일을 회사내의 윈도우 PC에 정기적으로 백업하기

먼저, 다른 필요에 의해 윈도우XP안에 guest OS로 우분투를 설치했다.
vmware player를 이용하면 어렵지 않게 윈도우안에 우분투를 설치할 수 있다.
그런 다음에 윈도우 안에 백업디렉토리르 생성하고 우분투에서 접근할 수 있게 마운트 해둔다.
그러기 위해서는 윈도우XP안에 공유폴더를 생성하고 (여기서는 share 라는 이름을 사용) smbfs를 이용하여 마운트를 수행한다.
) $ mount -t smbfs -o user=win_user_name -o rw //192.168.88.2/share /mnt/win_share
윈도우 공유폴더 시에 접근권한 준 사용자의 패스워드를 입력하면 아래와 같이 마운트에 성공한다.
위와 같이 192.168.88.2는 우분투에서 보면 자신의 Parent OS이다. 우분투에서만 접근 가능한 내부 IP이다.
(실제 윈도우 IP는 따로 있음)

$df h
(중략)
//192.168.88.2/share  181G  106G   76G  59% /mnt/win_share

시스템 구성은 아래와 같다.

윈도우(guest OS: 우분투) --- 외부서버 (우분투 터미널에서 외부서버에 ssh 터널링으로 접속한다.)
다음의 명령어를 사용한다.
$ ssh 외부서버IP -R 3336/localhost/22

이렇게해서 외부서버에 로그인하게 되면 외부서버에서 localhost 접근으로 3336 포트로 ssh 로그인을 하면 내부의 우분투로 접근할 수 있다.

, 외부서버에서 ssh p 3336 localhost 라고 입력하면 우분투 로그인 화면을 만날 것이다.

성공이 되면 서로간의 key 교환을 통해 패스워드 없이 로그인 되도록 설정하자. (~/.ssh/authorized_keys 파일을 이용하면 됨)

여기까지 성공하면 이제 외부서버에 다음과 같은 명령으로 윈도우의 share 폴더에 파일을 복사할 수 있다.

$ scp P 3336 myTestFile1 localhost:/mnt/win_share/

본인은 이러한 방법으로 외부서버에 생성한 ebs mp3 파일을 회사의 PC로 매일 백업 받고 있다.




2009년 10월 26일

비블록(non-block) 모드의 connect()는 상당히 복잡하다.

실제로 연결이 성공 되었는지 여부를 나중에 확인해야 하기 때문이다. 블록 모드 소켓에서 connect()는 연결이 완료되거나, 연결이 될 수 없음(에러)이 확실이 정해지면 리턴된다.

비블록 소켓은 연결이 완료되기를 기다려주지 않는다. 비블록 모드에서 가장 쉽게 구현되는 경우는 connect()를 호출하자마자 즉시 연결이 완료되고, 유효한 소켓을 생성하는 경우이다. 그러나, 이러한 상황은 거의 발생하지 않는다. 비블록 모드에서 connect()는 연결이 완료되지 않더라도 즉시 리턴된다. 따라서, 이 함수가 리턴되었을 때에는 반드시 먼저 리턴값을 체크해야 한다.

리턴값이 0이면 블록 모드의 소켓의 연결과 동일한 상황(거의 발생하지 않음)이 된 것이다.
이 경우에는 sockfd를 유효한 소켓으로 간주하고 구현하면 된다. 문제는 이 함수의 리턴값이 (-1)인 상황이다. 이 경우에는 반드시 errno 값을 검사해야 한다.

connect() 함수의 파라미터값들에 문제가 없다면 conect() 함수는 연결을 시도하게 되고, 연결의 성공여부와 관계 없이 리턴된다. 리턴값이 (-1)이고, errno가 EINPROGRESS 라면, 연결 작업이 이루어지고 있는 상태에서 connect() 함수가 리턴된 것이다.

비블록 모드의 소켓에서는 연결 완료와 무관하게 바로 리턴되는 방식으로 동작한다. 이 경우에는 어느 정도의 시간이 흐른 후에 실제로 연결이 완료 되었는지를 검사해야 한다.

연결 완료를 검사하는 몇 가지 방법이 존재하지만, 비교적 간단한 방법은 sockfd 소켓에 데이터를 전송할 수 있는, "쓰기가능" 상태인지를 검사하는 것이다. 쓰기가능 상태를 검사하려면 epoll() 또는 select()를 이용할 수 있다.

이 함수에 sockfd 값을 등록하면 쓰기가능이 되었을 때 바로 알려준다.(소켓이벤트 처리기법)

2009년 10월 17일

Linux epoll에서 Listen 소켓의 처리

epoll에서 Listen 소켓에 대하여...
 
edge trigger(default)로 등록해서 사용할 경우에, edge는 여러개가 동시에 오더라고 이벤트가

저장되지 않으므로(epoll man페이지에서 그렇다고 했다.) Listen 소켓은 level 트리거로 등록해야 한다.

또는, Non-blocking 소켓이므로 epoll에 등록하지 않고, accept()함수에서 이벤트가 발생하거나 할일이 없을 때, aceept()를 호출하는 방법도 있다.

Listen 소켓을 edge 트리거로 등록하여 접속 이벤트를 놓치는 버그가 발생하지 않도록 하자~~~
 

 

자체 제작 epoll 라이브러리(echo server 예제 포함)


C로 만든 echo server 이다. ( 소스:  sjang_epoll_lib.zip )
최고 성능이라고 평가받는 epoll()를 사용하였다.
accept()를 사용하는 서버소켓은 Level Trigger를 사용하고, 클라이언트와 연결된 소켓은 Edge Trigger를 사용하였다. Non-blocking 소켓을 다루는 기본 방법에 대해서도 첨부된 예제를 통해 배울 수 있다.

첨부된 파일은 epoll를 다루는 라이브러리를 제작하고, 그것을 사용하는 예제로 echo server를 제작한 것이다. 라이브러리를 통해 epoll에서 소켓을 어떻게 등록하고 해제하는지 알 수 있다.

아래에 echo server의 구현 코드를 보였다.

여기서 몇몇 에러처리가 빠져있는데, Non-blocking 소켓의 read에서 EAGIN 에러처리와 소켓 연결이 끊기는 이벤트를 감시하지 않은 것, 그리고 read 시에 버퍼를 넘었을 경우에 수신한 내용을 다른 곳에 저장하고 계속 read 해야 하는 것 등등...
(Edge Trigger를 사용할 경우에는 read 시에 모두 읽어와야 한다. 왜냐하면, 다시 이벤트가 온다는 보장이 없으므로 이벤트가 왔을 때 모두 Read 해야 한다.)

그러나, non-blocking 에서의 write 방법에 대해서는 정확하게 구현하였다.
다음의 내용처럼....
(non-blocking 소켓에서 write()를 수행할 경우에 write 하고자 하는 length만큼
write가 되지 않을 수 있음을 알아야 합니다.
예를 들어, 100바이트를 보내기 위해 write() 함수를 호출했을 때, 100바이트를 다 보내지 못하고 리턴됩니다. 리턴될 때, 보낸 length를 반드시 체크해야 합니다. 아마도 대부분의 경우에 100이 리턴되겠지요. 하지만, 네트웍이 바쁘거나 상태가 좋지 않거나 접속이 많은 경우에는 100보다 작은 숫자가 리턴됩니다. 원하는 바이트길이만큼 보내지 못한 것이지요. 이럴때에는 어떻게 해야할까요? 다시 write()를 바로 호출해야할까요? 그렇게 처리할 수도 있지만 바로 직전에 왜 100바이트를 모두 전송하지 못했는지를 이해한다면 그렇게 처리하는 것이 바람직하지 않습니다. 송신버퍼에 100바이트를 채울 여유가 없기때문에 100보다 작은 숫자가 리턴되는 것이기때문에 바로 다시 write()를 호출한다고해서 남은 바이트가 바로 전송된다는 보장이 없습니다.
write()처리를 이벤트를 통해서 해야하는 이유가 여기에 있습니다. 100바이트를 모두 보내지 못했을 때, 그 소켓의 writable를 계속 체크해서 그 이벤트가 오면(송신버퍼에 빈공간이 생기면) 그 시점에 write()를 호출하면 되는 것이죠.
따라서, non-blocking의 read/wirte는 소켓 이벤트의 결과에 따라 일괄적으로 처리를 해주어야 합니다.)

write를 이벤트로 처리해야함은 필수사항이다. 대부분의 epoll 예제가 이 부분을 따로 처리하지 않았지만, 이는 네트웍상태가 좋지 않거나 클라이언트가 바쁘거나 등등의 이유로 원하는 크기를 전송할 수 없는 경우에 필수적인 에러처리이다.
(스티븐 아저씨의 책에서도 명확하게 나와있음)

라이브러리 및 예제에서 사용한 것들...

1. TAILQ: 리눅스에서 기본제공하는 큐이다. 이것을 이용해서 echo 데이터 저장큐로 사용하였다. man page를 보면 친절한 예제와 함께 설명이 있다.
2. myapp.h: 이 헤더파일과 echo_server.[ch] 파일을 함께 보기 바란다. 라이브러리를 사용하여 구현이 쉽도록 하기 위해서 echo 뿐만 아니라, 다른 자료구조를 도입하기 쉽도록 void * 를 이용하여 어플리케이션 원하는 자료구조를 쉽게 구성할 수 있게 하였다.
3. 최대처리수: 최대 클라이언트 수를 소스안에 define를 통해 설정하게 하였다. 물론, 시스템 설정이 지원하는 최대개수 이내만 가능하다.

아쉬운 점....
doxygen를 이용하여 소스코드 문서를 만들어서 올릴 필요가 있겠다.. ^^


echo_server.c 소스

1 #include "epollio.h"
2 #include "socket_util.h"
3 #include "myapp.h"
4
5 #include 
6 #include 
7 #include 
8 #include 
9 #include 
10 #include 
11 #include 
12
13 #include "echo_server.h"
14
15
16 #define MAX_CLIENT 1000
17
18 int recv_count = 0;
19 int send_count = 0;
20
21 static void reset_myapp_data(myapp_data_t *myapp_data)
22 {
23 echo_data_t *echo_data = (echo_data_t *)myapp_data->data;
24
25 close(myapp_data->socket_fd);
26 myapp_data->socket_fd = -1;
27
28 memset(echo_data->client_ip, 0, sizeof(echo_data->client_ip));
29 memset(echo_data->in_msg, 0, sizeof(echo_data->in_msg));
30 memset(echo_data->out_msg, 0, sizeof(echo_data->out_msg));
31 echo_data->in_msg_len = 0;
32 echo_data->out_msg_len = 0;
33 echo_data->sent_len = 0;
34 }
35
36
37 void init_myapp_data_members(myapp_data_t *myapp_data)
38 {
39 reset_myapp_data(myapp_data);
40 }
41
42
43 int init_myapp_data(myapp_data_t **mydata, int max_client)
44 {
45 /* myapp_data_t *myapp_temp;*/
46 /* echo_data_t *echo_temp;*/
47 int i;
48
49 for(i = 0; i < max_client; i++) 50 { 51 if ((mydata[i] = (myapp_data_t *)malloc(sizeof(myapp_data_t))) == NULL) 52 { 53 return -1; 54 } 55 56 /* 응용 데이터 공간을 queue 공간의 데이터 포인터에 할당 */ 57 if ((mydata[i]->data = (echo_data_t *)malloc(sizeof(echo_data_t))) == NULL)
58 {
59 return -1;
60 }
61 /* mydata[i]->data = (echo_data_t *)echo_temp;*/
62 /* printf("myapp data Pointer [%x]\n", mydata[i]);*/
63 /* printf("echo data Pointer [%x]\n", mydata[i]->data);*/
64
65 mydata[i]->socket_fd = -1;
66 init_myapp_data_members(mydata[i]);
67
68 /* 확보한 공간을 Queue 안에 넣는다. */
69 if (i == 0)
70 {
71 TAILQ_INSERT_HEAD(&myapp_data_event_queue, mydata[i], mydata_next);
72 }
73 else
74 {
75 TAILQ_INSERT_TAIL(&myapp_data_event_queue, mydata[i], mydata_next);
76 }
77 }
78 return 0;
79 }
80
81 static int recv_msg(int fd, echo_data_t *echo_data)
82 {
83 int recv_len;
84 int buf_len = sizeof(echo_data->in_msg) - 1;
85 recv_len = recv(fd, echo_data->in_msg, buf_len, 0);
86 if (recv_len < 0) 87 { 88 /* non-blocking 소켓이므로 여기서 errno 체크해야함 */ 89 return -1; 90 } 91 if (recv_len == 0) /* 끊어진 소켓 */ 92 { 93 return -1; 94 } 95 echo_data->in_msg_len = recv_len;
96 recv_count++;
97 printf("in msg = [%s]\n", echo_data->in_msg);
98 printf("in msg len = [%d]\n", echo_data->in_msg_len);
99
100 return 0;
101 }
102
103 static int send_msg(int fd, echo_data_t *echo_data)
104 {
105 int sent_len;
106
107 printf("send buf = [%s]\n", echo_data->out_msg + echo_data->sent_len);
108 sent_len = send(fd, echo_data->out_msg + echo_data->sent_len, echo_data->out_msg_len - echo_data->sent_len, 0);
109 if (sent_len < 0) 110 { 111 printf("send_msg error\n"); 112 return -1; 113 } 114 printf("sent len = [%d]\n", sent_len); 115 116 echo_data->sent_len += sent_len;
117
118 return 0;
119 }
120
121
122 int echo_server_process(struct epoll_event *event)
123 {
124 myapp_data_t *myapp_data;
125 echo_data_t *echo_data;
126
127 myapp_data = (myapp_data_t *)(event->data.ptr);
128 /* printf("echo server myapp_data process Pointer [%x]\n", myapp_data); */
129 echo_data = (echo_data_t *)(myapp_data->data);
130 /* printf("echo server echo_data process Pointer [%x]\n", echo_data); */
131
132 if (event->events & EPOLLIN)
133 {
134 if (recv_msg(myapp_data->socket_fd, echo_data) < 0) 135 { 136 printf("recv msg error\n"); 137 return -1; 138 } 139 printf("in msg copy [%s]\n", echo_data->in_msg);
140 strncpy(echo_data->out_msg, echo_data->in_msg, echo_data->in_msg_len);
141 printf("out msg [%s]\n", echo_data->out_msg);
142 echo_data->out_msg_len = echo_data->in_msg_len;
143
144 /* reset read buf */
145 memset(echo_data->in_msg, 0, sizeof(echo_data->in_msg));
146 echo_data->in_msg_len = 0;
147
148 printf("writable on\n");
149 /* writable check로 변경. */
150 if (set_event_socket(&ed, myapp_data->socket_fd, EVENT_WRITE, myapp_data, FD_MODIFY) < 0) /* echo server 이므로 EVENT_READ가 설정되어야 한다. */ 151 { 152 fprintf(stderr, "epoll set insertion error: fd=%d", myapp_data->socket_fd);
153 return -1;
154 }
155 }
156 else if (event->events & EPOLLOUT)
157 {
158 if (send_msg(myapp_data->socket_fd, echo_data) < 0) 159 { 160 printf("recv msg error\n"); 161 return -1; 162 } 163 164 printf("out msg len = [%d]\n", echo_data->out_msg_len);
165 if(echo_data->out_msg_len == echo_data->sent_len)
166 {
167 /* reset write buf */
168 memset(echo_data->out_msg, 0, sizeof(echo_data->out_msg));
169 echo_data->out_msg_len = 0;
170 echo_data->sent_len = 0;
171
172 send_count++;
173
174 printf("readable on\n");
175 /* readable check로 변경. 즉, 받은데이터를 모두 echo 하기 전에 다시 받지 않음 */
176 if (set_event_socket(&ed, myapp_data->socket_fd, EVENT_READ, myapp_data, FD_MODIFY) < 0) 177 { 178 fprintf(stderr, "epoll set insertion error: fd=%d", myapp_data->socket_fd);
179 return -1;
180 }
181 }
182 }
183 return 0;
184 }
185
186 int main()
187 {
188 int nfds;
189 int listener;
190 int client_fd;
191 struct sockaddr_in local;
192 socklen_t addrlen;
193 int n;
194
195
196
197 myapp_data_t *new_myapp_data;
198
199
200 myapp_data_t *myapp_data[MAX_CLIENT]; /* 현재 응용의 처리를 위한 데이터 구조체의 공간 확보 */
201
202 TAILQ_INIT(&myapp_data_event_queue);
203
204 if (init_myapp_data(myapp_data, MAX_CLIENT) < 0)
205 {
206 printf("init echo data error\n");
207 return -1;
208 }
209
210 if ( init_epoll(&ed, 10000) < 0 )
211 {
212 printf("epoll_init error!\n");
213 return -1;
214 }
215
216 addrlen = sizeof(local);
217
218 listener = make_socket(10025);
219 if (listener < 0)
220 {
221 return -1;
222 }
223
224 if (add_epoll_listen_socket(&ed, listener, EVENT_READ) < 0 )
225 {
226 return -1;
227 }
228
229 while(1)
230 {
231 printf("--------------------------\n");
232 printf("recv count = [%d]\n", recv_count);
233 printf("send count = [%d]\n", send_count);
234 printf("--------------------------\n");
235 nfds = epoll_event_monitor(&ed, -1);
236 if (nfds < 0)
237 {
238 return -1;
239 }
240 printf("returned event [%d]\n", nfds);
241
242 for(n = 0; n < nfds; ++n)
243 {
244 if(ed.events[n].data.fd == listener)
245 {
246 new_myapp_data = (myapp_data_t *)get_new_mydata();
247 client_fd = accept(listener, (struct sockaddr *) &local, &addrlen);
248 if(client_fd < 0)
249 {
250 perror("accept");
251 continue;
252 }
253
254 /* printf("new_myapp_data Pointer [%x]\n", new_myapp_data);*/
255 if (set_event_socket(&ed, client_fd, EVENT_READ, new_myapp_data, FD_ADD) < 0) /* echo server 이므로 EVENT_READ가 설정되어야 한다. */
256 {
257 fprintf(stderr, "epoll set insertion error: fd=%d", client_fd);
258 return -1;
259 }
260 }
261 else
262 {
263 /* printf("ed event Pointer 1[%x]\n", &ed.events[n]);*/
264 if (echo_server_process(&ed.events[n]) < 0)
265 {
266 /* delete 추가 */
267 myapp_data_t *myapp_data_temp = (myapp_data_t *)ed.events[n].data.ptr;
268 reset_myapp_data(myapp_data_temp);
269 /* 다시 queue로 반환한다. */
270 TAILQ_INSERT_TAIL(&myapp_data_event_queue, myapp_data_temp, mydata_next);
271 }
272 }
273 }
274 }
275 return 0;
276 }
277


내용수정!!
스티븐 아저씨의 책에서 Non-blocking I/O에 대한 부분을 다시한번 읽었다. write()처리에 대한 것이다.

select()를 이용한 구현을 다시보니, read 후에, 바로 write 비트를 켜서 select로 다시 루프를 돌지 않고 바로 write를 시도했다. 물론, write length를 체크해서 다시 write 할 필요가 있는지 검사하고 더 있다면 write bit를 계속 켜두게(reset하고 다시 켜기)하고 있다.

이 부분의 구현은 구현자 마음이라는 코멘트가 있었다. 바로 write 하지 않고 비트만 켜서 select를 통해 처리하는 것과(이 방법은 루프한번의 오버헤드가 있다.) read 후에 바로 write를 수행하고 더 보낼게 남았으면 write 비트를 키는 방식이 있다고 했다. 물론, Writable를 체크하지 않고 쓰기때문에 write() 콜에서 EWOULDBLOCK를 발생(소켓에 쓰기를 받아줄 공간이 없는상태)할 수 있다.

어느 방법을 이용하든 구현자의 마음이다. 무작정 write를 한번 시도해보고 더 보낼게 있을때에만 write 비트를 키는 방법도 괜찮은거 같다.

소켓에서 발생하는 이벤트 감지의 기준. edge trigger와 level trigger

두 트리커의 차이점은 소켓버퍼에 데이터가 있는 경우에 그것을 소켓 이벤트로 간주하는 기준이다.

먼저, 쉬운것부터...

레벨트리거: 소켓버퍼에 데이터가 들어있으면 무조건 이벤트가 발생하는 트리거이다. 즉, 소켓에서 read 할 수 있는 데이터가 1바이트 이상 있을때 이벤트가 발생했다고 리턴해준다. 여기서 1바이트라고 했지만 그 기준을 소켓옵션설정을 통해 조정할 수 있는 것으로 알고 있다. 이것을 조정하면 10바이트 이상일 경우에만 이벤트가 발생하는 것으로 기준을 설정할 수도 있다. (select(), poll() 등이 레벨트리거에 속한다.)

에지트리거: 소켓버퍼의 데이터가 들어오는 시점을 알려주는 이벤트 트리거이다. 소켓버퍼가 비어 있다가 상대방으로부터 버퍼에 데이터가 들어오면 이때 이벤트가 발생한 것으로 간주한다. 들어온 데이터를 어플리케이션에서 read 하고 안하고는 무관하다. 즉, 에지트리거에서 이벤트가 발생했고(데이터가 들어왔고), 어플리케인션에서 read 하지 않더라도 그 이후에 다른 데이터가 추가로 들어오면 에지트리거는 이벤트가 발생했다고 리턴하게 된다. (epoll(), kqueue() 등이 에지트리거에 속한다. 이것들은 설정에 따라 레벨트리거로도 사용할 수 있다.)

일반적으로.... 에지트리거가 대량의 접속시에 더 나은 응답속도를 제공한다.
(이벤트 처리가 빠르다.)

이해를 돕기 위해서 그림하나 추가해 본다.





에지트리거를 다룰 때 주의점:
에지트리거를 통해 이벤트 받았고 read 작업을 해야하는데, 이 시점에 read 할 수 있는 모든 데이터를 read 해야한다. 왜냐하면, 에지트리거는 데이터 유입에 대한 이벤트이므로 추가로 데이터 유입이 없으면 이벤트가 오지 않으므로 이번 이벤트를 통해 read를 할 때 다음에 다시 이벤트가 온다는 보장이 없으므로 이 시점에 읽을 수 있는 모든 데이터를 read 해야만 정상적인 처리가 된다. 어플리케이션의 read 버퍼가 작아서 꽉찬 상태로 리턴되면 이것을 다른 버퍼에 저장해 두고, 나머지를 다시 소켓으로부터 read 해야한다. 더 읽을 것이 없을때까지 계속 read 해야 한다. 이번에 read를 다 하지 않으면 영원히 다시 read할 이벤트가 오지 않을 수 있기 때문이다.

키보드 마우스 공유 프로그램

하나의 키보드와 마우스로 여러 컴퓨터에서 동시에 사용할 수 있게 해주는 프로그램이다.

대표적으로...

synergy2: http://synergy2.sourceforge.net/
input director: http://www.inputdirector.com/

synergy2는 2006년 이후로 버전업이 없는 상태. 더 이상 기능추가가 필요없는 모양인데...

input director는 계속 버전업이 되고 있고...

두 가지를 모두를 사용해 본 경험으로는...

input director가 설정이 직관적이고, 사용하기 쉽고 다양하게 접근관리를 할 수 있어서 좋으나,

공유하려는(slave computer) 컴퓨터가 유동 ip(회사에서 무선랜으로 동적ip를 할당받을 때)일 경우에는 사용이 안되는 거 같다. 설정하려 잠시 노력해 보았으나.... 포기..

반면에 synergy2는 약간의 버그는 있으나, 클라이언트-서버로 동작하므로 클라이언트가 유동ip이어도

서버를 찾기만 하면 서버PC의 키보드와 마우스를 공유할 수 있게 된다.

그러나, 설정이 직관적이진 않다.

고정ip를 사용(집에서 공유기로 내부고정ip로 할당하는 경우)하는 경우에는 input director를 추천한다.

input director는 윈도우만 지원하지만 synergy는 윈도우,리눅스,mac도 지원한다.

2009년 9월 2일

[perl] access to mysql DB with perl DBI

 

#!/usr/bin/perl

 

#use strict;

use DBI;

 

my $cache_hit_days = 8;

 

my $base_id = "0000000000";

 

my $conf_file = "/etc/mig.conf";

my $installdir_key;

my $installdir_value;

 

open(MIG_CONF, $conf_file) || die "$conf_file is not valid\n";

while (<MIG_CONF>)

{

        chomp;

        ($installdir_key, $installdir_value) = split /=/, $_, 2;

        if ($installdir_key eq "InstallDir")

        {

#              print "$installdir_key = [$installdir_value]\n";

               last;

        }

}

my $first;

$first = substr($installdir_value, -1, 1);

#print "$first\n";

$first = substr($installdir_value, -2, 1);

#print "$first\n";

$first = substr($installdir_value, -3, 1);

#print "$first\n";

$first = substr($installdir_value, 0, 1);

#print "$first\n";

$first = substr($installdir_value, 1, 1);

#print "$first\n";

$first = substr($installdir_value, 2, 1);

#print "$first\n";

 

my $input_image_dir = $installdir_value."/input";

my $output_image_dir = $installdir_value."/output";

 

#my $image_sid = "12345678";

#my $conv_image_sid = "abcedfg";

 

#              my $image_sid_1 = substr($image_sid, -1, 1);

#              my $image_sid_2 = substr($image_sid, -2, 1);

#              my $image_sid_3 = substr($image_sid, -3, 1);

#              my $image_sid_4 = substr($image_sid, -4, 1);

#              my $original_image_filename = $input_image_dir."/".$image_sid_1."/".$image_sid_2."/".$image_sid_3."/".$image_sid_4."/".$image_sid;

 

 

#                      my $conv_image_sid_1 = substr($conv_image_sid, 0, 1);

#                      my $conv_image_sid_2 = substr($conv_image_sid, 1, 1);

#                      my $conv_image_sid_3 = substr($conv_image_sid, 2, 1);

#                      my $conv_image_sid_4 = substr($conv_image_sid, 3, 1);

#                      my $conv_image_filename = $output_image_dir."/".$conv_image_sid_1."/".$conv_image_sid_2."/".$conv_image_sid_3."/".$conv_image_sid_4."/".$conv_image_sid;

 

#print "ori = [$original_image_filename], conv = [$conv_image_filename]\n";

#exit;

 

 

# 현재 시각을 구한다.

#my $today_time;

my $cache_out_time;

my $day;

my $month;

my $year;

my $hour;

my $minutes;

my $seconds;

 

#($seconds, $minutes, $hour, $day, $month, $year) = (localtime)[0, 1, 2, 3, 4, 5];

($seconds, $minutes, $hour, $day, $month, $year) = localtime(time() - $cache_hit_days*(60*60*24));

#($day, $month, $year) = (localtime)[3, 4, 5];

$year += 1900;

$month += 1;

if ( $month < 10 )

{

    $month = "0".$month;

}

if ( $day < 10 )

{

    $day = "0".$day;

}

if ( $hour < 10 )

{

    $hour = "0".$hour;

}

if ( $minutes < 10 )

{

    $minutes = "0".$minutes;

}

if ( $seconds < 10 )

{

    $seconds = "0".$seconds;

}

#$today_time = $year."-".$month."-".$day." ".$hour.":".$minutes.":".$seconds;

$cache_out_time = $year."-".$month."-".$day." ".$hour.":".$minutes.":".$seconds;

#print "time = $cache_out_time\n";

#exit;

 

my $dbh = connect_to_db();

my $sth;

delete_cached_images($dbh);

disconnect_db($dbh);

 

 

sub connect_to_db

{

    my $server = 'my.host.com';

#    my $server = '1.2.3.4';

    my $db = 'DB_NAME';

    my $username = 'ID';

    my $password = 'PW';

 

    my $dbh = DBI->connect("dbi:mysql:$db:$server", $username, $password) or die print "connect error\n";

 

    return $dbh;

}

 

sub disconnect_db

{

    my $dbh = $_[0];

    $sth->finish();

    $dbh->disconnect;

}

 

sub delete_cached_images

{

 

# SELECT sid FROM source_image WHERE etime <= CURRENT_TIMESTAMP

# DELETE FROM source_image WHERE etime <= CURRENT_TIMESTAMP

# UPDATE conv_image b SET expired = 'Y' WHERE b.source_image_sid = ( SELECT a.sid FROM source_image a WHERE a.sid = ? )

 

    my $dbh = $_[0];

    my $db_query;

    my $row;

        my $count = 0;

#    $db_query = "SELECT sid FROM source_image WHERE etime <= '$today_time'";

#       $db_query = "SELECT sid FROM source_image WHERE htime <= '2006-11-25 00:30:00'";

    $db_query = "SELECT sid FROM source_image WHERE htime <= '$cache_out_time' and htime != '0000-00-00 00:00:00'";

#    $db_query = "SELECT sid FROM source_image WHERE ctime > '2007-01-18 18:00:00' and ctime <= '2007-01-18 19:00:00' and htime != '0000-00-00 00:00:00'";

#       $db_query = "UPDATE conv_image b SET expired = 'Y' WHERE b.source_image_sid = ( SELECT a.sid FROM source_image a WHERE a.sid = '10')";

        #print "query = $db_query\n";

    $sth = $dbh->prepare($db_query);

    $sth->execute();

        while($row = $sth->fetchrow_arrayref)

        {

               $count++;

###            print "----------------------------------------------------[$count]\n";

        #my @row = $sth->fetchrow_array;

       

               #print "row[$i] = $row[$i] \n";

               #print "sid = $row->[0] \n";

 

               my $image_sid = $row->[0];

 

               # DB에서 삭제

###            print "image_sid = [$image_sid]\n";

        my $db_query_delete = "DELETE FROM source_image WHERE sid = '$image_sid'";

        my $sth_delete = $dbh->prepare($db_query_delete);

        $sth_delete->execute();

 

               my $db_query_update = "UPDATE conv_image SET expired = 'Y' WHERE source_image_sid = $image_sid";

                my $sth_update = $dbh->prepare($db_query_update);

        $sth_update->execute();

 

               my $image_sid_length = length($image_sid);

               my $image_sid_ori = $image_sid;

 

               my $i = 0;

 

               # 10 original image의 파일이름 길이

               if ($image_sid_length <= 10)

               {

                       for ($i = $image_sid_length; $i < 10;  $i++)

                       {

                              $image_sid = "0".$image_sid;

                       }

               }

#              print "original image sid $image_sid\n";

 

 

               # original 이미지 파일 삭제

               my $image_sid_1 = substr($image_sid, -4, 1);

               my $image_sid_2 = substr($image_sid, -3, 1);

               my $image_sid_3 = substr($image_sid, -2, 1);

               my $image_sid_4 = substr($image_sid, -1, 1);

               my $original_image_filename = $input_image_dir."/".$image_sid_1."/".$image_sid_2."/".$image_sid_3."/".$image_sid_4."/".$image_sid;

###            print "original image delete $original_image_filename\n";

               system("rm $original_image_filename");

 

 

#              print "ori_image_sid = $image_sid_ori\n";

               # Conv 이미지 파일 삭제

               my $db_query_get_scode = "SELECT s_code, filetype FROM conv_image WHERE source_image_sid = $image_sid_ori";

#              print"query = $db_query_get_scode\n";

        my $sth_get_scode = $dbh->prepare($db_query_get_scode);

        $sth_get_scode->execute();

               while($row = $sth_get_scode->fetchrow_arrayref)

               {

                       my $conv_image_sid = $row->[0];

                       my $conv_image_ext = $row->[1];

#                      print "conv image sid = $conv_image_sid\n";

 

                       my $conv_image_sid_1 = substr($conv_image_sid, 0, 1);

                       my $conv_image_sid_2 = substr($conv_image_sid, 1, 1);

                       my $conv_image_sid_3 = substr($conv_image_sid, 2, 1);

                       my $conv_image_sid_4 = substr($conv_image_sid, 3, 1);

                       my $conv_image_filename = $output_image_dir."/".$conv_image_sid_1."/".$conv_image_sid_2."/".$conv_image_sid_3."/".$conv_image_sid_4."/".$conv_image_sid.".".$conv_image_ext;

###                    print "conv image delete $conv_image_filename\n";

                       system("rm $conv_image_filename");

 

               }

        }

        print "deleted Image count = [$count]\n";

 

}

[Linux] URL Encoding table for special characters

 

Use this URL Encoding table for special characters:

Character      Code    Character      Code

      %26%23169;     t       %74

�      %26%23174;     u       %75

      %E2%84%A2      v       %76

backspace      %08     w       %77

tab     %09     x       %78

linefeed       %0A     y       %79

creturn %0D     z       %7A

space   %20     {       %7B

!       %21     |       %7C

"       %22     }       %7D

#       %23     ~       %7E

$       %24           %A2

%       %25           %A3

&       %26           %A5

'       %27     |       %A6

(       %28     §      %A7

)       %29     ≪      %AB

*       %2A           %AC

+       %2B     ?       %AD

,       %2C     º      %B0

-       %2D     ±      %B1

.       %2E     ª      %B2

/       %2F     ,       %B4

0       %30     μ      %B5

1       %31     ≫      %BB

2       %32     ¼      %BC

3       %33     ½      %BD

4       %34     ¿      %BF

5       %35     A`      %C0

6       %36     A´     %C1

7       %37     A^      %C2

8       %38     A~      %C3

9       %39     A¨     %C4

:       %3A     A°     %C5

;       %3B     Æ      %C6

<       %3C     C¸     %C7

=       %3D     E`      %C8

>       %3E     E´     %C9

?       %3F     E^      %CA

@       %40     E¨     %CB

A       %41     I`      %CC

B       %42     I´     %CD

C       %43     I^      %CE

D       %44     I¨     %CF

E       %45     Р     %D0

F       %46     N~      %D1

G       %47     O`      %D2

H       %48     O´     %D3

I       %49     O^      %D4

J       %4A     O~      %D5

K       %4B     O¨     %D6

L       %4C     Ø      %D8

M       %4D     U`      %D9

N       %4E     U´     %DA

O       %4F     U^      %DB

P       %50     U¨     %DC

Q       %51     Y´     %DD

R       %52     Þ      %DE

S       %53     ß      %DF

T       %54     a`      %E0

U       %55     a´     %E1

V       %56     a^      %E2

W       %57     a~      %E3

X       %58     a¨     %E4

Y       %59     a°     %E5

Z       %5A     æ      %E6

[       %5B     c¸     %E7

\       %5C     e`      %E8

]       %5D     e´     %E9

^       %5E     e^      %EA

_       %5F     e¨     %EB

`       %60     i`      %EC

a       %61     i´     %ED

b       %62     i^      %EE

c       %63     i¨     %EF

d       %64     ð      %F0

e       %65     n~      %F1

f       %66     o`      %F2

g       %67     o´     %F3

h       %68     o^      %F4

i       %69     o~      %F5

j       %6A     o¨     %F6

k       %6B     ÷      %F7

l       %6C     ø      %F8

m       %6D     u`      %F9

n       %6E     u´     %FA

o       %6F     u^      %FB

p       %70     u¨     %FC

q       %71     y´     %FD

r       %72     þ      %FE

s       %73     y¨     %FF

 

 

[Linux] 리눅스에서 램디스크 생성(how to make ram disk in linux)

Creating a Linux ramdisk

 

While performing some testing a few weeks ago, I needed to create a ramdisk on one of my redhat AS 4.0 servers. I knew Solaris supported tmpfs, and after a bit of googling was surprised to find that Linux supported the tmpfs pseudo-file system as well. To create a ramdisk on a Linux host, you first need to find a suitable place to mount the tmpfs file system. For my tests, I used mkdir to create a directory valled /var/ramdisk:

 

$ mkdir /var/ramdisk

 

Once the mount point is identified, you can use the mount command to mount a tmpfs file system on top of that mount point:

 

$ mount -t tmpfs none /var/ramdisk -o size=28m

 

 

[perl] 파일의 생성시간과 현재 시간을 비교

#!/usr/bin/perl

 

 

use strict;

 

my $conv_imgae_root_dir = "/home/sjang/mydir1";

my $ori_imgae_root_dir = "/home/sjang/mydir2";

 

# expire time: 14days

my $expire_seconds = 10080*60;

 

my $current_time = time;

my $current_hour;

 

while(1)

{

        $current_hour = get_hour();

        if ($current_hour eq "04") # every dat 04:xx

        {

               # start to check...

               # print "$current_hour, 4 si\n";

               check_expire($conv_imgae_root_dir);

               check_expire($ori_imgae_root_dir);

        }

        # per hour

        sleep 60*60;

}

 

sub check_expire()

{

        my $check_dir = $_[0];

        my @files;

 

        #print "start ...$check_dir \n";

 

        opendir(DIR, $check_dir);

        @files = readdir(DIR);

        closedir(DIR);

         

         

        # build a unsorted list from the

        # # @files array:

 

        my $file;

        foreach $file (@files)

        {

               my $working_dir;

               my $ab_file;

               next if ($file eq "." or $file eq "..");

#              print "- $check_dir\/$file\n";

               $ab_file = "$check_dir/$file";

               if (-d $ab_file)

               {

#                      print "working dir = $ab_file\n";

                       check_expire($ab_file);

               }

               else

               {

                       my $file_time = get_file_time($ab_file);

                       my $diff_time = $current_time - $file_time;

                       if ( $diff_time > $expire_seconds)

                       {

#                             print "file remove = $ab_file\n";

                              system("rm $ab_file");

                       }

               }

        }

}

 

sub get_file_time()

{

        my $filename = $_[0];

        my $mtime;

        $mtime = (stat($filename))[9];

        return $mtime;

}

 

sub get_hour()

{

        my $today_time;

        my $day;

        my $month;

        my $year;

        my $hour;

        my $minutes;

        my $seconds;

 

        ($seconds, $minutes, $hour, $day, $month, $year) = (localtime)[0, 1, 2, 3, 4, 5];

        #($day, $month, $year) = (localtime)[3, 4, 5];

        $year += 1900;

        $month += 1;

        if ( $month < 10 )

        {

               $month = "0".$month;

        }

        if ( $day < 10 )

        {

               $day = "0".$day;

        }

        if ( $hour < 10 )

        {

               $hour = "0".$hour;

        }

        if ( $minutes < 10 )

        {

               $minutes = "0".$minutes;

        }

        if ( $seconds < 10 )

        {

               $seconds = "0".$seconds;

        }

 

        return $hour;

}