1.1 简介
因为要放上具体的代码,今天就从新浪转战CSDN了
原来对Python有了一定的了解,2和3的差距有时候还是挺折腾人的。原来刷过几个oj,上面都是用Python解决的,对于大部分人来说问题应该不是特别大。
今天开始lz就按照《Python Network Programming Cookbook》这本书来进行追踪,求监督!
ps.第一章还是按照Python3进行代码的,后面的章节,因为有涉及到操作系统的问题,话说我一直不知道为什么Python for Win里面竟然会有os.fork()……后面就直接在Ubuntu 12.04上面实验了。Ubuntu自带Python 2.7,正好和书本当中的版本一样。
1.2 打印设备名和IPv4地址
<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;"># 1_1_local_machine_info.py
import socket
def print_machine_info():
host_name = socket.gethostname()
ip_address = socket.gethostbyname(host_name)
print ("host name is %s " %host_name)
print ("ip address is %s" %ip_address)
if __name__ == '__main__':
print_machine_info()</span></span></span>__name__注意是两个下划线,表示调用程序的进程名,如果在命令行中运行脚本,__name__的值就是'__main__'print函数就是前面格式化,用引号包住,后面一个%,然后再把需要的输出用括号括起来。
1.3 获取远程设备的IP地址
<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;"># 1_2_remote_machine_info.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 as err_msg:
print("%s %s" %(remote_host, err_msg))
if __name__ == '__main__':
get_remote_machine_info()</span></span></span>主机名改变即可,socket.gethostbyname的参数改变。
1.4 将IPv4地址转换成不同的格式
<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;"># 1_3_ipv4_address_conversion.py
import socket
from binascii import hexlify
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))
if __name__ == '__main__':
convert_ipv4_address()</span></span></span>调用了binascii模块的hexlify函数,以十六进制形式表示二进制数据。
1.5 通过制定的端口和协议找到服务名
<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;"># 1_4_finding_service_name.py
import socket
def find_service_name():
protocolname = 'tcp'
for port in [80,25]:
#for port in range(20,81):
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()</span></span></span>1.6 主机字节序和网络字节序之间相互转换
<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;"># 1_5_integer_conversion.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)))
data1 = socket.htonl(data)
data2 = socket.ntohl(data1)
print("%s %s %s" %(data,data1,data2))
if __name__ == '__main__':
convert_integer()</span></span></span>socket库中的类函数ntohl()把网络字节序转换成了长整形主机字节序。函数名中的n表示网络,h表示主机,l表示长整形,s表示短整型,即16位。
1.7 设定并获取默认的套接字超时时间
<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;"># 1_6_socket_timeout.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()
</span></span></span>需要创建套接字对象。套接字构造方法第一个参数是地址族,第二个参数是套接字类型。socket=socket.socket(family,type),family的值可以是AF_UNIX(Unix域,用于同一台机器上的进程通讯),也可以是AF_INET(对于IPv4协议的TCP和UDP)。至于type参数,SOCK_STREAM(流套接字)或者SOCK_DGRAM(数据报文套接字),SOCK_RAW(raw套接字)。
gettimeout()方法获取套接字的超时时间,再调用settimeout()方法修改超时时间。传给settimeout()的参数可以是秒数,非负浮点数,也可以是None。如果是None,就禁用了套接字操作的超时检测。
这里提到了socket,那么就说一下socket套接字的用法。
先说一下SNTP服务器,(这样1.14好像没有写的必要了?)这里感谢点击打开链接,写的很详细,大家可以参考。后面还有更加复杂的多线程,后面章节会说明如何编写。
server端:
第一步,创建一个socket对象,参数已经说了,见上。
第二步,socket进行绑定到一个地址上面,进行监听。
<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;">socket.bind(address)
sock.listen(backlog)</span></span></span>address是一个(ip,port)的双元素二元组,绑定的时候需要这么操作 socket.bind((ip,port))
backlog表示最多连接数,server收到连接请求之后,这些请求会进行排队,如果队列已经满了,超过backlog数目,那么拒绝请求。
第三步,server通过socket.accept()接收client端来的请求链接。
代码如下,一般会有一个while True的循环,表示服务器一直进行监听。
<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;">while True:
client_socket, address = socket.accept()
data = client_socket.recv(data_payload).decode()
client_socket.sendall(msg.encode())</span></span></span>调用accept方法的时候,socket会进入waiting(或阻塞)状态。client端请求链接,建立连接并返回服务器。accept()方法返回一个含有两个元素的双元组,形如client_socket,address。第一个client_socket是新的socket对象,server通过它与client发送信息,address是client的address。第四步,接收、处理和发送信息。
代码和第三步的代码在一起了。这里需要注意一点,Python3.4有一个捉鸡的问题,上文档
In Python3, bytes strings and unicode strings are now two different types. Since sockets are not aware of string encodings, they are using raw byte strings, that have a slightly different interface from unicode strings.
encode it when unicode -> byte string
decode it when byte -> unicode strng
When you use client_socket.send(data), replace it by client_socket.send(data.encode())
When you get data, using data = client_socket.recv(1024), replace it by data = client_socket.recv(1024).decode()
就是说,平时代码当中的都是byte string,但是当我在socket当中进行传输的时候,用的是unicode string,这两个类型的string在Python2当中是不区分的,而在Python3就区分了。所以需要手动的encode()和decode()一下。
client端就留到1.14再详细描述。大家先感受一下server端,其实和java的很像。
1.8 优雅地处理套接字错误
<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;"># 1_7_socket_errors.py
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", required=False)
parser.add_argument("--file", action="store", dest="file", required=False)
given_args = parser.parse_args()
host = given_args.host
port = int(given_args.port)
filename = given_args.file
print("host: %s port: %s filename: %s" %(host, port, filename))
# First try-except block -- create socket
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error as 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 as e:
print("Address-related error connecting to server %s" %e)
sys.exit(1)
except socket.error as 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).encode())
#s.sendall('GET 1_7_socket_errors.py HTTP/1.0\r\n\r\n'.encode())
except socket.error as e:
print("Error sending data: %s" %e)
sys.exit(1)
# Fourth try-except block -- waiting to receive data from remote host
while 1:
try:
buf = s.recv(2048).decode()
except socket.error as e:
print("Error receiving data: %s" %e)
sys.exit(1)
if not len(buf):
break
# write the received data
sys.stdout.write(buf)
if __name__ == '__main__':
main()
</span></span></span>注意一下语法Python3: except socket.error as e
Python2: except socket.error, e
这个程序要运行,IDLE模式可能不行,还得在Linux的shell模式,或者windows的cmd模式下运行。输入
<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;">python 1_7_socket_errors.py --host=www.python.org --port=80</span></span></span>可以看到这里有一个args参数的用法,和java的public static void main(String[] args) args[0],args[1]功能一样。
<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;">parser = argparse.ArgumentParser(description="")
parser.add_argument( ……, dest='dest1', …… )
given_args = parser.parse_args()
variable1 = given_args.dest1</span></span></span>ArgumentParser Object
Create a new ArgumentParser object. All parameters should be passed as keyword arguments. Each parameter has its own more detailed description below, but in short they are:
- prog - The name of the program (default: sys.argv[0])
- usage - The string describing the program usage (default: generated from arguments added to parser)
- description - Text to display before the argument help (default: none)
- epilog - Text to display after the argument help (default: none)
- parents - A list of ArgumentParser objects whose arguments should also be included
- formatter_class - A class for customizing the help output
- prefix_chars - The set of characters that prefix optional arguments (default: ‘-‘)
- fromfile_prefix_chars - The set of characters that prefix files from which additional arguments should be read (default: None)
- argument_default - The global default value for arguments (default: None)
- conflict_handler - The strategy for resolving conflicting optionals (usually unnecessary)
- add_help - Add a -h/–help option to the parser (default: True)
add_argument() method
ArgumentParser.add_argument(name or flags...[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][,dest])¶ Define how a single command-line argument should be parsed. Each parameter has its own more detailed description below, but in short they are:
- name or flags - Either a name or a list of option strings, e.g. foo or -f, --foo.
- action - The basic type of action to be taken when this argument is encountered at the command line.
- nargs - The number of command-line arguments that should be consumed.
- const - A constant value required by some action and nargs selections.
- default - The value produced if the argument is absent from the command line.
- type - The type to which the command-line argument should be converted.
- choices - A container of the allowable values for the argument.
- required - Whether or not the command-line option may be omitted (optionals only).
- help - A brief description of what the argument does.
- metavar - A name for the argument in usage messages.
- dest - The name of the attribute to be added to the object returned by parse_args().
ArgumentParse的参数,用的比较多的是description,表示的是在help之后显示的内容
add_argument的参数,用的是我们在代码中显示的几个
name of flags: 在shell或者cmd模式下输入的名字
action: 这里用的都是store,表示的是保存某个参数的数值
dest: 用来指代输入的变量
type: 数值类型
required: 是否必须输入
1.9 修改套接字发送和接收的缓冲区大小
# 1_8_modify_buff_size.py
import socket
SEND_BUF_SIZE = 4096
RECV_BUF_SIZE = 4096
def modify_buff_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_buff_size()setsockopt()方法接受三个参数:level,optname和value。其中,optname是选项名,value是该选项的值。第一个参数所用的符号常量可以在socket模块中查看。level定义了哪个选项将被使用,通常情况下是SOL_SOCKET,意思是正在使用的socket选项。1.10 把套接字改成阻塞式或给阻塞模式
# 1_9_socket_modes.py
import socket
def test_socket_modes():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
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()setblocking(1)把套接字设置为阻塞模式,setblocking(0)把套接字设置为非阻塞模式。
1.11 重用套接字地址
# 1_10_reuse_socket_address.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("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 msg:
print("%s" %msg)
if __name__ == '__main__':
reuse_socket_addr()有时候需要在同一个端口上运行套接字服务器,比如client需要一直连接指定服务器端口,这么操作就很有用,因为无需改变服务器端口。
在一个cmd下输入telnet localhost 8282,即可连接到这个服务器。
代码中SO_REUSEADDR表示,当socket关闭后,本地端用于改socket的port立即可以被重用。默认情况下,只有经过系统定义的一段时间后才能被重用。
1.12 从网络时间服务器获取并打印当前时间
# 1_11_print_machine_time.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()用了一个ntplib,通过Network Time Protocol处理客户端和服务器之间的通信。
1.13 编写一个SNTP客户端
# 1_12_stnp_client.py
import socket
import struct
import sys
import time
NTP_SERVER = '0.uk.pool.ntp.org'
TIME1970 = 2208988800
def sntp_client():
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
data = '\x1b' + 47 * '\0'
client.sendto(data.encode(), (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()这个SNTP客户端创建了一个套接字链接,然后通过协议发送数据。从NTP服务器受到数据后,用struct模块取出数据。最后,减去1970年1月1日对应的时间戳,再用Python内置的time提供的ctime()方法打印时间。
1.14 编写一个简单的回显客户端/服务器应用
server端
# 1_13a_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)
print("Starting up echo server on %s port %s" %server_address)
sock.bind(server_address)
# Listening 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()
print("The client address is %s" %(str(address)))
data = client.recv(data_payload).decode()
if data:
print("Data: %s" %data)
client.send(data.encode())
print("sent %s bytes back to %s" %(data, address))
print()
#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)
echo_server(9900)运行 python 1_13a_echo_server.py --port=9900
client端
# 1_13b_echo_client
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.encode())
# Look for response
amount_received = 0
amount_expected = len(message)
while amount_received < amount_expected:
data = sock.recv(16).decode()
amount_received += len(data)
print("Received: %s" %data)
except socket.error as e:
print('Socket error: %s' %str(e))
except Exception as 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)
echo_client(9900)运行 python 1_13b_echo_client.py --port=9900
这里接着1.7继续讲一下客户端的socket创建,如果理解了server端,client其实就简单很多了。代码如下
sock = socket.socket(family,type)
sock.connect((host,port))
sock.recv(1024).decode()
sock.sendall(msg.encode())
sock.close()第一步,创建一个socket。
第二步,使用sock的connect方法,连接服务器。
第三步,接受、处理、发送信息。
第四步,关闭socket。
妈蛋,不小心排版错了,重新来了一遍,差点弄死我,眼睛废了的感觉。
anyway,总算是结束了第一章。下面继续哈。
我去,这个格式我调整了三次了,每次都没问题,发表之后都是问题……懒得弄了……能看懂就行。