클라이언트에서 비트맵 이미지와 추가로 2개의 데이터( Height, Width )를 전송할 때 이를 받고 통신할 수 있는 서버를 파이썬으로 구현했습니다.
테스트하기 위해 임시로 파이썬을 통해 클라이언트를 만들었구요. 실제로는 안드로이드가 클라이언트 역할을 할 예정입니다.
비트맵 이미지를 주고 받기 위해 클라이언트에서 바이트 형식으로 변환해서 주면, 서버에서는 필요에 따라 이를 바이트로 가지고 있거나, 이미지로 복원시키는 것이 가능합니다.
전체 구조는 아래와 같습니다.
(client_ex.py는 연습용으로 만들었던거라 무시하셔도 됩니다.)
클라이언트에 해당하는 client.py와 전송할 비트맵 이미지인 test.bmp가 client 폴더에 위치해 있습니다.
(test.bmp 이미지 입니다.)
서버는 총 4가지 파일로 이루어져있는데요. 우선 서버를 실행할 main.py와 서버 실행을 계속 유지시키기 위해 스레드를 구성한 나머지 파일로 이루어져 있습니다.
main.py
1 2 3 4 5 6 7 | import tcpServer import executer andRaspTCP = tcpServer.TCPServer("192.168.43.134", 4000) andRaspTCP.start() #commandExecuter = executer.Executer(andRaspTCP) | cs |
IP는 현재 자신의 ip, 포트는 자신이 원하는 곳으로 설정해주시면 됩니다. 저는 4000으로 했구요.
아래 주석으로 처리된 부분은 나중에 서버가 받은 데이터를 다시 클라이언트에 재전송해줄 때 사용하는데, 지금은 그 부분까지는 하지 않을 것이기 때문에 주석처리 했습니다.
tcpServer.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 | import socket, threading import tcpServerThread class TCPServer(threading.Thread): def __init__(self, HOST, PORT): threading.Thread.__init__(self) self.HOST = HOST self.PORT = PORT self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.serverSocket.bind((self.HOST, self.PORT)) self.serverSocket.listen(1) self.connections = [] self.tcpServerThreads = [] def run(self): try: while True: print( 'tcp server :: server wait...') connection, clientAddress = self.serverSocket.accept() self.connections.append(connection) print("tcp server :: connect :", clientAddress) subThread = tcpServerThread.TCPServerThread(self.tcpServerThreads, self.connections, connection, clientAddress) subThread.start() self.tcpServerThreads.append(subThread) except: print("tcp server :: serverThread error") def sendAll(self, message): try: self.tcpServerThreads[0].send(message) except: pass | cs |
실제로 클라이언트와 서버가 접속이 진행되는 부분입니다. ip나 포트 등 접속에 문제가 있으면 error가 뜰 것이며, 잘 접속된다면 서버 터미널에서 connect와 클라이언트의 접속경로가 출력됩니다.
나중에 클라이언트가 구현되고나서, 서버가 실행된 이후에 클라이언트를 실행했을 때 서버 터미널의 출력 결과는 아래와 같습니다.
또한 우리는 클라이언트가 접속을 종료해도 서버를 계속 유지할 것이기 때문에, 스레드를 활용하게 됩니다.
tcpServerThread.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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | import socket, threading from PIL import Image import os import io pr = ['height', 'width', ''] class TCPServerThread(threading.Thread): def __init__(self, tcpServerThreads, connections, connection, clientAddress): threading.Thread.__init__(self) self.tcpServerThreads = tcpServerThreads self.connections = connections self.connection = connection self.clientAddress = clientAddress def run(self): try: #바이트로 바꾼 이미지 받는 부분 byte_image = self.connection.recv(65536) print('tcp server(client) :: bytes image : ', byte_image) for i in pr: # when break connection if(i == ""): print("전송 완료. 바이트 이미지 복원하시겠습니까?(y/n)") ans = input(':') if(ans == 'y'): image = Image.open(io.BytesIO(byte_image)) image.show() print('tcp server(client) :: exit!!') print( 'tcp server :: server wait...') break data = self.connection.recv(1024) #int.from_bytes(data, byteorder='big') print('tcp server(client) -', i, end=" ") print(':', data.decode()) try: int_data = int(data.decode()) print('int형:', int_data) except: print('int형 변환 불가') pass except: self.connections.remove(self.connection) self.tcpServerThreads.remove(self) exit(0) self.connections.remove(self.connection) self.tcpServerThreads.remove(self) def send(self, message): print('tcp server :: ',message) try: for i in range(len(self.connections)): self.connections[i].sendall(message.encode()) except: pass | cs |
스레드 안에서 sub스레드를 생성해내면서, 클라이언트가 접속을 종료하면 exit으로 결과를 알려주면서 서버는 종료되지 않고 계속 실행이 되는걸 확인할 수 있습니다.
현재 만든 파이썬 서버는 바이트로 된 이미지 데이터 1개와 height, width 2가지 값을 받기 위한 코드로 구성되어 있습니다.
3가지 값을 모두 받은 이후에는, 바이트 이미지를 복원할 지 물어보고 서버 관리자가 원하면 이미지를 복원시켜 출력시켜줍니다.
추가로 executer 같은 경우는, 클라이언트가 준 데이터를 다시 재전송할 때 쓰이는 코드라 필요 시에 사용하면 되겠습니다.
executer.py
1 2 3 4 5 6 7 8 9 | class Executer: def __init__(self, tcpServer): self.andRaspTCP = tcpServer def startCommand(self, command): if command != None: self.andRaspTCP.sendAll(command) else: pass | cs |
이를 테스트하기 위해 임시로 만든 클라이언트 코드는 다음과 같습니다. 주석처리가 많은데 여러 테스트를 해보느라 그렇습니다ㅠ 주석을 제외한 코드만 보시면 될 것 같습니다.
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | #Client 예시 import socket import sys from PIL import Image import os import io from array import array import base64 host = '192.168.43.134' port = 4000 addr = (host, port) pr = ['1.height', '2.width'] def run(): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: try: s.connect(addr) except Exception as e: print('서버 (%s:%s)에 연결 할 수 없습니다.' % addr) sys.exit() print('서버 (%s:%s)에 연결 되었습니다.' % addr) #비트맵 바이트로 변환시키기 fd = open('test.bmp', "rb") b = bytearray(fd.read()) s.sendall(b) # 서버로 보내기 print('바이트 이미지 전송 완료') fd.close() #resp = s.recv(65536) #server_image = resp # 서버로부터 받은 이미지 바이트 값 server_image 변수에 저장 #print('서버에서 받은 바이트 이미지 길이 :', end=" ") #print(len(resp)) ''' #비트맵 문자열로 변환시키기 fd = open('test.bmp', "rb") str = base64.b64encode(fd.read()) s.sendall(str) fd.close() resp = s.recv(65536) print(resp) ''' for i in pr: # height과 width 값 입력 후 서버로 보내기 msg = input(i + ' : ') s.sendall(msg.encode()) print('서버로 보냄') #resp = s.recv(1024) #print(f'{resp.decode()} 서버로부터 다시 받음') ''' # 서버로 받은 이미지 바이트 값을 다시 이미지로 복원하기 print("서버로 받은 이미지를 보시겠습니까? (y/n)") ans = input(':') if(ans == 'y'): image = Image.open(io.BytesIO(server_image)) # 바이트 이미지 다시 이미지로 만듬 image.show() else: print('이미지 종료') ''' ''' stopper = 0 while True: if(stopper > 2): break resp = s.recv(1024) print(f'{resp.decode()}') stopper += 1 ''' s.close() if __name__ == '__main__': run() ''' def run(): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect(('127.0.0.1', 4000)) // 호스트와 포트로 서버 연결 진행 line = input(':') // 입력 값 s.sendall(line.encode()) // 입력된 값을 서버로 전송 resp = s.recv(1024) // 서버로부터 recv를 통해 데이터 받음 print(f'>{resp.decode()}') // 받은 데이터를 출력 if __name__ == '__main__': run() ''' | cs |
이제 서버와 클라이언트 실행시 터미널 출력 결과를 보겠습니다.
우선 python main.py를 통해 서버를 실행시켜주시구요.
그 다음 클라이언트를 실행해보도록 하겠습니다.
바이트 이미지는 자동으로 전송되고, height와 width를 직접 클라이언트가 입력해서 서버로 보냈습니다.
서버쪽 터미널을 볼까요?
바이트로 변환한 비트맵 이미지가 출력되고, 사용자가 입력한 height와 width도 받아오는 걸 확인할 수 있습니다.
마지막으로 바이트 이미지를 복원할지 물어보는데요. y라고 입력하면 아래와 같이 복원된 비트맵 이미지가 출력됩니다.
그리고 클라이언트는 종료되고, 서버 터미널에도 클라이언트가 exit했다고 알려주게 됩니다.
'파이썬(Python)' 카테고리의 다른 글
[Pandas] 데이터 분석 (Google Colab 활용) (0) | 2019.08.30 |
---|---|
TCP 통신 파이썬(Python) 소켓 프로그래밍 (0) | 2018.09.03 |