6 网络编程
1.硬件C/S架构(打印机)
2.软件C/S架构
互联网中处处是C/S架构
如黄色网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种)
腾讯作为服务端为你提供视频,你得下个腾讯视频客户端才能看它的视频)
C/S架构与socket的关系:
我们学习socket就是为了完成C/S架构的开发
客户端软件发送一串信息,会通过操作系统,操作系统在调用硬件,在通过网络。传输到服务器上,硬件接收,传给操作系统,操作系统在传给服务端软件。
1 osi模型
七层:
应用层--表示层--会话层--传输层--网络层--数据链路层--物理层
五层:
应用层--传输层--网络层--数据链路层--物理层
四层:
应用层--传输层--网络层--接口层
物理层:发送电信号。如:01001010 二进制数字
数据链路层:分组;
有Ethevent协议
报文:头部head(18个字节=6字节本机地址+6字节目标地址+6字节描述)-data(数据)
mac地址 ++ 以广播的方式找寻地址 ++ 只能在子网中传播
网络层:路由寻路
ip协议和arp协议(arp协议是通过IP地址找mac地址)
ip头部 - data
ip地址 ++ 找寻在哪个子网中
传输层:tcp/udp协议,基于端口协议
建立双向传输管道
tcp三次握手,保证了数据传输的安全,建立了双向链接;断开连接4次握手
udp数据直接传输,数据不安全。
应用层:http,ftp,自己的协议
2 socket层
为何学习socket一定要先学习互联网协议:
1.首先:本节课程的目标就是教会你如何基于socket编程,来开发一款自己的C/S架构软件
2.其次:C/S架构的软件(软件属于应用层)是基于网络进行通信的
3.然后:网络的核心即一堆协议,协议即标准,你想开发一款基于网络通信的软件,就必须遵循这些标准。

什么是socket?
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
也有人将socket说成ip+port,ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,ip地址是配置到网卡上的,而port是应用程序开启的,ip与port的绑定就标识了互联网中独一无二的一个应用程序
而程序的pid是同一台机器上不同进程或者线程的标识
3 套接字工作流程

先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
4 socket模块
服务端套接字函数
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() 创建一个与该套接字相关的文件
*tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端*
*tcp服务端*
1 ss = socket() #创建服务器套接字
2 ss.bind() #把地址绑定到套接字
3 ss.listen() #监听链接
4 inf_loop: #服务器无限循环
5 cs = ss.accept() #接受客户端链接
6 comm_loop: #通讯循环
7 cs.recv()/cs.send() #对话(接收与发送)
8 cs.close() #关闭客户端套接字
9 ss.close() #关闭服务器套接字(可选)
*tcp客户端*
1 cs = socket() # 创建客户套接字
2 cs.connect() # 尝试连接服务器
3 comm_loop: # 通讯循环
4 cs.send()/cs.recv() # 对话(发送/接收)
5 cs.close() # 关闭客户套接字
socket通信流程与打电话流程类似,我们就以打电话为例来实现一个low版的套接字通信
服务端:
import socket # from socket import *
# 初始化套接字
"""
参数1:套接字的类型
family = socket.AF_INET 基于网络通信
参数2:
type = socket.SOCK_STREAM 流式协议(TCP/IP协议)
type = socket.SOCK_DGRAM udp/ip套接字
参数3:
protocal=0 一般不填,默认值为 0。
"""
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 重用该端口地址
# 绑定端口
"""
传入元组形式参数:
(服务端IP地址,端口地址0~65535;0-1024操作系统所用)
"""
phone.bind(('127.0.0.1', 8800))
# 监听端口
"""
listen:最大挂起的链接数
"""
phone.listen(5)
# 建立连接
"""
accept:阻塞端口,等待客户端链接(三次握手)
conn:链接对象,与客户端通信使用
client_addr:客户端IP地址和端口
"""
while 1: # 链接循环
conn, client_addr = phone.accept()
# 连接成功,接收客户端数据
"""
recv(1024):收取1024个字节
1.单位:bytes
2.1024代表最大接收1024个bytes
"""
while 1: # 通信循环
"""
data = conn.recv(1024)
if not data: break # 如果接收数据为空,则终止循环(适用于linux系统)
print("客户端%s数据:" % client_addr[1], data)
# 给客户端发数据
conn.send(data.upper())
"""
# windows系统接收为空,解决方案:
try:
data = conn.recv(1024)
print("客户端%s数据:" % client_addr[1], data)
conn.send(data.upper())
except ConnectionResetError:
break
# 断开链接
conn.close()
# 关机
phone.close()
客户端:
import socket
# 初始化套接字
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
phone.connect(('127.0.0.1', 8800))
# 数据通信
while 1: # 通讯循环
"""发送的格式必须是bytes格式"""
msg = input('>>:').strip()
if not msg: continue # 输入数据为空,则重新输入
phone.send(msg.encode('utf-8'))
data = phone.recv(1024)
print(data.decode('utf-8'))
# 断开链接
phone.close()
pkill -9 python清理端口linux
tasklist python清理端口window
5 粘包套接字
windows:
dir:查看文件夹内容
ipconfig:ip地址信息
tasklist:查看运行进程
linux:
ls;ifconfig;ps aux
5-1 subprocess模块
调用系统的命令,进而使用它。与os模块的区别在于,os模块查出的信息会直接打印在终端上,不可存储起来。
而subprocess模块,可以将查询的命令结果赋值。进而使用。
os模块
res = os.system('dir /C/')
返回值:0:命令执行成功,非0执行失败
subprocess模块
obj = subprocess.Popen('参数1', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
参数1:
字符串形式的命令
shell:
启动一个终端,来解析参数1的命令
stdout:
命令的正确结果放入PIPE生成的管道中
stderr:
命令的错误结果放入PIPE生成的管道中
返回值:默认返还给终端。如果被引用,则不会在终端打印
print('obj 1>>', obj.stdout.read().decode('gbk)) # 查看成功结果
print('obj 2>>', obj.stderr.read().decode('gbk)) # 查看错误结果
5-2 struct模块
struct模块可以将数字打成固定长度的模块
import struct
res = struct.pack('i', 128) # 打包方式一,'i'范围小
res = struct.pack('l', 128) # 打包方式二,'l'范围大
print(res, type(res), len(res))
obj = struct.unpack('i', res) # 解包
print(obj[0])
5-3 套接字模板
1.recv和send都不是直接收到对方的数据,而是操作自己的操作系统内容--->不是一个send对应一个recv
2.recv接收:有个等待网络延时的阶段,耗时较长。还有数据拷贝阶段耗时短
send:只有一个拷贝阶段,将内存数据拷贝给缓存
服务端:
from socket import *
import struct
import json
import subprocess
phone = socket(AF_INET, SOCK_STREAM)
phone.bind(('127.0.0.1', 8920))
phone.listen(5)
while 1: # 链接循环
conn, client_addr = phone.accept()
while 1:
try:
# 1.收命令
data = conn.recv(8096)
print("客户端%s数据:" % client_addr[1], data)
# 2.执行命令拿到结果
obj = subprocess.Popen(data.decode('utf-8'), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
# 3.把命令的结果返回给客户端
# 第一步:制作固定长度的报头
header_dic = {
'filename': 'a.txt',
'md5': '9dis0diks',
'total_size': len(stdout) + len(stderr)
}
header_json = json.dumps(header_dic)
header_bytes = header_json.encode('utf-8')
# 第二步:先发送报头的长度
conn.send(struct.pack('i', len(header_bytes)))
# 第三步:在发报头
conn.send(header_bytes)
# 第四步:在发送真实数据
conn.send(stdout + stderr)
except ConnectionResetError:
break
conn.close()
phone.close()
客户端:
from socket import *
import struct
import json
phone = socket(AF_INET, SOCK_STREAM)
phone.connect(('127.0.0.1', 8920))
while 1:
# 发命令
msg = input('>>:').strip()
if not msg: continue
phone.send(msg.encode('utf-8'))
# 拿到结果并打印
# 第一步:先收到报头的长度
obj = phone.recv(4)
header_size = struct.unpack('i', obj)[0]
# 第二步:在收报头
header_bytes = phone.recv(header_size)
# 第三步:从报头拿出有用的信息
header_json = header_bytes.decode('utf-8')
header_dic = json.loads(header_json)
total_size = header_dic['total_size']
# 第三步:接收真实的数据
recv_size = 0
recv_data = b''
while recv_size < total_size:
res = phone.recv(1024)
recv_data += res
recv_size += len(res)
print(recv_data.decode('gbk'))
phone.close()
6 文件传输
服务端:
from socket import *
import struct
import json
import os
SHARE_DIR = "c/user/dir"
phone = socket(AF_INET, SOCK_STREAM)
phone.bind(('127.0.0.1', 8920))
phone.listen(5)
while 1: # 链接循环
conn, client_addr = phone.accept()
while 1:
try:
data = conn.recv(8096)
print("客户端%s数据:" % client_addr[1], data)
# 1.解析命令,提取相应命令参数
cmd = data.decode('utf-8').split()
filename = cmd[1]
header_dic = {
'filename': filename,
'md5': '9dis0diks',
'file_size': os.path.getsize('%s/%s' % (SHARE_DIR, filename))
}
header_json = json.dumps(header_dic)
header_bytes = header_json.encode('utf-8')
conn.send(struct.pack('i', len(header_bytes)))
conn.send(header_bytes)
# 2.以读的方式打开文件,读取文件内容,发送给客户端
with open('%s/%s' % (SHARE_DIR, filename), 'rb') as f:
for line in f:
conn.send(line)
except ConnectionResetError:
break
conn.close()
phone.close()
客户端:
from socket import *
import struct
import json
DOWNLOAD_DIR = "c/user/dir"
phone = socket(AF_INET, SOCK_STREAM)
phone.connect(('127.0.0.1', 8920))
while 1:
msg = input('>>:').strip()
if not msg: continue
phone.send(msg.encode('utf-8'))
obj = phone.recv(4)
header_size = struct.unpack('i', obj)[0]
header_bytes = phone.recv(header_size)
header_json = header_bytes.decode('utf-8')
header_dic = json.loads(header_json)
total_size = header_dic['file_size']
filename = header_dic['filename'] # 拿到文件名
# 1.以写的方式打开一个新文件,接收服务端内容。
with open('%s/%s' % (DOWNLOAD_DIR, filename), 'wb') as f:
recv_size = 0
while recv_size < total_size:
line = phone.recv(1024)
f.write(line)
recv_size += len(line)
print('总大小:%s 已下载:%s' % (total_size, recv_size))
phone.close()
7 面向对象版本
服务端:
import socket
import struct
import json
import subprocess
import os
class MYTCPServer:
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
allow_reuse_address = False
max_packet_size = 8192
coding='utf-8'
request_queue_size = 5
server_dir='file_upload'
def __init__(self, server_address, bind_and_activate=True):
"""Constructor. May be extended, do not override."""
self.server_address=server_address
self.socket = socket.socket(self.address_family,
self.socket_type)
if bind_and_activate:
try:
self.server_bind()
self.server_activate()
except:
self.server_close()
raise
def server_bind(self):
"""Called by constructor to bind the socket.
"""
if self.allow_reuse_address:
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind(self.server_address)
self.server_address = self.socket.getsockname()
def server_activate(self):
"""Called by constructor to activate the server.
"""
self.socket.listen(self.request_queue_size)
def server_close(self):
"""Called to clean-up the server.
"""
self.socket.close()
def get_request(self):
"""Get the request and client address from the socket.
"""
return self.socket.accept()
def close_request(self, request):
"""Called to clean up an individual request."""
request.close()
def run(self):
while True:
self.conn,self.client_addr=self.get_request()
print('from client ',self.client_addr)
while True:
try:
head_struct = self.conn.recv(4)
if not head_struct:break
head_len = struct.unpack('i', head_struct)[0]
head_json = self.conn.recv(head_len).decode(self.coding)
head_dic = json.loads(head_json)
print(head_dic)
#head_dic={'cmd':'put','filename':'a.txt','filesize':123123}
cmd=head_dic['cmd']
if hasattr(self,cmd):
func=getattr(self,cmd)
func(head_dic)
except Exception:
break
def put(self,args):
file_path=os.path.normpath(os.path.join(
self.server_dir,
args['filename']
))
filesize=args['filesize']
recv_size=0
print('----->',file_path)
with open(file_path,'wb') as f:
while recv_size < filesize:
recv_data=self.conn.recv(self.max_packet_size)
f.write(recv_data)
recv_size+=len(recv_data)
print('recvsize:%s filesize:%s' %(recv_size,filesize))
tcpserver1=MYTCPServer(('127.0.0.1',8080))
tcpserver1.run()
客户端:
import socket
import struct
import json
import os
class MYTCPClient:
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
allow_reuse_address = False
max_packet_size = 8192
coding='utf-8'
request_queue_size = 5
def __init__(self, server_address, connect=True):
self.server_address=server_address
self.socket = socket.socket(self.address_family,
self.socket_type)
if connect:
try:
self.client_connect()
except:
self.client_close()
raise
def client_connect(self):
self.socket.connect(self.server_address)
def client_close(self):
self.socket.close()
def run(self):
while True:
inp=input(">>: ").strip()
if not inp:continue
l=inp.split()
cmd=l[0]
if hasattr(self,cmd):
func=getattr(self,cmd)
func(l)
def put(self,args):
cmd=args[0]
filename=args[1]
if not os.path.isfile(filename):
print('file:%s is not exists' %filename)
return
else:
filesize=os.path.getsize(filename)
head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize}
print(head_dic)
head_json=json.dumps(head_dic)
head_json_bytes=bytes(head_json,encoding=self.coding)
head_struct=struct.pack('i',len(head_json_bytes))
self.socket.send(head_struct)
self.socket.send(head_json_bytes)
send_size=0
with open(filename,'rb') as f:
for line in f:
self.socket.send(line)
send_size+=len(line)
print(send_size)
else:
print('upload successful')
client=MYTCPClient(('127.0.0.1',8080))
client.run()
8 udp套接字
udp套接字不会发生粘包,一个sendto对应一个recvfrom
*udp是无链接的,先启动哪一端都不会报错*
udp服务端
1 ss = socket() #创建一个服务器的套接字
2 ss.bind() #绑定服务器套接字
3 inf_loop: #服务器无限循环
4 cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
5 ss.close() # 关闭服务器套接字
udp客户端
cs = socket() # 创建客户套接字
comm_loop: # 通讯循环
cs.sendto()/cs.recvfrom() # 对话(发送/接收)
cs.close() # 关闭客户套接字
*udp套接字简单示例*
服务端:
from socket import *
server = socket(AF_INET, SOCK_DGRAM)
server.bind(('127.0.0.1', 8900))
while 1:
data, client_addr = server.recvfrom(1024)
print(data, client_addr)
server.sendto(data.upper(), client_addr)
server.close()
客户端:
from socket import *
client = socket(AF_INET, SOCK_DGRAM)
while 1:
msg = input(">>>:").strip()
client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8900))
data, server_addr = client.recvfrom(1024)
print(data, server_addr)
client.close()

浙公网安备 33010602011771号