import socket'''socket.socket(socket_family,socket_type,protocal=0)socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。获取tcp/ip套接字tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)获取udp/ip套接字udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)由于 socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。使用 'from socket import *',我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。例如tcpSock = socket(AF_INET, SOCK_STREAM)'''
基于TCP的套接字
tcp服务端
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 ss = socket() #创建服务器套接字2 ss.bind() #把地址绑定到套接字3 ss.listen() #监听链接4 inf_loop: #服务器无限循环5 cs = ss.accept() #接受客户端链接6 comm_loop: #通讯循环7 cs.recv()/cs.send() #对话(接收与发送)8 cs.close() #关闭客户端套接字9 ss.close() #关闭服务器套接字(可选)
tcp客户端
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 cs = socket() # 创建客户套接字2 cs.connect() # 尝试连接服务器3 comm_loop: # 通讯循环4 cs.send()/cs.recv() # 对话(发送/接收)5 cs.close() # 关闭客户套接字
socket通信流程与打电话流程类似,我们就以打电话为例来实现一个low版的套接字通信
#_*_coding:utf-8_*_服务端__author__ = 'Linhaifeng'import socketip_port=('127.0.0.1',9000) #电话卡BUFSIZE=1024 #收发消息的尺寸s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机s.bind(ip_port) #手机插卡s.listen(5) #手机待机conn,addr=s.accept() #手机接电话# print(conn)# print(addr)print('接到来自%s的电话' %addr[0])msg=conn.recv(BUFSIZE) #听消息,听话print(msg,type(msg))conn.send(msg.upper()) #发消息,说话conn.close() #挂电话s.close() #手机关机
#_*_coding:utf-8_*_客户端__author__ = 'Linhaifeng'import socketip_port=('127.0.0.1',9000)BUFSIZE=1024s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect_ex(ip_port) #拨电话s.send('linhaifeng nb'.encode('utf-8')) #发消息,说话(只能发送字节类型)feedback=s.recv(BUFSIZE) #收消息,听话print(feedback.decode('utf-8'))s.close() #挂电话'''上述流程的问题是,服务端只能接受一次链接,然后就彻底关闭掉了,实际情况应该是,服务端不断接受链接,然后循环通信,通信完毕后只关闭链接,服务器能够继续接收下一次链接,下面是修改版'''
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#_*_coding:utf-8_*___author__ = 'Linhaifeng'import socketip_port=('127.0.0.1',8081)#电话卡BUFSIZE=1024s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机s.bind(ip_port) #手机插卡s.listen(5) #手机待机while True: #新增接收链接循环,可以不停的接电话 conn,addr=s.accept() #手机接电话 # print(conn) # print(addr) print('接到来自%s的电话' %addr[0]) while True: #新增通信循环,可以不断的通信,收发消息 msg=conn.recv(BUFSIZE) #听消息,听话 # if len(msg) == 0:break #如果不加,那么正在链接的客户端突然断开,recv便不再阻塞,死循环发生 print(msg,type(msg)) conn.send(msg.upper()) #发消息,说话 conn.close() #挂电话s.close() #手机关机服务端改进版
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#_*_coding:utf-8_*___author__ = 'Linhaifeng'import socketip_port=('127.0.0.1',8081)BUFSIZE=1024s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect_ex(ip_port) #拨电话while True: #新增通信循环,客户端可以不断发收消息 msg=input('>>: ').strip() if len(msg) == 0:continue s.send(msg.encode('utf-8')) #发消息,说话(只能发送字节类型) feedback=s.recv(BUFSIZE) #收消息,听话 print(feedback.decode('utf-8'))s.close() #挂电话客户端改进版
问题:有时在重启服务端时可能会遇到这个是由于你的服务端仍然存在四次挥手的time_wait状态在占用地址(如果不懂,请深入研究1.tcp三次握手,四次挥手 2.syn洪水攻击 3.服务器高并发情况下会有大量的time_wait状态的优化方法)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
解决方法:#加入一#调整socket配置,重用ip和端口phone=socket(AF_INET,SOCK_STREAM)phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加phone.bind(('127.0.0.1',8080))方法二发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决,vi /etc/sysctl.conf编辑文件,加入以下内容:net.ipv4.tcp_syncookies = 1net.ipv4.tcp_tw_reuse = 1net.ipv4.tcp_tw_recycle = 1net.ipv4.tcp_fin_timeout = 30 然后执行 /sbin/sysctl -p 让参数生效。 net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间
基于UDP的套接字
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
udp服务端1 ss = socket() #创建一个服务器的套接字2 ss.bind() #绑定服务器套接字3 inf_loop: #服务器无限循环4 cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)5 ss.close() # 关闭服务器套接字udp客户端cs = socket() # 创建客户套接字comm_loop: # 通讯循环 cs.sendto()/cs.recvfrom() # 对话(发送/接收)cs.close() # 关闭客户套接字
udp套接字简单示例#_*_coding:utf-8_*___author__ = 'Linhaifeng'import socketip_port=('127.0.0.1',9000)BUFSIZE=1024udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)udp_server_client.bind(ip_port)while True: msg,addr=udp_server_client.recvfrom(BUFSIZE) print(msg,addr) udp_server_client.sendto(msg.upper(),addr)#_*_coding:utf-8_*___author__ = 'Linhaifeng'import socketip_port=('127.0.0.1',9000)BUFSIZE=1024udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)while True: msg=input('>>: ').strip() if not msg:continue udp_server_client.sendto(msg.encode('utf-8'),ip_port) back_msg,addr=udp_server_client.recvfrom(BUFSIZE) print(back_msg.decode('utf-8'),addr)qq聊天(由于udp无连接,所以可以同时多个客户端去跟服务端通信)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#_*_coding:utf-8_*___author__ = 'Linhaifeng'import socketip_port=('127.0.0.1',8081)udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #买手机udp_server_sock.bind(ip_port)while True: qq_msg,addr=udp_server_sock.recvfrom(1024) print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8'))) back_msg=input('回复消息: ').strip() udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#_*_coding:utf-8_*___author__ = 'Linhaifeng'import socketBUFSIZE=1024udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)qq_name_dic={ '狗哥alex':('127.0.0.1',8081), '瞎驴':('127.0.0.1',8081), '一棵树':('127.0.0.1',8081), '武大郎':('127.0.0.1',8081),}while True: qq_name=input('请选择聊天对象: ').strip() while True: msg=input('请输入消息,回车发送: ').strip() if msg == 'quit':break if not msg or not qq_name or qq_name not in qq_name_dic:continue udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name]) back_msg,addr=udp_client_socket.recvfrom(BUFSIZE) print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))udp_client_socket.close()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#_*_coding:utf-8_*___author__ = 'Linhaifeng'import socketBUFSIZE=1024udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)qq_name_dic={ '狗哥alex':('127.0.0.1',8081), '瞎驴':('127.0.0.1',8081), '一棵树':('127.0.0.1',8081), '武大郎':('127.0.0.1',8081),}while True: qq_name=input('请选择聊天对象: ').strip() while True: msg=input('请输入消息,回车发送: ').strip() if msg == 'quit':break if not msg or not qq_name or qq_name not in qq_name_dic:continue udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name]) back_msg,addr=udp_client_socket.recvfrom(BUFSIZE) print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))udp_client_socket.close()
recv与recvfrom的区别
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
'''============part1:须知============ 收发消息的原理须知晓--->请见十一的图:发消息,都是将数据发送到己端的发送缓冲中,收消息都是从己端的缓冲区中收1. tcp:send发消息,recv收消息2. udp:sendto发消息,recvfrom收消息 ============part2:send与sendinto============ tcp是基于数据流的,而udp是基于数据报的:send(bytes_data):发送数据流,数据流bytes_data若为空,自己这段的缓冲区也为空,操作系统不会控制tcp协议发空包sendinto(bytes_data,ip_port):发送数据报,bytes_data为空,还有ip_port,所有即便是发送空的bytes_data,数据报其实也不是空的,自己这端的缓冲区收到内容,操作系统就会控制udp协议发包。============part3:recv与recvfrom============1.tcp协议:(1)如果收消息缓冲区里的数据为空,那么recv就会阻塞(阻塞很简单,就是一直在等着收)(2)只不过tcp协议的客户端send一个空数据就是真的空数据,客户端即使有无穷个send空,也跟没有一个样。(3)tcp基于链接通信基于链接,则需要listen(backlog),指定半连接池的大小基于链接,必须先运行的服务端,然后客户端发起链接请求对于mac系统:如果一端断开了链接,那另外一端的链接也跟着完蛋recv将不会阻塞,收到的是空(解决方法是:服务端在收消息后加上if判断,空消息就break掉通信循环)对于windows/linux系统:如果一端断开了链接,那另外一端的链接也跟着完蛋recv将不会阻塞,收到的是空(解决方法是:服务端通信循环内加异常处理,捕捉到异常后就break掉通讯循环) 2.udp协议(1)如果如果收消息缓冲区里的数据为“空”,recvfrom也会阻塞(2)只不过udp协议的客户端sendinto一个空数据并不是真的空数据(包含:空数据+地址信息,得到的报仍然不会为空),所以客户端只要有一个sendinto(不管是否发送空数据,都不是真的空数据),服务端就可以recvfrom到数据。(3)udp无链接无链接,因而无需listen(backlog),更加没有什么连接池之说了无链接,udp的sendinto不用管是否有一个正在运行的服务端,可以己端一个劲的发消息,只不过数据丢失recvfrom收的数据小于sendinto发送的数据时,在mac和linux系统上数据直接丢失,在windows系统上发送的比接收的大直接报错只有sendinto发送数据没有recvfrom收数据,数据丢失==============注意:===============1.你单独运行上面的udp的客户端,你发现并不会报错,相反tcp却会报错,因为udp协议只负责把包发出去,对方收不收,我根本不管,而tcp是基于链接的,必须有一个服务端先运行着,客户端去跟服务端建立链接然后依托于链接才能传递消息,任何一方试图把链接摧毁都会导致对方程序的崩溃。2.上面的udp程序,你注释任何一条客户端的sendinto,服务端都会卡住,为什么?因为服务端有几个recvfrom就要对应几个sendinto,哪怕是sendinto(b'')那也要有。'''
什么是粘包(解法:定长报头)
须知:只有TCP有粘包现象,UDP永远不会粘包,为何,且听我娓娓道来
首先需要掌握一个socket收发消息的原理
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。