1 套接字socket
1.1 套接字概述
套接字,一种网络通讯工具;用于进行网络间的通信,是一种特殊文件类型,
套接字,是一个通信链的句柄,用于描述IP地址和端口,实现向网络发出请求或应答网络请求。
socket可以实现对文件的打开、读写和关闭模式操作,与file有相似之处
file模块是针对某个指定文件进行打开、读写和关闭操作的;
socket模块是对服务器和客户端socket进行打开、读写和关闭的操作。
1.2 套接字分类
流式套接字:
按照字节流进行数据传输,提供面向连接的可靠的数据传输服务,TCP协议。
流式对数据来说是安全的,一次接受不完,下次还会继续接受。
数据报套接字:
按照数据包进行数据的传输,提供无连接的不可靠的传输服务,UDP协议
原始套接字:底层套接字
注:
面向连接:在进行数据通信之前需要先建立服务端和客户端的连接,以确保消息传输的可靠性;不允许有数据的丢失的,一旦出现数据丢失,则认为数据不可靠,断开连接
面向无连接:数据发送前不需要确定连接关系,数据的发送是不可靠的,
1.3 TCP协议 (面向连接)
适用于: 传输质量要求高,数据传输量大,且需要可靠性
三次握手:
1.客户端向服务器端发起连接请求ack
2.服务器收到请求后确认允许连接,返回给客户端
3.客户端确认可连接后告知服务器准备发送消息
四次挥手:
1. 主动方告知被动方要断开连接
2. 接受到主动方请求后告知主动方已经接受到请求
3. 接收端处理完网络消息等工作后再次告知主动端可以断开
4. 主动方完成连接的断开
以确保发出的消息不会出现残留,传输一半的情况
1.4 UDP协议(面向无连接)
适用于:对消息可靠型没有严格要求,需要传输效率,网络状态不佳
2 基于TCP流式套接字过程
2.1 TCP服务器
主要有6步
1 创建套接字
2 绑定服务器IP端口
3 将套接字变为监听套接字,使可以接受tcp连接
4. 准备接受客户端连接
5 收发消息
6 关闭套接字
2.1.1 创建套接字(流式套接字)
socket(family = AF_INET,type = SOCK_STREAM,proto = 0)
功能:创建一个套接字
参数:
family:地址族类型
基于不同主机间的通信
AF_INET IPv4(默认)
AF_INET6 IPv6
基于一台主机进程间的文件通讯
AF_UNIX 只能够用于单一的Unix系统进程间通信
type:套接字类型
SOCK_STREAM 流式套接字 TCP (默认)
SOCK_DGRAM 数据报套接字 UDP
proto:与特定的地址家族相关的协议
默认为0,此时系统就会根据地址格式和套接字类别,自动选择一个合适的协议
一般选择默认值0
返回值 : 返回一个套接字对象
2.1.2 绑定服务器IP、端口
sockfd.bind(address)
功能:绑定服务器的IP和端口
参数:address是一个元组(hostname, port)),第一个为字符串类型IP,第二个为数字类型端口号
示例 sockfd.bind('0.0.0.0',8888)
备注:socfkd为套接字句柄
2.3 监听套接字
sockfd.listen(backlog)
功能:开启TCP监听,创建监听队列,将套接字变为监听套接字。使其可以接受TCP连接
参数:backlog为正整数
备注:一个套接字可以连接很多客户端,不过瞬态只能连接一个(由于很快,所以无法模拟出来),处理完后再连接其他的;由于瞬态很快,所以可以理解为是一个队列。
2.1.4 准备接受客户端连接
sockfd.accept()
功能:阻塞等待客户端的连接,并一直处于等待状态
参数:无
返回值 :
第一个是一个与客户端通信的新的套接字
第二个是连接进来的客户端的地址2.1.5 收发消息
conn.recv(buffer)
功能:接受消息
参数:buffer:表示一次从缓冲区接受内容的大小
返回值:接受到的消息
conn.send()
功能 : 发送消息参数 : 要发送的消息
返回值 : 发送消息的大小
2.1.6 关闭套接字
sockfd.close()
2.2 TCP客户端
1 创建套接字
2 发起连接
3 收发消息
4 关闭套接字
注意:在python3消息收发为byte,故遇到字符串需要编译
3 TCP套接字示例
套接字中的两个阻塞函数:
recv 和 send 为阻塞函数,
当缓冲区满时,执行send函数时会阻塞;
当缓冲区空时,执行recv函数时会阻塞
如果连接断开则recv会马上返回空字符串,结束阻塞
telnet 可以测试TCP套接字讯号通断,而不能检测UDP信号的情况
3.1 创建TCP服务器
import socketHOST = '0.0.0.0'PROT = 8888ADDR = (HOST,PROT)# 指定 Socket 接收缓冲区的大小BUFFERSIZE = 1024# 创建套接字# 使用from socket import *全部导入后,可以直接去掉socket前缀sockfd = socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 绑定服务器IP端口sockfd.bind(ADDR)# 将套接字变成监听套接字,使可以接受TCP连接sockfd.listen(5)# 准备接受客户端连接print("wait for connect")# conn 为一个与客户端通信的新的套接字# addr 为连接进来的客户端的地址conn,addr = sockfd.accept()print("connect from",addr)# 收发消息data = conn.recv(BUFFERSIZE)print(data.decode())# 发送消息num = conn.send("Receive your request".encode())print(num)# 关闭套接字conn.close()sockfd.close()# 在python3消息收发为byte,而上面均是字符串,所以需要编译
3.2 创建TCP客户端
from socket import *SERVER_HOST = '0.0.0.0'SERVER_PORT = 8888SERVER_ADDR = (SERVER_HOST,SERVER_PORT)# 创建TCP套接字# 同类型套接字可以连接,非同类套接字不可连接sockfd = socket(AF_INET,SOCK_STREAM)# 发起连接sockfd.connect(SERVER_ADDR)# 向服务器发送信息sockfd.send(b'hello server')# 接受服务器返回信息,缓冲区大小为1024data = sockfd.recv(1024)print(data)# 关闭套接字sockfd.close()
3.3 运行
(1)运行服务器:
出现: wait for connect
(2)运行客户端:
此时客户端出现: b'Receive your request'
同时服务器出现:
connect from ('127.0.0.1', 57946)hello server20
注意事项:
(1)不能先运行客户端,否则会出现 ConnectionRefusedError
Traceback (most recent call last): File "t_c.py", line 12, insockfd.connect(SERVER_ADDR)ConnectionRefusedError: [Errno 111] Connection refused
找不到服务器,无法运行代码。
(2)连续运行服务器时,会出现 Address already in use
Traceback (most recent call last): File "t_s.py", line 15, insockfd.bind(ADDR)OSError: [Errno 98] Address already in use
连续运行服务器时,原端口尚没有释放,所以会出现 address 被占用情况。
解决方案:
(a)出现address被占用时,暂停一会再执行,等待端口从缓冲区释放
(b)再运行服务器前修改端口号: SERVER_PORT = 8888 比如改成9999等之类
(c) setsockopt(SOL_SOCKET,SO_REUSEADDR,1) ,将其设置为1,端口可重用,可以再重新启用。
4 沾包
沾包的定义:
在TCP流式套接字中,发送方发送的若干数据被接收方一次性接受,就好像一条数据一样粘连在一起,这种现象为数据沾包
(1)在文件传输过程中沾包是不需要处理的
(2)如果发送的每一条消息都有特定的意义,此时需要考虑沾包带来的影响
沾包的形成:
也即,粘包形成的必要条件,
(1)流式套接字
(2)接受的没有发送的快
沾包的解决:
解决沾包的方案:
(1)格式化数据
(2)发送数据时指明数据长度
(3)每次发送有一个时间间隔,一般而言,相隔0.1秒足够了,但该方法不适合数据量较大的、频繁发送的情况;该方法使用也较少。
4 数据报套接字UDP
4.1 关键字
sockfd.recvfrom()
功能:接受网络消息
参数:buffersize
返回值 :
data:接收到的消息
addr:消息来源的ADDRESS
每次recvfrom接受一个数据报,即使接受不全就会使其他数据丢失
sockfd.sendto()
功能:发送消息
参数:
data:要发送的消息
addr:将消息发送给谁
sendall() 和send()基本相同,试图发送所有消息,发送成功返回None,失败则报出异常
sendall()和sendto不一样,不能替代sendto4.2 服务器(UDP数据报套接字)
(1)创建数据报套接字
(2)绑定IP和端口
(3)接受发送消息
(4)关闭套接字
4.3 客户端(UDP数据报套接字)
与TCP数据流套接字类似。
5 UDP套接字示例
5.1 服务器(UDP)
from socket import *from time import *HOST = '127.0.0.1'PORT = 8888ADDR = (HOST,PORT)BUDDERSIZE = 1024# 创建数据报套接字sockfd = socket(AF_INET,SOCK_DGRAM)# 绑定IP端口sockfd.bind(ADDR)while True: # 接受发送消息 data, addr = sockfd.recvfrom(BUDDERSIZE) print('recv from ',addr) # 发送消息 sockfd.sendto(('%s:[%s]'%(ctime(),data)).encode(),addr)# 关闭套接字sockfd.close()
5.2 客户端(UDP)
from socket import *import sys# sys.argv 从外设获取的迭代对象并返回列表# sys.argv[0]为路径# 少于三个元素则退出运行if len(sys.argv) <3: print('argv is error') sys.exit(1)# 获取IP地址HOST = sys.argv[1]#从终端获得的为字符串,需转换成整形PORT = int(sys.argv[2])ADDR = (HOST,PORT)BUFFERSIZE = 1024# 创建数据报套接字sockfd = socket(AF_INET,SOCK_DGRAM)while True: data = input(">>") if not data: break # 发送消息,第一元素为发送内容,ADDR为内容接受的地址 sockfd.sendto(b"hello udp server",ADDR) data, addr = sockfd.recvfrom(BUFFERSIZE) print(data.decode())# 关闭套接字sockfd.close()
5.3 运行
1 先运行服务器,实际上没有任何显示
2 运行客户端
注意:
(a)由于客户端 len(sys.argv) > 3 的限制,所以必须输入三个元素以上才不会使程序运行结束
(b)t_c.py、‘127.0.0.1’、‘8888’三个元素中间必须用空格隔开,如果连在一起则会认为一个元素
(c)IP(‘127.0.0.1’)和端口('8888')必须与服务器名称一致, 否则 sockfd.sendto(b"hello udp server",ADDR) 中的ADDR找不到服务器
3 向客户端中输入内容
6 sys.argv()
示例:文件名s_argv.py
import sysif len(sys.argv) <3: print('argv is error') sys.exit(1)print(sys.argv)print(sys.argv[1])print(sys.argv[2])
运行: python3 s_argv.py
结果: argv is error
因为 len(sys.argv) < 3 所以会直接结束 sys.exit(1)
再运行结果
python3 s_argv.py '127.0.0.1' '8888'#直接显示结果['s_argv.py', '127.0.0.1', '8888']127.0.0.18888
再运行
python3 s_argv.py a 12['s_argv.py', 'a', '12']a12
详细信息可参考
7 TCP与UDP对比
UDP 比 TCP 更加适合提供循环服务
循环服务方式不适合传输时间过长,一个客户端需要长期占用服务器的情况,TCP连接则需要持续连接占用服务器。
8 套接字属性
套接字对象的属性
conn.getpeername() # 得到连接端的addrsockfd.getsockname() # 获取套接字对应主机的信息sockfd.type # 测试套接字类型getsockopt(level,optname) # 获取值setsockopt(level,optname,value) # 设置值setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 让套接字端口可以重用,当套接字结束时,可以再重新启用