반응형



파이썬에서 소켓을 사용한 TCP 서버/클라이언트 예제에 대해  다룹니다. 

클라이언트가 서버에 접속하여 통신이 시작되는 방식입니다.  




1. TCP 소켓


2. 간단한 에코 서버, 클라이언트 예제


3. 쓰레드를 사용한 에코 서버와 클라이언트 예제




최초작성 2019. 9. 27

최종작성 2019. 9. 29






관련 포스트


TCP 소켓을 사용하여 웹캠의 이미지를 송수신하는 서버/클라이언트 예제

(Python example - Send Webcam image to client over a TCP socket)


https://webnautes.tistory.com/1382 





1. TCP 소켓


파이썬에서  TCP(Transmission Control Protocol)를 사용하려면 소켓 타입을 socket.SOCK_STREAM으로 지정하고 socket.socket 함수를 사용하여 소켓 객체를 생성하면 됩니다. 


TCP의 장점은 다음과 같습니다. 

1. 수신자가 전달 받지 못한 패킷을 발신자가 감지하여 재전송하기 때문에 신뢰성이 높습니다. 

2. 발신자가 전송한 순서대로 수신자에서 읽게 됩니다.  



UDP(User Datagram Protocol)를 사용하려면 소켓 타입을  socket.SOCK_DGRAM으로 지정하여 소켓 객체를 생성하면 됩니다.  

UDP는 수신자가 패킷을 전달 받았는지를 발신자가 신경쓰지 않으며 발신자가 전송한 순서와 수신자가 수신한 순서가 다를 수 있습니다. 



네트워크는 전송한 데이터가 목적지에 도달했는지 여부랑 수신한 데이터가 자신을 위한 데이터라는 보장을 해주지 않기 때문에 TCP에서 해주는 처리가 유용합니다. 

TCP를 사용하면  네트워크를 통해 데이터 전송시 패킷 손실, 잘못된 순서로 도착 등의 문제에 대해 신경쓸 필요가 없습니다. 



아래 그림은 TCP에 대한 소켓 API 호출 순서와 데이터 플로우입니다. 



1. 서버에서는 socket(), bind(), listen(), accept()순으로 함수들을 호출하여 리스닝 소켓을 생성합니다.

리스닝 소켓을 클라이언트의 접속을 대기하는 역활을 합니다. 


2.클라이언트가 연결되면 accept()에서 새로운 소켓을 리턴하여 클라이언트와 통신시 사용하도록 합니다. 


3. 클라이언트는 connect 함수를 호출하여 서버에 연결을 시도합니다. 이때부터  3-way 핸드세이크를 시작합니다. 

핸드 세이크는 네트워크를 통해 양쪽이 연결되는 것을 보장하므로 중요합니다. 클라이언트가 서버에 도달할 수 있으며 그 반대도 마찬가지입니다. 


4.연결이 완료된 후, 서버와 클라이언트는  send 함수와 recv 함수를 호출하여 데이터를 주고 받습니다. 


5. 클라이언트가 연결 종료 메시지를 전송하거나 소켓을 닫으면  서버는 클라이언트와 통신을 위해 사용한 소켓을 닫습니다.



2. 간단한 에코 서버, 클라이언트 예제

클라이언트가 보낸 메시지를 서버가 다시 전송하는 예제입니다. 

서버를 먼저 실행시킨 후, 클라이언트를 실행합니다. 실행결과부터 보겠습니다.  


1.클라이언트가 접속하면 서버에서 접속한 클라이언트 정보를 보여줍니다.

2.클라이언트가 문자열을 전송하면 서버가 수신한 문자열을 출력하고 다시 에코해줍니다. 

3.클라이언트에서 수신받은 문자열을 출력합니다.


서버

클라이언트

Connected by ('127.0.0.1', 10055)

Received from ('127.0.0.1', 10055) 안녕

Received '안녕'



echo-server.py


import socket


# 접속할 서버 주소입니다. 여기에서는 루프백(loopback) 인터페이스 주소 즉 localhost를 사용합니다.
HOST = '127.0.0.1'

# 클라이언트 접속을 대기하는 포트 번호입니다.   
PORT = 9999        



# 소켓 객체를 생성합니다.
# 주소 체계(address family)로 IPv4, 소켓 타입으로 TCP 사용합니다. 
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 포트 사용중이라 연결할 수 없다는
# WinError 10048 에러 해결를 위해 필요합니다.
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)


# bind 함수는 소켓을 특정 네트워크 인터페이스와 포트 번호에 연결하는데 사용됩니다.
# HOST는 hostname, ip address, 빈 문자열 ""이 될 수 있습니다.
# 빈 문자열이면 모든 네트워크 인터페이스로부터의 접속을 허용합니다.
# PORT는 1-65535 사이의 숫자를 사용할 수 있습니다. 
server_socket.bind((HOST, PORT))

# 서버가 클라이언트의 접속을 허용하도록 합니다.
server_socket.listen()

# accept 함수에서 대기하다가 클라이언트가 접속하면 새로운 소켓을 리턴합니다.
client_socket, addr = server_socket.accept()

# 접속한 클라이언트의 주소입니다.
print('Connected by', addr)


# 무한루프를 돌면서
while True:

    # 클라이언트가 보낸 메시지를 수신하기 위해 대기합니다.
    data = client_socket.recv(1024)

    # 빈 문자열을 수신하면 루프를 중지합니다.
    if not data:
        break

    # 수신받은 문자열을 출력합니다.
    print('Received from', addr, data.decode())

    # 받은 문자열을 다시 클라이언트로 전송해줍니다.(에코)
    client_socket.sendall(data)


# 소켓을 닫습니다.
client_socket.close()
server_socket.close()




echo-client.py


import socket


# 서버의 주소입니다. hostname 또는 ip address를 사용할 수 있습니다.
HOST = '127.0.0.1' 
# 서버에서 지정해 놓은 포트 번호입니다.
PORT = 9999      


# 소켓 객체를 생성합니다.
# 주소 체계(address family)로 IPv4, 소켓 타입으로 TCP 사용합니다. 
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)


# 지정한 HOST와 PORT를 사용하여 서버에 접속합니다.
client_socket.connect((HOST, PORT))

# 메시지를 전송합니다.
client_socket.sendall('안녕'.encode())

# 메시지를 수신합니다.
data = client_socket.recv(1024)
print('Received', repr(data.decode()))

# 소켓을 닫습니다.
client_socket.close()




3. 쓰레드를 사용한 에코 서버와 클라이언트 예제

서버에서 쓰레드를 사용하여 하나 이상의 클라이언트 접속이 가능하게한 예제입니다. 


실행결과입니다. 


서버

클라이언트1

클라이언트2

server start

wait



wait

Connected by : 127.0.0.1 : 4624



Received from 127.0.0.1 : 4624 hello

Received from 127.0.0.1 : 4624 I'm webnautes

Received from 127.0.0.1 : 4624 안녕하세요

wait

Enter Message : hello

Received from the server : 'hello'

Enter Message : I'm webnautes

Received from the server : "I'm webnautes"

Enter Message : 안녕하세요

Received from the server : '안녕하세요'


Connected by : 127.0.0.1 : 4626



Received from 127.0.0.1 : 4626 Hi


Enter Message : Hi

Received from the server : 'Hi'

Disconnected by 127.0.0.1 : 4626


Enter Message : quit

Disconnected by 127.0.0.1 : 4624

강제 종료




multiconn-server.py


import socket
from _thread import *


# 쓰레드에서 실행되는 코드입니다. 

# 접속한 클라이언트마다 새로운 쓰레드가 생성되어 통신을 하게 됩니다.
def threaded(client_socket, addr):

    print('Connected by :', addr[0], ':', addr[1])


    # 클라이언트가 접속을 끊을 때 까지 반복합니다.
    while True:

        try:

            # 데이터가 수신되면 클라이언트에 다시 전송합니다.(에코)
            data = client_socket.recv(1024)

            if not data:
                print('Disconnected by ' + addr[0],':',addr[1])
                break

            print('Received from ' + addr[0],':',addr[1] , data.decode())

            client_socket.send(data)

        except ConnectionResetError as e:

            print('Disconnected by ' + addr[0],':',addr[1])
            break
           
    client_socket.close()


HOST = '127.0.0.1'
PORT = 9999

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((HOST, PORT))
server_socket.listen()

print('server start')

# 클라이언트가 접속하면 accept 함수에서 새로운 소켓을 리턴합니다.

# 새로운 쓰레드에서 해당 소켓을 사용하여 통신을 하게 됩니다.
while True:

    print('wait')


    client_socket, addr = server_socket.accept()
    start_new_thread(threaded, (client_socket, addr))

server_socket.close() 




multiconn-client.py


import socket


HOST = '127.0.0.1'
PORT = 9999

client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

client_socket.connect((HOST, PORT))


# 키보드로 입력한 문자열을 서버로 전송하고 

# 서버에서 에코되어 돌아오는 메시지를 받으면 화면에 출력합니다. 

# quit를 입력할 때 까지 반복합니다.
while True:

    message = input('Enter Message : ')
    if message == 'quit':
    break

    client_socket.send(message.encode())
    data = client_socket.recv(1024)

    print('Received from the server :',repr(data.decode()))


client_socket.close() 



참고 

https://realpython.com/python-sockets/


https://www.geeksforgeeks.org/socket-programming-multi-threading-python/ 




반응형

포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
댓글로 알려주시면 빠른 시일내에 답변을 드리겠습니다.

여러분의 응원으로 좋은 컨텐츠가 만들어집니다.
지금 본 내용이 도움이 되었다면 유튜브 구독 부탁드립니다. 감사합니다 : )

유튜브 구독하기


제가 쓴 책도 한번 검토해보세요.

  1. 2019.11.04 17:18

    비밀댓글입니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.11.05 00:27 신고

      아이피 주소와 포트번호가 있어야 서버에 접속이 됩니다. 아이피주소는 호스트네임으로 변경가능할 겁니다.


      1)
      클라이언트 코드에 서버의 아이피와 포트번호를 입력해야 합니다


      2)
      서버가 정한 포트 번호로 접속한 클라이언트를 연결합니다.


      3)
      포트번호를 알아야만 연결이 되는게 맞습니다.

  2. 00 2019.12.02 19:27

    서버에서 다시 문자를 입력하려면 어떻게 해야하나요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.12.02 22:55 신고

      서버에서 키보드 입력을 받아서 클라이언트에게 보내주는 부분을 작성하면 될듯합니다.

  3. 2019.12.26 10:53

    비밀댓글입니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.12.26 11:03 신고

      클라이언트에서 5초마다 특정 문자열을 보내보세요.
      서버에선 특정 문자열이 수신된경우 다시 에코해서 보내주면 됩니다.
      클라이언트에서 특정 문자열이 수신되었다면 연결이 유지된것입니다

  4. 19yangji10517 2020.01.11 19:10

    서버 삭제 할려면 어떻게 해요

  5. 19yangji10517 2020.01.12 14:00


    서버 호스트를 어떻게 바꿔요?

  6. ㅇㅇㅇ 2020.02.07 17:07

    쓰래드 서버에 접속한 클라이언트들끼리 데이터를 주고받으려면 어떻게 해야하나요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.02.07 20:53 신고

      파이썬으로 작성된 채팅서버 코드를 찾아보세요 구글에서 python chat server example로 검색해보세요

  7. 2020.02.20 19:33

    비밀댓글입니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.02.20 19:36 신고

      찾아보면 프로그래밍 언어별로 소켓을 사용하여 만든 채팅 예제 프로그램이 있습니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.02.20 19:36 신고

      채팅 예제를 참고하여 진행하면 될듯합니다

  8. 촙개발자 2020.03.04 15:44

    감사합니다 많은 도움 됐습니당~~

  9. 김다래 2020.03.12 09:33

    안녕하세요?
    많은 도움 받고 있습니다. 감사합니다.

    수신받은 문자열을 출력하면 다음과 같은 에러가 발생합니다.
    # 수신받은 문자열을 출력합니다.
    print('data : ', repr(data.decode()))

    UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 10: invalid start byte
    어떤 내용인지 몰라서, 또 어떻게 처리해야 하는지 몰라서 여쭈어 봅니다.
    감사합니다.


  10. 가난뱅이 2020.03.14 19:26

    안녕하십니까 형님!!
    인사 오지게 박습니다!!
    thread 모듈은 어디서 챙깁니까??

    • 가난뱅이 2020.03.14 19:28

      구독 바로 박았습니다!! 행님!!

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.03.15 00:03 신고

      구독 감사합니다~~

      _thread 모듈은 파이썬에 포함되어 있습니다. 따로 설치안해도 됩니다.

  11. 2020.05.02 19:34

    비밀댓글입니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.05.02 19:41 신고

      문자열 파싱을 하면 됩니다.

      , 를 기준으로 나눈후 다시 =를 기준으로 나누면 될듯합니다.

      split 같은 문자열 관련 함수를 살펴보세요

  12. 버듀 2020.08.07 17:39

    안녕하세요! 좋은 내용 감사합니다.
    작성해주신 코드를 참고하여 멀티 쓰레드를 이용해 다중채팅 프로그램을 짰는데, 로그아웃 기능을 구현하는 도중 문제가 생겨 질문드립니다.
    tkinter와 쓰레드를 이용하여 채팅 프로그램을 만들었는데, 프로그램을 아예 종료한 후에 다시 실행시키면 문제없이 다시 통신이 되지만, 일일히 프로그램을 껐다 키는 것이 불편하다고 생각했습니다.
    그래서 메인쓰레드에서 진행되는 GUI는 계속 두고, 서버에 접속하고 접속을 해제하는 로그인, 로그아웃 기능만을 사용하려고 나름대로 구현해보았는데

    서버에서는 "ConnectionAbortedError: [WinError 10053] 현재 연결은 사용자의 호스트 시스템의 소프트웨어의 의해 중단되었습니다" 이 오류가 나타났고

    클라이언트에서는 "OSError: [WinError 10038] 소켓 이외의 개체에 작업을 시도했습니다" 이 오류가 나왔습니다.

    이걸 보고 서버는 정상적으로 작동했지만, 클라이언트에서 문제가 생긴 것 같아 클라이언트의 소켓을 직접 프린트해봤는데요.
    첫 로그인 때는
    <socket.socket fd=1072, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 53380), raddr=('127.0.0.1', 9999)>

    로그아웃 후 다시 로그인 할 때는
    <socket.socket [closed] fd=-1, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>

    이렇게 소켓이 닫혀있다고 뜹니다.
    같은 ip주소와 포트로 가는 소켓은 한번 닫히면 다시 못여는 건가요?

    • BlogIcon 버듀 2020.08.07 17:55

      앗 해결했습니다..ㅎㅎ
      특정 조건일 때 소켓을 닫고 로그아웃을 시켰는데, 다시 로그인 할 때 그 특정조건을 초기화를 안시켜줘서 다시 소켓을 닫아버리니 오류가 생겼었네요..

      좋은 강의 감사합니다!
      비록 원래 궁금했던 것은 해결되었지만,
      다른 파이썬 소켓통신 강좌와 비교하다보니 든 궁금증이 하나 있습니다.

      다른 강의에서는 threading 모듈을 thread 모듈보다 우선해서 사용하던데, 혹시 _thread 모듈을 고르신 이유가 따로 있나요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.08.07 18:32 신고

      별다른 이유는 없습니다.

      차이는 링크를 참고해보세요
      https://stackoverflow.com/questions/5568555/thread-vs-threading

  13. iiinnssu 2020.09.23 18:25

    안녕하세요, 작성자님 코드를 참고하고 tkinter을 이용해서 채팅프로그램을 만들어 봤습니다
    그리고 서버를 열어두고 여러 클라이언트를 실행을 시켜봤습니다
    근데 여기서 클라이언트 gui를 하나 닫고나서 나머지 gui에서 채팅을 시도했을때
    'ConnectionAbortedError: [WinError 10053] 현재 연결은 사용자의 호스트 시스템의 소프트웨어의 의해 중단되었습니다' 오류가 발생 합니다
    코드안에 기능들에서 따로 종료할때의 조건도 작성해야 저 에러가 발생이 안하는걸까요?
    조언 부탁 드립니다
    감사합니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.09.23 21:27 신고

      gui 종료시 소켓 종료하는 것을 넣어야 할거 같습니다.

  14. mercury 2020.10.07 12:45

    소켓 통신을 이해하는데 많은 도움이 되었습니다. 감사합니다.

  15. 2020.10.16 09:59

    비밀댓글입니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.10.16 10:11 신고

      클라이언트에서 수신 성공시 메시지를 보내달라하고 서버에선 체크하는 코드만 넣으면 됩니다.

    • 2020.10.22 09:49

      비밀댓글입니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.10.22 09:50 신고

      iconv 라이브러리를 사용하여 한글 인코딩을 변환할 수 있습니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.10.22 09:51 신고

      변환후 전송해보세요

+ Recent posts