19 Nov 2017
|
디자인패턴
Single Thread Execution 패턴
Single Thread Execution 패턴은 공통의 오브젝트에 대해 다수의 Thread가 작업을 할 때, 동시에 작업을 하는 것이 아니라 한 번에 하나의 Thread가 대상 오브젝트를 사용할 수 있도록 하는 패턴입니다.
하나의 Thread만 처리가능한 영역을 Critical Section 또는 Critical Region이라고 부릅니다.
Java에서는 synchronized 키워드를 이용해서 쉽게 Critical Section을 만들 수 있습니다.
예제
public class CommonObject {
private int id = 0;
private String name = "name";
private String address = "address";
public synchronized void setAttribute(int id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
public synchronized String toString() {
return "(id: " + id + ", name: " + name + ", address: " + address + ")";
}
}
위와 같은 CommonObject 클래스의 인스턴스는 동시에 여러 개의 Thread가 접근하더라도 각 함수에 대해 오직 하나의 Thread만 접근을 허용합니다.
synchronized와 Lock
만약 특정 Thread가 synchronized 메소드를 실행하게 되면 해당 Thread는 Lock을 획득(Aquire)하게 됩니다. 그리고 해당 메소드를 빠져나가게 되면 Lock을 해제(Release)합니다.
Lock는 각 인스턴스마다 존재합니다. 만약 여러 개의 인스턴스를 생성했을 경우 Lock은 각각의 인스턴스마다 존재하게 됩니다.
주의점
Single Thread Execution 패턴은 다음과 같은 주의점이 있습니다.
- 복수의 Thread간 데드락(Dead Lock)이 발생할 가능성이 높습니다.
- 해당 클래스를 상속할 경우 서브 클래스에 의해 안정성이 무너질 가능성이 존재합니다.
- Lock은 Cost가 많이 드는 작업입니다. 여러 Thread에 의해 충돌이 날 문제가 없는 경우는 Lock을 사용하지 않는 것이 더 좋습니다.
- Thread간 충돌이 발생해서 성능 저하가 됩니다. 하나의 Thread가 Lock을 점유해서 다른 Thread들이 작업을 대기하고 있는 것을 충돌(Conflict)라고 합니다. Lock을 너무 많이 사용하게 될 경우 Multi-Thread 환경의 장점이 사라지고 단일 Thread를 사용한 것보다도 더 안 좋은 성능을 낼 수도 있습니다.
19 Nov 2017
|
용어
네트워크
TCP/IP 프로토콜에 대한 이해
TCP/IP 프로토콜 개념은 1973년 LAN 영역과 WAN 영역이 등장하면서 나왔습니다. 그 이후 인터넷 공간의 확장, 대역폭의 확대 및 기술의 발전으로 다양한 기능들이 추가되었습니다.
TCP/IP 방식의 계층적 구조
현재 TCP/IP 프로토콜은 4계층으로 이루어진 구조로 되어 있지만, 일반적으로 설명할 때는 아래의 5계층 구조로 설명하고 있습니다.
| 계층 구분 |
프로토콜 종류 |
| 응용 |
FTP, SSH, Telnet, SMTP, DNS, DHCP, HTTP, POP3, SNMP, SSL 등 |
| 전송 |
TCP, UDP |
| 네트워크 |
IP, ICMP, IGMP, ARP, RARP |
| 데이터 링크 |
이더넷, PPP 등 |
| 물리 |
|
TCP와 UDP
버퍼링(Buffering)과 단편화(Fragment) 유무에 따라 TCP/IP 프로토콜의 전송 방식을 TCP와 UDP 방식으로 나누고 있습니다. 버퍼링과 단편화가 있을 경우 TCP 방식이라고 하며, 없는 경우는 UDP 방식이라고 합니다.
버퍼링은 데이터를 전송하는 동안 일시적으로 해당 데이터를 메모리에 보관하는 것을 말하며, 단편화는 전송 데이터를 분할한다는 뜻입니다. 즉, TCP 방식에서는 해당 데이터를 여러 개의 조각으로 나누어서 전송합니다.
UDP 방식은 버퍼링이 없기 때문에 데이터 송신자와 수신자 사이에 상호 제어 절차가 존재하지 않습니다. 즉, 데이터가 발생하는 즉시 전송이 되며, 따라서 UDP 통신을 비연결형 전송이라고 부르고 있습니다.
TCP 방식의 경우 버퍼링과 단편화가 있기 때문에 송신자와 수신자간 상호 제어 절차가 필요합니다. 수신자의 데이터 수신 가능 여부를 묻기도 하며, 데이터의 전송 여부를 확인해서 오류가 발생한 경우 데이터 재전송도 해줍니다.
포트 번호(Port Number)
포트 번호는 TCP/IP 응용 계층의 수많은 프로토콜 및 서비스에 대한 고유 식별 번호입니다. 16비트로 이루어져 있기 때문에 0번부터 65,535번까지 존재합니다. 0번부터 1,023번까지는 ‘Well-Known Port Number’라고 부르며 주로 서버측에서 사용합니다. 1,024번 이후의 포트 번호는 주로 클라이언트 측에서 사용합니다.
| 구분 |
명칭 |
비고 |
| 0 ~ 1,023 |
Well-Known Port Number |
주로 서버에서 사용 |
| 1,024 ~ 49,151 |
등록 포트 번호 |
주로 클라이언트에서 사용 |
| 49,152 ~ 65,535 |
사설 또는 동적 포트 번호 |
주로 클라이언트에서 사용 |
18 Nov 2017
|
Python
Thread 기반의 TCP Server/Client
Thread 기반으로 동작하는 TCP Server/Client 예제입니다.
Common.constant.py
IP_ADDRESS = '127.0.0.1'
PORT = 10034
server_main.py
from TcpServer import tcpServer
if __name__ == '__main__':
print('TCP Server starts.')
while True:
print('<Menu>')
print(' 1. Start Server')
print(' 2. Stop Server')
print(' 99. Finish')
menu = input('Input the menu: ')
# print(' --> Your input is {}'.format(menu))
if menu == '1':
tcpServer.start_server()
elif menu == '2':
tcpServer.stop_server()
elif menu == '99':
print("Exit !!!")
tcpServer.stop_server()
break
TcpServer.tcpServer.py
import threading
import socketserver
import Common.constant as constant
TAG = 'TcpServer'
stdout_lock = threading.Lock()
class TcpServerHandler(socketserver.BaseRequestHandler):
def handle(self):
with stdout_lock:
print('Client({}) is connected.'.format(self.client_address[0]))
sock = self.request
while True:
buffer = sock.recv(1024).strip()
if not buffer:
sock.close()
with stdout_lock:
print('Client({}) is disconnected.'.format(self.client_address[0]))
break
received_message = str(buffer, encoding='utf-8')
with stdout_lock:
print('Received: {}'.format(received_message))
class TcpServerThread(threading.Thread):
socketserver.TCPServer.allow_reuse_address = True
server = None
def __init(self):
pass
def run(self):
if not self.server is None:
self.server.shutdown()
self.server = None
self.server = socketserver.TCPServer(('', constant.PORT), TcpServerHandler)
with stdout_lock:
print('TcpServerThread is started.')
print('Listening on port({})'.format(constant.PORT))
self.server.serve_forever()
with stdout_lock:
print('TcpServerThread is terminated.')
def stop(self):
self.server.shutdown()
self.server = None
server_thread = None
def start_server():
stop_server()
global server_thread
server_thread = TcpServerThread()
server_thread.start()
def stop_server():
global server_thread
if not server_thread is None:
server_thread.stop()
server_thread.join()
server_thread = None
tcp_client.py
from TcpClient import tcpClient
if __name__ == '__main__':
print('TCP Client starts.')
while True:
print('<Menu>')
print(' 1. Connect to Server')
print(' 2. Send a message to Server')
print(' 9. Disconnect from Server')
print(' 99. Finish')
menu = input('Input the menu: ')
# print(' --> Your input is {}'.format(menu))
if menu == '1':
tcpClient.connect()
elif menu == '2':
tcpClient.send_message('hello. snowdeer')
elif menu == '9':
tcpClient.disconnect()
elif menu == '99':
break
TcpClient.tcpClient.py
import threading
import time
import socket
import Common.constant as constant
from Common.log import Log as log
TAG = 'TcpClient'
class TcpClientThread(threading.Thread):
sock = None
def __init(self):
pass
def run(self):
log.i(TAG, 'Start TcpClientThread.')
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind(('127.0.0.1', 0))
self.sock.connect((constant.IP_ADDRESS, constant.PORT))
def stop(self):
self.sock.close()
def send_message(self, message):
buffer = bytes(message, encoding='utf-8')
self.sock.send(buffer)
client_thread = None
def connect():
disconnect()
global client_thread
client_thread = TcpClientThread()
client_thread.daemon = True
client_thread.start()
def disconnect():
global client_thread
if not client_thread is None:
client_thread.stop()
client_thread.join()
client_thread = None
def send_message(message):
try:
client_thread.send_message(message)
except:
print("Error to send a message !!")
18 Nov 2017
|
Python
Custom Logging 클래스
리눅스에서 사용하기 위한 Custom Logging 클래스를 간단하게 작성해보았습니다. 안드로이드에서의 로그 메소드들과 비슷하게 사용할 수 있도록 변경했습니다.
log.py
import logging
import logging.handlers
class Log:
__logger = logging.getLogger('SnowLog')
__logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s > [%(levelname)s] %(message)s')
fileHandler = logging.FileHandler('./log.txt')
fileHandler.setFormatter(formatter)
__logger.addHandler(fileHandler)
@classmethod
def d(cls, tag, message):
cls.__logger.debug('[' + tag + '] ' + message)
@classmethod
def i(cls, tag, message):
cls.__logger.info('[' + tag + '] ' + message)
@classmethod
def w(cls, tag, message):
cls.logger.warning('[' + tag + '] ' + message)
@classmethod
def e(cls, tag, message):
cls.logger.error('[' + tag + '] ' + message)
@classmethod
def c(cls, tag, message):
cls.logger.critical('[' + tag + '] ' + message)
리눅스에서 로그 확인 할 때
사실 로그를 콘솔창이 아닌 파일로 출력하도록 한 건 로그 백업의 목적도 있지만, 파이썬의 경우 콘솔창으로 출력을 하게 될 경우 콘솔에서의 입력인 input() 메소드와 매끄럽게 동작하지 않아서였습니다. 단일 Thread의 경우는 문제가 없지만 복수의 Thread에서 로그를 출력할 경우 출력시마다 input() 버퍼의 내용을 지워버리는 문제가 있었습니다.
그래서 파일로 로그를 남길 경우 새로운 터미널을 하나 열고 다음 명령어를 입력해서 로그를 실시간으로 확인할 수 있습니다.
tail -f [파일명]
ex) tail -f log.txt
17 Nov 2017
|
Python
logging 모듈
Python에서는 로그 출력을 위한 logging 모듈을 제공합니다. 아주 간단히 사용할 수 있으며, print 함수 등을 통해 콘솔창에 지저분하게 출력하는 것보다 logging 모듈을 사용하는 것을 추천합니다.
로그 레벨
Python에서 로그 레벨은 다음과 같습니다. 안드로이드 등에서와 거의 동일합니다.
- DEBUG
- INFO
- WARNING
- ERROR
- CRITICAL
간단한 logging 모듈 사용 예제
import logging as log
log.basicConfig(filename='./log.txt', level=log.DEBUG)
if __name__ == '__main__':
log.debug('debug')
log.info('info')
log.warning('warning')
log.error('error')
log.critical('critical')
출력 결과는 다음과 같습니다.
DEBUG:root:debug
INFO:root:info
WARNING:root:warning
ERROR:root:error
CRITICAL:root:critical
basicConfig 함수를 통해서 로그 파일의 이름을 지정할 수 있고, filemode 속성의 값을 a, w 등으로 지정할 수 있습니다. a의 경우 기존 파일에 이어서 기록(Append)하며, w의 경우 기존 파일 내용을 삭제하고 새로 작성합니다.
콘솔창과 파일에 동시에 로그 남기기
import logging
import logging.handlers
log = logging.getLogger('snowdeer_log')
log.setLevel(logging.DEBUG)
fileHandler = logging.FileHandler('./log.txt')
streamHandler = logging.StreamHandler()
log.addHandler(fileHandler)
log.addHandler(streamHandler)
if __name__ == '__main__':
log.debug('debug')
log.info('info')
log.warning('warning')
log.error('error')
log.critical('critical')
import logging
import logging.handlers
log = logging.getLogger('snowdeer_log')
log.setLevel(logging.DEBUG)
formatter = logging.Formatter('[%(levelname)s] (%(filename)s:%(lineno)d) > %(message)s')
fileHandler = logging.FileHandler('./log.txt')
streamHandler = logging.StreamHandler()
fileHandler.setFormatter(formatter)
streamHandler.setFormatter(formatter)
log.addHandler(fileHandler)
log.addHandler(streamHandler)
if __name__ == '__main__':
log.debug('debug')
log.info('info')
log.warning('warning')
log.error('error')
log.critical('critical')
출력 결과는 다음과 같습니다.
[DEBUG] (log_test.py:19) > debug
[INFO] (log_test.py:20) > info
[WARNING] (log_test.py:21) > warning
[ERROR] (log_test.py:22) > error
[CRITICAL] (log_test.py:23) > critical
| 이름 |
포맷 |
설명 |
| asctime |
%(asctime)s |
날짜 시간, 밀리세컨드까지 출력. ex) 2017.11.17 12:31:45,342 |
| created |
%(created)f |
생성 시간 출력 |
| filename |
%(filename)s |
파일명 |
| funcnName |
%(funcName)s |
함수명 |
| levelname |
%(levelname)s |
로그 레벨(DEBUG, INFO, WARNING, ERROR, CRITICAL) |
| levelno |
%(levelno)s |
로그 레벨을 수치화해서 출력(10, 20, 30, …) |
| lineno |
%(lineno)d |
소스의 라인 넘버 |
| module |
%(module)s |
모듈 이름 |
| msecs |
%(msecs)d |
로그 생성 시간에서 밀리세컨드 시간 부분만 출력 |
| message |
%(message)s |
로그 메시지 |
| name |
%(name)s |
로그 이름 |
| pathname |
%(pathname)s |
소스 경로 |
| process |
%(process)d |
프로세스(Process) ID |
| processName |
%(processName)s |
프로세스 이름 |
| thread |
%(thread)d |
Thread ID |
| threadName |
%(threadName)s |
Thread Name |
파일의 최대 용량 및 새로운 파일 생성하는 방법
RotatingFileHandler 함수를 이용해서 파일의 최대 크기 및 새로운 파일을 생성할 때 최대 개수를 지정해줄 수 있습니다. 아래와 같은 코드를 이용해서 최대 10MB씩, 총 20개의 로그 파일을 만들어서 순환하도록 할 수 있습니다.
log_max_size = 10 * 1024 * 1024
log_file_count = 20
fileHandler = logging.handlers.RotatingFileHandler(filename='./log.txt', maxBytes=log_max_size,
backupCount=log_file_count)
logging.handlers
위에서는 FileHandler와 StreamingHandler 및 RotatingFileHandler 등을 사용했었는데 이외에도 다양한 핸들러들이 존재합니다.
| 핸들러 |
설명 |
| SocketHandler |
외부 로그 서버로 소켓을 통해 전송 |
| DatagramHandler |
UDP 통신을 통해 외부 서버로 전송 |
| SysLogHandler |
Unix 류의 syslog 데몬에게 로그 전송 |
| SMTPHandler |
메일로 로그 전송 |
| HTTPHandler |
HTTP를 통해 로그 전송 |