1.套接字概述

1.套接概述: 套接是进行网络通信的一种手段(socket)

2.套接字分类:
        流式套接字(SOCK_STREAM): 传输层基于tcp协议进行通信

        数据报套接字(SOCK_DEGAM): 传输层基于udp协议进行通信

        原始套接字(SOCK_RAW): 访问底层协议的套接字

3.TCP与UDP通讯模型流程图: https://www.processon.com/view/link/5ef43bfd1e0853263742690b

4.套接字属性和方法

import socket

s = socket.socket()  # 默认会创建流式套接字

# 功能: 获取套接字的描述符
# 描述符: 每一个IO操作系统都会分配一个不同的整数与之对应,该整数极为此IO操作的描述符
s.fileno() #
print(s.fileon()) # # 获取套接字类型
s.type # <SocketKind.SOCK_STREAM: 1>
print(s.type) # SocketKind.SOCK_STREAM # 获取套接字绑定的地址
s.getsockname() # ('0.0.0.0', 0)
s.bind(("127.0.0.1", 7890))
print(s.getsockname()) # ('127.0.0.1', 7890) # 使用accept生成的套接字调用,获取该套接字对应的客户端的地址,在一个服务器有多个客户端连接时常会用到这个方法
s.listen(128)
conn, addr = s.accept() # 阻塞时需要找一个用户连接
conn.getpeername() # ('127.0.0.1', 53519)
print(conn.getpeername()) # ('127.0.0.1', 53519) # s.setsockopt(level, optname, value) 设置套接字选项
# 参数 level: 定义的选项类型,常用选项(IPPROTO_TCP,IPPROTO_IP,SOL_SOCKET)
# 参数 optname: 根据level选项确定的子选项
# 参数 value: 根据子选项设置的值
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 设置端口重用
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # 设置套接字允许接收广播 # s.getsockopt(level, optname) 获取套接字选项
# 参数 level: 定义的选项类型,常用选项(IPPROTO_TCP,IPPROTO_IP,SOL_SOCKET)
# 参数 optname: 根据level选项确定的子选项
# 返回值: 返回根据子选项设置的值
s.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) # # 面向锁的套接字方法
s.setblocking() # 设置套接字阻塞与非阻塞模式,参数默认为True表示阻塞
s.settimeout() # 设置阻塞套接字操作的超时时间
s.gettimeout() # 获取阻塞套接字操作的超时时间 # 面向文件的套接字函数
s.fileno() # 套接字的文件描述符
s.makefile() # 创建一个与该套接字相关的文件

2.TCP流式套接字

1.TCP服务端流程

1.创建套接字
            sockfd = socket(socket_family=AF_INET, socket_type=SOCK_STERAM, proto=0)  # 创建套接字并返回套接字对象

            参数:

                socket_family: 选择地址族种类AF_INET(UNIX)

                socket_type: 套接字类型 流式(SOCK_STREAM),数据报(SOCK_DGRAM)

                proto: 子协议类型,tcp和udp都没有用到自协议因此默认为0

2.绑定IP和端口号

            sockfd.bind(("", 7890))  # 绑定IP和端口号

            参数: 类型为元组('127.0.0.1', 7890)  # 第一项是字符串形式的IP,第二项是端口号

3.让套接字具有监听功能

            sockfd.listen(n)  # 使套接字变为监听套接字,同时创建监听队列

            参数: n监听队列大小,一般设置为128

4.等待客户端连接

            new_socket, client_addr = socket.accept()  # 阻塞等待客户端连接

            返回值: 类型为元祖(new_socket, client_addr)

                第一项: 返回一个新的套接字用来和客户端通信

                第二项: 返回连接的客户端的地址

5.消息的收发

            接收: new_socket.recv(buffer)  # 接收消息

                参数: 一次接收消息的大小, 即一次收多少字节

                返回值: 接收到的内容, 类型为字节

            发送: new_socket.send(data)  # 发送消息,当没有接收端的时候send操作会导致管道破裂(broken pipe)

                参数: 发送的内容(bytes), 类型为字节

                返回值: 发送了多少个字节

6.关闭套接字

            new_socket.close()  # 关闭为客户端服务的套接字

            sockfd.close()  # 关闭监听套接字

2.TCP客户端流程

1.创建流式套接字: sockfd = socket(socket_family=AF_INET, socket_type=SOCK_STREAM, proto=0)

2.发起连接请求

        sockfd.connect(('127.0.0.1', 7890))  # 发起连接请求

        参数(元组): 第一项是服务器的IP,第二项是服务器的PORT

3.收发消息: sockfd.recv() sockfd.send()

4.关闭套接字: sockfd.close()

3.TCP服务端-通讯示例

import socket

def main():
# 创建数据流套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口重用
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定端口
tcp_server_socket.bind(("", 7890))
# 让默认的套接字由主动变为被动监听 listen
tcp_server_socket.listen(128) # 循环目的: 多次调用accept等待客户端连接,为多个客服端服务
while True:
print("等待一个新的客户端的到来...")
new_client_socket, client_addr = tcp_server_socket.accept()
print("客户端' %s '已经到来" % str(client_addr))
# 循环目的: 为同一个客服端服务多次
while True:
# 接收客户端发送过来的请求
recv_data = new_client_socket.recv(1024) # recv解堵塞的两种方式: 1.客户端发送过来数据,2.客户端调用了close
if recv_data:
print("客户端' %s '发送过来的请求是: %s" % (str(client_addr), recv_data.decode("utf-8")))
# 回送数据给客户端表示响应客户端的请求
send_data = "----ok----Request accepted"
new_client_socket.send(send_data.encode("utf-8"))
else:
break # 关闭accept返回的套接字
new_client_socket.close()
print("已经为 %s 客户端已经服务完毕" % str(client_addr))
# 关闭监听套接字
tcp_server_socket.close() if __name__ == "__main__":
main()

4.TCP客户端-通讯示例

import socket

def main():
# 创建数据流套接字
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
server_ip = input("请输入要连接的服务器的ip:")
serve_port = int(input("请输入要连接的服务器的port:"))
server_addr = (server_ip, serve_port)
tcp_client_socket.connect(server_addr) # 连接失败会报错
# s = tcp_client_socket.connect_ex(server_addr) # 有返回值,如果连接失败不会报错,会返回错误的编码
# 发送数据
send_data = input("请输入要发生的数据:")
tcp_client_socket.send(send_data.encode("utf-8"))
# 接收服务器发送过来的数据
recv_data = tcp_client_socket.recv(1024)
print("接收到的数据为:%s" % recv_data.decode("utf-8"))
# 关闭套接字
tcp_client_socket.close() if __name__ == "__main__":
main()

5.TCP服务端-文件下载示例

import socket

def send_file_client(nwe_client_socket, client_addr):
# 1.接收客户端发送过来的需要下载的文件名
file_name = nwe_client_socket.recv(1024).decode("utf-8")
print("客户端 %s 要下载的文件是:%s" % (str(client_addr), file_name))
# 2.打开文件读取数据
file_content = None
try:
f = open(file_name, "rb")
file_content = f.read()
f.close()
except Exception as ret:
print("没有要下载的文件%s" % file_name)
# 3.发送文件的数据给客户端
if file_content:
nwe_client_socket.send(file_content) def main():
# 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口重用
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定本地信息
local_addr = ("", 7890)
tcp_server_socket.bind(local_addr)
# 开启监听
tcp_server_socket.listen(128)
while True:
# 等待用户连接
nwe_client_socket, client_addr = tcp_server_socket.accept()
# 调用发送文件函数,完成为客户端服务
send_file_client(nwe_client_socket, client_addr) # 关闭套接字
nwe_client_socket.close()
# 关闭监听套接字
tcp_server_socket.close() if __name__ == "__main__":
main()

6.TCP客户端-文件下载示例

import socket

def main():
# 创建套接字
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
dest_ip = input("请输入下载服务器的ip:")
dest_port = int(input("请输入下载服务器的port:"))
dest_addr = (dest_ip, dest_port)
tcp_client_socket.connect(dest_addr)
# 获取要下载的文件名
download_file_name = input("请输入要下载的文件名:")
# 发送要下载的文件名
tcp_client_socket.send(download_file_name.encode("utf-8"))
# 接收文件数据
recv_data = tcp_client_socket.recv(1024) # 一次接收1k数据
# 打开文件写入数据
if recv_data:
with open("[new]" + download_file_name, "wb") as f:
f.write(recv_data)
# 关闭套接字
tcp_client_socket.close() if __name__ == "__main__":
main()

7.TCP粘包现象

1.发送接收缓冲区
            发送和接收消息均放到缓存区再进行处理

            当recv接收消息一次接收不完的时候下次会继续接收,当recv阻塞时,如果客户端断开则recv立即返回空字符串

2.TCP粘包概述:

            1.TCP中数据以数据流的方式发送接收,每次发送的数据间没有边界,在接收时可能造成数据的粘连即为粘包

            2.合包机制造成数据混乱: nagle算法将多次连续发送且间隔较小的数据进行打包成一个数据传输

            3.拆包机制造成数据混乱: 在发送端因为受到数据链路层网卡的MTU限制,会将大的超过MTU限制的数据进行拆分成多个小的数据包进行传输

                当传输到目标主机的操作系统层时,会将多个小的数据包合并成原本的数据包

3.粘包如何处理方案:

            1.每次发送消息和结束位置加标志,每次收到消息按结束标准切割

            2.发送的消息添加结构描述,例如加一个包头,包头里记录 name:大小, password: 大小

            3.当连续发送的时每次发送有一个短暂延迟sleep(0.1)

4.tcp粘包参考链接: https://www.cnblogs.com/LY-C/p/9120992.html

8.TCP服务端-粘包现象验证

import socket

def main():
# 创建数据流套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定端口
tcp_server_socket.bind(("", 7890))
# 让默认的套接字由主动变为被动监听 listen
tcp_server_socket.listen(128)
# 等待客户端连接
new_client_socket, client_addr = tcp_server_socket.accept()
print("客户%s端已连接" % str(client_addr)) # 循环目的: 为同一个客服端服务多次
while True:
# 接收客户端发送过来的请求
recv_data = new_client_socket.recv(1024)
# recv解堵塞的两种方式: 1.客户端发送过来数据,2.客户端调用了close
if not recv_data:
break # 客户端是先遍历["Coco", "1355656****", "Coco@xxx.com", "xx省xx市xx区xxx"]列表,再分了4次发送用户信息的数据
# 但是此时服务端收到的数据却是连续的: Coco1355656****Coco@xxx.comxx省xx市xx区xxx
print("客户端' %s '发送过来的请求是: %s" % (str(client_addr), recv_data.decode("utf-8")))
# 回送数据给客户端表示响应客户端的请求
send_data = "----ok----Request accepted"
new_client_socket.send(send_data.encode("utf-8")) # 关闭accept返回的套接字
new_client_socket.close()
print("已经为 %s 客户端已经服务完毕" % str(client_addr))
# 关闭监听套接字
tcp_server_socket.close() if __name__ == "__main__":
main()

9.TCP客户端-粘包现象验证

import socket
import time def main():
# 创建数据流套接字
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
server_addr = ("", 7890)
tcp_client_socket.connect(server_addr)
# 发送数据
user_info = ["Coco", "1355656****", "Coco@xxx.com", "xx省xx市xx区xxx"]
for i in user_info:
tcp_client_socket.send(str(i).encode("utf-8"))
# 发送时添加延时可以解决粘包现象
# time.sleep(0.1)
# 接收服务器发送过来的数据
recv_data = tcp_client_socket.recv(1024)
print("接收到的数据为:%s" % recv_data.decode("utf-8"))
# 关闭套接字
tcp_client_socket.close() if __name__ == "__main__":
main()

10.TCP服务端-大文件上传

import socket
import json
import struct def main():
# 创建数据流套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口重用
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定端口
tcp_server_socket.bind(("", 7890))
# 让默认的套接字由主动变为被动监听 listen
tcp_server_socket.listen(128) # 循环目的: 多次调用accept等待客户端连接,为多个客服端服务
while True:
print("等待一个新的客户端的到来...")
new_client_socket, client_addr = tcp_server_socket.accept()
print("客户端' %s '已经到来" % str(client_addr)) # 接收客户端发送过来的请求
b_len_dic = new_client_socket.recv(4) print(b_len_dic)
len_dic = struct.unpack("i", b_len_dic)[0] # unpack得到一个元祖,取下标0的位置获取到int类型的字典长度
recv_data = new_client_socket.recv(len_dic).decode("utf-8") # new_client_socket.send(b"OK") # 向客户端发送文件准备就绪标识,同时也避免接收数据过快产生粘包 recv_dic = json.loads(recv_data)
if recv_dic["opt"] == "upload":
filename = "副本" + recv_dic["filename"]
with open(filename, "ab") as f:
while recv_dic["filesize"]:
content = new_client_socket.recv(1024)
f.write(content)
recv_dic["filesize"] -= len(content)
elif recv_dic["opt"] == "download":
pass # 关闭accept返回的套接字
new_client_socket.close()
print("已经为 %s 客户端已经服务完毕" % str(client_addr))
# 关闭监听套接字
tcp_server_socket.close() if __name__ == "__main__":
main()

11.TCP客户端-大文件上传

import socket
import os
import json
import struct def main():
# 创建数据流套接字
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
server_ip = input("请输入要连接的服务器的ip:")
serve_port = int(input("请输入要连接的服务器的port:"))
server_addr = (server_ip, serve_port)
tcp_client_socket.connect(server_addr) # 连接失败会报错
# s = tcp_client_socket.connect_ex(server_addr) # 有返回值,如果连接失败不会报错,会返回错误的编码 menu = {"": "upload", "": "download"}
for i in menu:
print(i, menu[i])
num = input("请输入功能选项: ")
if num == "":
send_dic = {"opt": menu.get(num), "filename": None, "filesize": None}
file_path = input("请输入一个绝对路径: ") # 要上传文件的绝对路径
filename = os.path.basename(file_path) # 获取要上传文件的文件名
filesize = os.path.getsize(file_path) # 获取文件大小 send_dic["filename"] = filename
send_dic["filesize"] = filesize
send_data = json.dumps(send_dic) len_dic = len(send_data) # 获取字典长度,是一个int数据类型,可能是30,也可能是120
b_len_dic = struct.pack("i", len_dic) # 加字典长度打包成一个4位的bytes类型
# 将bytes类型的字典长度 + bytes类型的字典内容,一起发送给服务器
tcp_client_socket.send(b_len_dic + send_data.encode("utf-8")) # tcp_client_socket.recv(1024) # 1.向服务器确认是否可以上传文件;2.避免与下列代码的send过快造成粘包 with open(file_path, "rb") as f:
while filesize:
content = f.read(1024)
tcp_client_socket.send(content)
filesize -= len(content) # 发送数据
tcp_client_socket.send(send_data.encode("utf-8"))
elif num == "":
pass # 关闭套接字
tcp_client_socket.close() if __name__ == "__main__":
main()

12.TCP服务端-身份加密验证

import socket
import os
import hmac def main():
# 创建数据流套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口重用
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定端口
tcp_server_socket.bind(("", 7890))
# 让默认的套接字由主动变为被动监听 listen
tcp_server_socket.listen(128) # 循环目的: 多次调用accept等待客户端连接,为多个客服端服务
while True:
print("等待一个新的客户端的到来...")
new_client_socket, client_addr = tcp_server_socket.accept()
print("客户端' %s '已经到来" % str(client_addr)) sor = b"hmy" # 服务器加盐
r_str = os.urandom(16) # 随机一个16位长度的bytes类型数据
new_client_socket.send(r_str) # md5加密
md5_obj = hmac.new(sor, r_str)
result = md5_obj.digest() # 接收客户端发送过来的密文与服务器的密文对比
recv_msg = new_client_socket.recv(1024)
if recv_msg == result:
new_client_socket.send(b"success")
else:
new_client_socket.send(b"failed") # 关闭accept返回的套接字
new_client_socket.close()
print("已经为 %s 客户端已经服务完毕" % str(client_addr))
# 关闭监听套接字
tcp_server_socket.close() if __name__ == "__main__":
main()

13.TCP客户端-身份加密验证

import socket
import hmac def main():
# 创建数据流套接字
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
server_ip = input("请输入要连接的服务器的ip:")
serve_port = int(input("请输入要连接的服务器的port:"))
server_addr = (server_ip, serve_port)
tcp_client_socket.connect(server_addr) # 连接失败会报错
# s = tcp_client_socket.connect_ex(server_addr) # 有返回值,如果连接失败不会报错,会返回错误的编码 sor = b"hmy" # 客户端加盐
r_str = tcp_client_socket.recv(1024) # md5加密
md5_obj = hmac.new(sor, r_str)
result = md5_obj.digest()
# 向服务器发送验证密文
tcp_client_socket.send(result) # 接收验证结果
recv_msg = tcp_client_socket.recv(1024)
print(recv_msg) # 关闭套接字
tcp_client_socket.close() if __name__ == "__main__":
main()

14.TCP服务端-切换目录

import socket
import os def send_data(new_client_socket, path):
"""你给我一个目录,我把目录发给client"""
lis_dir = os.listdir(path)
str_dir = '--'.join(lis_dir)
new_client_socket.send(str_dir.encode('utf-8')) def main():
# 创建数据流套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口重用
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定端口
tcp_server_socket.bind(("", 7890))
# 让默认的套接字由主动变为被动监听 listen
tcp_server_socket.listen(128) # 循环目的: 多次调用accept等待客户端连接,为多个客服端服务
while True:
print("等待一个新的客户端的到来...")
new_client_socket, client_addr = tcp_server_socket.accept()
print("客户端' %s '已经到来" % str(client_addr)) abs_path = new_client_socket.recv(1024).decode('utf-8') # 获取用户输入的绝对路径
current_dir = abs_path + '/' # 以下再处理,都要根据当前路径去处理,无论是返回上一层,还是进入下一层
send_data(new_client_socket, current_dir) # 把用户输入的路径下的所有文件及文件夹返回给客户端 while True:
# 测试输入: /Users/tangxuecheng
cmd = new_client_socket.recv(1024).decode('utf-8')
if cmd == '..':
current_dir = current_dir.split('/')[:-2]
current_dir = '/'.join(current_dir) + '/'
# if 如果当前是根目录:
# 就返回给客户端告诉说没有上一层了
send_data(new_client_socket, current_dir)
else:
filename = cmd.split(' ')[1] # 获取用户输入的文件名字
current_dir += filename + '/' # 将文件名字添加到当前路径下,组成一个完整的新路径
if os.path.isdir(current_dir): # 如果客户输入的文件名字是一个文件夹
send_data(new_client_socket, current_dir)
else: # 如果不是一个文件夹
new_client_socket.send(b'not a folder') # 关闭accept返回的套接字
new_client_socket.close()
print("已经为 %s 客户端已经服务完毕" % str(client_addr))
# 关闭监听套接字
tcp_server_socket.close() if __name__ == "__main__":
main()

15.TCP客户端-切换目录

import socket

def main():
# 创建数据流套接字
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
server_ip = input("请输入要连接的服务器的ip:")
serve_port = int(input("请输入要连接的服务器的port:"))
server_addr = (server_ip, serve_port)
tcp_client_socket.connect(server_addr) # 连接失败会报错
# s = tcp_client_socket.connect_ex(server_addr) # 有返回值,如果连接失败不会报错,会返回错误的编码 # 测试输入: /Users/tangxuecheng
abs_path = input('请输入您的根目录:')
tcp_client_socket.send(abs_path.encode('utf-8'))
current_dir = tcp_client_socket.recv(1024).decode('utf-8')
print(current_dir.split('--')) while True:
cmd = input('请输入>>>')
# cd + 文件夹 ..
if cmd == '..':
tcp_client_socket.send(cmd.encode('utf-8'))
current_dir = tcp_client_socket.recv(1024).decode('utf-8')
print(current_dir.split('--'))
if cmd == 'cd':
filename = input('请输入一个文件夹名:')
tcp_client_socket.send((cmd + ' ' + filename).encode('utf-8'))
current_dir = tcp_client_socket.recv(1024).decode('utf-8')
print(current_dir.split('--')) # 关闭套接字
tcp_client_socket.close() if __name__ == "__main__":
main()

16.TCP应用-web静态服务器

# html文件夹网盘链接: https://pan.baidu.com/s/1wbZ92KLstff3GurLV_C0lg  密码: hp9o
# 下载完命令行解压后放到程序的同级路径下,解压命令: tar -zxvf 06_html.tar.gz -C ./
import socket def client_handle(client_socket):
# 接收客户端发送过来的请求数据
request = client_socket.recv(2048)
request_handles = request.splitlines()
for line in request_handles:
print(line)
try:
f = open("./html/index.html", "r")
except IOError:
# 找不到文件时返回404错误
response = "HTTP/1.1 404 not found\r\n"
response += "\r\n"
response += "sorry file not found"
else:
# 找到文件时读取文件内容
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
for line in f:
response += line
finally:
# 向浏览器返回信息
client_socket.send(response.encode())
# 关闭客户端套接字
client_socket.close() def main():
# 1.创建套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.设置端口重用
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 4.允许局域网内用户访问
local_addr = ("0.0.0.0", 7890)
tcp_server_socket.bind(local_addr)
# 5.监听
tcp_server_socket.listen(128)
# 5.循环服务
while True:
# 客户端连接
client_socket, client_addr = tcp_server_socket.accept()
# 为客户端服务
client_handle(client_socket) # 6.关闭监听套接字
tcp_server_socket.close() if __name__ == "__main__":
main()

3.UDP数据报套接字

1.UDP套接字使用流程

1.创建数据报套接字
            sockfd = socket(AF_INET,SOCK_DGRAM)

            参数: AF_INET表示ipv4,SOCK_DGRAM表示数据报套接字

2.绑定服务端地址: sockfd.bind("127.0.0.1", 7890)

3.收发消息

            接收消息(recvfrom)

                data, addr = sockfd.recvfrom(buffersize) # 一次接收一个数据包,如果是数据包一次没有接收完则会丢失没有接收的内容

                参数: 每次最多接收消息的大小(字节)

                返回值:

                    data: 接收到的消息

                    addr: 消息发送者的地址

            发送消息(sendto)

                sockfd.sendto(data, addr) # 发送消息

                参数:

                    data:要发送的消息

                    addr: 发送给某个主机的地址

                返回值: 发送消息的字节数

4.关闭套接字: sockfd.close()

2.UDP应用-发送消息

import socket

def main():
# 创建一个udp套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 指定接消息收方的地址
dest_addr = ("", 7788) while True:
# 从键盘上获取数据
send_data = input("请输入要发送的数据:")
# 如果输入的数据是exit则退出程序
if send_data.lower() == "exit":
break
# 使用套接字收发数据
# udp_socket.sendto(b"hello world!", ("169.254.119.158", 8080))
udp_socket.sendto(send_data.encode("utf-8"), dest_addr) # 关闭套接字
udp_socket.close() if __name__ == "__main__":
main()

3.UDP应用-接收消息

import socket

def main():
# 1.创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2.绑定本地信息
local_addr = ("", 7788)
udp_socket.bind(local_addr)
# 3.接收数据
while True:
recv_data = udp_socket.recvfrom(1024) # 1024表示本次接收的最大字节数
# 4.打印接收的数据
# recv_data这个变量中存储的是一个元祖(接收到的数据, (发送方的ip, port))
recv_msg = recv_data[0] # 第1个元素是对方发送的数据,类型为字节
recv_addr = recv_data[1] # 第2个元素是对方的ip和端口,类型为元祖
print(type(recv_msg), type(recv_addr)) # <class 'bytes'> <class 'tuple'> # 功能扩展: 含有敏感词汇消息在输出时进行颜色标识
color_dic = {"用户名": "\033[32m", "密码": "\033[33m", "验证": "\033[0;32;40m"}
msg = recv_msg.decode("utf-8")
for i in color_dic:
if i in msg:
color = color_dic[i]
print("%s%s:%s\033[0m" % (color, str(recv_addr), msg))
break
else:
print("%s:%s" % (str(recv_addr), msg)) # ('127.0.0.1', 61804):Tom你好吗?
# 5.关闭套接字
udp_socket.close() if __name__ == "__main__":
main()

4.UDP应用-实现广播的设置

1.将广播接收端套和发送端的接字属性设置为允许接收广播
        2.将广播发送端的地址设置为发送给局域网所有终端: ("192.168.0.255", 7890) 或 ("", 7890)

        3.广播风暴: 在一个网络中大量发送广播会占用大量带宽,解决方案是组播

5.UDP应用-广播接收端

# broadcast_recv.py
import socket def main():
# 1.创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2.设置套接字允许接收广播
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
# 3.设置端口重用
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 4.固定接收端的端口号
local_addr = ("", 7890)
udp_socket.bind(local_addr)
# 5.接收数据
while True:
try:
recv_msg, recv_addr = udp_socket.recvfrom(1024) # 1024表示本次接收的最大字节数
# 6.打印接收的数据
# print(type(recv_msg), type(recv_addr)) # <class 'bytes'> <class 'tuple'>
print("从广播{}获取消息: {}".format(str(recv_addr), recv_msg.decode("utf-8"))) # 7.回送消息
udp_socket.sendto(b"ok", recv_addr)
except (KeyboardInterrupt, SyntaxError): # control + c 终止程序或者语法错误时捕获异常
raise
except Exception as e:
print(e) # 8.关闭套接字
udp_socket.close() if __name__ == "__main__":
main()

6.UDP应用-广播发送端

# broadcast_send.py
import socket
import time def main():
# 1.创建一个udp套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2.设置套接字允许接收广播
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
# 3.设置发送广播的地址
# dest_addr = ("192.168.0.255", 7890) # 这种写法也可以
dest_addr = ("<broadcast>", 7890) # 将消息发送给局域网所有终端 while True:
time.sleep(1) # 每隔1秒发送一次广播
# 使用套接字收发数据
udp_socket.sendto("开始广播了...".encode("utf-8"), dest_addr)
recv_msg, recv_addr = udp_socket.recvfrom(1024) # 1024表示本次接收的最大字节数
print("从接收端{}获取消息: {}".format(str(recv_addr), recv_msg.decode("utf-8"))) # 关闭套接字
udp_socket.close() if __name__ == "__main__":
main()

7.函数实现-UDP聊天器

import socket

def send_msg(udp_socket):
"""获取键盘数据,并将其发送给对方"""
# 1. 从键盘输入数据
msg = input("\n请输入要发送的数据:")
# 2. 输入对方的ip地址
dest_ip = input("\n请输入对方的ip地址:")
# 3. 输入对方的port
dest_port = int(input("\n请输入对方的port:"))
# 4. 发送数据
udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port)) def recv_msg(udp_socket):
"""接收数据并显示"""
# 1. 接收数据
recv_msg = udp_socket.recvfrom(1024)
# recv_msg, recv_ip = udp_socket.recvfrom(1024)
# 2. 解码
recv_ip = recv_msg[1]
recv_msg = recv_msg[0].decode("utf-8")
# 3. 显示接收到的数据
print(">>>%s:%s" % (str(recv_ip), recv_msg)) def main():
# 1. 创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 绑定本地信息
udp_socket.bind(("", 7788))
while True:
# 3. 选择功能
print("="*30)
print("1:发送消息")
print("2:接收消息")
print("0:退出系统")
print("="*30)
op_num = input("请输入要操作的功能序号:") # 4. 根据选择调用相应的函数
if op_num == "":
send_msg(udp_socket)
elif op_num == "":
recv_msg(udp_socket)
elif op_num == "":
break
else:
print("输入有误,请重新输入...")
# 5.关闭套接字
udp_socket.close() if __name__ == "__main__":
main()

8.对象实现-UDP聊天器

import socket

class UdpSocket:

    def __init__(self):
# 1. 创建套接字
self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 绑定本地信息
self.udp_socket.bind(("", 7788)) # 发送消息
def send_msg(self):
"""获取键盘数据,并将其发送给对方"""
# 1. 从键盘输入数据
msg = input("\n请输入要发送的数据:")
# 2. 输入对方的ip地址
dest_ip = input("\n请输入对方的ip地址:")
# 3. 输入对方的port
dest_port = int(input("\n请输入对方的port:"))
# 4. 发送数据
self.udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port)) # 接收消息
def recv_msg(self):
"""接收数据并显示"""
# 1. 接收数据
recv_msg = self.udp_socket.recvfrom(1024)
# recv_msg, recv_ip = self.udp_socket.recvfrom(1024)
# 2. 解码
recv_ip = recv_msg[1]
recv_msg = recv_msg[0].decode("utf-8")
# 3. 显示接收到的数据
print(">>>%s:%s" % (str(recv_ip), recv_msg)) # 功能选择
def start_menu(self):
while True:
print("="*30)
print("1:发送消息")
print("2:接收消息")
print("0:退出系统")
print("="*30)
op_num = input("请输入要操作的功能序号:") # 4. 根据选择调用相应的函数
if op_num == "":
self.send_msg()
elif op_num == "":
self.recv_msg()
elif op_num == "":
break
else:
print("输入有误,请重新输入...") # 关闭套接字
def __del__(self):
print("%s对象已回收" % self)
self.udp_socket.close() def main():
# 实例化一个udp聊天器对象
udp_chat = UdpSocket()
# 启动聊天器
udp_chat.start_menu() if __name__ == "__main__":
main()

9.自定义socket类

import socket

class MySocket(socket.socket):
"""自定义MySocket类继承自socket模块中的socket""" def __init__(self, encoding="utf-8"):
# 调用父类中的初始化方法
super().__init__(type=socket.SOCK_DGRAM)
# 自定义默认的编码格式
self.encoding = encoding def my_sendto(self, send_msg, send_addr):
return self.sendto(send_msg.encode(self.encoding), send_addr) def my_recvfrom(self, num):
recv_msg, recv_addr = self.recvfrom(num)
return recv_msg.decode(self.encoding), recv_addr

10.UDP收发数据包

1.UDP不会发生粘包现象

2.UDP收发数据包大小取舍
            udp协议本身对一次收发消息数据大小限制是: 65535 - ip包头(20) - udp包头(8) = 65507

            在数据链路层网卡的MTU一般为1500的限制,所以在链路层收发数据包大小限制为: 1500 - ip包头(20) - udp包头(8) = 1472

3.UDP收发数据包大小结论

            接收数据包: recvfrom(num)  # num < 65507

            发送数据包: sendto(num)

                num > 65507  # 报错

                1472 < num < 65507  # 在数据链路层拆包发送,然而udp本身就是不可靠协议

                    # 所以一旦拆包后,造成的多个小数据包在网络传输中,如果丢失任意一个,那么此次数据传输失败

                num < 1472  # 是比较理想的状态

4.本地套接字

1.Linux下常用文件辨识(ls -lh 第一个用来辨识文件类型)

d: 文件夹

-: 普通文件

l: 链接文件

s: 套接字文件,是一个虚拟文件且大小在显示上永远为0,存在的意义是在Linux/Unix下提供本地进程间通信的一种方式

p: 管道文件

2.UNIX本地套接字使用流程

服务端流程

            1.创建本地套接字: unix_socket = socket(AF_UNIX, SOCK_STREAM)

            2.绑定套接字文件: unix_socket.bind("./unix_socket_test")

            3.监听: unix_socket.listen(128)

            4.等待客户端连接: new_client_socket, client_addr = unix_socket.accept()

            4.接收连接: recv_data = unix_socket.recv(1024)

            5.收发消息: new_client_socket.send()

            6.关闭客户端套接字: new_client_socket.close()

            8.关闭本地套接字: unix_socket.close()

客户端流程

            1.创建本地套接字: unix_client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

            2.连接本地套接字文件: unix_client_socket.connect(unix_server_address)

            3.发送消息: unix_client_socket.send()

            4.接收消息: recv_data = unix_client_socket.recv(1024)

            5.关闭套接字: unix_client_socket.close()

3.UNIX应用-客户端

import socket
import sys
import traceback def main():
try:
# 创建unix本地套接字
unix_client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
# 文件已经被服务端创建,且和服务端使用的是同一个socket文件
unix_server_address = "./unix_socket_test"
# 连接本地套接字文件
unix_client_socket.connect(unix_server_address)
except socket.error:
traceback.print_exc()
sys.exit(1)
while True:
# 发送数据
send_data = input("请输入要发生的数据:")
if send_data:
unix_client_socket.sendall(send_data.encode("utf-8"))
# 接收服务器发送过来的数据
recv_data = unix_client_socket.recv(1024)
print("接收到的数据为:%s" % recv_data.decode("utf-8"))
else:
break
# 关闭套接字
unix_client_socket.close() if __name__ == "__main__":
main()

4.UNIX应用-服务端

import socket
import os def main():
# 确定那个文件作为通讯文件
unix_server_address = "./unix_socket_test"
# 判断通讯文件是否存在,如果存在则删除文件
if os.path.exists(unix_server_address):
os.unlink(unix_server_address) # 创建一个unix本地套接字
unix_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
# 绑定本地套接字文件
unix_socket.bind(unix_server_address)
# 监听
unix_socket.listen(128) # 循环目的: 多次调用accept等待客户端连接,为多个客服端服务
while True:
print("等待一个新的客户端的到来...")
new_client_socket, client_addr = unix_socket.accept()
print("客户端' %s '已经到来" % str(client_addr))
# 循环目的: 为同一个客服端服务多次
while True:
# 接收客户端发送过来的请求
recv_data = new_client_socket.recv(1024) # recv解堵塞的两种方式: 1.客户端发送过来数据,2.客户端调用了close
if recv_data:
print("客户端' %s '发送过来的请求是: %s" % (str(client_addr), recv_data.decode("utf-8")))
# 回送数据给客户端表示响应客户端的请求
send_data = "----ok----Request accepted"
new_client_socket.send(send_data.encode("utf-8"))
else:
break
# 5.关闭客户端套接字
new_client_socket.close() # 关闭本地套接字
unix_socket.close() if __name__ == "__main__":
main()

5.TCP和UDP区别

1.TCP传输数据使用字节流方式传输,UDP是数据包

2.TCP会产生粘包现象,UDP不会

3.TCP对网络条件要求高,UDP不需要

4.TCP编程可以保证传输的可靠性,UDP不保证

5.TCP使用listen和accept,UDP不需要

6.收发消息
            TCP使用recv send sendall # sendall用法和send一样,只是返回值不同,成功返回None, 失败则产生异常

            UDP使用recvfrom sendto

02_套接字编程(socket抽象层)的更多相关文章

  1. Android网络编程系列 一 Socket抽象层

     在<Android网络编程>系列文章中,前面已经将Java的通信底层大致的描述了,在我们了解了TCP/IP通信族架构及其原理,接下来我们就开始来了解基于tcp/ip协议层的Socket抽 ...

  2. 《Python》网络编程之客户端/服务端框架、套接字(socket)初使用

    一.软件开发的机构 我们了解的涉及到两个程序之间通讯的应用大致可以分为两种: 第一种是应用类:QQ.微信.网盘等这一类是属于需要安装的桌面应用 第二种是web类:比如百度.知乎.博客园等使用浏览器访问 ...

  3. 基于TCP协议的socket套接字编程

    目录 一.什么是Scoket 二.套接字发展史及分类 2.1 基于文件类型的套接字家族 2.2 基于网络类型的套接字家族 三.套接字工作流程 3.1 服务端套接字函数 3.2 客户端套接字函数 3.3 ...

  4. Linux Socket 原始套接字编程

    对于linux网络编程来说,可以简单的分为标准套接字编程和原始套接字编程,标准套接字主要就是应用层数据的传输,原始套接字则是可以获得不止是应用层的其他层不同协议的数据.与标准套接字相区别的主要是要开发 ...

  5. Python套接字编程(1)——socket模块与套接字编程

    在Python网络编程系列,我们主要学习以下内容: 1. socket模块与基本套接字编程 2. socket模块的其他网络编程功能 3. SocketServer模块与简单并发服务器 4. 异步编程 ...

  6. Linux之socket套接字编程20160704

    介绍套接字之前,我们先看一下传输层的协议TCP与UDP: TCP协议与UDP协议的区别 首先咱们弄清楚,TCP协议和UCP协议与TCP/IP协议的联系,很多人犯糊涂了,一直都是说TCP/IP协议与UD ...

  7. 网络编程之网络架构及其演变过程、互联网与互联网的组成、OSI七层协议、socket抽象层

    目录 网络架构及其演变过程 单机架构 CS架构 BS架构 BS架构和CS架构的区别 C/S架构的优缺点: B/S架构的优缺点: 互联网与互联网的组成 互联网的组成(教科书版) 互联网的组成(科普版) ...

  8. day31 socket套接字编程

    为什么要有套接字编程? 在上节课的学习中,我们学习了OSI七层协议,但是如果每次进行编程时我们都需要一层一层的将各种协议使用在我们的程序中,这样编写程序实在是太麻烦了,所以为了让程序的编写更加的简单, ...

  9. socket套接字编程 HTTP协议

    socket套接字编程  套接字介绍  1. 套接字 : 实现网络编程进行数据传输的一种技术手段  2. Python实现套接字编程:import  socket  3. 套接字分类 >流式套接 ...

随机推荐

  1. Surface Pro 6 遇到的一系列问题

    当屏幕很烫的时候,触摸屏会部分失灵,越烫越明显,但是 Surface Pen 仍然可以使用,建议这个时候关机,等它冷静了再开机 不过不排除更新导致的触控失灵(新的更新没有考虑到老的硬件,微软也许之后永 ...

  2. 微信公众号怎么发PDF文件?

    微信公众号怎么发PDF文件?   我们都知道创建一个微信公众号,在公众号中发布一些文章是非常简单的,但公众号添加附件下载的功能却被限制,如今可以使用小程序“微附件”进行在公众号中添加附件. 以下是公众 ...

  3. 在centos 上安装python

    1.下载最新版本python源码包 下载地址为https://www.python.org/ftp/python/3.8.5/Python-3.8.5.tgz 2.解压源码包 tar -zxf Pyt ...

  4. 小白学习Python之路---py文件转换成exe可执行文件

    一.背景 今天闲着无事,写了一个小小的Python脚本程序,然后给同学炫耀的时候,发现每次都得拉着其他人过来看着自己的电脑屏幕,感觉不是很爽,然后我想着网上肯定有关于Python脚本转换成可执行文件的 ...

  5. C# winform 弹出窗体给父窗体传值

    Winform程序有很多传值的方法,抱着学习的态度.利用委托注册事件的方法,给窗体统一添加事件: 首先定义一个Frm_Base: namespace 任意 { public partial class ...

  6. C#LeetCode刷题之#101-对称二叉树(Symmetric Tree)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4068 访问. 给定一个二叉树,检查它是否是镜像对称的. 例如,二 ...

  7. flask_restful 的reqparse获取验证前端参数

    required是设置必选非必选,nullable允不允许向传null,location指定参数获取的位置,可以多选,按前后顺序获取 parser.add_argument('app_id', typ ...

  8. Excel清除隐藏的引号或空格

    问题场景 导出到Excel的数据内容有时候被"暗中"添加了[引号]或[空格]等字符. 尤其还"隐藏"了,以至于相同的内容,数据格式都没有问题,不能进行函数操作, ...

  9. Android TextView 字数过多,用跑马灯滚动形式实现

    上代码: <TextView android:layout_width="120dp" android:layout_height="wrap_content&qu ...

  10. LinkedBlockingQueue 和 ConcurrentLinkedQueue的区别

    1. 简单的开篇 LinkedBlockingQueue 和 ConcurrentLinkedQueue 是 Java 高并发场景中最常使用的队列.尽管这两个队列经常被用作并发场景的数据结构,但它们之 ...