python基础——socket网络编程

一、网络编程

通过某种计算机语言来实现不同设备间的资源共享和信息传递

 

二、OSI模型

拿QQ来说明下两台设备怎么通过OSI网络模型怎么通信的?

1、首先计算机A发送一条qq信息“你好”

2、这条信息通过OSI网络模型的一层层的封装(每一层有自己特定的功能)到最下面一层,然后通过网络光纤传到计算机B的最下面一层

3、计算机B通过OSI网络模型一层层的解封装,一直到最后应用层显示出信息“你好”

具体流程如下:

 

 

 

三、网络通信的三要素

1、IP地址:用来标识网络上一台独立主机

2、端口:

      1)用于标识进程的逻辑地址,不通的进程有不同的端口 

      2)要将数据发送到对方指定的应用程序,为了标识这些应用程序,所以用数字来标识这些应用程序,为了方便称呼这些数字就叫做端口

3.传输协议:

      1)TCP:需要通过三次握手进行一个可靠的连接,可靠协议,效率低

     2)UDP协议:不需要面向连接,不可靠协议,传输速度快

四、socket编程

概念:应用程序两端通过“套接字”向网络发送请求或应用网络请求

其中应用程序两端指的是客户端和服务器端

套接字指的是客户端的socket对象

 

五、socket通信流程

 

 

 1)首先服务端和客户端都通过socket模块的socket类创建socket对象,sk=socket.socket()

2)   服务器端绑定IP和端口  sk.bind(address)   ,其中address包括两部分,一个是服务器的IP地址,另一个是服务器端应用程序端口

3)服务器端监听设置端口可等待客户端连接数,sk.listen(count)  ,count是一个整数

4)服务器端等待客户端的连接,sk.accept()

5)  客户端连接服务器端建立通信通道,sk.connect(address),这个address也就是服务器端的那个address,连接成功后服务器端sk.accept()会得到一个元组形式的返回值,一个是客户端的socket对象,另一个是客户端的address(包括IP和端口),之后服务器和这个客户端之间的通信就是通过这个客户端的socket对象来进行通信的

6)客户端通过客户端的socket对象发送消息  sk.send(data)

7)服务器端通过这个客户端的socket对象接收消息,conn.recv(1024),其中conn代指的就是客户端的socket对象,1024是每次可接收数据的字节数

8)服务器端通过这个客户端的socket对象发送消息返回给客户端conn.send(data)

9)  客户端接收服务器返回的消息,sk.recv(1024)

10)客户端关闭socket通道

11)服务器端关闭socket通道

 

注意:服务器端与客户端建立正确连接哦,两边的通信都是通过客户端的那个通道conn(socket对象)来进行通信的

 

六、socket对象创建

sk=socket.socket()

socket()主要有两个参数,一个是family,一个是type,其中family用来,type用来表示使用什么协议来进行通信

family有三个值:

1)family=AF_INET:默认是这个参数,表示使用TCP/IPv4版本的协议

2)family=AF_INET6:表示使用TCP/IPv6版本的协议

3)family=AF_UNIX:表示的是建立unix不同进程之间的通信

Type主要有两个值:

1)type = SOCK_STREAM:表示基于TCP传输方式传输,默认是这个参数

2)type = SOCK_DGRAM:表示基于UDP传输方式传输

 

七、常用相关方法介绍

sk.bind(address)

  #s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。

sk.listen(backlog)

  #开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。

       #backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5
       #这个值不能无限大,因为要在内核中维护连接队列

sk.setblocking(bool)

  #是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。

sk.accept()

  #接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。

  #接收TCP 客户的连接(阻塞式)等待连接的到来

sk.connect(address)

  #连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

sk.connect_ex(address)

  #同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061

sk.close()

  #关闭套接字

sk.recv(bufsize[,flag])

  #接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。

sk.recvfrom(bufsize[.flag])

  #与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

sk.send(string[,flag])

  #将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。

sk.sendall(string[,flag])

  #将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。

       #内部通过递归调用send,将所有内容发送出去。

sk.sendto(string[,flag],address)

  #将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。

sk.settimeout(timeout)

  #设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )

sk.getpeername()

  #返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

sk.getsockname()

  #返回套接字自己的地址。通常是一个元组(ipaddr,port)

sk.fileno()

  #套接字的文件描述符

 

八、相关案例

1、最简单的socket通信

server端:

import socket
sk=socket.socket()
print(sk)
address=("127.0.0.1",8000)
sk.bind(address)
sk.listen(3)
#conn=sk.accept()
#print(conn)

conn,addr=sk.accept() #等待客户端连接,会得到一个元组形式的返回值,conn是客户端的socket对象,addr是客户端的IP和端口

# inp=input(">>>>>>")
# conn.send(bytes(inp,"utf-8")) #recv和send方法都只能接收bytes类型数据

data=conn.recv(1024) #接收客户端请求,接收的内容为bytes类型
print(str(data,"utf-8"))

 

client端:

import socket
sk=socket.socket()
print(sk) #客户端的socket对象
address=("127.0.0.1",8000) #服务端的IP地址和端口号
sk.connect(address) #连接服务端,通过端口和IP地址可以找到并连接服务端
# data=sk.recv(1024) #1024最大可 接收字节数,通用就写1024
# print(str(data,"utf-8")) #recv和send方法都只能接收bytes类型数据,所以data拿到的是bytes类型的数据,需要通过str方法进行加密编程str进行打印
sk.send(bytes("余额","utf-8"))
sk.close()

2、不间断通信

server端:

import socket
sk=socket.socket()
addrs=("127.0.0.1",8001)
sk.bind(addrs)
sk.listen(3)
print("waiting......")
while 2:
conn,addr=sk.accept()
print(addr)
while 1:
try:
data=conn.recv(1024)
except Exception as e:
break
if not data:
break
print(str(data,"utf-8"))
inp=input("正在输入...")
conn.send(bytes(inp,"utf-8"))
conn.close()

client端:

import socket
sk=socket.socket()
address=("127.0.0.1",8001)
sk.connect(address)
while True:
inp=input("please input your name>>>>")
if inp=="exit":
break
sk.send(bytes(inp, "utf-8"))
data1=sk.recv(1024)
print(str(data1,"utf-8"))
sk.close()

3、shell命令执行

server端:

import socket
import subprocess #subprocess 模块:启动一个新进程,并连接到它们的输入/输出/错误管道,从而获取返回值
sk=socket.socket() #创建socket对象
address=("127.0.0.1",8002) #服务器IP和端口
sk.bind(address) #给socket对象绑定IP地址和端口
sk.listen(3) #监听设置sk端口等待客户端的请求数
print("waiting......")


while 1: #这个循环主要用来可以和多个客户端进行通信
conn,addr=sk.accept() #等待客户端发送请求进行连接,会得到一个元组形式的返回值,conn是客户端的socket对象,addr是客户端的IP和端口
while 1: #这个循环主要用来可以不断进行收信息和发信息
try:
data=conn.recv(1024) #接收客户端发送过来的内容,内容为bytes类型的,1024表示一次最多可接收内容字节数
except Exception as e:break
if not data:break
print("执行命令为:%s",str(data,"utf-8")) #将客户端发送过来的内容转化为字符串进行打印
obj = subprocess.Popen(str(data,"utf-8"), shell=True, stdout=subprocess.PIPE)#创建一个shell命令子进程执行客户端发送过来的命令,并将结果通过管道输出到obj对象
cmd_result=obj.stdout.read() #执行subprocess.Popen的结果,cmd_result是bytes类型
result_len=str(len(cmd_result)) #获取执行命令后的结果的长度,将int类型转化为str类型
print(result_len)
conn.send(bytes(result_len,"utf-8")) #发送命令结果的长度大小,转化为bytes类型进行发送
conn.send(cmd_result) #将命令的执行结果发送给客户端

#注意:两个send在一起的时候可能会有粘包现象,所以这个时候可以再两个send之间加一个recv进行隔断,同时客户端对应位置也要添加对应的send语句
conn.close()

client端:

import socket
sk=socket.socket() #创建socket对象
addr=("127.0.0.1",8002) #服务器IP和端口
sk.connect(addr) #连接服务器

while True:
inp=input("请输入命令>>>")
if inp=="exit":break
sk.send(bytes(inp,"utf-8")) #像服务器发送内容,发送的内容需要是bytes类型
result_len=int(str(sk.recv(1024),"utf-8")) #接收命令执行后结果的长度,bytes先转str类型后再转化为int类型进行显示
print("数据长度为:%s"% result_len)#打印结果长度大小
data=bytes() #定义初始存放服务器发送回来的数据变量值为0,因为接收的是bytes类型数据,所以就是ytes(),也就是0字节
while len(data)!=result_len: #判断接收到的内容长度是否等于服务器返回的长度,如果不一样就继续接收,如果一样就是完成接收并停止
rec=sk.recv(1024)
data+=rec#接收服务器返回的内容,内容为bytes类型的
print(str(data,"gbk")) #将服务器返回的bytes类型的数据转化成gbk(采用的windows系统的gbk编码)进行打印
sk.close()

4、文件上传

server端:

import socket
import os
import subprocess #subprocess 模块:启动一个新进程,并连接到它们的输入/输出/错误管道,从而获取返回值
sk=socket.socket() #创建socket对象
address=("127.0.0.1",8002) #服务器IP和端口
sk.bind(address) #给socket对象绑定IP地址和端口
sk.listen(3)
base_dir=os.path.dirname(os.path.abspath(__file__))##返回当前文件路径的目录(即去掉文件名)
print("waiting......")

while 1: #这个循环主要用来可以和多个客户端进行通信
conn,addr=sk.accept() #等待客户端发送请求进行连接,会得到一个元组形式的返回值,conn是客户端的socket对象,addr是客户端的IP和端口
while 1: #这个循环主要用来可以不断进行收信息和发信息
data=conn.recv(1024) #接收客户端发送的文件信息数据
cmd,filename,file_size=str(data,"utf8").split(" ") #用|分割数据,拿到三部分对应部分内容:命令、文件名、文件大小
path=os.path.join(base_dir,"fileupload",filename) #通过字符串拼接:定义上传文件存放路径
file_size=int(file_size) #将拿到的文件大小str数据类型转为int数据类型
has_recvsize=0 #定义初始收到文件大小
f=open(path,"wb") #打开文件
while has_recvsize!=file_size: #判断文件是否接收完成,没有接收完成则循环进行接收
data=conn.recv(1024) #接收客户端发送的文件内容
f.write(data) #写入文件
has_recvsize+=len(data) #计算已接收文件大小
f.close() #关闭文件
sk.close()

client端:

import socket
import os
sk=socket.socket() #创建socket对象
addr=("127.0.0.1",8002) #服务器IP和端口
sk.connect(addr) #连接服务器
#print(os.path.abspath(__file__)) #当前文件的绝对路径 F:\PycharmProjects\week1\20201109\uploadFile_client.py
base_dir=os.path.dirname(os.path.abspath(__file__)) #返回当前文件路径的目录(即去掉文件名) F:\PycharmProjects\week1\20201109

while True:
inp=input("请输入命令>>>") #假设输入命令的格式为:post|11.png
cmd,path=inp.split(" ") #拿到命令的两部分内容
path=os.path.join(base_dir,path) #路径拼接得到要上传文件的绝对路径

filename=os.path.basename(path) #返回path最后面的文件名
#file_size=os.stat(path).st_size #取到要上传的文件的大小
file_size=os.path.getsize(path) #获取文件的大小

file_info="%s %s %s" %(cmd,filename,file_size) #得到传送的文件的信息:命令、文件名、文件大小
sk.sendall(bytes(file_info,"utf8")) #发送文件信息到服务器
has_sendsize=0 #定义初始已发送文件大小
f=open(path,"rb") #打开文件
while has_sendsize!=file_size: #判断文件是否已发送完成,没有发送完成则循环发送
data=f.read(1024) #读取文件,每次读取1024个字节
sk.send(data) #发送文件内容
has_sendsize+=len(data) #统计已发送文件大小
f.close() #关闭文件
print("上传成功!")
sk.close()

 

posted @ 2020-11-16 10:17  机智的老猫咪  阅读(131)  评论(0编辑  收藏  举报