python socket API 와 non-blocking socket server client example code

 본 포스트는 기본적인 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

댓글

이 블로그의 인기 게시물

간단한 cfar 알고리즘에 대해

바로 프로젝트 적용 가능한 FIR Filter (low/high/band pass filter )를 c나 python으로 만들기

windows에서 간단하게 크롬캐스트(Chromecast)를 통해 윈도우 화면 미러링 방법

CA-CFAR 예제 코드

python asyncio를 이용한 async socket server client example code