网络编程之UDP套接字(四)

news/2024/5/18 14:26:35 标签: udp, 网络, tcp/ip

12. 基于 UDP 的套接字

UDP 是无链接的,可以实现服务端与多个客户端进行同时进行通讯,无论先启动哪一端都可以。(qq 聊天 UDP 服务)

udp__4">12.1 基于 udp 套接字模板

UDP 服务端

s = socket(AF_INET, SOCK_DGRAM)		# 创建一个服务器的套接字
s.bind()	# 绑定服务器套接字
inf_loop:	# 服务器无限循环
    data,addr = s.recvfrom(buffer_size)		# 接收
    s.sendto(data.upper(), addr)	# 发送
s.close()	# 关闭服务器套接字

UDP 客户端

s = socket(AF_INET, SOCK_DGRAM)		# 创建客户端套接字
comm_loop:		# 通讯循环
    s.sendto('xxx', ip_port)	# 发送
    data,addr = s.recvfrom(2024)	# 接收
s.close()	# 关闭客户端套接字

示例:

服务端

from socket import *

ip_port = (gethostname(), 8080)
buffer_size = 1024

udp_server = socket(AF_INET, SOCK_DGRAM)
udp_server.bind(ip_port)

while True:
    conn, addr = udp_server.recvfrom(buffer_size)
    print('客户端发来信息:', conn.decode('utf-8'))
    udp_server.sendto(conn.upper(), addr)

udp_server.close()

客户端

from socket import *

ip_port = (gethostname(), 8080)
buffer_size = 1024

udp_client = socket(AF_INET, SOCK_DGRAM)

while True:
    msg = input('>>>').strip()
    udp_client.sendto(msg.encode('utf-8'), ip_port)
    data, addr = udp_client.recvfrom(buffer_size)
    print('服务端发来信息:', data.decode('utf-8'))
udp_client.close()

12.2 基于 UDP 实现 ntp 服务

ntp 服务(Network Time Protocol),是用来使计算机时间同步化的一种协议。

需求:基于 UDP 服务实现 ntp 服务,获取服务端时间:

服务端:

from socket import *
import time

ip_port = (gethostname(), 8080)
buffer_size = 1024

ntp_server = socket(AF_INET, SOCK_DGRAM)
ntp_server.bind(ip_port)

while True:
    data, addr = ntp_server.recvfrom(buffer_size)  # data: 客户端发来的信息  addr:客户端地址
    print('客户端时间格式:', data.decode('utf-8'))

    if not data:  # 如果客户端输入的为空
        fmt = '%Y-%m-%d %X'
    else:
        fmt = data.decode('utf-8')  # 客户端自定义的时间格式
    back_time = time.strftime(fmt)
    ntp_server.sendto(back_time.encode('utf-8'), addr)

ntp_server.close()

客户端:

from socket import *

ip_port = (gethostname(), 8080)
buffer_size = 1024

ntp_client = socket(AF_INET, SOCK_DGRAM)

while True:
    msg = input('>>>')		# 客户端输入时间格式
    ntp_client.sendto(msg.encode('utf-8'), ip_port)
    data, addr = ntp_client.recvfrom(buffer_size)
    print('服务端时间:', data.decode('utf-8'))

ntp_client.close()

13. socketserver 实现并发

利用 socketserver 模块可以实现基于 tcp 服务的套接字并发功能。基于 tcp 的套接字主要有两个关键循环:一个链接循环,一个通讯循环。

sockeserver 模块分为两大类:server 类(解决链接问题),request 类(解决通讯类)。

server 类

  • 与链接相关:BaseServer、TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer
  • 基于多线程实现并发:ThreadingMixIn、ThreadingTCPServer、ThreadingUDPServer
  • 基于多进程实现并发:ForkingMinIn、ForkingTCPServer、ForkingUDPServer

request 类

BaseRequestHandler、StreamRequestHandler、DatagramRequestHandler

继承关系

源码分析

  • 基于 tcp 的 sockserver 我们自己定义的类中的:
    • self.server:套接字对象
    • self.request:一个链接
    • self.client_address:客户端地址
  • 基于 udp 的 sockserver 我们自己定义的类中的:
    • self.request:是一个元组,第一个元素是客户端发来的数据,第二个元素是服务端的 udp 套接字对象
    • self.client_address:客户端地址

示例

TCP 实现并发

服务端:

import socketserver

ip_port = ('127.0.0.1', 8080)
buffer_size = 1024


class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        print('客户端地址', self.client_address)
        print('客户端链接', self.request)

    # 通讯循环
        while True:
            try:
                # 收消息
                data = self.request.recv(1024)
                if not data :break
                print('收到客户端%s的消息是:%s' %(self.client_address, data.decode('utf-8')))

                # 发消息
                self.request.send(data.upper())
            except Exception as e:
                print(e)

# 链接循环
if __name__ == '__main__':
    # 内部会产生一个 socket 对象,并连接客户端
    s = socketserver.ThreadingTCPServer(ip_port, MyServer)  # 第一个参数:ip_port,第二个:自定义的类名
    s.serve_forever()

客户端:

from socket import *

ip_port = ('127.0.0.1', 8080)
buffer_size = 1024

client = socket(AF_INET, SOCK_STREAM)
client.connect(ip_port)

while True:
    msg = input(">>>").strip()
    if not msg:continue
    if msg == 'quit':break
    client.send(msg.encode('utf-8'))

    data = client.recv(buffer_size)

    print('收到服务端的消息', data.decode('utf-8'))

client.close()

socketserver 模块利用进程、线程来实现并发功能,其内部封装了很多类(如:创建socket对象,实现链接循环,以及利用进程、线程实现并发)。我们利用这些接口去实现通讯循环即可。

我们必须定义一个自己的类,并实现 handle() 方法 。需要注意的是我们不需要修改客户端。

UDP 实现并发

服务端:

import socketserver

ip_port = ('127.0.0.1', 8080)


class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        print(self.request)
        # self.request: (b'ahhd', <socket.socket fd=216, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>)
        # 第一个元素:客户端发来的消息,第二个:服务端 socket 对象
        
        print('收到客户端消息是', self.request[0])
        self.request[1].sendto(self.request[0].upper(), self.client_address)

if __name__ == '__main__':
    s = socketserver.ThreadingUDPServer(ip_port, MyServer)
    s.serve_forever()

客户端:

from socket import *

ip_port = ('127.0.0.1', 8080)
buffer_size = 1024

client = socket(AF_INET, SOCK_DGRAM)

while True:
    msg = input('>>>').strip()
    if not msg:continue
    if msg == 'quit':break
    client.sendto(msg.encode('utf-8'), ip_port)

    data, addr = client.recvfrom(buffer_size)
    print('服务端发来消息', data.decode('utf-8'))

client.close()

14. 认证客户端链接合法性

如果想在分布式系统中实现一个简单的客户端链接认证功能,又不像 SSL 那么复杂,那么可以采用 hmac(算法加密)+ 盐(随机密码)的方法来实现。

当服务端受到 syn 洪水攻击(即攻击者利用很多客户端发送空信息,造成服务端奔溃)时,验证客户端链接合法性很有必要。

服务端:

# _*_coding:utf-8_*_
__author__ = 'Hubery_Jun'

from socket import *
import hmac, os

# 验证 key,即 salt
secret_key = b'Life is Short, you need Python'


def conn_auth(conn):
    """
    验证链接合法性
    :param conn:
    :return:
    """
    print('开始验证链接合法性')
    msg = os.urandom(32)    # 随机产生一个 32 字节长的字节对象,用于加密
    conn.sendall(msg)       # 发送给客户端验证
    
    h = hmac.new(secret_key, msg)   # 使用 hmac 模块(msg + salt)进行算法加密
    digest = h.digest()
    res = conn.recv(len(digest))    # 接收客户端的验证信息
    return hmac.compare_digest(res, digest)  # 两者比较,相同返回 True


def data_handler(conn, buffer_size=1024):
    """
    接收数据
    :param conn:链接
    :param buffer_size:接收数据大小
    :return:
    """
    if conn_auth(conn):
        print('链接不合法')
        conn.close()
        return
    print('链接合法,开始通讯')
    while True:
        data = conn.recv(buffer_size)
        if not data:break
        print('客户端发来消息', data.decode('utf-8'))
        conn.sendall(data.upper())


def server_handler(ip_port, buffer_size, back_log=5):
    """
    处理链接
    :param ip_port: ip + port
    :param buffer_size: 接收数据大小
    :return:
    """
    s = socket(AF_INET, SOCK_STREAM)
    s.bind(ip_port)
    s.listen(back_log)
    while True:
        conn, addr = s.accept()
        print('客户端链接', addr)
        data_handler(conn, buffer_size)

if __name__ == '__main__':
    ip_port = (gethostname(), 8080)
    buffer_size = 1024
    server_handler(ip_port, buffer_size)

客户端:

# _*_coding:utf-8_*_
__author__ = 'Hubery_Jun'

# 验证密码
secret_key = b'Life is Short, you need Python'


from socket import *
import hmac


def conn_auth(client):
    """
    验证客户端到服务端的链接
    :param client:
    :return:
    """
    msg = client.recv(32)
    h = hmac.new(msg, secret_key)
    digest = h.digest()
    client.sendall(digest)


def client_handler(ip_port, buffer_size):
    """
    通讯循环
    :param ip_port: ip + port
    :param buffer_size: 数据大小
    :return:
    """
    client = socket(AF_INET, SOCK_STREAM)
    client.connect(ip_port)

    conn_auth(client)

    while True:
        data = input('>>>').strip()
        if not data:continue
        if data == 'quit':break

        client.sendall(data.encode('utf-8'))
        res = client.recv(buffer_size)
        print('服务端发来消息', res.decode('utf-8'))
    client.close()

if __name__ == '__main__':
    ip_port = (gethostname(), 8080)
    buffer_size = 1024
    client_handler(ip_port, buffer_size)
客户端链接 ('192.168.152.1', 9866)
开始验证链接合法性
链接合法,开始通讯
客户端发来消息 dir
  • 服务端: 利用 os.urandom(32) 产生一个随机的 32字节长度的字节对象 msg,并发送给客户端验证。自己随便编写一个 key,然后利用 hamc 模块进行算法加密。
  • 客户端: 接收服务端发来的 msg,再利用 hmac 模块进行算法加密,然后再把加密后的信息发送给服务端,进行比较。如客户端发过去的信息和服务端加密后的信息相符,则返回 True,链接合法,否则不合法,直接关闭链接。

当客户端不知道 key 时,或者不知道加密方法时,向服务端发起链接请求,就会验证失败。

  • AF_INET:基于网络
  • SOCK_DGRAM:基于 UDP ,数据报,无链接
  • SOCK_STREAM:基于 TCP,数据流
  • 发送数据要是字节形式(二进制)
  • udp 发送数据,后面要跟 host+port
  • udp 接收数据,接收的是个元组(数据,主机+port)
  • udp 可以接收多个客户端同时
  • tcp:不能实现并发,只能同时服务一个客户端
  • udp:可以实现并发,能同时服务多个客户端,因为它没有链接
  • recvfrom 接受空也不会报错

http://www.niftyadmin.cn/n/127600.html

相关文章

跨域解决方案(9种)

什么是跨域 说起跨域&#xff0c;就要知道什么是浏览器同源策略 浏览器同源策略&#xff1a;必须是协议、域名、端口完全一致的才符合同源策略 如果以上三项&#xff0c;有一项不同都涉及到跨域问题 为什么浏览器要设置同源策略呢&#xff1f; 没有同源策略限制的两大危险场…

Qt QTreeView简单使用

QT-QTreeView使用方法 QTreeView: 用于显示树状结构数据&#xff0c;适用于树状结构数据的操作。 一、初始化 ​ 利用QStandardlternModel来初始化数据&#xff0c;标准的基于项数据的数据模型类&#xff0c; 每个项数据可以是任何数据类型。 // 初始化model QStandardItem…

国内开源镜像站点汇总

国内开源镜像站点汇总 一、站点版 &#xff08;一&#xff09;、企业站 网易&#xff1a;https://mirrors.163.com/ 搜狐&#xff1a;http://mirrors.sohu.com/&#xff08;Ubuntu 旧发行版同步被冻结&#xff0c;不同步 Ubuntu 新发行版&#xff0c;Ubuntu 源同步正常&#xf…

统计代码量

一 windows 在 Windows 系统上&#xff0c;您可以使用 PowerShell 命令行工具来统计项目的代码量。下面是使用 PowerShell 统计项目代码量的步骤&#xff1a; 打开 PowerShell 终端&#xff1a;按下 Win X 键&#xff0c;选择「Windows PowerShell&#xff08;管理员&#xf…

大厂面试总结大全五

1、Rpc和Http有什么区别 举例&#xff1a;Dubbo属于rpc协议&#xff0c;Feign属于http协议。 RPC接口即相当于调用本地接口一样调用远程服务的接口&#xff1b;HTTP接口是基于http协议的post接口和get接口&#xff08;等等&#xff0c;2.0版本协议子支持更多&#xff09;。 传…

院士交锋,专家论道|NLP大模型技术与应用十大挑战,剑指AI未来

2023年2月24日下午&#xff0c;第四届OpenI/O启智开发者大会NLP大模型分论坛在深圳人才研修院隆重举办。NLP大模型论坛会议现场众多NLP领域顶级专家学者与多家国产NLP大模型开发团队汇聚一堂&#xff0c;学术界与产业界破圈交流&#xff0c;激荡尖端思想、分享前沿动态&#xf…

流量分类与标记

流量分类与标记流量的分类和标记流量分类简单的流分类在产品中的实现简单流分类场景举例复杂的流分类场景举例复杂的流分类在产品中的实现总结&#xff1a;为了在internet上针对不同业务提供有差别的Qos服务质量&#xff0c;人们根据报文头部的某些字段记录QOS值&#xff0c;从…

外贸新手找客户的开发信修炼之旅(一)

开发信是一种传统的开发海外客户的方式&#xff0c;相信即便是外贸新手也或多或少有所耳闻&#xff0c;甚至已经通过邮件开发到了一些有意向的客户&#xff0c;但有时也会遇到开发信效果不好的情况&#xff0c;收到的回复寥寥无几。其实说白了开发信的本质与派发传单相同&#…