使用socket可以实现网络通信,告别单机脚本
1.socket的简单认识
服务端
import socket
#创建socket对象
server = socket.socket()
#绑定ip和端口
server.bind(("128.0.0.1",8000))
#表示可以接受客户端的个数
server.listen(5) #最多可以接受5个客户端
#等待客户端的连接,如果没人连就阻塞
#如果客户端连接,获取到客户端的连接进行通信
#conn是客户端与服务端连接的中间体对象,address是客户端的地址信息
conn,address = server.accept()
#通过连接对象去获取信息
data = conn.recv(1024)
print(data)
#给客户端发送信息
conn.send(b"stop")
#与客户端断开连接
conn.close()
#关闭服务端
server.close()
客户端
#client中的阻塞:
#client.connect()
#conn.recv()
import socket
client = socket.socket()
#客户端发起连接请求
#阻塞,一直等待连接成功,连接成功后才会继续往下走
client.connect(("128.0.0.1",8000))
#向服务端发信息
client.send(b'hello')
#接受服务端返回的信息
data = client.recv(1024)
print(data)
#完成服务,关闭客户端
client.close()
2.实现登录判断
服务端
#根据本地的csv文件中保存的账号和密码对客户端输入的用户名和密码进行比对,然后判断是否登录成功
import csv
import socket
def login(content_lst):
with open("uid&upd.csv","r",encoding="UTF-8") as f:
reader = csv.reader(f)
for content in reader:
if content_lst == content:
status = "200"
return status
else:
status = "400"
return status
server = socket.socket()
server.bind(("192.168.0.108",8001))
server.listen(5)
while 1:
conn,address = server.accept()
while 1:
response = conn.recv(1024).decode("UTF-8")
if response == "exit":
break
content_lst = response.strip().split(",") #对客户端发送来的信息先去空格,再转化为list
status = login(content_lst) #调用登录函数,判断状态码
conn.send(status.encode("UTF-8")) #把状态码发送给客户端
conn.close()
客户端
#让用户输入账号和密码,传给服务端进行登录
import socket
chance = 3 #用户可以输入的次数
sk = socket.socket()
sk.connect(("128.0.0.1",8001))
while chance > 0:
uname = input("请输入用户名:")
upassword = input("请输入密码:")
msg = uname + "," + upassword
sk.send(msg.encode("UTF-8")) #将用户输入信息发送给服务端
response = sk.recv(1024) #拿到服务端给的状态码
if response.decode("UTF-8") == "200": #根据状态码判断登录状态
print("登陆成功")
break
else:
print("用户名或密码错误")
chance -= 1
print("你还有{}次机会".format(chance))
sk.send("exit".encode("UTF-8"))
sk.close()
3.使用struct解决黏包问题
黏包问题的本质:接收方发送方发送数据的分界线
解决黏包问题:
发送端:1.构建报头(数据分界线) 2.发送报头 3.发送数据
接收端:1.接收报头 2.根据报头接受数据
黏包的类型:1.数据连续发送,接收方无法分别 2.数据过大,接受方接受不完,服务方又继续发送
服务端
#根据客户端输入的内容返回特定的内容
import subprocess
import socket
import struct
server = socket.socket()
server.bind(("192.168.0.108",8001))
server.listen(5)
while 1:
conn,address = server.accept()
print("server is working")
while 1:
try :
response = conn.recv(1024).decode("UTF-8")
print(response)
if response == "exit":
break
res = subprocess.Popen(response,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out = res.stdout.read()
err = res.stderr.read()
if err:
header = struct.pack("i",len(err)) #构建报头
conn.send(header) #发送报头
conn.send(err) #发送数据
else:
header = struct.pack("i",len(out)) #构建报头
conn.send(header) #发送报头
conn.send(out) #发送数据
except Exception as e:
print(e)
break
conn.close()
客户端
import socket
import struct
sk = socket.socket()
sk.connect(("192.168.0.108",8001))
while 1:
cmd = input("请输入命令:")
if cmd == "":
continue
if cmd == "exit":
break
sk.send(cmd.encode("UTF-8"))
header = sk.recv(4) #获取报头
length = struct.unpack("i",header)[0] #将报头解析出来
data_length = 0
response = b''
while data_length < length: #接受数据
data = sk.recv(1024)
response += data
data_length += len(data)
print(response.decode("GBK"))
sk.close()
4.基于socket实现简单的文件上传和下载
服务端
#借助struct来解决黏包
import socket
import json
import struct
import hashlib
import os
from 网络编程.FTP import verify
server = socket.socket()
server.bind(("127.0.0.1",8080))
server.listen(5)
while 1:
conn,address = server.accept()
print("server is working...")
#前四个字节是报头的长度
#获取报头长度之后就可以获取报头
#获取报头之后就能获取报头中的文件大小信息
while 1:
try :
info_lst = conn.recv(1024).decode("UTF-8").split(",")
type = info_lst[0]
if type == "put": #上传文件,服务端接受
md5 = hashlib.md5()
headers_len_byte = conn.recv(4)
print(headers_len_byte)
headers_len = struct.unpack("i",headers_len_byte)[0] #将请求头长度解包
print(headers_len)
headers = json.loads(conn.recv(headers_len).decode("UTF-8")) #将请求头反格式化
print(headers)
type = headers.get("type")
file_name = headers.get("file_name")
file_size = headers.get("file_size")
recv_data_size = 0
with open(file_name,"wb") as f: #接受数据
while recv_data_size < file_size:
data = conn.recv(1024)
f.write(data)
recv_data_size += len(data)
print("文件大小:{},已接受:{}".format(file_size,recv_data_size))
print("接收成功")
hex = conn.recv(1024).decode("UTF-8")
if hex == verify.jiaoyan(file_name):
print("数据完整")
else:
print("数据有丢失")
else: #下载文件,服务端发送
file_name = info_lst[1]
file_size = os.path.getsize(file_name)
file_info = {"type": type, "file_name": file_name, "file_size": file_size} # 文件信息字典
file_info_json = json.dumps(file_info) # 将请求头格式化
# 也可以使用struct模块来解决年黏包
print(len(file_info_json))
headers_len_byte = struct.pack("i", len(file_info_json))
conn.send(headers_len_byte) # 1
print(len(headers_len_byte))
conn.send(file_info_json.encode("UTF-8")) # 2
with open(file_name, "rb") as f: # 发送文件
for line in f:
conn.send(line) # 3
f.close()
print(conn.recv(1024).decode("UTF-8"))
md5_str = verify.jiaoyan(file_name)
conn.send(md5_str.encode("UTF-8"))
except Exception as e:
print(e)
break
conn.close()
客户端
from 网络编程.FTP import verify
import socket
import os
import json
import struct
import hashlib
sk = socket.socket()
sk.connect(("127.0.0.1",8080))
while 1:
user_input = input("请输入命令:") #最后输入文件的绝对路径
try:
if user_input == "exit":
sk.send(user_input.encode("UTF-8"))
break
if user_input == "":
continue
else:
info_lst = user_input.strip().split(" ")
print(info_lst)
type = info_lst[0]
file_name = info_lst[1]
sk.send(",".join(info_lst).encode("UTF-8"))
if type == "put": #上传文件,客户端发送
md5 = hashlib.md5()
file_size = os.path.getsize(file_name)
file_info = {"type":type,"file_name":file_name,"file_size":file_size} #文件信息字典
file_info_json = json.dumps(file_info) #将请求头格式化
#也可以使用struct模块来解决年黏包
print(len(file_info_json))
headers_len_byte = struct.pack("i",len(file_info_json))
sk.send(headers_len_byte) #1
print(len(headers_len_byte))
sk.send(file_info_json.encode("UTF-8")) #2
with open(file_name, "rb") as f: #发送文件
for line in f:
sk.send(line) #3
f.close()
sk.send(verify.jiaoyan(file_name).encode("UTF-8"))
else: #下载文件,客户端接受
md5 = hashlib.md5()
headers_len_byte = sk.recv(4)
print(headers_len_byte)
headers_len = struct.unpack("i", headers_len_byte)[0] # 将请求头长度解包
print(headers_len)
headers = json.loads(sk.recv(headers_len).decode("UTF-8")) # 将请求头反格式化
print(headers)
type = headers.get("type")
file_name = headers.get("file_name")
file_size = headers.get("file_size")
recv_data_size = 0
with open(file_name, "wb") as f: # 接受数据
while recv_data_size < file_size:
data = sk.recv(1024)
f.write(data)
recv_data_size += len(data)
print("文件大小:{},已接受:{}".format(file_size, recv_data_size))
print("接收成功")
sk.send("ok".encode("UTF-8"))
hex = sk.recv(1024).decode("UTF-8")
print(hex)
md5_str = verify.jiaoyan(file_name)
if hex == md5_str:
print("数据完整")
else:
print("数据有丢失")
#1,2,3出可能会发生黏包
except Exception as e:
print("命令错误")
5.socket解决并发
import socketserver
class Myserver(socket.BaseRequestHandler):
def handle(self):
pass
server = socketserver.ThreadingTCPServer(("127.0.0.1",8080),Myserver)
server.server_forever() #
6.网络编程零碎知识点
1.服务端的conn是连接的客户端的socket对象
2.ssh:客户端通过给服务端传递cmd命令,服务端执行命令后结果返回给客户端,从而到达客户端控制服务端的目的
3.命令行显示的数据编码格式为GBK
4.send()传送的数据为str,不为空,且为byte类型
5.管道里的数据只能读取一次
6.subprocess中的stdout的数据读取出来为byte类型
7.解决黏包:
发送端:1.构建报头(采用struct模块) 2.发送报头 3.发送数据
接收端:1.接收报头 2.接受数据
8.先启动服务端,再启动客户端,客户端与服务端的ip和端口号要一致,而且保证服务端的端口号没有被占用
9.黏包问题的本质就是接收方不知道数据的分界线,所以发送方只需要告诉接收方的分界线就可以解决黏包问题
10.应用程序不能直接调用硬件,要通过操作系统的帮助
11.在windows平台上,如果断开客户端的socket,服务端会报错,而且windows平台上不能发送空数据
在linux和ios平台上,如果断开客户端的socket,会给服务端发送一个空数据
12.server.listen(a) 表示的是排列的客户端最多为a个,算上正在连接的客户端一共最多可以有a+1个客户端,类似于队列
13.在写网络编程的代码时注意事项:
1.连接对象的使用
2.客户端和服务端的ip和端口号一样,不同的服务端是否占用同一个端口号
3.发送数据之前判断是否会黏包
4.根据不同的情况选择不同的方式去处理黏包
14.为什么socket收发消息时要转化成字节类型?将数据转化为utf8或gbk的格式,是为了压缩数据,节省空间
15.socketserver不仅简化了服务端的写法,最重要的是解决了并发问题+
self.request == conn
self.client_address == address
16.MAC地址:每个网卡有唯一的MAC地址
17.dhcp服务:路由器或交换机中的dhcp服务会自动为pc分配ip地址
18.如果两个机器的ip地址冲突,都上不了网
19.子网掩码:eg 255.255.255.0 挡住的ip位数作为网段,未挡住的是可变的值,表示在局域网中ip地址的前三位固定
20.网关:局域网与局域网通信要借助网关,网关地址是网络地址(子网掩码与局域网中的任意ip进行与运算的结果)
21.arp协议:局域网中的发送数据格式要遵循arp协议
22.dns协议,我们访问一个网站时通过dns协议将域名解析为ip地址,然后连接对应ip地址就可以进行浏览
本地电脑上域名与ip的对应关系在:C:\Windows\System32\drivers\etc\hosts
全球一共有13台顶级dns服务器,来存放域名与ip的对应关系
23.建立一个网站①租一个服务器+公网ip②租域名
24.通过划分局域网来避免广播风暴