初学Python——Socket网络编程

简介: 认识socketsocket本质上就是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实现数据的互相传递。我们知道网络 通信 都 是基于 ip+port(端口) 方能定位到目标的具体机器上的具体服务,操作系统有0-65535个端口,每个端口都可以独立对外提供服务,如果 把一个公司比做一台电脑 ,那公司的总机号码就相当于ip地址, 每个员工的分机号就相当于端口, 你想找公司某个人,必须 先打电话到总机,然后再转分机 。

认识socket

socket本质上就是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实现数据的互相传递。我们知道网络 通信 都 是基于 ip+port(端口) 方能定位到目标的具体机器上的具体服务,操作系统有0-65535个端口,每个端口都可以独立对外提供服务,如果 把一个公司比做一台电脑 ,那公司的总机号码就相当于ip地址, 每个员工的分机号就相当于端口, 你想找公司某个人,必须 先打电话到总机,然后再转分机 。

建立一个socket必须至少有2端, 一个服务端,一个客户端, 服务端被动等待并接收请求,客户端主动发起请求, 连接建立之后,双方可以互发数据。

基本参数

Socket Families(地址簇)

socket.AF_UNIX  本机进程间通信 

socket.AF_INET  IPV4(默认) 

socket.AF_INET6  IPV6

Socket Types(类型)

socket.SOCK_STREAM  流式socket,代表TCP协议(默认)

socket.SOCK_DGRAM  数据报式socket,代表UDP协议

socket方法

sk = socket.socket(family=AF_INETtype=SOCK_STREAMproto=0fileno=None)

建立socket连接对象

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,将所有内容发送出去。

服务端步骤:

步骤:
1.server = socket.socket() 声明实例,生成连接对象
2.server.bind() 绑定要监听的端口
3.server.listen() 开始监听
4.conn,addr = server.accept()等待客户端发起连接,阻塞
5.接收数据(发送数据)
6.当客户端断开连接后,继续监听等待下一个客户端建立连接
……
关闭连接对象

代码示例:

import socket
"服务器端"

server = socket.socket() # 生成连接对象
server.bind(("localhost",6000)) # 绑定要监听的端口
server.listen(5) # 开始监听(最大允许挂起的连接)
while True:
    print("\n服务器在等待...")
    conn,addr = server.accept() # 等待客户端发起建立连接,起到阻塞作用
    # conn 是客户端链接过来而在服务器端为其生成的连接实例,addr是IP地址+端口
    print("已成功连接")
    print("连接对象:{0},地址:{1}\n".format(conn,addr))
    while True:
        try:
            data = conn.recv(1024)  # 接收数据
            # (如果客服端断开连接,此步骤将会被无限循环操作,所以一定要有检查机制)
            if data != b"000001":  # 接收到的信号不是"000001"的话就正常执行
                print("接收客户端信息:", data.decode())
                msg = input(">>输入返回客户端的数据:")
                conn.send(msg.encode(encoding="utf-8")) # 向客户端发送数据
            else:
                print("该客户已主动断开连接")
                break
        except ConnectionResetError as e:
            print("该客户机异常!已被强迫断开连接",e)
            break
        else:
            print("It's OK !")

server.close()

客户端步骤:

步骤:
1.client = socket.socket() 声明实例,生成连接对象
2.client.connect() 与服务器建立连接
3.与服务器交互(发送接收数据)
4.client.close() 断开连接

代码示例:

import socket
"通信案例客户端消息接收与发送"
client = socket.socket() # 声明socket类型,并生成socket连接对象
try:
    client.connect(("localhost",6000)) # 与服务器建立连接

    while True:
        msg = input(">>输入要向服务器发送的信息:")
        client.send(msg.encode(encoding="utf-8")) # 向服务器发送信息(只能发送bytes字节类型,不能是str字符类型)
        data = client.recv(1024) # 接收来自服务器的1024个字节
        print("接收来自服务器的数据:",data.decode()) # 打印服务器发送的数据
        chioce = input("按任意键继续,按0退出客户端")
        if chioce == "0":
            client.send(b"000001") # 发送此信号表明客户端要断开连接
            break
        client.close()
except ConnectionRefusedError as e:
    print("服务器还没开机!请静候")

需要注意的是:

1.客户端再发送数据时,要主要服务器接收的大小限制。如果超过了这个限制,超出的部分暂时存在系统缓冲区,第二次接收的时候再输出剩下的部分。例如服务器端的recv(1024),而客户端一次发了2024字节,那么剩下的1000字节存在缓冲区,第二次接收的时候会接收缓冲区的内容,将不会接收新发来的数据,会造成数据错误。官方建议一次性不超过8192字节

2.双发收发数据只能是bytes类型

3.粘包问题,下面讲

socket粘包问题

什么是粘包呢?我们知道发送数据,并且数据量比较大时,并不会一次性发送,即使能一次发送,客户端也不一定能一次性接收,所以服务器有个缓冲区,等客户端下次再接收数据的时候再发送给客户端,所以,就需要将数据分成几次发送,客户端分成几次接收。那又出来问题了,客户端知道数据(文件)有多大么?它怎么知道要接收几次?当然是要服务器告诉他啦!

于是,我们设计服务器首先发送数据大小(数据),再开始分批发送数据,客户端先接收文件大小(数据),再开始分批接收。问题就有可能在这里出现了。如果连续2次send数据,很有可能将两次的数据黏在一起发送出去,在客户端也无法将其分开,怎么办呢?

我们可以让服务器每次发送数据后,接收来自客户端的确认,这样会强制清空缓冲区,就不会造成粘包。当然,基于上面讲的方法,只需要在发送正式数据之前接收确认就好。

最后,如果希望100%确认双发收发数据是否一致,可以采用MD5校验。

服务器端步骤:

1.读取文件名
2.检测文件是否存在
3.打开文件
4.检测文件大小
5.将文件大小发给客户端
6.确认
7.开始边读边发(循环发送)
8.发送MD5

代码:

import socket,os,hashlib

ser = socket.socket()
ser.bind(("localhost",5000))
ser.listen()

while True:
    try:
        print("正在等待客户端连接...")
        conn,addr = ser.accept()
        print("已连接,new conn:",addr)
        while True:
            data = conn.recv(8192)
            filename = data.decode()
            print("寻找文件",filename)
            if os.path.isfile(filename):
                conn.send(b"01")
                conn.recv(1024)
                f = open(filename,"rb")
                m = hashlib.md5()
                file_size = os.stat(filename).st_size
                conn.send(str(file_size).encode(encoding="utf-8"))
                client_ack = conn.recv(1024)  # 接收确认信息
                if client_ack == b"1":
                    print("开始发送数据")
                for line in f:
                    m.update(line)
                    conn.send(line)
                f.close()
                conn.send(m.hexdigest().encode(encoding="utf-8")) # 发送MD5值
            else:
                conn.send(b"00") #表示文件不存在
                print("该文件不存在!")
    except ConnectionResetError:
        print("该客户端已断开连接")


ser.close()
print("服务器已关闭")

客户端步骤:

1.发送接收文件请求,同时将文件名发送给服务器

2.接收文件长度

3.本地新建同名文件,循环接收数据,并将其写入文件

4.同时更新本地MD5值

5.接收数据完毕后,再接收服务器的MD5值,与本地MD5值进行比较

代码:

import socket,hashlib


def receive1(client):
    "真正的数据接收"
    while True:
        res = b""
        res = res + client.recv(1024)
    return res

def receive(client,filename):
    "接收处理"
    m = hashlib.md5()
    rece_res_size = int(client.recv(1024).decode())  # 接收的结果长度,转成int型
    client.send(b"1")
    rece_size = 0
    res = b""
    filename = filename.decode()
    f = open(filename + ".new","wb")
    while rece_size < rece_res_size:
        if rece_res_size - rece_size >1024: # 如果不是最后一次接收数据
            size = 1024
        else: # 最后一次接收数据
            size = rece_res_size - rece_size
        a = client.recv(size) # 循环接收数据
        res = res + a
        rece_size = len(res)
        m.update(a)
        f.write(a)
        print("发送数据量:{0},接收数据量:{1}".format(rece_res_size,rece_size))
    else:
        serves_md5 = client.recv(1024).decode()
        print("服务器MD5:",serves_md5)
        print("客户端MD5:",m.hexdigest())
        if serves_md5 == m.hexdigest():
            print("文件接收并校验完毕!")
    res.decode()
    f.close()

def main():
    client = socket.socket()
    try:
        client.connect(("localhost", 5000))
        while True:
            filename = input("请输入需要的文件名").strip().encode(encoding="utf-8")
            if len(filename) == 0:
                print("输入为空,重新输入")
                continue
            client.send(filename)
            is_file = client.recv(1024)
            if is_file == b"01":
                client.send(b"OK")
                receive(client,filename) # 调用函数接收数据,返回结果res(bytes)
            else:
                print(" {0} 文件不存在!".format(filename.decode()))

    except ConnectionRefusedError:
        print("等待服务器开机")
    client.close()

main()

socketserver

什么是socketserver?为什么需要它呢?

我们在前面的文章中普通的socket并不能同时处理多个客户端,当一个客户端在与服务器连接时,其它客户端只能排队。而sockerserver则不同,它可以并发地处理多个客户端请求。

import socketserver
'''
每一个客户端请求过来,都会实例化 MyTCPHandler
'''

class MyTCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        "跟客户端所有的交互都是在handle里完成的"
        while True:
            try:
                self.data = self.request.recv(1024).strip()
                print("{0} wrote:".format(self.client_address[0]))
                print(self.data)
                if not self.data:
                    print("输入为空")
                    self.request.send(bytes("输入为空", "utf-8"))
                else:
                    self.request.send(self.data.upper())
            except ConnectionResetError :
                print("客户已断开连接")
                break

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999
    #server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) # 实例化一对一的连接对象
    server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler) # 实例化多并发的连接对象(多线程)
    server.serve_forever()

客户端并没有什么区别:

import socket

HOST, PORT = "localhost", 9999
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
while True:
    data = input("输入字符")
    try:
        sock.sendall(bytes(data + "\n", "utf-8"))

        received = str(sock.recv(1024), "utf-8")
    finally:
        print("Sent:     {0}".format(data))
        print("Received: {0}".format(received))

sock.close()

 

相关文章
|
3月前
|
机器学习/深度学习 算法 量子技术
GQNN框架:让Python开发者轻松构建量子神经网络
为降低量子神经网络的研发门槛并提升其实用性,本文介绍一个名为GQNN(Generalized Quantum Neural Network)的Python开发框架。
66 4
GQNN框架:让Python开发者轻松构建量子神经网络
|
9天前
|
JavaScript Java 大数据
基于python的网络课程在线学习交流系统
本研究聚焦网络课程在线学习交流系统,从社会、技术、教育三方面探讨其发展背景与意义。系统借助Java、Spring Boot、MySQL、Vue等技术实现,融合云计算、大数据与人工智能,推动教育公平与教学模式创新,具有重要理论价值与实践意义。
|
2月前
|
运维 Linux 开发者
Linux系统中使用Python的ping3库进行网络连通性测试
以上步骤展示了如何利用 Python 的 `ping3` 库来检测网络连通性,并且提供了基本错误处理方法以确保程序能够优雅地处理各种意外情形。通过简洁明快、易读易懂、实操性强等特点使得该方法非常适合开发者或系统管理员快速集成至自动化工具链之内进行日常运维任务之需求满足。
126 18
|
3月前
|
JSON 网络安全 数据格式
Python网络请求库requests使用详述
总结来说,`requests`库非常适用于需要快速、简易、可靠进行HTTP请求的应用场景,它的简洁性让开发者避免繁琐的网络代码而专注于交互逻辑本身。通过上述方式,你可以利用 `requests`处理大部分常见的HTTP请求需求。
317 51
|
2月前
|
数据采集 存储 数据可视化
Python网络爬虫在环境保护中的应用:污染源监测数据抓取与分析
在环保领域,数据是决策基础,但分散在多个平台,获取困难。Python网络爬虫技术灵活高效,可自动化抓取空气质量、水质、污染源等数据,实现多平台整合、实时更新、结构化存储与异常预警。本文详解爬虫实战应用,涵盖技术选型、代码实现、反爬策略与数据分析,助力环保数据高效利用。
123 0
|
10月前
|
机器学习/深度学习 人工智能 算法
猫狗宠物识别系统Python+TensorFlow+人工智能+深度学习+卷积网络算法
宠物识别系统使用Python和TensorFlow搭建卷积神经网络,基于37种常见猫狗数据集训练高精度模型,并保存为h5格式。通过Django框架搭建Web平台,用户上传宠物图片即可识别其名称,提供便捷的宠物识别服务。
841 55
|
9月前
|
机器学习/深度学习 人工智能 算法
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法
眼疾识别系统,本系统使用Python作为主要开发语言,基于TensorFlow搭建卷积神经网络算法,并收集了4种常见的眼疾图像数据集(白内障、糖尿病性视网膜病变、青光眼和正常眼睛) 再使用通过搭建的算法模型对数据集进行训练得到一个识别精度较高的模型,然后保存为为本地h5格式文件。最后使用Django框架搭建了一个Web网页平台可视化操作界面,实现用户上传一张眼疾图片识别其名称。
493 5
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法
|
3月前
|
存储 监控 算法
基于 Python 跳表算法的局域网网络监控软件动态数据索引优化策略研究
局域网网络监控软件需高效处理终端行为数据,跳表作为一种基于概率平衡的动态数据结构,具备高效的插入、删除与查询性能(平均时间复杂度为O(log n)),适用于高频数据写入和随机查询场景。本文深入解析跳表原理,探讨其在局域网监控中的适配性,并提供基于Python的完整实现方案,优化终端会话管理,提升系统响应性能。
86 4
|
5月前
|
调度 Python
探索Python高级并发与网络编程技术。
可以看出,Python的高级并发和网络编程极具挑战,却也饱含乐趣。探索这些技术,你将会发现:它们好比是Python世界的海洋,有穿越风暴的波涛,也有寂静深海的奇妙。开始旅途,探索无尽可能吧!
114 15
|
6月前
|
数据采集 存储 监控
Python 原生爬虫教程:网络爬虫的基本概念和认知
网络爬虫是一种自动抓取互联网信息的程序,广泛应用于搜索引擎、数据采集、新闻聚合和价格监控等领域。其工作流程包括 URL 调度、HTTP 请求、页面下载、解析、数据存储及新 URL 发现。Python 因其丰富的库(如 requests、BeautifulSoup、Scrapy)和简洁语法成为爬虫开发的首选语言。然而,在使用爬虫时需注意法律与道德问题,例如遵守 robots.txt 规则、控制请求频率以及合法使用数据,以确保爬虫技术健康有序发展。
770 31

推荐镜像

更多