May 30, 2019

Python Network Socket (소켓)

Python 2.7 Network Socket 소켓 통신

요새 Python Flask와 Docker을 공부하고 있어서 블로그에 정리를 해보려고 합니다.

띠용!!
본글은 Linux Ubuntu 환경의 Python 2.7 기반으로 진행합니다.

Description: Ubuntu 19.04
Release: 19.04
Codename : Disco Dingo




Network Socket

Network Socket Address에는 IP Address와 Port Number을 포함한다.
Socket을 생성하기 위해 Python의 socket 모듈의 socket.socket() 함수를 사용해야 한다. 아래 예시 하나를 보자!!

soc = socket(socket_family, socket_type, protocol = 0)
  • socket_family = socket.AF_INET,  PF_PACKET

    1. AF_INET은 IPv4를 위한 Address Failmy이며, PF_PACKET은 Device Driver 계층에서 동작(Linux Pcap 라이브러리를 사용)
  • socket_type = socket.SOCK_DGRAM,  socket_SOCK_STREAM

    1. socket.SOCK_DGRAM은 UDP 신뢰성이 떨어지는 비연결이다.
    2. socket.SOCK_STREAM은 TCP 신뢰성이 높은 연결형이다.


Server Socket

  • socket.bind(Address)

    1. IP Address와 Port Number를 socket에 연결하기 위해 사용되는 함수 (메소드)이며, socket은 주소에 연결되기전에 반드시 먼저 선언되어 열려 있어야 한다.
  • socket.listen(q)

    1. TCP listener (리스너)를 실행시키는 메소드이며, q 인자는 순차적으로 접속할 수 있는 최대 수 이다.
  • socket.accept()

    1. 클라이언트로부터 연결을 허용할 때 사용하는 메소드이며, 해당 메소드를 사용하기 위해서는 bind()와 listen() 메소드를 사용해야 한다.
    2. socket.accept() 메소드는 client socket과 Address 2가지 값을 반환한다.
    3. client socket은 연결을 통해서 데이터를 주고 받을 새로운 socket object가 생성되며, Address는 Client IP 주소이다.



Client Socket

  • socket.connect(Server Address)

    1. 클라이언트에서 서버로 연결하는 메소드이다.



Socket Method

  • socket.recv(buf_size)

    1. TCP 메시지를 받는 메소드이며, 최대 한번에 받을 수 있는 값을 정의한다.
  • socket.recvfrom(buf_size)

    1. 소켓으로부터 데이터를 받기 위한 메소드이며, 데이터와 클라이언트 소켓 주소를 값을 반환한다.
  • socket.recv_into(buf)

    1. Buffer 보다 작거나 같은 크기의 데이터를 받기 위한 메소드이다.
  • socket.recvfrom_into(buf)

    1. 소켓으로부터 데이터를 받고, 전달 받은 데이터를 Buffer에 저장한다.
  • socket.send(bytes)

    1. 데이터를 소켓으로 보내기 위해 사용되는 메소드이다. 전송한 바이트의 수를 반환한다.
  • socket.sendto(data, address)

    1. socket.send 메소드와 같이 데이터를 소켓으로 보내기 위해 사용되지만, 해당 메소드는 UDP에서 사용된다.
  • socket.sendall(data)

    1. 모든 데이터를 소켓으로 보내기 위해 사용되는 메소드이며, 에러가 발생시, 예외처리가 되며 close() 메소드가 소켓을 종료 시킨다.



Server Socket 파일 만들기

import socket

host = "192.168.219.133" # Server IP
port = 1234 # Port Number

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # TCP 소켓 생성
soc.bind((host, port)) # host와 port로 소켓 연결
soc.listen(5) # 클라이언트 최대 5개 까지 받으며 접속할 때 까지 대기

conn, addr = soc.accept() # 연결 허용, conn 변수에는 클라이언트 소켓이 저장되고, addr에는 클라이언트 아이피 주소가 저장된다.
print addr # 클라이언트 IP 주소 출력

conn.send("Hi!! Server is DongDongE") # 클라이언트에 문자열 전송
conn.close() # 소켓 종료

serv.py



Server Socket 파일 만들기

import socket

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

host = "192.168.219.133" # 서버 아이피
port = 1234 # 서버 포트

soc.connect( (host, port) ) # 서버측으로 연결한다.
print soc.recv(1024) # 서버측에서 보낸 데이터 1024 버퍼만큼 받는다.

soc.send("Client. Hello!!!") # 서버측으로 문자열을 보낸다.
soc.close() # 연결 종료

cli.py



[Server에서 sev.py 실행]

서버측에서 sev.py을 실행시키면 클라이언트가 접속하기전 까지 터미널창에는 아무런 반응이 보이지 않는다. 하지만 아래 사진과 같이 프로세스를 확인해보면 정상적으로 port가 열린걸 볼 수 있다.


[1234 port 확인]



[Client에서 cli.py 실행]

Client측에서 실행한다면 서버에 연결하고나서 서버측에서 보낸 문자열을 받는다.


[Server]

서버측에서 클라이언트의 IP주소와 클라이언트 포트번호를 확인할 수 있다.
해당 코드는 서버측으로 연결을 하면 계속 유지하지 않고 연결을 바로 끊어 버린다. 만약 다른 클라이언트(유저)를 받기 위해서 서버측 소켓을 계속 활성화 (while 반복문 사용)을 시켜줘야 한다.


[Socket 통신 패킷]

캡처된 패킷을 Wireshark을 통해 Server와 Client간 패킷을 볼 수 있다.
먼저 TCP 3-way handshake 과정을 거친 다음 PSH,ACK로 데이터를 보내는걸 볼 수 있다.
아래 사진에서 전송된 데이터를 확인 해보자.


[서버가 전송한 데이터]

Data부분을 보면 Server가 Client로 "Hi! is..." 문자열을 전송하는걸 볼 수 있다. 전송시 평문으로 전송하기 때문에 Wireshark로 패킷을 캡처하여 문자열을 볼 수 있다.


import socket

host = "192.168.219.133"
port = 1234

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.bind((host, port))
soc.listen(5)

while(True):
	conn, addr = soc.accept()
	print addr
	conn.send("Hi!! Server is DongDongE")
	conn.close()

while_sev.py

위의 코드중 while문을 통해서 하나의 서버에서 여러대의 클라리언트 요청을 계속 처리 및 제공할 수 있다.



[Server측에 다수의 클라이언트 연결]

While 구문을 통해 다수의 연결접속에도 서버가 종료되지 않고 계속 작동되는걸 볼 수 있다.
아래는 클라이언트가 연속적으로 연결 접속을 시도한 사진이다.


[Client Connect]



UDP Server Socket

import socket

host = "192.168.219.133"
port = 4321

soc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
soc.bind((host, port))

data, addr = soc.recvfrom(1024)

print "IP: ", addr
print "Data: ", data

soc.close()

udp_sev.py

UDP는 비연결형 프로토콜이므로, 연결 설정 (3 way handshake)과정이 필요 없다.


import socket

host = "192.168.219.133"
port = 4321

soc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

print soc.sendto("Hello All", (host, port))
soc.close()

udp_cli.py

TCP와 다르게 Connect후 전송하는 과정 없이 바로 sendto 메소드에 전송할 문자열과 Server IP, PORT을 적어 전송한다.


UDP Packet Client -> Server

위 사진을 보면 연결 과정 없이 Client가 Server로 바로 데이터를 전송한다.

하지만 초록색 박스를 보면 "192.168.219.134" 클라이언트가 서버로 "192.168.219.133" 데이터를 전송했지만 서버측에서 "ICMP Destination unreachable" 패킷이 반환된 걸 확인할 수 있다.
이 패킷은 해당 프로세스에서 정상적으로 열려 있지 않거나 방화벽으로 인해 정상적인 처리를 할 수 없을 때 반환되는 패킷이다. (TCP 경우 RST 패킷을 반환한다.)
아래 사진에서는 전송된 데이터를 확인 해보자.


전송된 Hello All 문자열

UDP Packet의 Data 부분에 "Hello All" 문자열을 담아 전송된걸 확인 할 수 있다.