1、什么是C/S架构
Client客户端 软件 Server服务端软件
一个C
/
S架构就是,实现服务端软件与客户端软件基于网络通信。
互联网中处处是C/S架构:
Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求。
栗子:
如12306网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种)
腾讯作为服务端为你提供视频播放,你下载腾讯视频客户端观看
C/S架构与socket的关系:
我们学习socket就是为了完成C/S架构的开发
2、互联网协议是什么?分别介绍 五层协议中的每一层的功能?
互联网协议:世界上所有计算机之间通信的标准协议,就像英语是世界上通用的语言一样
简单的说,计算机之间的通信标准就是互联网协议
按功能不同,人们将互联网协议分为osi七层、tcp/ip五层、tcp/ip四层
osi七层:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
tcp/ip五层:应用层、传输层、网络层、数据链路层、物理层
tcp/ip四层:应用层、传输层、网络层、网络接口层
五层:
应用层: 软件如:qq,浏览器
传输层: 建立端口到端口的通信 0-65535 0-1023为系统占用端口 两种协议:tcp ucp
网络层: 引入一套新的地址来区分不同的广播域/子网,这套地址即网络地址(ip协议)
数据链路层: 定义了电信号的分组方式 以太网协议(ethernet) mac地址指网卡地址 广播
物理层: 主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0
3、基于TCP协议通信,为何建立连接需要三次握手,而断开连接却需要四次挥手
建立连接 三次握手:client发送请求建立通道;server收到请求并同意,同时也发送请求建通道;client收到请求并同意,建立完成
断开连接 四次挥手:client发送请求断开通道;server收到请求并同意,同时还回复client上一条消息;server也发送请求断开通道;client受到消息结束
4、为何基于TCP协议的通信比基于UDP协议的通信更可靠
TCP:可靠,对方给了确认收到信息,才发下一个,如果没有收到确认信息就重发
UDP:不可靠一直发数据,不需要对方回应
5、流式协议指什么协议,数据报协议指的是什么协议?
流式协议:TCP协议,可靠传输
数据报协议:UDP协议,不可靠传输
也就是TCP和UDP的区别:
TCP是面向连接的,可靠的字节流服务
UDP是面向无连接的数据报服务
6、什么是socket?简述基于tcp协议的套接字通信流程
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/ip
协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部。
7、什么是粘包?socket中造成粘包的原因是什么?那些情况会发生粘包现象?
粘包:
数据粘在一起
主要因为:接收方不知道消息之间的界限,不知道一次性提取
多少字节的数据造成的数据量比较小,时间间隔比较短,就合并成了一个包,
这是底层的一个优化算法(Nagle算法)
from socket import * phone = socket(AF_INET,SOCK_STREAM) phone.setsockopt(SOL_SOCKET,SOCK_STREAM,1) phone.bind(('127.0.0.1',8080)) phone.listen(5) print('start running...') coon,addr = phone.accept() #等待连接 data1 = coon.recv(10) data2 = coon.recv(10) print('------------>',data1.decode('utf-8')) print('------------>',data2.decode('utf-8')) coon.close() phone.close()
客户端:
from socket import * import time phone = socket(AF_INET,SOCK_STREAM) phone.connect(('127.0.0.1',8080)) phone.send('hello'.encode('utf-8')) phone.send('helloworld'.encode('utf-8')) phone.close()
服务端:
from socket import * phone = socket(AF_INET,SOCK_STREAM) phone.setsockopt(SOL_SOCKET,SOCK_STREAM,1) phone.bind(('127.0.0.1',8080)) phone.listen(5) print('start running...') coon,addr = phone.accept() #等待连接 data1 = coon.recv(2) #一次没有接收完整 data2 = coon.recv(10) #下一次接收的时候会先取旧的数据,然后取新的 # data3 = coon.recv(1024) #接收等5秒后的信息 print('------------>',data1.decode('utf-8')) print('------------>',data2.decode('utf-8')) # print('------------>',data3.decode('utf-8')) coon.close() phone.close()
客户端:
from socket import * import time phone = socket(AF_INET,SOCK_STREAM) phone.connect(('127.0.0.1',8080)) phone.send('hello'.encode('utf-8')) time.sleep(5) phone.send('haiyan'.encode('utf-8')) phone.close()
8、基于Socket开发一个聊天程序,实现两端互相发送和接收消息
8.1、tcp协议实现
TCP协议: 服务端: import socket server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port = ('127.0.0.1',8081) MAX_RECV_SIZE = 1024 server_socket.bind(ip_port) server_socket.listen(5) while True : conn, client_addr = server_socket.accept() while True: data = conn.recv(MAX_RECV_SIZE).decode('utf-8') print(data) msg = input('(q 退出)>>').strip() if not msg:break if msg == 'q': exit() conn.send(msg.encode('utf-8')) 客户端: import socket client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port = ('127.0.0.1',8081) MAX_RECV_SIZE = 1024 client_socket.connect(ip_port) while True: msg = input('(q 退出)>>').strip() if not msg:continue if msg == 'q': exit() client_socket.send(msg.encode('utf-8')) data = client_socket.recv(MAX_RECV_SIZE) print(data.decode('utf-8'))
8.2、udp协议实现,客户端:不需要:connect(),服务端不需要:listen() accept()
s.recvfrom(1024) 主要用在udp,得到参数为数据和地址,从而可以根据地址返回消息 msg,addr = s.recvfrom(1024) addr 收到来自发送端的地址
s.sendto(字节,address)主要用在udp根据地址发送 sendto(msg,addr) addr地址是接收方的地址
客户端: import socket ip_port = ('127.0.0.1',9000) BUFSIZE = 1024 udp_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) while True: MSG = input('>>').strip() udp_client.sendto(MSG.encode('utf-8'),ip_port) back_msg,addr = udp_client.recvfrom(BUFSIZE) print(back_msg.decode('utf-8')) udp_client.close() 服务端: import socket ip_socket =('127.0.0.1',9000) BUFSIZE = 1024 udp_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) udp_server.bind((ip_socket)) while True: msg,addr = udp_server.recvfrom(BUFSIZE) print(msg.decode('utf-8'),addr) response = input('>>').strip() udp_server.sendto(response.encode('utf-8'),addr) udp_server.close()
9、基于TCP/UDP SOCKET 开发简单的远程命令执行程序ssm,允许用户执行命令,并返回结果
TCP服务端:
import socket import struct import subprocess # subprocess模块来实现对系统命令或脚本的控制 ip_port = ('127.0.0.1',8080) server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server_socket.bind(ip_port) server_socket.listen(5) while True: conn,addr = server_socket.accept() while True: client_data = conn.recv(1024).decode('utf-8') res = subprocess.Popen(client_data,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) # stdin标准输入;stdout输出,stderr错误句柄 stdout = res.stdout.read() stderr = res.stderr.read() # print('stdout', stdout, type(stdout)) # b'' <class 'bytes'> 二进制模式 # print('stderr', stderr, type(stderr)) # b'' <class 'bytes'> 二进制模式 # stdin = res.stdin.read() # 先发报头 ---- struct header = struct.pack('i',len(stdout+stderr)) conn.send(header) conn.send(stdout) conn.send(stderr) conn.close()
TCP客户端:
import socket import struct client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port = ('127.0.0.1',8080) client_socket.connect(ip_port) while True: cmd = input('>>').strip() if not cmd:continue client_socket.send(cmd.encode('utf-8')) head = client_socket.recv(4) head_len = struct.unpack('i',head)[0]# 解包返回元祖(a,) recv_size = 0 total_data = b'' while recv_size < head_len: recv_data = client_socket.recv(1024) recv_size += len(recv_data) total_data += recv_data print("返回的消息:%s" % total_data.decode('gbk'))
#----------------------------------------------------------------------------------------------------------
UDP服务端:
import subprocess,struct,socket server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) ip_port = ('127.0.0.1',8080) server.bind(ip_port) while True: cmd,addr = server.recvfrom(1024) res = subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) stdout = res.stdout.read() stderr = res.stderr.read() server.sendto(stdout+stderr,addr) server.close()
UDP客户端:
import subprocess,struct,socket client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) ip_port = ('127.0.0.1',8080) while True: cmd = input('>>').strip() if not cmd:continue client.sendto(cmd.encode('utf-8'),ip_port) data,addr = client.recvfrom(1024) print(data.decode('gbk')) client.close()
10、基于TCP协议编写简单ftp(面向对象)(文件传输协议FTP的传输有两种方式:ASCII、二进制。)程序
实现上传、下载文件功能,并解决粘包问题
说明:1、没有考虑断点续传;2、没有考虑,允许上传和下载文件,并保证文件的一致性(md5值)
服务端:
import socket,os,sys import struct,pickle import subprocess BASE_DIR = os.path.dirname(os.path.abspath(__file__)) sys.path.append(BASE_DIR) class ftp_server: ip_port = ('127.0.0.1',8080) RECV_MAX = 1024 LISTEN_MAX = 5 def __init__(self): self.server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) self.server_socket.bind(self.ip_port) self.server_socket.listen(self.LISTEN_MAX) def get(self,conn): """ 1 判断文件在服务器是否存在 1.1存在下载,解决粘包问题 1.1.1.得到文件大小(字节为单位)file_size,发送报头给客户端 1.2不存在 报错 """ file_path = os.path.join(BASE_DIR,'db',self.msg[1]) if os.path.exists(file_path): header_dic = { 'filename': 111, 'file_md5': 111, 'file_size': os.path.getsize(file_path) } header_bytes = pickle.dumps(header_dic) # 文件的大小以字节为单位 head_size_len = struct.pack('i',len(header_bytes)) # 打 包成 四个字节 conn.send(head_size_len) conn.send(header_bytes) self.handle_file(file_path,conn,1) else: print('文件不存在') head_size = struct.pack('i', 0) # 打 包成 四个字节 conn.send(head_size) def put(self,conn): """ 0.判断根据文件名判断,服务端是否已存在该文件 1.存在----返回 0 2.不存在---返回 1 2.1.解压 报头 head """ file_path = os.path.join(BASE_DIR, 'db',self.msg[1]) if os.path.exists(file_path): file_exist = struct.pack('i', 0) # 打 包成 四个字节 conn.send(file_exist) else: file_exist = struct.pack('i', 1) # 打 包成 四个字节 conn.send(file_exist) file_size_len = struct.unpack('i',conn.recv(4))[0] # file_size = conn.recv(file_size_len) get_size = 0 with open(file_path,'wb') as f: while True: if get_size < file_size_len: recv_size = conn.recv(1024) f.write(recv_size) get_size += len(recv_size) else: print('下载成功') res = struct.pack('i', 0) # 打 包成 四个字节 conn.send(res) break @property def server_accept(self): conn, addr = self.server_socket.accept() try: self.handle(conn) except Exception as e: print(e) conn.close() def handle(self,conn): while True: try: cmd_data = conn.recv(self.RECV_MAX).decode('utf-8') if not cmd_data:break self.msg = cmd_data.split() cmd = self.msg[0] if hasattr(self,cmd): getattr(self,cmd)(conn) except Exception as e: print(e) conn.close() break def handle_file(self,file_path,conn,num): if num == 1: with open(file_path,'rb') as f: while True: data = f.read(1024) if data: conn.send(data) else: break if __name__ == '__main__': obj = ftp_server() obj.server_accept
客户端:
import socket import struct import subprocess,os,sys,pickle BASE_DIR = os.path.dirname(os.path.abspath(__file__)) sys.path.append(BASE_DIR) class ftp_client: ip_port = ('127.0.0.1',8080) def __init__(self): self.client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) def get(self): """" 0.判断指令是否正确 1.客户端判断文件是否存在 1.1、存在---输出该文件已存在 1.2、不存在:执行下载功能 1.2.1.解压,服务端传来的文件报头长度-file_size_len, 1.2.1.1.如果file_size_len==0,需要下载的文件不存在 1.2.1.2.file_size_len!=0, handle_file进行写入文件 1.2.1.3.接受文件大小file_size """ if len(self.cmd)>1: file_path = os.path.join(BASE_DIR,self.cmd[1]) if os.path.exists(file_path): print('----该文件已存在----') else: file_size_len = struct.unpack('i',self.client_socket.recv(4))[0] #数据的总长度,以字节为单位 if file_size_len == 0: print('----需要下载的文件不存在----') #接收数据 else: recv_size = 0 header_bytes = self.client_socket.recv(file_size_len) # 字节大小 head_dic = pickle.loads(header_bytes) file_size = head_dic['file_size'] self.handle_file(file_path,file_size) else: print('没有输入文件名') def put(self): """ 0.判断指令是否正确 1.判断文件是否存在 1.客户端存在 1.2.判断服务端是否已存在该文件 1.2.1.存在 服务器返回------服务端已存在该文件---- 1.2.2.不存在 发送报头的长度,再发送报头 """ if len(self.cmd)>1: file_path = os.path.join(BASE_DIR, self.cmd[1]) if os.path.exists(file_path): # file_name = struct.pack('i',self.cmd[1]) # 打包成了字节模式 # self.client_socket.send(file_name) """判断服务端是否存在该文件""" file_exist=struct.unpack('i',self.client_socket.recv(4))[0] if file_exist == 0: print('----服务端已存在该文件----') else: self.handle_file(file_path,1) #上传的结果反馈 res = struct.unpack('i',self.client_socket.recv(4))[0] if res == 0: print('put sucess') else: print('该文件不存在') else: print('没有输入文件名') @property def handle(self): while True: msg = input('>>').strip() if not msg:continue self.client_socket.send(msg.encode('utf-8')) #发送完整的指令给服务端口 self.cmd = msg.split() if hasattr(self,self.cmd[0]): getattr(self,self.cmd[0])() def handle_file(self,file_path,file_size): if self.cmd[0] == 'get': with open(file_path,'wb') as f: get_size = 0 while True: if get_size < file_size: file_bytes = self.client_socket.recv(1024)# 收到的是字节 f.write(file_bytes) get_size += len(file_bytes) # self.progress_bar(1, get_size, file_size) else: print('下载成功') break if self.cmd[0] == 'put': file_size2 = os.path.getsize(file_path)# 文件的大小本质上就是指字节的长度 # head_dic间接封装报头的长度 # head_dic={'filename':self.cmd[1], # 'file_md5':'aaaa', # 'file_size':file_size2 # } # head_pickle = pickle.dumps(head_dic) file_bytes = struct.pack('i', file_size2)#直接封装报头的长度 self.client_socket.send(file_bytes) # 发送报头长度 with open(file_path,'rb') as f: while True: data = f.read(1024) if data: self.client_socket.send(data) else: break @property def connect(self): self.client_socket.connect(self.ip_port) self.handle def progress_bar(self,num,get_size, file_size): pass if __name__ == '__main__': obj = ftp_client() obj.connect
11、基于UUP协议编写程序,实现功能
(1)执行指定的指令,让客户端可以查看服务端的时间
(2)执行指定的指令,让客户端可以与服务端的时间同步
服务端:
# UDP协议的两个主要方法sendto和recvfrom详解 # TypeError: sendto() takes 2 or 3 arguments (1 given) import socket import os,json,time ip_port = ('127.0.0.1',8081) server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) server.bind(ip_port) # udp 不需要 监听listen ,不需要接收 accept while True: msg,addr= server.recvfrom(1024) if msg.decode('utf-8') == 'time': server_time = time.strftime("%Y-%m-%d %X",time.localtime()) server.sendto(server_time.encode('utf-8'),addr) # print(server_time) server.close()
客户端:
import socket import time import json ip_port = ('127.0.0.1',8081) client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) while True: cmd = input('>>').strip() if not cmd:continue client.sendto(cmd.encode('utf-8'),ip_port) server_time,addr = client.recvfrom(1024) print('服务端的时间',server_time.decode('utf-8'))