《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程

时间:2022-05-20 00:00:34

《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程

1、获取主机名和IP地址

.py:

import socket

def print_machine_info():
host_name = socket.gethostname()
ip_address= socket.gethostbyname(host_name)
print "Host name: %s" % host_name
print "IP address: %s" % ip_address

if __name__ == '__main__':
print_machine_info()

《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程

2、获取远程设备的IP地址
.py:
import socket

def get_remote_machine_info():
remote_host = 'www.python.org'
try:
print "IP address: %s" %socket.gethostbyname(remote_host)
except socket.error, err_msg:
print "%s: %s" %(remote_host,err_msg)

if __name__ == '__main__':
get_remote_machine_info()
《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程
3、将IPv4地址转换成不同的格式
定义convert_ip4_address()函数,调用inet_aton()和inet_ntoa()转换IP地址。
import socket
from binascii import hexlify

def convert_ip4_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 Adress: %s => Packed: %s, Unpacked: %s"\
%(ip_addr,hexlify(packed_ip_addr),unpacked_ip_addr)

if __name__ == '__main__':
convert_ip4_address()
《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程

4、通过制定的端口和协议找到服务名

如果想找到网络服务,最好知道该服务运行在TCP或UDP协议的哪个端口上。

使用for—in语句比那里一组变量。在每次遍历中,获取端口对应的服务名。

.py:

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

《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程

5、主机字节序和网络字节序之间相互转换

编写底层网络应用是,或许需要处理通过电缆在两台设备之间传送的底层数据。在这种操作中,需要把主机操作系统发出的数据转换成网络格式,或者做你想转换,因为这两种数据的表示方式不一样。

Python的socket库提供了将数据在网路字节序和主机字节序之间相互转换的函数,例如ntohl()和htonl(),ntohs()和htons()。

.py:

import socket

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

if __name__ == '__main__':
convert_integer()
以整数为例,演示了如何把它转换成网络自己序和主机字节序。socket库中的类函数ntohl()把网络自己序转换成长整形主机字节序。函数名中的n表示网络;h表示主机;l表示长整形;s表示短整形,即16位。

《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程


6、设定并获取默认的套接字超时时间

有时需要处理socket库某些属性的默认值,例如套接字超时时间。

.py:

import socket

def test_socket_timeout():
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()

if __name__ == '__main__':
test_socket_timeout()
首先创建一个套接字对象。套接字构造方法的第一个参数是地址簇,第二个参数是套接字类型。然后,调用gettimeout()方法获取套接字超时时间,再调用settimeout()方法修改超时时间。传给settimeout()方法的参数可以是秒数(非浮点数)也可以是None。这个方法在处理阻塞式套接字操作时使用。如果把超时时间设为None,则禁用了套接字操作的超时检测。

《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程


7、优雅地处理套接字错误

在网络中经常会遇到这种情况:一方尝试连接,但另一方由于网络媒介失效或者其他原因无法响应。Python的socket库提供了一个方法,能通过socket.error异常优雅地处理套接字错误。

import socket
import sys
import argparse

def main():
#setup argument parsing
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 -- creat 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 "Connect 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:
#Forth try-except block -- waiting to recieve data 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;
#write the recieved data
sys.stdout.write(buf)


if __name__ == '__main__':
main()

8、修改套接字发送和接收的缓冲区大小

       很多时候,默认的套接字缓冲大小可能不够用。此时,可以将默认的套接字缓冲区大小改成一个更合适的值。

       在套接字对象上可调用方法getsockopt()he setsockopt()分别获取和修改套接字对象的属性。setsockopt()方法接收三个参数:level、opename和value。其中,optname是选项名,value是该选项的值。第一个参数所用的符号变量是(SO_*等)可在socket模块中查看。

.py:

import socket   

SEND_BUF_SIZE = 4096
RECV_BUF_SIZE = 4096

def modify_buf_size():
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

#Get the size of the socket's send buffer
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_buf_size()

《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程

9、把套接字改成阻塞或费阻塞模式

      默认情况下,TCP套接字处于阻塞模式中。也就是说,除非完成了某项操作,否则不会把控制权交还给程序。例如,调用connect()API后,链接操作会组织程序继续往下执行,直到链接成功为止。很多情况下,你并不想让程序一直等待服务器响应或者有异常终止操作。例如,如果编写了一个网页浏览器客户端链接服务器,你应该考虑提供取消功能,以便在操作过程中取消连接。这是就要把套接字设置成非阻塞模式。

       在Python中,套接字可以被设置为阻塞模式或者非阻塞模式。在非阻塞模式中,调用API后,例如send()或recv()方法,如果遇到问题就会跑出异常。但在阻塞模式中,遇到错误并不会阻止操作。我们可以创建一个普通的TCP套接字,分别在阻塞模式和非阻塞模式中执行操作实验。

        为了能在阻塞模式中处理套截止,首先要创建一个套接字对象。然后调用setblocking(1)把套接字设为阻塞模式,或者调用setblocking(0)把套接字设为非阻塞模式。最后,吧套接字绑定到指定的端口上,监听进入的链接。

.py:

import socket   

SEND_BUF_SIZE = 4096
RECV_BUF_SIZE = 4096

def test_socket_modes():
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#blocking mode
s.setblocking(1)
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(1):
s.listen(1)


if __name__ == '__main__':
test_socket_modes()

《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程

将s.setblocking(1)改为s.setblocking(0),即由阻塞模式改为非阻塞模式,运行:

《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程


10、重用套接字地址

        不管链接是被有意还是无意关闭,有时你想时钟在同一个端口上运行套接字服务器。某些情况下,如果客户端程序需要一直链接指定的服务器端口,这么做就很有用,因为无需改变服务器端口。

        创建套接字对象之后,我们可以查询地址重用的状态,比如说旧状态。然后,调用setsocketopt()方法,修改地址重用状态的值。再按照常规的步骤,把套接字绑定到一个地址上,监听进入的客户端链接。在这个例子中,我们要捕获KeyboardInterrupt异常,这样按下Ctrl+C键后,Python脚本会终止运行,但不会显示任何异常消息。

.py:

import socket   
import sys

def reuse_socket_addr():
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

#Get the old state of the SO_REUSEADDR option
old_state = sock.getsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR)
print "Old sock state: %s" %old_state

#Enable the SO_REUSEADDR option
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 "Listen on port: %s" %local_port
while True:
try:
connection, addr =srv.accept()
print 'Connected by %s' %(addr[0], addr[1])
except KeyboardInterrupt:
break
except socket.error, msg:
print '%s' % (msg,)


if __name__ == '__main__':
reuse_socket_addr()

《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程

        可以在一个终端窗口运行这个脚本,然后在另一个终端窗口中输入telnet localhost 8282,尝试连接这个服务器。关闭服务器后,还可以使用同一个端口再次连接。然而,如果你把设定SO_REUSEADDR的那行代码注释掉,服务器将不会再次运行脚本。


11、从网络时间服务器获取并打印当前时间

        很多程序要求设备的时间精准,例如Unix系统中的make命令。设备上的时间可能不都准确,需要和网络中的时间服务器同步。

你可以编写一个python客户端,让设备上的时间和某个网络时间服务器同步。要完成这一操作,需要使用ntplib,通过“网络时间协议”(Network Time Protocol,简称NTP)处理客户端和服务器之间的通信。如果设备中没有安装ntplib,可以使用pip或easy_install从PyPI中安装,命令如下:

$pip install ntplib

        如果是Windows系统,到官方网站上,https://pypi.python.org/pypi/ntplib,下载ntplib-0.3.3.tar.gz ,然后解压,将ntplib.py拷贝到D:\Python27\Lib(这是我的路径,自己的安装路径自己注意),这就解决了,为啥Linux的包解压的文件能在Windows里用,这个我就不解释了。

.py:

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

《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程

12、编写一个SNTP客户端

        与上一个程序不同,有时并不需要从NTP服务器上获取精确的时间。遇到这种情况,就可以使用NTP的简化版本,叫作“简单网络时间协议(SNTP)”

         不使用任何第三方库来编写一个简单的SNTP客户端。

        首先定义两个常量:NTP_SERVER和TIME1970。NTP_SERVER是客户端要链接的服务器地址,TIME1970是指1970年1月1日(也叫Epoch)。在http://www.epochconverter.com/上可以查看Epoch时间值,或者把时间转换成Epoch时间值。这个客户端通过UDP协议创建一个UDP套接字(SOCK_DGRAM),用于连接服务器。然后,客户端要在一个数据包中把数据'\x1b' + 47 * '\0'发送给SNTP服务器。UDP客户端分别使用sendto()和recvfrom()方法发送和接收数据。

         服务器返回的时间信息打包在一个数组中,客户端需要使用struct模块取出数据。我们所需的数据是数组中的第11个元素。最后,我们要从取出的数据上减掉TIME1970,得到真正的当前时间。

        这个SNTP客户端创建一个套接字连接,然后通过协议发送数据。从NTP服务器(这里使用的是0.uk.pool.ntp.org)收到数据后,使用struct模块取出数据。最后,减去1970年1月1日对应的时间截,再使用Python内置的time模块提供的ctime()方法打印时间。

.py:

import socket
import struct
import sys
import time

NTP_SERVER = "0.uk.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 receiveed from:', address
t=struct.unpack('!12I', data)[10]
t -= TIME1970
print '\tTime = %s' %time.ctime(t)

if __name__ == '__main__':
sntp_client()

《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程

13、编写一个简单的回显客户端/服务器应用

        尝试过Python中socket模块的基本API后,现在来编写一个套接字服务器和客户端。这里,你将有机会利用在签署攻略中掌握的基本知识。

       不管服务器从客户端收到什么输入,都会将其回显来。我们要使用Python中的argparse模块,在命令行中指定TCP端口。服务器脚本和客户端脚本都要用到这个参数。

       先写一个服务器。首先创建一个TCP套接字对象。然后设定启用重用地址,这样想运行多少次服务器就能运行多少次。我们把套接字绑定在本地设备的指定端口上。在监听阶段,把backlog参数传入listen()方法中,让服务器在队列中监听多个客户端。最后,等待客户端连接,向服务器发送一些数据。收到数据后,服务器会把数据回显给客户端。

echo_server.py:

import socket
import sys
import argparse

host = 'localhost'
data_payload = 2048
backlog = 5

def echo_server(port):
"""A simple echo server"""
#create a TCP socket
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#Enable reuse address/port
sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
#bind the socket to the port
server_address = (host,port)
"""Pay attention to " %s port %s",or it will trow exceptions"""
print "Starting up echo server on %s port %s" %server_address
sock.bind(server_address)
#Listen to clients, backlog argument specifies the max no. of queued connections
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)
#end connection
client.close()

if __name__ == '__main__':
parser = argparse.ArgumentParser(description = 'Socket Server Example')
"""Pay attention to tab position ,or it will throw exceptions!!!"""
parser.add_argument('--port',action="store",dest="port",type=int,
required=True)
given_args = parser.parse_args()
port = given_args.port
echo_server(port)

       Python的语法格式一定要多加注意,否则一直编译出错。

      客户端中,我们创建一个客户端套接字,然后使用命令行参数中指定的端口链接服务器。客户端把消息Test message. This will be echoed发送给服务器后,立即就会在几个数据片段中收到返回的消息。这里用到了两个try-except块,捕获交互过程中发生的任何异常。

echo_client.py:

import socket
import sys

import argparse

host = 'localhost'

def echo_client(port):
"""A simple echo client"""
#create a TCP/IP socket
sock =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
sock.connect(server_address)

#send data
try:
#Send data
message = "Test message. This will be echoed"
print "Sending %s " %message
sock.sendall(message)
#Look for the response
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')
parser.add_argument('--port',action="store",dest="port",type=int,
required=True)
given_args = parser.parse_args()
port = given_args.port
echo_client(port)

《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程

3、重复步骤1,键入echo_client.py --port=9900

《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程

然后服务器端窗口显示如下:《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程

4、修改接收的消息长度,将客户端的data = sock.recv(16)修改为32:

重复上述步骤:

《Python网络编程》Part1 Socket、IPv4和简单的客户端/服务器编程