这一部分主要介绍python中socket模块的相关内容,socket即套接字。
socket是使用TCP/IP协议的应用程序通常采用的应用编程接口,它位于运输层和应用层之间,起源于UNIX,由于遵从UNIX“一切皆文件的”思想故socket可看作一种特殊的文件,对其的操作基本可以视为读写I/O、打开、关闭。关于套接字的基本概念@吴秦的写的很详细,大家可以参考。
在下面列出的各个部分中我将先贴出代码,然后对其进行解释。
- 通过python3获得本机名和ip地址
1 >>> import socket2 >>> host_name = socket.gethostname()3 >>> print "Host name: %s" %host_name4 Host name: debian65 >>> print "IP address: %s" %socket.gethostbyname(host_name)6 IP address: 127.0.1.1
上面代码中gethostname方法返回字符串形式的当前主机名,gethostbyname方法返回对应主机的IPv4地址,注意,这里的host可以是类似‘www.baidu.com’等因特网上主机名。
gethostname() -> hostgethostbyname(host) -> address
代码很简单明了,不详细说了。
- 获得远端计算机的Ip地址
将前面的gethostbyname(host)方法的参数host设为'www.baidu.com'格式的远端主机名就能获得其IP地址。
1 import socket2 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 socketdef 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]) -> valuesetsockopt(level, option, value)
socket.socket.getsocket方法可以获得当前缓存大小信息,socket.socket.setsocket方法可以重新设置缓存大小:
import socketSEND_BUF_SIZE = 4096RECV_BUF_SIZE = 4096def 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 socketdef 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 socketdef 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 ntplibfrom time import ctimedef 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 socketimport argparseHOST = 'localhost'DATA_PAYLOAD = 2048#The max number of clients.BACKLOG = 5def 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 socketimport argparseHOST = '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=9900Starting up echo server on localhost port 9900Waiting to receive message from clientNow, run the client from another terminal as follows:$ python3 1_13b_echo_client.py --port=9900Connecting to localhost port 9900Sending Test message. This will be echoedReceived: Test message. ThReceived: is will be echoeReceived: dClosing connection to the serverUpon connecting to the localhost, the client server will also print the following message:Data: Test message. This will be echoedsent Test message. This will be echoed bytes back to ('127.0.0.1', 42961)Waiting to receive message from client