본 포스트는 기본적인 python socket API를 살펴본 후 이 API들을 사용하여
non-blocking socket 설정 방법 및 서버 클라이언트 예제 코드를 싣고 있다.
socket APIs
소켓 라이브러리는 아래와 같이 불러올 수 있다.
import socket
예제 코드에서 사용된 socket의 API는 다음과 같다.
socket 생성 및 연결
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
소켓을 생성 한다.
family
- AF_INET, AF_INET6, AF_UNIX, AF_CAN, AF_PACKET, or AF_RDS.
type
- SOCK_STREAM, SOCK_DGRAM, SOCK_RAW
proto
- family가 AF_CAN인 경우 CAN_RAW,CAN_BCM, CAN_ISOTP
fileno
- 지정된 경우 파일 디스크립터로부터 자동으로 socket의 family, type,
proto 값을 결정한다.
socket.bind(address)
소켓에 주소를 바인딩한다.
socket.listen([backlog])
서버 소켓에서 클라이언트의 연결을 기다린다.
backlog은 허용되지 않는 연결 수를 지정한다. 만약 지정하지 않으면, 자동으로
적당한 값이 지정된다.
socket.accept()
클라이언트의 연결을 받아들인다. 연결된 socket와 주소를 반환한다.
socket.connect(address)
주어진 주소로 연결을 시도한다. address는 (host,port)로 지정한다.
socket mode
socket.setblocking(flag)
소켓을 blocking 또는 non-blocking 모드로 설정한다. flag가 False인 경우
non-blocking모드.
non-blocking 모드일 경우 BlockingIOError exception 처리가 필요하다.
try:
data = conn.recv(1024)
except BlockingIOError:
return None
socket.settimeout(value)
blocking 소켓에서 timeout을 결정한다. 단위는 초.
timeout을 설정한 경우 socket.timeout exception 처리가 필요하다.
try:
conn, addr = self.sock.accept()
return conn
except socket.timeout:
return None
데이터 보내기/받기
socket.send(bytes[, flags])
socket.sendall(bytes[, flags])
연결된 소켓으로 데이터를 전송한다. socket.send는 보내진 데이터의
길이를 반환한다. socket.sendall은 주어진 모든 데이터를 전송하고, 만약 실패할
경우 exception이 발생한다.
socket.sendto(bytes, address)
socket.sendto(bytes, flags, address)
주어진 주소로 데이터를 전송한다.
socket.recv(bufsize[, flags])
socket.recvfrom(bufsize[, flags])
소켓으로 수신된 데이터를 읽어온다. socket.recvfrom은 데이터를 보내온 소켓의
주소도 (bytes, address) 형식으로 함께 반환한다.
[note] 소켓 송수신 데이터는 bytes-like object가 사용된다.
bytes-like object와 str은 아래와 같이 변환할 수 있다.
'bytes-like object'.decode() -> str
'str'.encode() -> bytes-like
object
Socket API는 아래 사이트에서 확인할 수 있다.
https://docs.python.org/3.7/library/socket.html
non-blocking socket example code
예제는 서버와 클라이언트 연결 후 10회 'ping'/'pong'데이터를 주고받고 마지막엔
'done'을 보내 종료하는 시나리오를 가지고 있다.
서버 예제 코드
import time
import socket
class ServerSocket(object):
def __init__(self, port=8000,waittimeout=5):
self.host = 'localhost'
self.port = port
self.sock =
socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.sock.settimeout(waittimeout)#sec
self.sock.bind((self.host,self.port))
self.sock.listen()
def waitforclient(self):
try:
conn, addr = self.sock.accept()
print('accept :',addr)
#set non-blocking mode
conn.setblocking(False)
return conn
except socket.timeout:
return None
def close(self):
self.sock.close()
def __enter__(self):
return self.waitforclient()
def __exit__(self, *args):
self.close()
def recv(conn, len):
try:
data = conn.recv(1024)
except BlockingIOError:
return None
except socket.timeout:
return None
return data.decode()
def send(conn, data):
conn.send(data.encode())
def handle_client(conn):
terminate_cmd = False
while True:
data = recv(conn, 1024)
if data is not None:
if len(data) == 0:
print('connection
closed')
conn.close()
return terminate_cmd
else:
if data == 'ping':
print('recv: ping -> send: pong')
send(conn,'pong')
elif data == 'done':
terminate_cmd = True
conn.close()
print('recv: terminate cmd\nclose connection')
return
terminate_cmd
else:
time.sleep(1)
return terminate_cmd
def server_main1():
run = True
server = ServerSocket(waittimeout=60)
while run:
conn = server.waitforclient()
if conn:
terminate = handle_client(conn)
if terminate:
run = False
else:
time.sleep(1)
server.close()
del server
def server_main2():
with ServerSocket(waittimeout=60) as conn:
if conn is None:
return
handle_client(conn)
if __name__ == "__main__":
#server_main1()
server_main2()
server_main1(), server_main2() 방식 모두 사용 가능하다.
accept후 연결된 소켓은 socket.setblocking을 사용하여 non-blocking 모드로
설정하였다.
exception 핸들링과 bytes-like object 변환을 위해 send/recv 함수를 만들어 함수
내부에서 exception 처리와 인코딩/디코딩을 수행하도록 하였다.
클라이언트 예제 코드
import socket
import time
def connect(sock, host, port):
try:
sock.connect((host,port))
except socket.timeout:
return False
return True
def send(sock, data):
#a bytes-like object is required, not 'str'
sock.send(data.encode())
def recv(sock,size,timeout_ms):
while timeout_ms > 0:
try:
data = sock.recv(size)
if data is not None:
return data.decode()
except BlockingIOError:
timeout_ms -= 1
time.sleep(0.001)
return None
def client_main():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if sock is None:
print('create sock fail!')
return
if connect(sock,'localhost',8000) is False:
print('sock.connect fail!')
return
sock.setblocking(False)
for _ in range(10):
#send ping
send(sock,'ping')
print('send: ping')
#wait for recv
data = recv(sock,1024,1000*20)
if data:
print('recv:',data)
else:
print('recv fail')
time.sleep(1)
send(sock,'done')
print('send: done')
time.sleep(1)
sock.close()
if __name__ == "__main__":
client_main()
클라이언트는 서버와 연결 후 socket.setblocking을 사용하여 non-blocking 모드로
설정하였으며, recv 함수에서 timeout을 두어 주어진 시간 동안 수신 데이터가
있는지 확인하도록 코드를 구성했다.
실행 결과
서버와 클라이언트의 실행 결과는 아래와 같다. 통신이 잘 이루어지는 것을 볼 수
있다.
서버
accept : ('127.0.0.1', 58556)
recv: ping -> send: pong
recv: ping -> send: pong
recv: ping -> send: pong
recv: ping -> send: pong
recv: ping -> send: pong
recv: ping -> send: pong
recv: ping -> send: pong
recv: ping -> send: pong
recv: ping -> send: pong
recv: ping -> send: pong
recv: terminate cmd
close connection
클라이언트
send: ping
recv: pong
send: ping
recv: pong
send: ping
recv: pong
send: ping
recv: pong
send: ping
recv: pong
send: ping
recv: pong
send: ping
recv: pong
send: ping
recv: pong
send: ping
recv: pong
send: ping
recv: pong
send: done
댓글
댓글 쓰기