我们都知道: 一个完整的计算机系统是由 硬件 / 操作系统 / 应用程序 三部分组成, 具备了这三个条件, 一台计算机系统就可以自己跟自己玩了(打个单机游戏, 玩个蜘蛛纸牌啥的)但是, 如果你要跟别人一起玩, 那你就必须要上网了, 那么什么是互联网呢?
互联网的核心就是一堆协议, 协议就是规定, 就是标准, 如果不按照标准去执行就会出错, 就好比一个国家有法律, 如果你不按照法律去执行, 那么你就会受到处罚. 在互联网中, 如果你不按照标准去执行, 那么你受到的处罚就是你无法连接至互联网.
我们前面说过, 网络编程架构模式: C / S架构也就是服务端与客户端进行通讯, 我们所写出来的是应用程序, 传输层的作用就是为了帮助我们实现应用程序与应用程序之间的通讯.
传输层的由来:网络层的ip帮我们区分子网,以太网层的mac帮我们找到主机,然后大家使用的都是应用程序,你的电脑上可能同时开启qq,暴风影音,等多个应用程序,
那么我们通过ip和mac找到了一台特定的主机,如何标识这台主机上的应用程序,答案就是端口,端口即应用程序与网卡关联的编号。
传输层功能:建立端口到端口的通信
补充:端口范围0-65535,0-1023为系统占用端口
TCP协议:
又称为可靠传输,流式协议,TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割。
通过osi前四层协议, 我们可以定位到唯一的一款软件(ip和mac地址可以定位到唯一一台计算机, 端口号可以定位到计算机上的唯一一个应用程序), 那么TCP协议是如何实现应用程序与应用程序之间的连接去进行数据交互的呢?
前提: 一般是由服务端发起断开连接请求. (原因: 服务端需要承载大量的客户端, 所以事情一干完就要断开连接释放自己的压力. )服务端干完事情的时候, 客户端不一定干完事情了!
UDP协议:
又称为不可靠传输,”报头”部分一共只有8个字节,总长度不超过65,535字节,正好放进一个IP数据包。
UDP协议客户端与服务端无须建立连接, 数据发送全靠缘分(开玩笑), 因为其独特的无连接模式, 其发送数据以后无须等待对方发送响应信息, 这样引出了一个好处就是它的传输速度特别快, 但是相应的数据也容易丢失.
TCP与UDP的区别:
为什么TCP可靠而UDP不可靠呢?
有些人上来就会说, 因为TCP协议有连接. 这是不对的, TCP协议每次发送数据以后, 都会等待对方回应ack, 如果一段时间没有收到对方回应的ack, TCP协议会尝试再次发送数据, 直到尝试多次对方都没有响应ack(对方已下线)或者收到ack确认信息以后, TCP才会停止发送这份数据. 这就是TCP协议是可靠协议的原因
Socket是应用层与TCP/IP协议层通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
工作流程:
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
# 基于TCP协议的C/S架构模式通讯
# 服务端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 买电话
phone.bind(('127.0.0.1', 8081)) # 插手机卡,补充:0-65535 0-1024给系统用的. 这里传入的必须是一个元组, 第一个元素为ip地址, 第二个元素为端口号
phone.listen(5) # 开机
print('start...')
conn, client_addr = phone.accept() # 等电话连接
print('连接来了:', conn, client_addr)
# 收发消息
msg = conn.recv(1024) # 收消息,1024是一个最大的限制
print('客户端的消息: ', msg)
conn.send(msg + b'SB')
# 挂电话
conn.close()
# 关机
phone.close()
#############################################################
# 客户端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 买电话
phone.connect(('127.0.0.1', 8081)) # 拨电话,地址为服务端的ip和端口
phone.send('你好'.encode('utf-8')) # 发消息b'hello'
data = phone.recv(1024) # 收消息
print(data.decode('utf-8'))
phone.close()
# 基于UDP的C/S架构通讯
# 服务端
import socket
server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 数据报协议
server.bind(('127.0.0.1',8080))
client_data,client_addr=server.recvfrom(1024)
msg=input('回复%s:%s>>>:' %(client_addr[0],client_addr[1]))
server.sendto(msg.encode('utf-8'),client_addr)
#############################################################
# 客户端
import socket
client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 数据报协议
msg=input('>>>: ').strip()
client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
res,server_addr=client.recvfrom(1024)
print(res.decode('utf-8'))
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
关于listen()函数
在TCP协议中:
listen函数接收一个参数, 该参数的目的是限制最大的半连接数.
注意: 半连接数和可以接收的最大连接数是两个概念!
半连接的意思是指: 在TCP协议中, 服务端与客户端要经过三次握手建立两个通道, 当只建立了客户端通向服务端的通道时, 叫做半连接状态, 我们的listen限制的就是这种状态的最大连接数.