天天看點

Python的套接字、IPv4和簡單的用戶端/伺服器程式設計

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import socket
from binascii import hexlify
import sys
# 對使用者輸入的指令行參數進行解析
import argparse

# 先$ pip install ntplib 安裝需要的python庫檔案
import ntplib
from time import ctime
import struct

# python中每個函數之間要隔2行

# 發送資料緩沖區的大小
SEND_BUF_SIZE = 4096
# 接受資料緩沖區的大小
RECV_BUF_SIZE = 4096

NTP_SERVER = "0.uk.pool.ntp.org"
TIME1970 = 2208988800L


def print_local_machine_infor():
    """列印本地主機名和本地主機的ip位址"""
    host_name = socket.gethostname()
    host_ip_address = socket.gethostbyname(host_name)

    print "Host name: %s" % host_name
    print "Host ip address: %s" % host_ip_address


def get_remote_machine_infor(remote_host_name="www.python.org"):
    """擷取遠端伺服器的ip位址"""
    try:
        print "Remote ip address: %s" % socket.gethostbyname(remote_host_name)
    except socket.error, err_msg:
        print "%s: %s" % (remote_host_name, err_msg)


def convert_ip4_address(ip_address):
    # 将一個字元串IP位址轉換為一個32位的網絡位元組序列(ascii碼轉二進制)
    packed_ip_address = socket.inet_aton(ip_address)
    # 将32位網絡位元組序列轉換成字元串形式的ip位址(二進制轉ascii碼)
    unpacked_ip_address = socket.inet_ntoa(packed_ip_address)

    # 将二進制資料的網絡位元組序列hexlify轉換成16進制顯示
    print "Ip Address: %s => Packed: %s, Unpacked: %s" \
        % (ip_address, hexlify(packed_ip_address), unpacked_ip_address)


def find_service_name(protocol_name, port_number):
    """根據網絡協定和端口号,擷取服務名稱"""
    print "Port: %s => service name: %s" % (port_number, socket.getservbyport(port_number, protocol_name))


def convert_integer(port_number):
    """網絡端口之間序列的轉換"""
    # 32-bit--将資料從網絡位元組序和主機位元組序之間進行轉換
    print "Original: %s => Long  host byte order: %s, Network byte order: %s" \
          % (port_number, socket.ntohl(port_number), socket.htonl(port_number))
    # 16-bit--将資料從網絡位元組序和主機位元組序之間進行轉換
    print "Original: %s => Short  host byte order: %s, Network byte order: %s" \
          % (port_number, socket.ntohs(port_number), socket.htons(port_number))


def set_tcp_socket_timeout(wait_time):
    """設定tcp套接字的逾時等待時間"""
    # 建立流式tcp套接字
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 列印套接字的預設的逾時等待時間
    print "Default socket timeout: %s" % s.gettimeout()

    # 設定套接字的逾時等待時間
    s.settimeout(wait_time)
    print "Current socket timeout: %s" % s.gettimeout()


def send_data_to_server():
    # 建構指令行參數解釋器
    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=www.python.org", "--port=80", "--file=Hello World"])
    # 擷取指令行輸入的主機名
    host = given_args.host
    # 擷取指令行輸入的端口号
    port = given_args.port
    # 擷取指令行輸入的發送資料
    filename = given_args.file

    # 建立tcp網絡套接字
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    except socket.error, e:
        # 列印建立網絡套接字的錯誤
        print "Error creating socket: %s" % e
        # 退出程序
        sys.exit(1)

    # 向指定的網絡伺服器發起網絡連接配接
    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)

    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:
        # 接受請求伺服器傳回的資料
        try:
            buf = s.recv(2048)
        except socket.error, e:
            print "Error receiving data: %s" % e
            sys.exit(1)
        # 判斷伺服器傳回的資料是否為null
        if not len(buf):
            break
        # 将指定伺服器傳回的資料列印出來
        sys.stdout.write(buf)
    # 換行
    sys.stdout.write("\r\n")


def modify_buff_size():
    # 建立流式tcp套接字
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 擷取套接字預設發送資料緩沖區的大小
    bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
    print "Orig Send Buffer size [Before]:%d" % bufsize

    bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
    print "Orig Recv 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 "Send Buffer size [After]:%d" % bufsize

    # 列印設定以後的網絡套接字的接受資料緩沖區的大小
    bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
    print "Recv Buffer size [After]:%d" % bufsize


def reuse_socket_addr():
    # 建立tcp流式套接字
    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

    # 設定網絡套接字的位址重用狀态的值為1即重用套接字位址
    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(("127.0.0.1", 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 KeyboardInterrupt:
            break
        except socket.error, msg:
            print '%s' % (msg,)


def test_socket_modes():
    # 建立tcp網絡套接字
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 設定網絡模式為阻塞模式
    s.setblocking(1)
    # 設定逾時等待時間
    s.settimeout(0.5)

    # 綁定ip位址和端口号
    s.bind(("127.0.0.1", 0))

    # 擷取網路套接字的資訊("ip位址", 端口号)
    socket_address = s.getsockname()
    # 列印網絡套接字的名稱
    print "Trivial Server lauched on socket: %s" % str(socket_address)

    # 進行網絡套接字的監聽,等待用戶端的連接配接
    while 1:
        s.listen(1)


def print_ntp_time():
    # 建立NTPClient執行個體對象
    ntp_client = ntplib.NTPClient()
    # 向NTP伺服器發送時間請求
    response = ntp_client.request('pool.ntp.org')
    # 列印擷取的時間
    print "ntp time: "+str(ctime(response.tx_time))


def sntp_client():
    # 建立udp套接字
    client = socket.socket( socket.AF_INET, socket.SOCK_DGRAM)
    # 建構發送的資料包
    data = '\x1b' + 47 * '\0'
    # 将資料發送給伺服器
    client.sendto(data, (NTP_SERVER, 123))
    # 擷取服務傳回的資料和ip位址(元組)
    data, address = client.recvfrom(1024)
    if data:
        print 'Response received from:', address
    # 解包服務傳回的資料即時間
    t = struct.unpack('!12I', data)[10]
    # 減去從1970年1月1日對應的時間戳
    t -= TIME1970

    # 列印擷取到的時間資訊
    print '\tTime=%s' % ctime(t)


# 若目前子產品,直接執行時,執行下面的代碼
if __name__ == '__main__':

    # 列印本地主機的名稱和ip位址
    print_local_machine_infor()

    # 擷取遠端伺服器的ip位址
    get_remote_machine_infor()

    # 進行ip位址的轉換
    convert_ip4_address("192.168.0.1")

    # 通過端口号和協定,擷取服務名稱
    for obj in [("tcp", 80), ("tcp", 25), ("tcp", 53)]:
        find_service_name(obj[0], obj[1])

    # 網絡端口的位元組序的轉換
    convert_integer(80)

    # 設定tcp套接字的逾時等待時間
    set_tcp_socket_timeout(10)

    # 向指定的服務發送資料
    send_data_to_server()

    # 修改網路套接字的接受資料和發送資料的緩沖區的大小
    modify_buff_size()

    # 列印ntp網絡時間
    print_ntp_time()

    # 擷取目前時間
    sntp_client()

    # 建立阻塞模式的伺服器套接字
    #test_socket_modes()

    # 重用套接字位址
    reuse_socket_addr()      

簡單的網路連接配接服務端

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import socket
import argparse

host = 'localhost'
data_payload = 2048
backlog = 5 


def echo_server(port):
    """ 簡單的網絡連接配接伺服器 """
    # 建立流式TCP網絡套接字
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 設定可以重用網絡套接字位址
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 建構綁定網絡套接字的資訊對象
    server_address = (host, port)
    print "Starting up echo server  on %s port %s" % server_address

    # 綁定ip位址和端口
    sock.bind(server_address)
    # 進行網絡套接字的監聽
    sock.listen(backlog)

    while True: 
        print "Waiting to receive message from client"
        # 接受用戶端的網絡連接配接
        client, address = sock.accept()
        # 接受用戶端發送來的資料
        data = client.recv(data_payload)

        # 向用戶端發送資料
        if data:
            print "Data: %s" % data
            client.send(data)
            print "sent %s bytes back to %s" % (data, address)

        # 關閉網絡套接字
        client.close() 
   
if __name__ == '__main__':

    # 建構指令行參數解釋器
    parser = argparse.ArgumentParser(description='Socket Server Example')
    # 添加非可選參數--port(端口号)
    parser.add_argument('--port', action="store", dest="port", type=int, required=False)

    # 解析使用者輸入的指令行參數
    given_args = parser.parse_args(["--port=8777"])
    # 擷取網絡連接配接的端口号
    port = given_args.port

    # 建立網絡連接配接的伺服器
    echo_server(port)      

簡單的網絡連接配接用戶端

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import socket
import argparse

host = 'localhost'


def echo_client(port):
    """ 簡單網絡連接配接用戶端 """
    # 建立TCP流式網絡套接字
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 建構連接配接伺服器的資訊對象(ip位址,端口号)
    server_address = (host, port)
    print "Connecting to %s port %s" % server_address

    # 向伺服器發起網絡連接配接
    sock.connect(server_address)

    try:
        # 發送給伺服器的資料
        message = "Test message. This will be echoed"
        print "Sending %s" % message

        # 向伺服器發送資料資訊
        sock.sendall(message)

        amount_received = 0
        # 期望收到伺服器傳回的資料長度
        amount_expected = len(message)
        # 循環擷取伺服器傳回給用戶端的資料
        while amount_received < amount_expected:
            # 擷取伺服器傳回的資料
            data = sock.recv(16)
            amount_received += len(data)
            print "Received: %s" % data
    except socket.errno, e:
        print "Socket error: %s" % str(e)
    except Exception, e:
        print "Other exception: %s" % str(e)
    finally:
        print "Closing connection to the server"
        # 關閉網絡套接字
        sock.close()


if __name__ == '__main__':

    # 建構指令行參數解釋器對象
    parser = argparse.ArgumentParser(description='Socket Server Example')
    # 添加非可選的參數--port(端口号)
    parser.add_argument('--port', action="store", dest="port", type=int, required=False)

    # 解析使用者輸入的指令行參數
    given_args = parser.parse_args(["--port=8777"])
    # 擷取使用者輸入的網絡端口号
    port = given_args.port

    # 建立網絡連接配接用戶端
    echo_client(port)