Python网络编程(1)---套接字, IPv4, 简单的Client/Server程序

  这一部分主要介绍python中socket模块的相关内容,socket即套接字。

  socket是使用TCP/IP协议的应用程序通常采用的应用编程接口,它位于运输层和应用层之间,起源于UNIX,由于遵从UNIX“一切皆文件的”思想故socket可看作一种特殊的文件,对其的操作基本可以视为读写I/O、打开、关闭。关于套接字的基本概念@吴秦的Linux Socket编程(不限Linux)写的很详细,大家可以参考。

  在下面列出的各个部分中我将先贴出代码,然后对其进行解释。

  • 通过python3获得本机名和ip地址

  

1 >>> import socket
2 >>> host_name = socket.gethostname()
3 >>> print "Host name: %s" %host_name
4 Host name: debian6
5 >>> print "IP address: %s" %socket.gethostbyname(host_name)
6 IP address: 127.0.1.1

  上面代码中gethostname方法返回字符串形式的当前主机名,gethostbyname方法返回对应主机的IPv4地址,注意,这里的host可以是类似‘www.baidu.com’等因特网上主机名。

gethostname()    -> host
gethostbyname(host)    -> address

  代码很简单明了,不详细说了。

  • 获得远端计算机的Ip地址

    将前面的gethostbyname(host)方法的参数host设为'www.baidu.com'格式的远端主机名就能获得其IP地址。

1 import socket
2    def get_remote_machine_info():
3        remote_host = 'www.baidu.com'
4        try:
5            print("IP address: %s" % socket.gethostbyname(remote_host))
6        except socket.error as err_msg:
7            print("%s: %s" %(remote_host, err_msg))
8    if __name__ == '__main__':
9        get_remote_machine_info()
  • 转换IPv4地址的格式

    处理到底层网络时,通常用到的是32位二进制形式的而不是字符串形式的IP地址,socket库提供了相互转换的方法:inet_aton()和inet_ntoa()

1 socket.inet_aton(string) #将字符串形式的IP地址转换为用于底层网络的32位形式地址
2 socket.inet_ntoa(packed_ip) -> ip_address_string #将32位形式ip地址转化为字符串形式

>>> socket.inet_aton('127.0.0.1')

b'\x7f\x00\x00\x01'

>>> socket.inet_ntoa(b'\x7f\x00\x00\x01')

'127.0.0.1'

 

  • 通过端口和协议获得服务名称
getservbyport(port[, protocolname]) -> string

    getservbyport方法可以方便的通过查看指定端口对应的服务名称,其中port为端口号,可选参数protocolname为使用协议,以下例子获取80 25 53端口对应的服务:

import socket

def find_service_name():
    protocolname = 'tcp'
    for port in [80, 25]:
        print("Port: %s => service name: %s" % (port, socket.getservbyport(port, protocolname)))
    print("Port: %s => service name: %s" % (53, socket.getservbyport(53,'udp')))

if __name__ == '__main__':
    find_service_name()

[output]

Port: 80 => service name: http

Port: 25 => service name: smtp

Port: 53 => service name: domain

  • 转换整数的本地和网络字节顺序

    

def convert_integer():
    """将数据字节在本地顺序和网络顺序间转换"""
    data = 1234
    #将data转换为32位长格式
    print("Original: %s => Long host byte order: %s, Network byte order: %s" % (data, socket.ntohl(data),socket.htonl(data)))
    #将data转换为16位短格式
    print("Original: %s => Short host byte order: %s, Network byte order: %s" % (data, socket.ntohs(data), socket.htons(data)))

Original: 1234 => Long host byte order: 3523477504, Network byte order: 3523477504

Original: 1234 => Short host byte order: 53764, Network byte order: 53764

 

  • 获得和设置socket的超时时间
def test_socket_timeout():
    """通过gettimeout和settimeout方法获取和设置timeout时间."""
    # first args of s is socket family, second args is socket type.
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    print("Default socket timeout: %s" % s.gettimeout())
    s.settimeout(100)
    print("Current socket timeout: %s" % s.gettimeout())

Default socket timeout: None

Current socket timeout: 100.0

  • 修改socket的发送和接受缓存大小
getsockopt(level, option[, buffersize]) -> value
setsockopt(level, option, value)

    socket.socket.getsocket方法可以获得当前缓存大小信息,socket.socket.setsocket方法可以重新设置缓存大小:

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()

[output]

Buffer size [Before]:131072

Buffer size [After]:4096

  • 改变socket为阻塞或非阻塞模式

    socket默认为阻塞模式,即调用connect等API时程序会被阻塞直到操作完成,然而很多时候这不是我们所希望的,幸运的是可以通过socket.socket.setblocking来设置:

setblocking(flag)
    Set the socket to blocking (flag is true) or non-blocking (false).
    setblocking(True) is equivalent to settimeout(None);
    setblocking(False) is equivalent to settimeout(0.0).

    下面的例子将socket设为了非阻塞模式:

import socket

def test_socket_modes():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setblocking(0)
    s.settimeout(0.5)
    s.bind(("127.0.0.1", 0))

    socket_address = s.getsockname()
    print("Trivial Server launched on socket: %s" % str(socket_address))
    while True:
        s.listen(1)

if __name__ == '__main__':
    test_socket_modes()
  • 重用socket地址和端口

  当我们关闭某个特定端口上的python服务端后如果试图重新在这个端口上打开它系统将会提示“Address already in use”错误,然而很多情况下客户端都需要通过某个特定的端口连接服务程序,这可以通过开启socket地址和端口重用实现。

import socket

def reuse_socket_addr():
    """ 使端口在关闭或者发生异常而退出时能重新使用"""
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #获得SO_REUSEADDR状态
    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 KeyboardInterrupt:
            break
        except socket.error as e:
            print('%s' % (e,))

if __name__ == '__main__':
    reuse_socket_addr()
  • 从因特网获得当前时间(ntp)

  很多程序的正常运行依赖于精确的时间,这就需要使本地时间与网络时间进行同步以保持其精确性。NTP(Network Time Protocol, 网络时间协议)就是用来使网络中各个计算机时间同步的一种协议,下面就是一个简单的同步时间程序,在运行前请先安装ntplib库:

pip3 install ntplib
import ntplib
from time import ctime

def print_time():
    """First: pip3 install ntplib, NTP(Network Time Protocol)"""
    ntp_client = ntplib.NTPClient()
    response = ntp_client.request('pool.ntp.org')
    print(ctime(response.tx_time))

if __name__ == '__main__':
    print_time()
  • 练习:编写一个简单的C/S架构的“回声”应用

  在介绍完前面的一些基本的socket API后,我们来动手写一个简单的“回声”程序来进行巩固、复习。

  在这个应用中,服务端和客户端都通过argparse模块得到port参数,在服务端,首先建立一个基于TCP的套接字,并设置其可以被重用使服务端程序能打开任意次,然后将它绑定在本机上命令行指定的端口上;接着,在监听阶段,我们设置backlog参数使得服务端能同时监听多个客户端连接,最后等待客户端连接进来并发送一些数据,在接收到这些数据之后,再将其原封不动地返回回去。

注意: 由于python 2.x和python3.x编码类型地不同,python2.x中直接s.send(data)就行,然而,socket中还不支持python3的编码,因此,python3中需要先对str型的data进行encode, 在从socket接受下来的数据打印前也需要先decode处理!否则会报错。

[服务端程序]

import socket
import argparse

HOST = 'localhost'
DATA_PAYLOAD = 2048
#The max number of clients.
BACKLOG = 5

def echo_server(port):
    """A simple echo server"""
    #Create a TCP socket
    serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #Enable reuse address/port
    serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    #Bind the socket to the port
    server_address = (HOST, port)
    print("Starting up echo server on %s port %s" % server_address)
    serv.bind(server_address)
    #Listen to clients, backlog argumen specifies the max no. of queued connections
    serv.listen(BACKLOG)
    while True:
        print("waiting to receive message from client")
        client, address = serv.accept()
        data = client.recv(DATA_PAYLOAD)
        if data:
            print("Data: %s" % data.decode())
            client.send(data)
            print("sent %s bytes back to %s" % (data.decode(), address))
        #end connection
        client.close()

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Socket Server Example')
    parser.add_argument("--port", action = 'store', dest='port', type = int, required=True)
    given_args = parser.parse_args()
    port = given_args.port
    echo_server(port)

  在client端,我们使用命令行参数获得的server端口号创建socket并且与server连接,成功后向服务器发送信息,之后马上按一次16字节接收server回传的信息。

import socket
import argparse

HOST = 'localhost'

def echo_client(port):
    """A simple echo client"""
    # Create a TCP/IP socket
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # Connect the socket to the server
    server_address = (HOST, port)
    print("Connecting to %s port %s" % server_address)
    client.connect(server_address)
    try:
        #Send data
        message = " Test message. This will be echoed"
        print("Sending %s" % message)
        client.sendall(message.encode())
        # Receive from server.
        amount_received = 0
        amount_excepted = len(message)
        while amount_received < amount_excepted:
            data = client.recv(16)
            amount_received += len(data)
            print("Received: %s" % data.decode())
    except socket.error as e:
        print("Socket error %s" % str(e))
    except Exception as e:
        print("Other exception: %s" % str(e))
    finally:
        client.close()

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Socket Client Example')
    parser.add_argument('--port',action='store', dest='port', type=int,required=True)
    given_args = parser.parse_args()
    port = given_args.port
    echo_client(port)

[output]

$ python3 1_13a_echo_server.py --port=9900
Starting up echo server  on localhost port 9900
Waiting to receive message from client
Now, run the client from another terminal as follows:
$ python3 1_13b_echo_client.py --port=9900
Connecting to localhost port 9900
Sending Test message. This will be echoed
Received: Test message. Th
Received: is will be echoe
Received: d
Closing connection to the server
Upon connecting to the localhost, the client server will also print the following message:
Data: Test message. This will be echoed
sent Test message. This will be echoed bytes back to ('127.0.0.1', 42961)
Waiting to receive message from client

 

posted on 2015-07-22 00:05  weixinbupt  阅读(373)  评论(0编辑  收藏  举报

导航