파이썬(Python) 서버로 비트맵 이미지와 데이터 소켓 통신하기
파이썬(Python)

파이썬(Python) 서버로 비트맵 이미지와 데이터 소켓 통신하기

반응형

클라이언트에서 비트맵 이미지와 추가로 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했다고 알려주게 됩니다.


반응형