Python之socket网络编程
1.什么是socket
了解socket之前首先要了解osi七层模型,或者说五层模型:应用层,传输层,网络层,数据链路层,物理层
其中,数据一定是转化成物理信号通过物理层的物理设备才能传输;但是我们不谈物理设备,我们今天的重点是 :网络上的两个程序怎么通过一个连接实现数据交换。怎么唯一标示一个程序呢,是通过ip+端口的方式。socket就是用来描述ip和端口的,可以理解socket为把tcp,udp协议封装成一组接口,我们不需要知道复杂的tcp/udp协议,只需要按照socket的规定编程,写出的程序自然就是遵循tcp/udp协议的。
socket又被称为套接字,我们研究套接字只需要记住两件事:建立连接,收发数据,就行了在写代码之前我们还需要知道一个c/s模型,就是client/sever(客户端与服务端),交换数据至少得两个人吧,在网络编程中就是客户端和服务端。客户端向服务端发送请求,服务端响应客户端的请求。
我们先写一个简单的基于tcp的socket程序:
#服务端 import socket phone_sever=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone_sever.bind(('127.0.0.1',8080))#绑定ip和端口,我们用的本地回环网卡演示 phone_sever.listen(5)#监听,等待连接 conn,addr=phone_sever.accept() #客户端连入 ret=conn.recv(1024)#接收客户端发来的消息 conn.close()#断开连接 phone_sever.close()#服务端关闭
#客户端 import socket phone_client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone_client.connect(('127.0.0.1',8080))#连接服务端的ip和端口 phone_client.send('hello'.encode('utf-8'))#向服务端发送消息 phone_client.close()#客户端关闭
写好后,先运行服务端,在运行客户端,这样就成功写了一个socket。这是最简单的socket例子了,只是介绍了基本语法,我们下面优化一下代码,
注意:传输的数据都应该是bytes类型
2.基于TCP的套接字
我们基于上面的例子,优化一下代码
1.服务端应该是循环接收数据,并能循环发送数据
2.客户端应该也能接收服务端的消息,并且也是循环收发
3.服务端可以与多个客户端连接
4.某一个客户端断开连接或者发生故障不能导致服务端崩溃
5.顺便模拟一下客户端输入命令,服务端把命令的执行结果返回给客户端
6.解决OSError:[Errno 48] Address already in use的问题
#服务端 import subprocess import socket phone_sever=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#买手机 phone_sever.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)#解决问题6,加上这句话就行 phone_sever.bind(('127.0.0.1',8080))#这里是元组形式 phone_sever.listen(5) print('sever run ') while True:#这个循环解决问题3 conn,client_addr=phone_sever.accept() print('客户端',client_addr) while True:#这个while循环解决问题1 try:#捕捉异常,解决问题4 cmd=conn.recv(1024)#收消息 res=subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)#利用subprocess这个模块,解决问题5 stdout=res.stdout.read() stderr=res.stderr.read() sever_res=stdout+stderr if not sever_res: sever_res=b'is vaild' conn.sendall(sever_res)#发消息 except Exception: break conn.close()#挂电话 phone_sever.close()#关机
#客户端
import socket phone_client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#买手机 phone_client.connect(('127.0.0.1',8080))#拨号 # phone_client.connect(('192.168.16.253',8080))#拨号 while True:#这个循环解决问题2 msg=input('>>>>:') if not msg:continue#解决客户端输入空,服务端会死掉的问题 phone_client.send(msg.encode('utf-8')) server_res=phone_client.recv(1024) print('server_res:',server_res.decode('gbk')) phone_client.close()
当我们在实验的时候,可能会遇到这种情况:客户端输入为空,服务端就卡住了。解释这个问题就要说到网络传输的原理了
我们前面说了,两台机器之间传输数据一定是通过底层网卡等物理设备的,而应用程序是不能操作底层硬件的,这时就需要调用操作系统了,当用send发数据时,其实是由应用程序把数据发给操作系统,然后由操作系统把数据放到一块缓存上,然后由这块缓存再将数据发送给服务端的缓存,如果我们send的是一个空数据,那肯定就不会发送成功了,但是我们的客户端以为按了回车就发送了,就开始等待服务端的回应了,所以就卡住了。
3.基于UDP的套接字
#服务端
import socket udp_sever=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) udp_sever.bind(('127.0.0.1',8080)) while True: msg,addr=udp_sever.recvfrom(1024) print(msg,addr) udp_sever.sendto(msg.upper(),addr)
#客户端 import socket ip_port=('127.0.0.1',8080) udp_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) while True: msg=input('>>>>>>:').strip() if not msg:continue udp_client.sendto(msg.encode('utf-8'),ip_port) back_msg,addr=udp_client.recvfrom(1024) print(back_msg.decode('utf-8'),addr)
udp不需要建立连接,所以可以实现与多个客户端同时建立连接。但是相比于tcp,udp是不可靠传输,但是速度快。可以把tcp协议理解为打电话,必须双方建立连接,然后说一句回一句。udp就像是发短信,客户端只关心消息有没有发出去就行了,不用关心对方有没有收到。具体udp的不可靠传输,下面会说到
在代码方面的区别,tcp的recv就相当于udp的recvfrom,tcp的send就相当于udp的sendto,另外因为udp不建立连接,所以发送消息的时候需要指定ip和端口号
4.粘包现象
先来看我们前面写的tcp的代码,recv()括号里的1024,这个1024限制了接收消息的最大字节数,想象一下,如果我们接收的数据长度超过1024字节,为了便于观察,我们把这个接受消息的最大字节数改成10,代码如下,看看如果发送的消息超过10,接收端会发生什么:
1 import subprocess 2 3 import socket 4 phone_sever=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#买手机 5 phone_sever.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 6 phone_sever.bind(('127.0.0.1',8080)) 7 8 phone_sever.listen(5) 9 10 print('sever run ') 11 while True: 12 13 conn,client_addr=phone_sever.accept() 14 print('客户端',client_addr) 15 while True: 16 try: 17 cmd=conn.recv(10)#收消息 18 print(cmd.decode('utf-8')) 19 msg=input('>>>>>').strip() 20 conn.send(msg.encode('utf-8')) 21 except Exception: 22 break 23 conn.close()#挂电话 24 25 phone_sever.close()#关机
1 import socket 2 3 phone_client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 phone_client.connect(('127.0.0.1',8080)) 5 while True: 6 msg=input('>>>>:') 7 if not msg:continue 8 phone_client.send(msg.encode('utf-8')) 9 10 server_res=phone_client.recv(10)#收消息 11 print('server_res:',server_res.decode('gbk')) 12 13 phone_client.close()