Python Socket 编程
Python Socket 编程
Python 提供了两个基本的 socket 模块:
Socket
它提供了标准的BSD Socket API。SocketServer
它提供了服务器重心,可以简化网络服务器的开发。
下面讲解下 Socket模块功能。
Socket 类型
套接字格式:socket(family, type[,protocal]) 使用给定的套接族,套接字类型,协议编号(默认为0)来创建套接字
socket 类型 | 描述 | |
---|---|---|
family | ||
socket.AF_UNIX | UNIX系统进程间传输数据 | |
socket.AF_INET | IPv4网络传输数据(IPv4 | |
socket.AF_INET6 | IPv6网络传输数据 | |
Type | ||
TCP | socket.SOCK_STREAM | 基于TCP的流式socket通信,面向连接可靠的传输,TCP传输 |
UDP | socket.SOCK_DGRAM | 基于UDP的数据报式socket通信,面向无连接不可靠的传输,UDP传输 |
socket.SOCK_RAW | 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次SOCK_RAW也可以处理特殊的IPV4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头 | |
socket.SOCK_SEQPACKET | 连续的数据包传输(已废弃) |
####套接字家族
基于文件类型的套接字家族名:AF_UNIX
unix
一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信。
基于网络类型的套接字家族名:AF_INET
还有AF_INET6
被用于ipv6
,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET
是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET
。
创建TCP Socket:
1 |
|
创建UDP Socket:1
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
Socket 函数
TCP发送数据时,已建立好TCP链接,所以不需要指定地址,而UDP是面向无连接的,每次发送都需要指定发送给谁。
服务器与客户端不能直接发送列表,元素,字典等带有数据类型的格式,发送的内容必须是字符串数据。socket传输字符串需要bytes
总结
1、socket分为服务端和客户端。
2、TCP传输不需要IP,UDP传输需要IP地址。
3、socket传输字符串需要变成byte型。
4、列表、字典等数据也需要成变byte型。json处理过的数据是字符型的,decode后可以进行send。
5、传输大数据,使用长度时,要注意len的对象是原数据,还是encode后的数据,接收方也得计算相应的数据。否则会造成文件长度不匹配
服务器端 Socket 函数
Socket 函数 | 描述 |
---|---|
s.bind(address) | 将套接字绑定到地址,在AF_INET下,以tuple(host, port)的方式传入,如s.bind((host, port)) |
s.listen(backlog) | 开始监听TCP传入连接,backlog指定在拒绝链接前,操作系统可以挂起的最大连接数,该值最少为1,大部分应用程序设为5就够用了(客户端连接数量,数字) |
s.accept() | 接受TCP链接并返回(conn, address),其中conn是新的套接字对象(即,接受的信息),可以用来接收和发送数据,address是链接客户端的地址(ip,随机端口)。 |
客户端 Socket 函数
Socket 函数 | 描述 |
---|---|
s.connect(address) | 链接到address处的套接字,一般address的格式为tuple(host, port),如果链接出错,则返回socket.error错误(绑定服务端地址) |
s.connect_ex(address) | 功能与s.connect(address)相同,但成功返回0,失败返回errno的值 |
公共 Socket 函数
Socket 函数 | 描述 |
---|---|
s.recv(bufsize[, flag]) | 接受TCP套接字的数据,数据以字符串形式返回,buffsize指定要接受的最大数据量,flag提供有关消息的其他信息,通常可以忽略。 bufsize官方建议8192,不同系统最大数值不同,一般一次可以收10M左右。 |
s.send(string[, flag]) | 发送TCP数据,将字符串中的数据发送到链接的套接字,返回值是要发送的字节数量,该数量可能小于string的字节大小 |
s.sendall(string[, flag]) | 完整发送TCP数据,将字符串中的数据发送到链接的套接字,但在返回之前尝试发送所有数据。成功返回None,失败则抛出异常 |
s.recvfrom(bufsize[, flag]) | 接受UDP套接字的数据u,与recv()类似,但返回值是tuple(data, address)。其中data是包含接受数据的字符串,address是发送数据的套接字地址 |
s.sendto(string[, flag], address) | 发送UDP数据,将数据发送到套接字,address形式为tuple(ipaddr, port),指定远程地址发送,返回值是发送的字节数 |
s.close() | 关闭套接字 |
s.getpeername() | 返回套接字的远程地址,返回值通常是一个tuple(ipaddr, port) |
s.getsockname() | 返回套接字自己的地址,返回值通常是一个tuple(ipaddr, port) |
s.setsockopt(level, optname, value) | 设置给定套接字选项的值 |
s.getsockopt(level, optname[, buflen]) | 返回套接字选项的值 |
s.settimeout(timeout) | 设置套接字操作的超时时间,timeout是一个浮点数,单位是秒,值为None则表示永远不会超时。一般超时期应在刚创建套接字时设置,因为他们可能用于连接的操作,如s.connect() |
s.gettimeout() | 返回当前超时值,单位是秒,如果没有设置超时则返回None |
s.fileno() | 返回套接字的文件描述 |
s.setblocking(flag) | 如果flag为0,则将套接字设置为非阻塞模式,否则将套接字设置为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。 |
s.makefile() | 创建一个与该套接字相关的文件 |
1 |
|
Socket 编程思想
1 |
|
TCP 服务器
1、创建套接字,绑定套接字到本地IP与端口
1 |
|
2、开始监听链接1
s.listen()
3、进入循环,不断接受客户端的链接请求
1 |
|
4、接收客户端传来的数据,并且发送给对方发送数据1
2s.recv()
s.sendall()
5、传输完毕后,关闭套接字1
s.close()
TCP 客户端
1、创建套接字并链接至远端地址1
2s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect()
2、链接后发送数据和接收数据1
2s.sendall()
s.recv()
3、传输完毕后,关闭套接字
#####send和sendall区别
1 |
|
1024 是缓冲区数据大小限制最大值参数 bufsize
,并不是说 recv()
方法只返回 1024个字节的内容
send()
方法也是这个原理,它返回发送内容的字节数,结果可能小于传入的发送内容,你得处理这处情况,按需多次调用 send()
方法来发送完整的数据
应用程序负责检查是否已发送所有数据;如果仅传输了一些数据,则应用程序需要尝试传 递剩余数据 引用
我们可以使用 sendall()
方法来回避这个过程
和 send() 方法不一样的是,
sendall()
方法会一直发送字节,只到所有的数据传输完成 或者中途出现错误。成功的话会返回 None 引用
socket(创建套接字) —> bind(绑定地址) —> listen(设置监听)—> accept(等待链接) —> recv/send(收/发消息) —> close ()
收发函数特性:
recv特征:
- 如果建立的另一端链接被断开, 则recv立即返回空字符串
- recv是从接受缓冲区取出内容,当缓冲区为空则阻塞
- recv如果一次接受不完缓冲区的内容,下次执行会自动接受
send特征:
如果发送的另一端不存在则会产生Pipe Broken异常
send是从发送缓冲区发送内容,当缓冲区为满则堵塞
Socket tcp服务器端代码
1 |
|
Socket tcp客户端代码
1 |
|
基于udp的服务端编程:
1 |
|
问题:
有的同学在重启服务端时可能会遇到
这个是由于你的服务端仍然存在四次挥手的time_wait状态在占用地址(如果不懂,请深入研究1.tcp三次握手,四次挥手 2.syn洪水攻击 3.服务器高并发情况下会有大量的time_wait状态的优化方法)
解决方法:
1 |
|
粘包现象
让我们基于tcp先制作一个远程执行命令的程序(1:执行错误命令 2:执行ls 3:执行ifconfig)
1 |
|
注意:
如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码
且只能从管道里读一次结果,PIPE称为管道。
1 |
|
什么是粘包
须知:只有TCP有粘包现象,UDP永远不会粘包,为何,且听我娓娓道来
首先需要掌握一个socket收发消息的原理
两种情况会发生粘包
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
1 |
|
Client
1 |
|
拆包的发生情况
当发送端缓冲区的长度大于网卡的MTU时,网络层限制是1500B,tcp会将这次发送的数据拆成几个数据包发送出去。
补充问题一:为何tcp是可靠传输,udp是不可靠传输
tcp在数据传输时,发送端先把数据发送到自己的缓存中,然后协议控制将缓存中的数据发往对端,对端返回一个ack=1,发送端则清理缓存中的数据,对端返回ack=0,则重新发送数据,所以tcp是可靠的
而udp发送数据,对端是不会返回确认信息的,因此不可靠
补充问题二:send(字节流)和recv(1024)及sendall
recv里指定的1024意思是从缓存里一次拿出1024个字节的数据
send的字节流是先放入己端缓存,然后由协议控制将缓存内容发往对端,如果待发送的字节流大小大于缓存剩余空间,那么数据丢失,用sendall就会循环调用send,数据不会丢失
解决粘包的low比处理方法
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据
low版本的解决方法
服务端
1 |
|
client
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!