9.2 Socket编程
远程管理软件和黑客软件大多依赖于Socket来实现特定功能,前几年流行的端口反弹更是把这项技术发挥到了极致。
如前所述,UDP和TCP是网络体系结构的传输层运行的两大重要协议,其中,TCP适用于对效率要求相对低而准确性要求相对高的场合,如文本传输、电子邮件等;而UDP适用于对效率要求相对高,对准确性要求相对低的场合,如视频在线点播、网络语音通话等。在Python中,主要使用socket模块来支持TCP和UDP编程。
9.2.1 UDP编程
在很多年以前普通家庭还没有手机、电话、传呼机的时候,主要靠信件往来联系,发信人提哦啊好收信人地址然后把信件寄出去就可以了,但是没法保证对方一定能收到这封信(例如对方换了地址),也不能保证不同时间的几封信按照发出的顺序到达目的地。UDP的工作过程就类似于邮寄普通信件,它属于无连接协议,在UDP编程时不需要首先建立连接,而是直接向接收方发送信息。UDP也不提供应答和重传机制,无法保证数据一定鞥能够到达目的地。UDP最大的优点是效率高,其首部中只包含双发地址于校验等很少的字段,额外开销很小。UDP编程经常用到的socket模块方法如下。
(1)socket([family[,type[,proto]]] :创建一个Socket对象,其中family为socket.AF_INET表示IPv4,AF_INET6表示IPv6;type为SOCK_STREAM表示使用TCP,SOCK_DGRAM表示使用UDP。
(2)sendto(string,address):把string指定的内容发送给address指定的地址,其中address是一个包含接收方主机IP地址的应用进程端号的元组,格式为(IP地址,端口号)。
(3)recvfrom(bufsize[,flags]):接收数据。
下面通过一个示例来简单了解一个如何使用UDP进行网络通信。
发送端发送一个字符串,假设接收端在本机5000端口进行监听,并显示接受的内容,如果收到字符串'bye'(忽略大小写)则结束监听。
接收端代码:
1 import socket
2
3 #使用IPv4协议,使用UDP传输数据
4 s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
5
6 #绑定端口和端口好,空字符串表示本机任何可用IP地址
7 s.bind('',5000)
8
9 while True:
10 data,addr = s.recvfrom(1024)
11
12 #显示接收到的内容
13 print('received message:{0} from PORT{1} on {2}'.format(data.decode(),addr[1],addr[0]))
14
15 if data.decode().lower() == 'bye':
16 break
17
18 s.close()
发送端代码:
import socket
import sys
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#假设192.168.1.103是接收端机器的IP地址
s.sendto(sys.argv[1].encode(),('192.168.1.103',5000))
s.close()
将上面的代码分别保存为receiver.py和sender.py,然后首先启动一个命令提示符环境并运行接收端程序,这时接收端程序处于阻塞状态,接下来再启动一个新的命令体书法环境并运行发送端程序,此时会看到接收端程序继续运行并显示接收到的内容以及发送端程序所在计算机IP地址和占用的端口号。当发送端发送字符串'bye'后,接收单程序结束,此后再次运行发送端程序时接收端没有任何反应,但发送端程序也并不报错。这就是UDP的特点,即“尽最大努力传输”,并不能保证非常好的服务质量。
可以使用下面的Python代码来获取本机的IP地址和网卡的物理地址。
1 ip = socket.gethostbyname(socket.gethostname()) #本机IP地址
2 node = uuid.getnode()
3 print('node:',node)
4
5 macHex = uuid.UUID(int=node).hex[-12:]
6 mac = []
7 print(macHex)
8 for i in range(len(macHex))[::2]:
9 mac.append(macHex[i:i+2])
10
11 mac1 = ':'.join(mac) #网卡的物理地址
12
13 print('IP:',ip)
14 print('MAC:',mac1)
拓展知识:发送数据时,如果目标IP地址中最后一组数字是255,表示广播地址,也就是说局域网内的所有主机都会受到信息。
9.2.2 TCP 编程
TCP一般用于要求可靠数据传输的场合。编写TCP程序是需要用到的socket模块方法主要如下。
(1)connect(address):连接远程计算机。
(2)send(bytes[,flags]):发送数据。
(3)recv(bufsize[,flags]):接收数据。
(4)bind(address):绑定地址。
(5)listen(backlog):开始监听,等待客户端连接。
(6)accept():响应客户端的请求,接收一个连接。
下面通过一个示例来演示如何使用TCP进行通信。会聊天的小机器人。
使用TCP进行铜线需要首先在客户端和服务端之间建立连接,并且要在通信结束后关闭连接以释放资源。TCP能够提供比UDP更好的服务质量,通信可靠性有本质上的提高。下面的代码简单模拟了机器人聊天软件原理,服务端提前建立好字典,可以根据收到的内容自动回复。
服务端代码:
1 import socket
2
3 words = {'how are you?':'Fine,thank you','how old are you?':'38','what is your name?':'Dong FuGuo',"what's your name?":'DongFuGuo',
4 'where do you work?':'SDIBT','bye':'BYE'}
5
6 HOST = ''
7 PORT=50007
8 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
9 #绑定socket
10 s.bind((HOST,PORT))
11
12 #开始监听一个客户端连接
13 s.listen(1)
14 print('Listening at port:',PORT)
15 conn,addr = s.accept()
16
17 print('Connected by',addr)
18
19 while True:
20 data = conn.recv(1024)
21 data = data.decode()
22 if not data:
23 break
24
25 print('Received message:',data)
26 conn.sendall(words.get(data,'Nothing').encode())
27 conn.close()
28 s.close()
客户端代码:
1 import socket, sys
2
3 # 服务端主机的IP地址和端口好
4 HOST = '192.168.1.101'
5 PORT = 50007
6 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
7 try:
8 # 连接服务器
9 s.connect((HOST, PORT))
10 except Exception as e:
11 print('Server not found or not open')
12 sys.exit()
13
14 while True:
15 c = input('Input the content you want to send:')
16 # 发送数据
17 s.sendall(c.encode())
18
19 # 从服务器端接收数据
20 data = s.recv(1024)
21 print('Received:', data)
22 if c.lower() == 'bye':
23 break
24
25 # 关闭连接
26 s.close()
扩展知识:Python标准库socket处理支持UDP和TCP编程之外,还提供了用来获取本地主机名的gethostname()、根据主机名获取IP地址的gethostbyname()、根据IP地址获取主机名的gethostbyaddr()、根据端口号获取对应服务名称的getservbyport()、根据服务名称获取对应端口号的getservbyname()等方法。