python网络编程
Posted woz333333
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python网络编程相关的知识,希望对你有一定的参考价值。
知识内容:
1.socket语法及相关
2.黏包
3.struct模块
4.subprocess模块
5.socketserver模块
6.验证客户端连接的合法性
参考:
http://www.cnblogs.com/Eva-J/articles/8244551.html
http://www.cnblogs.com/alex3714/articles/5227251.html
关于网络基础概念:http://www.cnblogs.com/wyb666/p/9014857.html
一、socket语法及相关
1.基于TCP协议的socket
TCP是基于连接的,必须先启动服务端,然后再启动客户端去链接服务端;另外双方一定要有一方发消息一方收消息,不能同时收消息或同时发消息
TCP编程中用到的socket模块中的方法如下:
- socket([family, [, type, [proto]]]): 创建一个socket对象
- connect(address): 连接远程主机
- send(bytes[, flags]): 发送数据
- recv(bufsize[, flags]): 接受数据
- bind(address): 绑定地址
- listen(backlog): 开始监听,等待客户端连接
- accept(): 响应客户端的请求
- close(): 关闭连接或关闭服务器
server.py
1 # __author__ = "wyb" 2 # date: 2018/5/8 3 4 import socket # 导入socket模块 5 sk = socket.socket() 6 sk.bind((\'127.0.0.1\', 8888)) # bind((\'ip\', \'port\')) 绑定ip和端口号 7 sk.listen() # 监听连接 8 9 conn, addr = sk.accept() # 接收客户端连接 10 11 res = conn.recv(1024) # 接收信息 12 print(res) 13 conn.send(b\'hi!\') # 发送信息,必须传一个bytes类型 14 res = conn.recv(1024) 15 print(res.decode(\'utf-8\')) # 解码 16 conn.send(bytes("吃包子啊!".encode(\'utf-8\'))) # 编码 17 18 conn.close() # 关闭连接 19 sk.close() # 关闭服务器 20 21 22 # 有收必有发,收发必相等
client.py
1 # __author__ = "wyb" 2 # date: 2018/5/8 3 import socket 4 5 sk = socket.socket() 6 7 sk.connect((\'127.0.0.1\', 8888)) # 连接服务器 8 sk.send(b\'wyb\') # 发送信息 9 res = sk.recv(1024) # 接受信息 10 print(res) 11 sk.send(bytes("中午吃什么".encode(\'utf-8\'))) # 编码 12 res = sk.recv(1024) 13 print(res.decode("utf-8")) # 解码 14 15 sk.close() # 关闭客户端
2.基于UDP协议的socket
udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接,而是直接向接收方发送信息
UDP编程中用到socket模块中的如下方法:
(1)socket([family, [, type, [proto]]]): 创建一个socket对象,参数如下:
- family为socket.AF_INET表示IPV4,family为socket.INET6表示IPV6
- type为SOCK_STREAM表示TCP,type为SOCK_DGRAM表示UDP
- proto为协议号,通常为零,可以省略
(2)sendto(string, address):把string指定的内容发送给address指定的地址,其中address是一个包含接受方主机IP地址和应用进程端口号的元组,
格式为(IP地址, 端口号)
(3)recvfrom(bufsize[, flags]):接受数据,bufsize是可接受的最大数据,单位为kb
(4)bind(address): 绑定地址
(5)close(): 关闭服务器
server.py
1 # __author__ = "wyb" 2 # date: 2018/5/9 3 import socket 4 5 sk = socket.socket(type=socket.SOCK_DGRAM) # DGRAM datagram 6 sk.bind((\'127.0.0.1\', 8888)) # \'127.0.0.1\', 8888 -> server端地址 7 8 msg, addr = sk.recvfrom(1024) # 接受消息 9 print(msg.decode("utf-8")) 10 sk.sendto(b"bye", addr) # 发送消息 addr -> client端地址 11 12 sk.close() 13 # UDP的server不需要进行监听,也不需要建立连接,在启动服务之后只能被动的等 14 # 客户端发送消息过来,客户端发送消息的同时还会自带地址信息 15 # 消息回复的时候,不仅需要发送消息也要填上对方的地址
client.py
1 # __author__ = "wyb" 2 # date: 2018/5/9 3 import socket 4 5 sk = socket.socket(type=socket.SOCK_DGRAM) # DGRAM datagram 6 7 ip_port = (\'127.0.0.1\', 8888) # 发送的地址 \'127.0.0.1\', 8888 -> server端地址 8 9 sk.sendto(b"hello", ip_port) # 发送消息 10 ret, addr = sk.recvfrom(1024) # 接受消息 addr -> server端地址 11 print(ret.decode("utf-8")) 12 13 sk.close()
3.相关练习
(1)话痨对话
server.py
1 # __author__ = "wyb" 2 # date: 2018/5/8 3 import socket 4 5 sk = socket.socket() 6 7 sk.bind((\'127.0.0.1\', 8888)) 8 sk.listen() 9 10 conn, addr = sk.accept() 11 12 while True: 13 res = conn.recv(1024).decode(\'utf-8\') # 接收消息(解码) 14 if res == "bye": # 退出 15 conn.send(b"bye") 16 break 17 print(res) # 输出消息 18 mes = input(">>>") # 输入消息 19 conn.send(mes.encode(\'utf-8\')) # 发送消息(编码) 20 21 conn.close() 22 sk.close()
client.py
1 # __author__ = "wyb" 2 # date: 2018/5/8 3 import socket 4 5 sk = socket.socket() 6 7 sk.connect((\'127.0.0.1\', 8888)) 8 9 while True: 10 msg = input(">>>") # 输入消息 11 sk.send(msg.encode(\'utf-8\')) # 传输消息(编码) 12 res = sk.recv(1024).decode("utf-8") # 接收消息(解码) 13 if res == \'bye\': # 退出 14 sk.send(b\'bye\') 15 break 16 print(res) # 输出消息 17 18 sk.close()
(2)时间服务器
server.py
1 # __author__ = "wyb" 2 # date: 2018/5/8 3 import socket 4 from time import strftime 5 6 ip_port = (\'127.0.0.1\', 8888) # server端地址 7 8 udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 9 udp_server.bind(ip_port) 10 11 while True: 12 msg, addr = udp_server.recvfrom(1024) # 接受消息 13 print(msg.decode("utf-8")) 14 15 # 根据接受到的消息进行处理 16 if not msg: 17 time_fmt = \'%Y-%m-%d %X\' 18 else: 19 time_fmt = msg.decode(\'utf-8\') 20 # 根据时间格式生成时间信息 21 back_msg = strftime(time_fmt) 22 23 # 发送消息 24 udp_server.sendto(back_msg.encode("utf-8"), addr) 25 26 27 udp_server.close()
client.py
1 # __author__ = "wyb" 2 # date: 2018/5/8 3 import socket 4 5 ip_port = (\'127.0.0.1\', 8888) 6 udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 7 8 while True: 9 msg = input(\'请输入时间格式(例%Y %m %d)>>: \').strip() # 输入信息 10 udp_client.sendto(msg.encode(\'utf-8\'), ip_port) # 发送信息 11 12 data = udp_client.recv(1024) # 接受消息 13 print(data.decode("utf-8")) 14 15 16 udp_client.close()
(3)QQ聊天
server.py
1 import socket 2 3 ip_port = (\'127.0.0.1\', 8005) 4 sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 5 sk.bind(ip_port) 6 7 while True: 8 data, addr = sk.recvfrom(1024) # 接受消息 9 if data == b"bye": 10 break 11 print(data.decode("utf-8")) 12 info = input(">>>").encode("utf-8") 13 sk.sendto(info, addr) 14 15 sk.close()
client.py
1 import socket 2 3 ip_port = (\'127.0.0.1\', 8005) 4 sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 5 6 while True: 7 msg = input("老王: ") 8 msg = "\\033[34m来自老王的消息: %s\\033[0m" % msg 9 sk.sendto(msg.encode("utf-8"), ip_port) 10 data, addr = sk.recvfrom(1024) 11 if data == b"bye": 12 break 13 print(data.decode("utf-8")) 14 15 sk.close()
(4)远程执行命令
server.py
1 import socket 2 sk = socket.socket() 3 4 sk.bind((\'127.0.0.1\', 8888)) 5 sk.listen() 6 7 conn, addr = sk.accept() 8 9 while True: 10 cmd = input(">>>") 11 if cmd == \'q\' or cmd == \'exit\': 12 conn.send(b\'q\') 13 break 14 conn.send(cmd.encode("gbk")) 15 res = conn.recv(1024).decode("gbk") 16 print(res) 17 18 19 conn.close() 20 sk.close()
client.py
1 import socket 2 import subprocess 3 sk = socket.socket() 4 5 sk.connect((\'127.0.0.1\', 8888)) 6 7 while True: 8 cmd = sk.recv(1024).decode("gbk") 9 if cmd == "q" or cmd == "exit": 10 break 11 res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 12 sk.send(res.stdout.read()) 13 sk.send(res.stderr.read()) 14 15 16 sk.close()
二、黏包
1.什么是黏包
黏包问题是因为发送方把若干数据发送,接收方收到数据时候黏在一包,从接受缓冲区来看,后一包的数据黏在前一包的尾部,当连续send多个小数据包时就可能会发生黏包,其是TCP内部的优化算法造成的
注只有TCP才有黏包问题,UDP没有黏包问题:
1 UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。 2 不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。 3 对于空消息:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息头发送过去。 4 不可靠不黏包的udp协议:udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y;x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠
2.黏包成因
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据
补充说明:
1 用UDP协议发送时,用sendto函数最大能发送数据的长度为: 2 65535- IP头(20) – UDP头(8)=65507字节 3 用sendto函数发送数据时,如果发送数据长度大于该值,则函数会返回错误,将会丢弃这个包,不进行发送 4 5 用TCP协议发送时,由于TCP是数据流协议,因此不存在包大小的限制,只是暂不考虑缓冲区的大小,是指在用send函数时,数据长度参数不受限制 6 而实际上,所指定的这段数据并不一定会一次性发送出去,如果这段数据比较长,会被分段发送,如果比较短,可能会等待和下一次数据一起发送
3.黏包具体情况
(1)发送方的缓存机制
连续send两个小包时,就会黏包,TCP会将这两个包合为一体,变成一个包 eg: 2+8 -> 10,其本质是发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,多个小数据会当做一个包发出去,产生粘包)
示例:
server.py
1 import socket 2 3 sk = socket.socket() 4 sk.bind(("127.0.0.1", 8888)) 5 sk.listen() 6 7 conn, addr = sk.accept() 8 res = conn.recv(200) 9 print("res:", res) 10 11 conn.close() 12 sk.close()
client.py
1 import socket 2 3 sk = socket.socket() 4 sk.connect(("127.0.0.1", 8888)) 5 sk.send(b"hello, world!") 6 sk.send(b"wyb666") 7 8 sk.close()
(2)接受方的缓存机制
连续两个recv,第一个recv特别小,假如说此时发送的是一个长数据,则会出问题(没接受完的数据缓存下来),其本质是接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
示例:
server.py
1 import socket 2 3 sk = socket.socket() 4 sk.bind(("127.0.0.1", 8888)) 5 sk.listen() 6 7 conn, addr = sk.accept() 8 res = conn.recv(2) 9 res2 = conn.recv(10) 10 print("res:", res) 11 print("res2:", res2) 12 13 conn.close() 14 sk.close()
client.py
1 import socket 2 3 sk = socket.socket() 4 sk.connect(("127.0.0.1", 8888)) 5 sk.send(b"hello, world!") 6 7 sk.close()
总结:
黏包现象只发生在tcp协议中:
1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。
2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
4.黏包的解决方案
(1)解决原理
问题的根源在于接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是在发送包之前发送数据的大小,再根据数据的大小来接受数据
根据上述方法解决黏包如下所示:
server.py
1 import socket 2 sk = socket.socket() 3 4 sk.bind((\'127.0.0.1\', 8888)) 5 sk.listen() 6 7 conn, addr = sk.accept() 8 9 while True: 10 cmd = input(">>>") 11 if cmd == \'q\': 12 conn.send(b\'q\') 13 break 14 conn.send(cmd.encode("gbk")) 15 num = conn.recv(1024).decode("utf-8") # 接受数据的大小 16 conn.send(b"ok") # 响应ok 17 res = conn.recv(int(num)).decode("gbk") # 根据数据大小接受数据 18 print(res) 19 20 21 conn.close() 22 sk.close()
client.py
1 import socket 2 import subprocess 3 4 sk = socket.socket() 5 sk.connect((\'127.0.0.1\', 8888)) 6 7 while True: 8 cmd = sk.recv(1024).decode("gbk") 9 if cmd == "q": 10 break 11 res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 12 std_out = res.stdout.read() # 原理是队列 所以只能读一次 13 std_err = res.stderr.read() # 原理是队列 所以只能读一次 14 sk.send(str(len(std_out)+len(std_err)).encode("utf-8")) # 发送数据的大小 15 sk.recv(1024) # 接收ok 16 sk.send(std_out) 17 sk.send(std_err) 18 19 20 sk.close()
这样解决的好处与坏处:
- 好处:确定我到底要接受多大的数据
- 坏处:多了一次交互
注意:
最好在文件中配置一个配置选项,指定每一次recv的大小,也就是指定buffer,最大为4096
当我们要发送大的数据时,应该明确的告诉接收方要发送多大的数据,以便能准确的接受到所有数据
以上的方法多用在文件传输的过程中,在大文件的传输过程中发送方一边读一边传,接收方一边收一边写
(2)解决黏包问题升级版 - 借助struct模块
server.py
1 import socket 2 import struct 3 sk = socket.socket() 4 5 sk.bind((\'127.0.0.1\', 8888)) 6 sk.listen() 7 8 conn, addr = sk.accept() 9 10 while True: 11 cmd = input(">>>") 12 if cmd == \'q\': 13 conn.send(b\'q\') 14 break 15 conn.send(cmd.encode("gbk")) 16 num = conn.recv(4) # 接受数据的大小 17 num = struct.unpack(\'i\', num)[0] # 将4个字节转化成整数 18 res = conn.recv(int(num)).decode("gbk") # 根据数据大小接受数据 19 print(res) 20 21 22 conn.close() 23 sk.close()
client.py
1 import socket 2 import struct 3 import subprocess 4 5 sk = sock以上是关于python网络编程的主要内容,如果未能解决你的问题,请参考以下文章