파이썬 소켓 통신

Python

Posted by kwon on 2019-11-10

파이썬 소켓 통신

이번 한이음 공모전에서 서버로 Django를 사용하고 있는데 안드로이드와 유니티 사이에 리모컨과 같은 통신 기능을 구현하기 위해 안드로이드와 장고 사이에는 기존에 사용하던 http통신을 유지하고 장고와 유니티(C#)간의 통신을 소켓을 이용하여 구현해보기로 하였다.

이전에 공개S/W개발자 대회에서 안드로이드와 라즈베리파이간의 통신을 구현할 때는 Polling방식을 통해 라즈베리파이가 지속적으로 서버에 요청을 보내 데이터베이스 값을 읽어 변화한 값이 있는 경우 그에 따른 동작을 수행하도록 구현하였었고 이번에 파이썬을 통해 소켓을 처음 사용해보기 때문에 간단히 공부한 내용을 리뷰해보자.

소켓이란?

소켓은 네트워크 상에서 돌아가는 두 개의 프로그램 간 양방향 통신의 하나의 엔드포인트이다. 소켓은 포트 번호에 바인딩되어 TCP레이어에서 데이터가 전달되어야 하는 어플리케이션을 식별할 수 있게 한다.

엔드 포인트

여기서 엔드 포인트아이피 주소포트번호의 조합을 의미한다. 모든 TCP연결은 2개의 엔드 포인트로 유일하게 식별되어질 수 있다. 따라서 클라이언트와 서버 간 여러 개의 연결이 맺어질 수도 있다.

소켓 객체

  • socket() 함수는 첫 번째 인자로 Address Family(AF)와 Socket Type(Enum형태의 Int값)을 받는다.
  • socket() 함수 인자 Address Family와 Socket Type의 기본값은 각각 AF_INET, SOCKET_STREAM이다.
  • 아래부터는 이해를 돕기 위해 서버 소켓(요청 수신 및 응답)과 클라이언트 소켓(요청 송신)으로 나누어 설명한다.

서버 소켓

  • 예시로 서버 소켓은 대기 소켓(수신 소켓)과 실제 통신을 담당하는 소켓(응답 소켓 또는 반환 소켓)으로 설정한다.
  • 서버 소켓은 연결 요청을 대기하다가 연결을 수락하는 경우 새로운 Socket 객체를 반환한다.
  • 실제 외부와의 통신은 여기서 반환된 새로운 Socket객체를 통해 통신한다.

socket.bind(address) - 소켓 맵핑

  • 생성한 소켓에 고유한 호스트와 포트를 매핑한다.
  • 인자로 address(호스트와 포트 정보)를 튜플 형태로 전달받는다. ex) socket.bind(HOST, PORT)
  • Socket 객체(프로그램 인터페이스)에 고유한 네트워크 IP자원(호스트와 포트)를 맵핑함으로써 프로그램 인터페이스와 네트워크 지원을 연결시킨다.

socket.listen() - 연결 요청 대기 상태 설정

  • 소켓은 생성된 이후 연결 요청 대기를 한 이후에만 연결이 가능하므로 소켓 맵핑 후에는 반드시 연결 요청 대기 상태를 설정해야 한다.
  • 연결 대기 상태는 오로지 대기(listen)만 할 뿐 실제 연결이 성립되면 새로운 소켓을 반환한다.

socket.accept() - 연결 승낙 후 실제 통신 소켓 반환

  • 연결 요청 대기 중인 소켓은 socket.accept()를 사용하여 연결을 승낙하고 연결이 성립된 새로운 소켓과 주소정보를 반환한다.
  • 실제 외부와의 통신은 여기서 생성된 새로운 소켓을 이용한다.

socket.close() - 연결 요청 대기 종료

  • socket.close()를 사용할 경우 해당 소켓은 종료된다.

클라이언트 소켓

  • 클라이언트 소켓은 서버 소켓과 달리 오로지 클라이언트 소켓 하나로 구성된다.

socket.connect(address) - 서버 소켓에 연결 요청

  • socket.connect()를 사용하여 서버 소켓에 연결 요청을 보낸다.
  • 인자로 address(연결할 소켓의 호스트와 포트 정보)를 튜플 형태로 전달 받는다.
  • 파이썬 3.5버전 이후에는 연결이 종료된 경우 InterruptedError에러나 socket.timeout 없이 대기 상태로 전환된다.

서버 소켓과 클라이언트 소켓의 통신

서버 소켓과 클라이언트 소켓 간 데이터 송수신을 설명한다.

socket.send(byte), socket.sendall(byte) - 데이터 송신

  • 클라이언트 소켓에서 서버 소켓으로 데이터를 전송할 때는 socket.send() 혹은 socket.sendall()을 이용한다.
  • 인자인 byte는 송신할 데이터를 의미한다.
  • socket.send()socket.sendall()은 기본적으로 같은 역할을 하지만 sendall()의 경우는 전송이 완료된 데이터의 바이트 수를 리턴한다.

socket.recv(bufsize) - 데이터 수신

  • 데이터를 수신할 때 사용되며 수신한 데이터(바이트 객체)를 반환한다.
  • 인자인 bufsize는 한 번에 수신할 수 있는 최대 데이터 크기를 의미한다.

통신 예제

통신을 위해 server에 해당하는 파일과 client에 해당하는 2개의 파이썬 파일을 작성한다.

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from socket import *
from select import *

HOST = ''
PORT = 10000
BUFSIZE = 1024
ADDR = (HOST, PORT)

# 소켓 생성
serverSocket = socket(AF_INET, SOCK_STREAM)

# 소켓 주소 정보 할당
serverSocket.bind(ADDR)
print('bind')

# 연결 수신 대기 상태
serverSocket.listen(100)
print('listen')

# 연결 수락
clientSocekt, addr_info = serverSocket.accept()
print('accept')
print('--client information--')
print(clientSocekt)

# 클라이언트로부터 메시지를 가져옴
while True:
data = clientSocekt.recv(65535)
print('recieve data : ',data.decode())
msg = data.decode()
if msg == 'exit': # exit라는 메세지를 받으면 종료
break;

# 소켓 종료
clientSocekt.close()
serverSocket.close()
print('close')

client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

from socket import *
from select import *
import sys
from time import ctime

HOST = '127.0.0.1'
PORT = 10000
BUFSIZE = 1024
ADDR = (HOST,PORT)

clientSocket = socket(AF_INET, SOCK_STREAM) # 서버에 접속하기 위한 소켓을 생성한다.

try:
clientSocket.connect(ADDR) # 서버에 접속을 시도한다.

except Exception as e:
print('%s:%s' % ADDR)
sys.exit()

print('connect is success')

while True:
sendData = input("input data : ")
clientSocket.send(sendData.encode())

로컬에서 서버에 데이터를 계속해서 보내고 서버는 받은 데이터를 출력하며 exit라는 문자열을 받으면 종료하도록 구성하였다. 간단하게 실행 화면을 보자.

cmd에서 해당 파일의 위치로 이동하여 python server.py 로 먼저 서버 파일을 실행하고 클라이언트 파일을 실행하여 데이터를 송수신한다.

위는 서버의 실행화면이고 클라이언트측에서 보낸 데이터를 출력하는 것을 볼 수 있다.

위는 클라이언트 실행화면이고 데이터를 전송하는 모습을 볼 수 있다.

추가로 조금 정리하자면 파이썬에서 다른 언어로 된 소켓 서버에 전송하는 경우 그 언어가 읽어들이는 방식에 따라 라인을 단위로 읽어들이는 경우 아래와 같이 개행문자를 추가로 붙여서 인코딩하여 보내주면 문제가 해결될 수 있다.

1
2
sendData = input("input data : ") + "\n"
clientSocket.send(sendData.encode())

참조
https://m.blog.naver.com/nonamed0000/221259426463
https://djangoworld.tistory.com/12