day(socket模块)

2022/4/16 6:23:59

本文主要是介绍day(socket模块),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

今日内容概要:

昨日内容回顾:

互联网协议:
 实现了物理链接介质之后还需要遵循一些公共的协议才能正常交互
OSI:七层协议或者说是五层协议:
    分别是:应用层、表示层、会话层、传输层、网络层、数据链路层、物理链接层
    五层的话就是:应用层、传输层、网络层、数据链路层、物理链接层
    1.物理连接层:
    确保通信的前提>>>彼此互联
    2.数据链路层:
    规定了电信号的分组方式
    (以太网协议:计算机出产必须有一块网卡 并且上面有一串数字 类似于身份证号)这一串数字叫 mac地址
    # 局域网内可以直接基于mac地址传输数据 不能跨局域网
    3.网络层
    IP协议:IP地址可以跨局域网
    IPV4
    	十进制
      	0.0.0.0
        255.255.255.255
    IPV6
    	十六进制
  # IP地址能够唯一标识全世界独一无二的一台接入互联网的计算机
4。传输层
TCP与UDP协议
PORT协议:端口号可区分同一台计算机上面不同的应用程序
0~65535
    	0~1024:一般是系统需要使用的默认端口号
      1024~8000:常见软件端口号(酷狗音乐默认端口号5000...)
      8000之后:推荐我们使用
  端口号是动态分配的 并且同一时间同一台计算机端口号不能冲突
IP+PORT:能够识别标识全世界独一无二一台接入互联网的计算机上面的某一个应用
ps:我们使用的网址其实内部本质都是IP+PORT
	127.0.0.1:8080
	之所以使用网址就是为了方便用户记忆
  5.应用层
	这一层协议非常的多 每个公司甚至都可以自己自定义
  HTTP、HTTPS、FTP...
1.TCP协议
	三次握手建链接
  	链接是两条通道 每条数据都是单向流动
  四次挥手断链接
  	链接是两条通道 彼此断开不是同步进行
    
2.UDP协议
	没有通道 直接发送消息
"""
TCP类似于打电话
UDP类似于发短信
"""

面试题
	TCP为什么比UDP传输数据安全
  	反馈机制

今日内容详细:

socket简介:socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭),
socket套接字是一门技术
  socket模块>>>:提供了快捷方式 不需要自己处理每一层
名 称 描 述
服务端 服务器套接字方法
s.bind(ADDR) 将地址(主机名、端口号对)绑定到套接字上
s.listen([backlog]) 设置并启动 TCP 监听器,如果指定backlog,则必须至少为0(如果低于0,则设置为0);
s.accept() 被动接受 TCP 客户端连接,一直等待直到连接到达(阻塞)
客户端 客户端套接字方法
s.connect() 主动发起 TCP 服务器连接
s.connect_ex() connect()的扩展版本,此时会以错误码的形式返回问题,而不是抛出一个异常
普通通用 普通的套接字方法
s.recv() 接收 TCP 消息
s.recv_into() 接收 TCP 消息到指定的缓冲区
s.send() 发送 TCP 消息
s.sendall() 完整地发送 TCP 消息
s.recvfrom() 接收 UDP 消息
s.recvfrom_into() 接收 UDP 消息到指定的缓冲区
s.sendto() 发送 UDP 消息
s.getpeername() 连接到套接字(TCP)的远程地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回给定套接字选项的值
s.setsockopt() 设置给定套接字选项的值
s.shutdown() 关闭连接
s.close() 关闭套接字
s.detach() 在未关闭文件描述符的情况下关闭套接字,返回文件描述符
s.ioctl() 控制套接字的模式(仅支持 Windows)
阻塞 面向阻塞的套接字方法
s.setblocking() 设置套接字的阻塞或非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 获取阻塞套接字操作的超时时间
文件方法 面向文件的套接字方法
s.fileno() 套接字的文件描述符
s.makefile() 创建与套接字关联的文件对象
属性 数据属性
s.family 套接字家族
s.type 套接字类型
s.proto 套接字协议

socket模块:

Socket的实例化:
#scoket(family,type[,protocal])
第二个参数表示Socket类型,这里使用的值有三个:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW。
family参数表示地址族,常用的协议族有:AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE等,默认值为AF_INET,通常使用这个即可。
可靠连接:SOCK_STREAM是TCP类型,保证数据的顺序和可靠性
非可靠连接:SOCK_DGRAW是UDP类型不保证数据接收的顺序,非可靠连接;
SOCK_RAW是原始类型,允许对底层协议如IP、ICMP进行直接访问,基本不会用到。
默认值是第一个。
第三个参数是指定协议,这个是可选的,通常赋值为0,由系统选择。
例如想初始化一个TCP类型的Socket,语句如下:
x = socket.socket()

这条语句相当于:
x = socket.socket(sokcet.AF_INET, socket.SOCK_STREAM)
因为这里都是默认值所以可以省略掉
而如果想初始化一个UDP类型,则语句如下:
x = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

socket常用函数:

bind():

socket常用函数:
# 服务端
import socket

x = socket.socket()

# 这条语句相当于:
# x = socket.socket(sokcet.AF_INET, socket.SOCK_STREAM)
# 因为这里都是默认值所以可以省略掉
# 而如果想初始化一个UDP类型,则语句如下:
x = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 当成功初始化一个 Socket之后,就可以使用Socket类所提供的函数。
# Socket类中主要提供如下所示常用的函数:\\

# bind():这个函数由服务端Socket调用,会将之前创建Socker与指定的IP地址和端口进行绑定。
# 如果之前使用了AF_INET(默认值为AF_INET,通常使用这个即可。)初始化Socket,那么这里可以使用元组(host, port) 的形式表示地址。
# 例如,要将刚才创建的Socket套接字绑定到本机的2345端口,就可以使用如下语句:
x.bind(("127.0.0.1",234511))
127.0.0.1是计算机的本地回环地址 只有当前计算机本身可以访问

listen():

# listen():这个函数用于在使用TCP的服务端开启监听模式。这个函数可以使用一个参数来指定可以挂起的最大连接数量。这个参数的值最小为1,一般设置为5。
# 例如,要在服务端开启一个监听, 可以使用如下语句:
x.listen(5)

accept()

# accept():这个函数用于在使用TCP的服务端接收连接,一般是阻塞态。
# 接受TCP连接并返回(conn,address),
# 其中,conn 是新的套接字对象,可以用来接收和发送数据; address是连接客户端的地址。
# 查看源码有两个返回值
conn, address = x.accept()
# 以上三个函数是用于服务端的Socket 函数,listen和accept对应TCP三次握手服务端的两个状态

connect()

# 这个函数用于在使用TCP的客户端去连接服务端时使用,使用的参数是一个元组,
# 形式为(hostname, port)
# 在客户端程序初始化了一个Socker 之后,
# 就可以使用这个函数去连接到服务端。例如,现在要连接本机的2345端口,可以使用如下语句:
x.connect (("127.0.0.1",2345))

send():

sendll():

# send():这个函数用于在使用TCP时发送数据,完整的形式为send(string[,flag])利用这个函数可以将string代表的数据发送到已经连接的Socket,返回值是发送字节的数量。但是可能未将指定的内容全部发送。
# sendall():这个函数与send()相类似,也是用于在使用TCP时发送数据,完整的形式为sendall(string[,falg]),与send()的区别是完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
# 例如,使用这个函数发送一段字符到Socket,可以使用如下语句:

x.sendall(bytes("Hello. My Friend! ", encoding="utf-8"))

recv():

recv():这个两数用于在使用TCP时接收数据,完整的形式为recv(bufsize[,flag]),接收Soker的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量,flag这个参数一般不会使用。
例如,通过这个函数接收一段长度为1024的字符Socket,可以使用如下语句:
x.recv(1024)

cs架构的软件无论是在编写还是运行
	都应该先考虑服务端 
  	有了店运营了才可以接待客人
    
# 服务端
import socket


server = socket.socket()  # 买手机
"""
通过查看源码得知 
括号内不写参数默认就是基于网络的遵循TCP协议的套接字
"""
server.bind(('127.0.0.1', 8080))  # 插电话卡
"""
服务端应该具备的特征
    固定的地址
    ...  
127.0.0.1是计算机的本地回环地址 只有当前计算机本身可以访问
"""
server.listen(5)  # 开机
"""
半连接池(暂且忽略 先直接写 后面讲)
"""
sock, addr = server.accept()  # 等待并接听电话  没有人来就原地等待(程序阻塞)
"""
listen和accept对应TCP三次握手服务端的两个状态
"""
print(addr)  # 客户端的地址
data = sock.recv(1024)  # 听别人说话
print(data.decode('utf8'))
sock.send('你好啊'.encode('utf8'))  # 回复别人说的话
"""
recv和send接收和发送的都是bytes类型的数据
"""
sock.close()  # 挂电话
server.close()  # 关机

# 客户端
import socket


client = socket.socket()  # 产生一个socket对象
client.connect(('127.0.0.1', 8080))  # 根据服务端的地址链接

client.send(b'hello sweet heart!!!')  # 给服务端发送消息
data = client.recv(1024)  # 接收服务端回复的消息
print(data.decode('utf8'))

client.close()  # 关闭客户端

###########################
服务端与客户端首次交互
	一边是recv那么另一边必须是send  两边不能相同 否则就'冷战'了
###########################

使用socket模块编写简单的客户端服务端 ,通信循环

1.先解决消息固定的问题
	利用input获取用户输入
2.再解决通信循环的问题
	将双方用于数据交互的代码循环起来
# 服务端
# 1.导入 socket
import socket
# 2.产生一个socket对象。。# 括号内不写参数默认就是基于网络的遵循TCP协议的套接字
server1 = socket.socket()
# 3.根据服务端的地址链接,,两个参数一个是是计算机的本地回环地址一个是端口号
#  bind():这个函数由服务端Socket调用,会将之前创建Socker与指定的IP地址和端口进行绑定。
server1.bind(('127.0.0.1', 8080))
# 4.listen这个函数可以使用一个参数来指定可以挂起的最大连接数量。这个参数的值最小为1,一般设置为5。
server1.listen(5)
# 5.这个函数用于在使用TCP的服务端接收连接,一般是阻塞态。
sock, addr = server1.accept()
# 6.addr是连接客户端的地址。
print(addr)
while True:
    # 7.接收Soker的数据。数据以字符串形式返回,
    # 通过这个函数接收一段长度为1024的字符Socket, 可以使用如下语句:
    data = sock.recv(1024)
    print(data.decode('utf8'))
    msg = input('请回复>>>>')
    # 8利用send这个函数可以将string代表的数据发送到已经连接的Socket,返回值是发送字节的数量。
    # 9.编码发送
    sock.send(msg.encode('utf8'))
#     10结束进程
sock.close()
# 直接关闭
server1.close()

#recv和send接收和发送的都是bytes类型的数据

# 客户端
# 1.导入 socket
import socket
# 2.产生socket对象
client1 = socket.socket()
# 3.这个函数用于在使用TCP的客户端去连接服务端时使用在客户端程序初始化了一个Socker 之后,
#  就可以使用这个函数去连接到服务端。# 根据服务端的地址链接
client1.connect(('127.0.0.1', 8080))
while True:

    msg = input('请输入你要发送的消息')
    # 4.解码回复 给服务端发送消息
    client1.send(msg.encode('utf8'))
    #  接收服务端回复的消息
    data = client1.recv(1024)

    print(data.decode('utf8'))

client1.close()# 关闭客户端
###########################
服务端与客户端首次交互
	一边是recv那么另一边必须是send  两边不能相同 否则就'冷战'了
###########################

代码优化及链接循环

1.发送消息不能为空
if len():判断输入是否为空
	统计长度并判断即可
2.反复重启服务端可能会报错>>>:address in use
  这个错在苹果电脑报的频繁 windows频率较少
  from socket import SOL_SOCKET,SO_REUSEADDR
  server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 在bind前加
3.链接循环
  """
  如果是windows 客户端异常退出之后服务端会直接报错
  	处理方式
  		异常处理
  如果是mac或linux 服务端会接收到一个空消息
  	处理方式
  		len判断
  """
  客户端如果异常断开 服务端代码应该重新回到accept等待新的客人

# 目前我们的服务端只能实现一次服务一个人 不能做到同事服务多个 学了并发才可以实现

半连接池

listen(5)
# py文件默认同一时间只能运行一次 如果想单独分开运行多次

# 半连接池
	设置的最大等待人数  >>>:  节省资源 提高效率

黏包问题

# TCP协议的特点
	会将数据量比较小并且时间间隔比较短的数据整合到一起发送
  并且还会受制于recv括号内的数字大小(核心问题!!!)
  流式协议:跟水流一样不间断
问题产生的原因其实是因为recv括号内我们不知道即将要接收的数据到底多大
如果每次接收的数据我们都能够精确的知道它的大小 那么肯定不会出现黏包

解决黏包问题

方向:精准获取数据的大小
  
# struct模块
	import struct

  data1 = 'hello world!'
  print(len(data1))  # 12
  res1 = struct.pack('i', len(data1))  # 第一个参数是格式 写i就可以了
  print(len(res1))  # 4
  ret1 = struct.unpack('i', res1)
  print(ret1)  # (12,)


  data2 = 'hello baby baby baby baby baby baby baby baby'
  print(len(data2))  # 45
  res2 = struct.pack('i', len(data2))
  print(len(res2))  # 4
  ret2 = struct.unpack('i', res2)
  print(ret2)  # (45,)


  """
  pack可以将任意长度的数字打包成固定长度
  unpack可以将固定长度的数字解包成打包之前数据真实的长度


  思路:
      1.先将真实数据打包成固定长度的包
      2.将固定长度的包先发给对方
      3.对方接收到包之后再解包获取真实数据长度
      4.接收真实数据长度
  """


这篇关于day(socket模块)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程