반응형

파이썬으로 작성한 TCP 서버/클라이언트 소켓 예제코드입니다. 

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




    1. TCP 소켓

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

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




최초작성 2019. 9. 27

최종작성 2023. 7. 23




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/ 




반응형

문제 발생시 지나치지 마시고 댓글 남겨주시면 가능한 빨리 답장드립니다.

도움이 되셨다면 토스아이디로 후원해주세요.
https://toss.me/momo2024


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

+ Recent posts