tcp:tcp使用较多.直接使用较少,使用 封装之后上层的库 较多. 不会有人从头开始写一个tcp的协议,
然后做个什么软件的,造轮子这事情,差不多就得了.知道原理,会使用别人造的库就行.出错了能够找到错误的原因,处理掉就好.
面试常问:tcp和udp的区别
tcp:Transmission Control Protocol 传输控制协议
1.>面向连接(就是打电话的时候先拨号的操作)
2.>可靠的(ACK应答)
3.>基于字节流 stream的方式
面向连接:在通信之前确认双方在线.--> 在通信之前需要先建立连接<拨号>
字节流:无界限,没有量词来界定这个消息的体量,都是连接在一起的.
没有消息边界,对方多次发送的消息,我一次就接收.不是每次都接收一部分.从接收的数据中看不到数据是哪一次发送的. 简称tcp的 "粘包" 问题.
特点:面向连接,可靠,基于字节流
优点:可靠,稳定
缺点:慢,占用系统资源高
步骤:创建连接,数据传送,
不适用广播,一对一的单播
为啥可靠?原因:
1.>发送应答:每发一次都必须得到对方的应答(ACK)
2.>超时重传:一定的时间内没有收到回复,就认为这个数据包已经出错或者丢失,发送方会再次发这个数据包
3.>错误校验:奇偶校验,循环冗余校验等等
4.>流量控制与阻塞管理:发送方有一个动态调整的发送速度的机制.
5.>有序编号
udp中无序,可能重复,有可能乱序.
最大特点有了connect()方法
socket就是一个手机,这么理解即可
1.创建socket(买个手机)
2.建立连接(拨号)
3.发送数据(通话)
4.关闭socket(挂掉)
tcp客户端:
""" socket就是一个手机,这么理解即可 1.创建socket(买个手机) 2.建立连接(拨号) 3.发送数据(通话) 4.关闭socket(挂掉) """ import socket # 创建 tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 连接 tcp_client_socket.connect(('192.168.14.26', 8089)) while True: # 发送数据 send_data = input('输入发送给服务器的数据:') tcp_client_socket.send(send_data.encode()) # 参数是一个字节流bytes类型的数据 # 接收返回 ,面向连接的(地址肯定是连接的对方的地址)不再需要对方的地址,只需要对方发送的数据 # 阻塞等待的接收返回 # recv()返回值的含义: # recv()正常情况下是接收到的数据<bytes类型> # 如果对方断开连接,返回值就是b''<空字节>,不是对方发送的数据,是系统提示对方断开的标志 recv_data = tcp_client_socket.recv(1024) # recv()返回一个数据(bytes类型)即可,没有地址信息 # print(recv_data.decode('gbk')) # 解码解得是调试助手发来的数据,而调试助手发送的时候是gbk的编码格式 # 断开连接输出:b'' <空字节> # print(recv_data) # 解码解得是调试助手发来的数据,而调试助手发送的时候是gbk的编码格式 # recv_data != None and recv_data !='' if recv_data: # 如果recv_data不为空,为真 print(recv_data.decode('gbk')) else: # 为空,终止 print('服务器断开连接') break # 关闭 tcp_client_socket.close()
tcp服务器:
""" tcp服务器: 1.>socket()买个手机 2.>bind()绑定10086 3.>listen()安装客户服务系统,设定等待服务区的大小,从等待区域中取出一个客户(和客户建立连接) 4.>accept()并转接到分机,具体的同事使用分机和客户交流 5.>close()分机挂机 总计挂机 总机不会挂机 分机会挂机 实际在交流信息的时候是和分机交流 总机只是和客户建立连接,分到等待区 服务器分为两种socket,一种专门来接受客户的连接请求,一种才是和客户进行数据交流 服务器是被动的 """ import socket # 创建服务器socket tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定,如果不绑定,别人就找不到这个地址了 tcp_server_socket.bind(('', 9999)) # ip为空,意味着绑定了这台主机上面的所有网卡,一台机子上面不是只有一个网卡,至少是2个网卡 # 监听等待 安装客户服务系统 --->1.将socket设置为被动socket<被动接受连接 默认是主动连接> 2.设置等待服务区的大小为128 tcp_server_socket.listen(128) print('服务器已建立连接') # 转到分机接收 从等待服务区取出一个客户端来服务 # 返回值是一个元组,两个元素,第一个元素是客户端socket<分机>,第二个是客户端的(ip,port)<地址> tcp_client_socket, client_info = tcp_server_socket.accept() # 无参数,只有返回值,返回值是socket和 address_info print('接受到客户%s的连接请求' % str(client_info)) while True: # 使用分机和客户端进行交流 ----> 数据收发 recv_data = tcp_client_socket.recv(1024) if recv_data: print('接收到的客户数据:', recv_data.decode()) else: # b'' print('客户端下线了') break # 服务器返回给客户信息 tcp_client_socket.send('are you OK?'.encode()) # 分机关闭 tcp_client_socket.close() # 总机关闭 tcp_server_socket.close() # 只有客户端才需要知道服务器的ip和端口信息,服务器不需要知道客户的信息,再建立了连接之后会收到的 """<对象>""" """一定是先开服务器,后开客户端"""
tcp服务器升级版:
""" tcp服务器: 1.>socket()买个手机 2.>bind()绑定10086 3.>listen()安装客户服务系统,设定等待服务区的大小,从等待区域中取出一个客户(和客户建立连接) 4.>accept()并转接到分机,具体的同事使用分机和客户交流 5.>close()分机挂机 总计挂机 总机不会挂机 分机会挂机 实际在交流信息的时候是和分机交流 总机只是和客户建立连接,分到等待区 服务器分为两种socket,一种专门来接受客户的连接请求,一种才是和客户进行数据交流 服务器是被动的 """ import socket # 创建服务器socket tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定,如果不绑定,别人就找不到这个地址了 tcp_server_socket.bind(('', 9999)) # ip为空,意味着绑定了这台主机上面的所有网卡,一台机子上面不是只有一个网卡,至少是2个网卡 # 监听等待 安装客户服务系统 --->1.将socket设置为被动socket<被动接受连接 默认是主动连接> 2.设置等待服务区的大小为128 tcp_server_socket.listen(128) print('服务器已建立连接') # 就是领导接活,下面的小弟只管干活 while True: # 转到分机接收 从等待服务区取出一个客户端来服务 # 返回值是一个元组,两个元素,第一个元素是客户端socket<分机>,第二个是客户端的(ip,port)<地址> tcp_client_socket, client_info = tcp_server_socket.accept() # 无参数,只有返回值,返回值是socket和 address_info print('接受到客户%s的连接请求' % str(client_info)) while True: # 使用分机和客户端进行交流 ----> 数据收发 recv_data = tcp_client_socket.recv(1024) # 服务器返回给客户信息, 接收到的是gbk的编码数据流,需要解码后再次编码为utf-8 tcp_client_socket.send(recv_data.decode('gbk').encode()) if recv_data: print('接收到的客户数据:', recv_data.decode('gbk')) else: # b'' print('客户端下线了') break # 分机关闭 tcp_client_socket.close() # # 总机关闭 # tcp_server_socket.close() # 我需要被别人找到,就要使用bind() # 想要接受连接,必须先listen() # 收发数据都是client_socket # accept() 接受:被动的 # close() 断开连接请求 # 0字节长度 ---> 对方断开连接了 # 解阻塞:由卡死到不卡的状态变化.
文件下载器-客户端:
""" 客户端: 1.连接服务器(ip,port) 2.下载文件的名字 服务器接收到客户端需要下载的文件名字,搜索文件,传给客户端 客户端写入到自己的文件中. """ # 1. 输入服务器ip,端口 # 2. 建立socket 连接服务器 # 3. 输入文件名 # 4. 发送文件名给服务器 # 5. 使用socket接收来自服务器的数据(保存在文件中) # 6. 如果收到b'',说明服务器断开连接-已经下载好 # 7. 关闭socket import socket server_ip = input('输入server的ip:') server_port = int(input('输入server的port:')) client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect((server_ip, server_port)) file_name = input('输入下载的文件名:') client_socket.send(file_name.encode()) # 文本方式打开 str类型 需要解码 图片无法解码,所以直接使用二进制方式打开即可 with open("download" + file_name, 'wb') as file: while True: file_data = client_socket.recv(4096) # ConnectionAbortedError: [WinError 10053] 你的主机中的软件中止了一个已建立的连接。原因是服务器出错了 # 如果对方断开连接,我们就接收到b'' if not file_data: print('服务器断开连接,说明文件传输完成') break # 否则就将收到的数据直接写入带文件中 file.write(file_data) client_socket.close()
文件下载器-服务器:
""" 服务器: # 1. 创建服务器socket # 2. bind() listen # 3. 接受客户端连接请求,取出一个客户端关联的socket # 4. 使用关联的socket和客户端进行通信 # 收客户端需要下载的文件名,发送文件名对应的数据 # 5. 发送完成,断开连接<就是关闭了client_socket # 关闭服务器socket """ import socket server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置选项 SO_REUSEADDR:二次利用,释放地址,重新利用地址 # 忽略2msl等待时间,直接进行这个地址的再次利用 server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(('', 9991)) server_socket.listen(128) while True: client_socket, client_info = server_socket.accept() # print('接收到连接请求%s' % client_info) # TypeError: not all arguments converted during string formatting # print('接收到%s连接请求' % str(client_info)) # 记得这里的client_info是一个元组类型,不能使用%号来连接,客户端那边也会报错 print('接收到连接请求:', client_info) # 记得这里的client_info是一个元组类型,不能使用%号来连接,客户端那边也会报错 file_name = client_socket.recv(256).decode() # 文件名一般很短,256字节长度可以了 # 打开本地文件,读取数据 try: # 捕获如果没有这个文件的异常 file = open(file_name, 'rb') except Exception as e: pass else: # 读取 file_data = file.read() # 发送 client_socket.send(file_data) # 关闭文件 file.close() finally: # 关闭socket client_socket.close() server_socket.close()