파이썬

Python Network Programming- Socket Library

gyuho.kim 2018. 7. 11. 01:28

Socket ,IPv4,  Simple Client/Server Programming


Module Socket 

  • 네트워크를 경유하는 프로세스간 통신의 양 끝단을 추상화 시킨 개념.
  • 대부분 통신은 인터넷 프로토콜 기반으로 , 대부분의 네트워크 소켓은 인터넷 소켓이다. (RFC 147 기술)
  • 통신을 위해 양 끝단에서 소켓을 생성하고, 생성한 소켓을 통해 데이터 교환을 수행한다. 

구성요소

- Internet Protocol : TCP, UDP rawIP

- Local IP Address / Local Port number

- Remote IP Address / Remote Port number



1. Getting the Machine Information

(1) gethostname() return STRING

현재 호스트의 이름을 반환

(2) gethostbyname(hostname) return STRING

파라미터로 입력된 호스트의 IP 주소를 반환

     hostname ①gethostname() 함수를 통해 얻어온 호스트명 ② 원격지 주소

(3) gethostbyname_ex(hostname) return LIST (hostname,aliaslist,ipaddrlist)

1
2
3
4
5
6
7
8
9
10
11
12
def get_remote_machine_info():
 
    remote_host = 'kiss-PC'
    ip_addr = socket.gethostbyname_ex(remote_host)
    host_name = socket.gethostname()
 
    try:
        print("Host Name: %s" %host_name)
        print("IP Address: %s" %ip_addr[2][0])
    except (socket.error, err_msg):
        print("%s: %s" %(remote_host, err_msg))
 
cs

    

2. Converting IPv4 Address to different format

로우 레벨 네트워크 함수를 사용하기 위해서 일반적인 문자열 표기법은 유용하지 않기 때문에 문자열을 packed 32-bit binary 형식으로 변환해줄 필요가 있다.

(1) inet_aton(ip_string) 

'192.168.0.1'과 같은 IPv4 주소 포맷을 packed 32-bit binary로 변환 

(2) inet_ntoa(binary)

inet_aton() 과 반대로 packed 32-bit binary를 IPv4 주소 포맷으로 변환

Module binascii 

The binascii module contains a number of methods to convert between binary and various ASCII-encoded binary representations. Normally, you will not use these functions directly but use wrapper modules like uubase64, or binhex instead. The binascii module contains low-level functions written in C for greater speed that are used by the higher-level modules.

hexlify(binary_data) : binary 데이터를 16진수 표현으로 값을 반환

 

1
2
3
4
5
6
7
def convert_ipv4_address():
 
    for ip_addr in ['127.0.0.1''192.168.0.1']:
        packed_ip_addr = socket.inet_aton(ip_addr)
        unpacked_ip_addr = socket.inet_ntoa(packed_ip_addr)
        print("IP Address: %s => Packed: %s, Unpacked: %s"\
              %(ip_addr, hexlify(packed_ip_addr), unpacked_ip_addr))
cs


3. Finding Service Name 

getservbyport(port,[protocol]) : 포트번호를 매개변수로 입력해 해당 포트에 해당하는 서비스 이름을 출력해준다. 추가적으로 protocol name 파라미터 값을 줄 수 있다.

1
2
3
4
5
6
7
def find_service_name():
    protocol_name = 'tcp'
    for port in [80,25,23]:
        print("Port: %s => service name: %s" \
              %(port, socket.getservbyport(port,protocol_name)))
    print("Port : %s => service name:%s"\
          %(53, socket.getservbyport(53'udp')))
cs


4. Integer Convert to and from host to network byte order

바이트오더(Byte Order)

바이트를 배열하는 방법을 말한다. Little-Endian & BigEndian 방식


소켓을 통해 양 단말 간 통신을 위해 호스트에서 네트워크로 데이터를 전송하기 위해 네트워크 포맷으로 데이터를 변환해 주는 작업이 필요하다

Because 호스트와 네트워크 사이의 서로다른 Byte Order 사용


1
2
3
4
5
6
7
8
def convert_integer():
    data = 1234
    # 32-bit
    print("Original: %s => Long host byte order: %s, Network byte order: %s"\
          %(data, socket.ntohl(data), socket.htonl(data)))
    # 16-bit
    print("Original: %s => Short host byte order: %s, Network byte order: %s"\
          %(data, socket.ntohs(data), socket.htons(data)))
cs


5. Setting / Getting Socket timeout

gettimeout(): 소켓 객체의 타임아웃 값을 반환

settimeout() : 소켓의 타임아웃 값을 설정

 

socket.getdefaulttimeout() : 소켓의 디폴트 타임아웃 값을 반환

socket.getdefaulttimeout(timeout_seconds) : 소켓의 디폴트 타임아웃 값을 변경

1
2
3
4
5
6
def test_socket_timeout():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    print("Default socket timeout: %s" %s.gettimeout())
    s.settimeout(100)
    print(socket.getdefaulttimeout())
    print("Current socket timeout: %s" %s.gettimeout())
cs


6.Python handling Socket errors

네트워킹을 하는 애플리케이션들은 보통 한쪽에서 연결을 시도하고, 다른 한 쪽에서 그 연결에 대해 네트워크 미디어 실패나 다른 이유로 응답에 실패 할 수 있다. 파이썬은 socket.error를 통해 예외들을 제어하기 위한 핸들링 메소드를 제공한다.

 

기본적인 예외처리

try except code block 사용

 

(+) Additional!....

argparse : 사용자 입력을 받기 위한 모듈 . 여러 명령어들 보면 인자를 입력하도록 하는 형태가 있는데 이 모듈을 통해 만들 수 있다. sys.argv로도 가능하지만 더 강력한 기능을 제공한다.

 

Changed in version 3.5: The method now waits until the connection completes instead of raising an InterruptedError  exception if the connection is interrupted by a signal, the signal handler doesn’t raise an exception and the socket is blocking or has a timeout (see the  PEP 475  for the rationale).

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
import socket
import sys
import argparse
 
def main():
    parser = argparse.ArgumentParser(description='Socket Error Examples')
    parser.add_argument('--host', action="store", dest="host",\
                        required=False)
    parser.add_argument('--port', action="store", dest="port",\
                        type=int, required=False)
    parser.add_argument('--file', action="store", dest="file",\
                        required=False)
    given_args = parser.parse_args()
    host = given_args.host
    port = given_args.port
    filename = given_args.file
 
    # First try-except block -- create socket
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    except socket.error, e:
        print("Error creating socket: %s" %e)
        sys.exit(1)
 
    # Second try-except block -- connect to given host/port
    try:
        s.connect((host, port))
    except socket.gaierror, e:
        print("Address-related error connecting to server: %s" % e)
        sys.exit(1)
    except socket.error, e:
        print("Connection error: %s" %e)
        sys.exit(1)
 
    # Third try-except block -- sending data
    try:
        s.sendall("GET %s HTTP/1.0 \r\n\r\n" %filename)
    except socket.error, e:
        print("Error sending data:%s" %e)
        sys.exit(1)
 
    while 1:
        # Fourth try-except block -- waiting to receive dta from remote host
        try:
            buf = s.recv(2048)
        except socket.error, e:
            print("Error receiving data:%s" % e)
            sys.exit(1)
        if not len(buf):
            break
 
        sys.stdout.write(buf)
 
if __name__ == '__main__':
    main()
cs


7. Modifying socket's buffer size

기본적으로 소켓의 버퍼 사이즈는 여러 상황들에 적합하게 되어 있지 않다. 몇 몇 상황에서는 디폴트 소켓 버퍼의 사이즈를 더 적합한 크기로 변경할 필요가 있을 것이다.


(1) setsockopt(level, optname, value:int|buffer)

소켓 속성값을 설정해줌

  • level : socket.SOL_IP | SOL_TCP | SOL_UDP | SOL_SOCKET
  • optname: socket.OP_ *..
  • value : integer | buffer

(2) getsockopt()

소켓 속성값을 반환

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
import socket
 
SEND_BUF_SIZE = 4096
RECV_BUF_SIZE = 4096
 
def modify_buff_size():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
    bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
    print("Buffer size [Before]:%d" %bufsize)
 
    sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
    sock.setsockopt(
        socket.SOL_SOCKET,
        socket.SO_SNDBUF,
        SEND_BUF_SIZE)
    sock.setsockopt(
        socket.SOL_SOCKET,
        socket.SO_RCVBUF,
        RECV_BUF_SIZE)
 
    bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
    print("Buffer size [After]:%d" %bufsize)
 
if __name__ == '__main__':
    modify_buff_size()
cs




8. Scoket's Blocking / Non-Blocing mode

기본값으로 TCP 소켓은 블로킹 모드로 되어 있다. 이는 특정 명령어가 완료될 때 까지 프로그램에게 제어권을 반환하지 않는 것을 의미한다.

(1) setblocking(flag)

소켓을 blocking(1) 또는 non-blocking(0) 모드로 설정합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import socket
 
def test_socket_modes():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setblocking(1)
    sock.settimeout(0.5)
    sock.bind(("127.0.0.1"0))
 
    socket_address = sock.getsockname()
    print("Trivial server launched on socket: %s" %str(socket_address))
    while(1):
        sock.listen(1)
 
if __name__ == '__main__':
    test_socket_modes()
cs


9. Reusing socket address


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
import socket
import sys
 
def reuse_socket_addr():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
    old_state = sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)
    print("Old sock state: %s" %old_state)
 
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    new_state = sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)
    print("new sock state: %s" %new_state)
 
    local_port = 8282
 
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind(('',local_port))
    srv.listen(1)
    print("Listening on port: %s" %local_port)
    while True:
        try:
            connection, addr = srv.accept()
            print("connected by %s:%s" %(addr[0]. addr[1]))
        except socket.error, msg:
            print("%s" %(msg,))
 
if __name__ == '__main__':
    reuse_socket_addr()
cs



 10. time synchronization

유닉스의 make 명령어와 같이 시스템의 정확한 시간에 의존하는 프로그램들을 위해 시간 동기화기 필요하다. ntp를 이용한 시간 동기화를 한다.


(1) Network Time Protocol(NTP) 설치하기 

pip install ntplib


(2) time.ctime([secs])

로컬 시간을 나타내는 문자열을 초로 나타내는 시간 표현으로 변환해준다.


1
2
3
4
5
6
7
8
9
10
import ntplib
from time import ctime
 
def print_time():
    ntp_client = ntplib.NTPClient()
    response = ntp_client.request('pool.ntp.org')
    print(ctime(response.tx_time))
 
if __name__ == '__main__':
    print_time()
cs


Module time

This module provides various time-related functions. For related functionality, see also the datetime and calendar modules.


11. SNTP Client 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import socket
import struct
import sys
import time
 
NTP_SERVER = '0.kr.pool.ntp.org'
TIME1970 = 2208988800L
 
def sntp_client():
    client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    data = '\x1b' + 47 * '\0'
    client.sendto(data, (NTP_SERVER,123))
    data, address = client.recvfrom(1024)
    if data:
        print("Response received from:", address)
        t = struct.unpack('!12I', data)[10]
        t -= TIME1970
        print("\tTime=%s" %time.ctime(t))
 
if __name__ == '__main__':
    sntp_client()
 
cs