目录:
一、TCP编程
二、UDP编程
Python 提供了两个级别访问的网络服务:
低级别的网络服务支持基本的Socket
,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的全部方法。
高级别的网络服务模块SocketServer
, 它提供了服务器中心类,可以简化网络服务器的开发。SocketServer
其实是在Socket基础上的一个多线程升级,作为入门教程,我们不做涉及。
我们在此将socket编程方式分为面向连接的方式(即TCP)和不面向连接的方式(即UDP)进行分别讨论。
一、TCP编程
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,对应到编程中,它的特点就是需要先建立连接,再进行数据传输。
在此盗一张很棒的图解方便大家理解:
然后,临时为了演示需要,将一个日历查看功能布在了服务端,可通过接受客户端提交的请求,返回对应的请求内容。当然,你完全可以根据自己的需要布置更加有意义的小程序替换这里,如:nmap等。
需要注意的是,socket在python3中的数据传输均使用bytes的方式,所以我们需要及时的进行编码解码操作。
其中socket的基本语法为socket.socket([family[, type[, proto]]])
family: 套接字家族可以使AF_UNIX或者AF_INET
type: 套接字类型可以根据是面向连接的还是非连接分为SOCK_STREAM或SOCK_DGRAM
protocol: 一般不填默认为0
作为入门系列我们不在这些概念上拓展,具体用法参见以下完整实例:
#TCP服务端
import socket
import calendar
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ip_port=('127.0.0.1',11111) #定义绑定ip和端口
s.bind(ip_port) #绑定监听
s.listen(5) #最大连接数
while True:
print('正在等待客户端连接……')
conn,addr=s.accept() #接收连接请求
print("接入地址: %s" % str(addr))
msg='连接成功!\n【欢迎来到史上最辣鸡的日历系统】\n输入"exit"退出本系统'
conn.send(msg.encode()) #发送应答信息
while True: #持续接收数据
data=conn.recv(1024)
if data==b'exit': #收到exit断开连接
break
try:
conn.send(calendar.month(2019, int(str(data.decode()))).encode())
except IndexError: #异常处理机制,防止用户乱输造成服务端崩溃
pass
conn.send('请正确输入月份:(1-12)'.encode())
conn.close() #断开连接
#输出
正在等待客户端连接……
接入地址: ('127.0.0.1', 16942)
正在等待客户端连接……
#TCP客户端
import socket
c=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ip_port=('127.0.0.1',11111) #定义访问ip和端口
c.connect(ip_port) #连接服务器
while True:
data = c.recv(1024) # 接收返回信息
print(data.decode())
msg=input('请输入想要查看的月份: 例如:1\n>>>>>')
c.send(msg.encode())
if msg=='exit': #用户输入exit时退出
break
c.close() #断开连接
#输出:
连接成功!
【欢迎来到史上最辣鸡的日历系统】
输入"exit"退出本系统
请输入想要查看的月份: 例如:1
>>>>>1
January 2019
Mo Tu We Th Fr Sa Su
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
请输入想要查看的月份: 例如:1
>>>>>2
February 2019
Mo Tu We Th Fr Sa Su
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28
请输入想要查看的月份: 例如:1
>>>>>1111
请正确输入月份:(1-12)
请输入想要查看的月份: 例如:1
>>>>>exit
Process finished with exit code 0
其中有一处类型转换是使用的int(str())
,即bytes类型解码转字符串再转数字的形式,暂时没有找到更有效且稳定的替换方案,在线求助各位大佬~
关于常用套接字函数在上面的例子中已通过注释的形式进行了说明,在此进行统一整理:
#服务器端套接字
s.bind()
绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。
s.listen()
开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
s.accept()
被动接受TCP客户端连接,(阻塞式)等待连接的到来
#客户端套接字
s.connect()
主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
s.connect_ex()
connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
#公共用途的套接字函数
s.recv()
接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
s.send()
发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.sendall()
完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
s.recvfrom()
接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
s.sendto()
发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.close()
关闭套接字
s.getpeername()
返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
s.getsockname()
返回套接字自己的地址。通常是一个元组(ipaddr,port)
s.setsockopt(level,optname,value)
设置给定套接字选项的值。
s.getsockopt(level,optname[.buflen])
返回套接字选项的值。
s.settimeout(timeout)
设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
s.gettimeout()
返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
s.fileno()
返回套接字的文件描述符。
s.setblocking(flag)
如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
s.makefile()
创建一个与该套接字相关连的文件
二、UDP编程
UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。对应到编程中,就是不需要连接的建立,直接进行数据传输。
我们直接改写上面的例子进行对比讲解:
#服务端
import socket
import calendar
s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ip_port=('127.0.0.1',22222) #定义绑定ip和端口
s.bind(ip_port) #绑定监听
while True:
print('UDP服务端监听开启……\n')
data,addr = s.recvfrom(1024) #recvfrom()获取数据和地址
if data==b'exit':
break
print('Received from %s:%s.' % addr)
try: #异常处理机制,防止用户乱输造成服务端崩溃
s.sendto(calendar.month(2019, int(str(data.decode()))).encode(),addr)
except:
s.sendto('请正确输入月份:(1-12)'.encode(),addr)
s.close()
#输出:
UDP服务端监听开启……
Received from 127.0.0.1:61716.
UDP服务端监听开启……
Received from 127.0.0.1:61716.
UDP服务端监听开启……
Received from 127.0.0.1:61716.
UDP服务端监听开启……
#客户端
import socket
c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ip_port = ('127.0.0.1', 22222) #定义访问ip和端口
print('【欢迎来到史上最辣鸡的日历系统】\n输入"exit"退出本系统')
while True:
msg=input('请输入想要查看的月份: 例如:1\n>>>>>')
if msg=='exit':
break
c.sendto(msg.encode(), ip_port)
data=c.recv(1024) #接收服务器返回数据
print(data.decode())
c.close() #断开连接
#输出:
【欢迎来到史上最辣鸡的日历系统】
输入"exit"退出本系统
请输入想要查看的月份: 例如:1
>>>>>1
January 2019
Mo Tu We Th Fr Sa Su
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
请输入想要查看的月份: 例如:1
>>>>>2
February 2019
Mo Tu We Th Fr Sa Su
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28
请输入想要查看的月份: 例如:1
>>>>>11111
请正确输入月份:(1-12)
请输入想要查看的月份: 例如:1
>>>>>exit
Process finished with exit code 0
简单说下区别,UDP编程,服务端不需要创建连接等待,直接接收;也不需要listen设置最大连接数。客户端同样不需要先创建连接,也就是不需要进行connect()操作,直接发送数据即可。数据发送方式改为sendto(),需要制定地址,毕竟UDP是非面向连接的。
TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。