热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

python网络通信socket

socketsocket通常被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过socket这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据

socket

socket 通常被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过socket这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。

Python标准库提供了socket模块来实现这种网络通信。实例化一个socket类便能得到一个socket对象sock = socket.socket(),使用这个socket对象就可以进行通信了。常用的socket有两种。

SOCK_STREAM 面向连接的流式socket,基于TCP协议
SOCK_DGRAM 无连接的数据报式socket,基于UDP协议

相同类型的socket才能正常的通信,因为他们都有各自发送和接收消息的协议。

socket对象

import socket
s = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, fileno=None)

实例化时指定对应的参数可以得到不同类型的socket,默认使用IPV4和TCP协议的类型

参数 可选值 说明
family socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
  socket.AF_INET 默认使用IPv4协议
  socket.AF_INET6 使用IPv6协议
type socket.SOCK_STREAM 面向连接的流式socket,基于TCP协议
  socket.SOCK_DGRAM 无连接的数据报式socket,基于UDP协议

实践

通过写一个聊天的服务器和客户端体验这种通信

TCP服务端

使用socket构建一个最简单TCP服务器可接收客户端的连接。我们需要一个socket用于网络通信,并监听一个地址和端口,等待其他的网络连接访问该端口,代码如下。

server = socket.socket()  # 创建

server.bind(('127.0.0.1', 8000))   # 绑定本机地址和端口

server.listen() # 开始监听端口

# 阻塞等待客户端的连接,连接后返回一个新的可与客户端通信的socket和客户端的(ip,port)
s, raddr = server.accept()

当执行上面的python程序后,操作系统将会启动一个进程,该服务进程正在监听8000端口,在Windows命令行中使用netstat -anp tcp | findstr 8000查询监听状态。在Linux上可以使用ss -tanl | grep 8000命令查看。

C:\Users\user>netstat -anp tcp | findstr 8000
TCP    127.0.0.1:8000         0.0.0.0:0              LISTENING

下面构建一个完整的TCP服务器。这是基本的服务器和客户端通信结构图。根据结构图构建聊天服务器

python网络通信 --- socket

 

简单步骤和思路

  • 创建socket
  • 绑定一个ip地址和端口
  • 开始监听(listen)
  • 阻塞等待连接(accept)
  • 客户端连接到来后,开启新线程与该客户端交互,发送和接收消息。(recv和send)
  • 同时我们使用主线程操作服务端退出。

通过以上分析,我们需要使用多线程,分别与服务器交互,等待客户端连接,与一个连接后的客户端交互;每当成功的连接一个客户端,都需要新启动一个线程进行交互。

import socket
import threading

class Server:
    def __init__(self, ip='127.0.0.1', port=8000):  # 设置默认值
        self.addr = ip, port
        self.lock = threading.Lock()
        self.sock = socket.socket()
        self.sock.bind(self.addr)
        self.socks = {"accept": self.sock}  # 将所有创建的socket都放字典,方便释放

    def start(self):  # 启动接口
        self.sock.listen()
        threading.Thread(target=self.accept, name="accept", daemon=True).start()

    def accept(self):  # 该线程等待连接并创建处理线程
        while True:
            s, raddr = self.sock.accept()
            with self.lock:
                self.socks[raddr] = s
            threading.Thread(target=self.recv, args=(s, raddr), name="recv", daemon=True).start()

    def recv(self, s, raddr):  # 每个客户端开启一个线程与其交互
        while True:
            data = s.recv(1024).decode()
            if data.strip() == "" or data.strip() == "quit":  # 客户端结束条件
                with self.lock:
                    self.socks.pop(raddr)
                    s.close()
                    break
            print(data)
            s.send("server:{}\n".format(data).encode())

    def stop(self):
        with self.lock:
            for s in self.socks.values():
                s.close()
s = Server()
s.start()

while True:
    cmd = input("server commond:>>>")
    if cmd == "quit":  # 服务器退出条件
        s.stop()
        break
    print(threading.enumerate())

我们需要注意的问题:

  1. 服务端需要与多个不同客户端进行交互,所以我们需要开启不同线程去处理各自的业务,
  1. 为了服务端在启动后可以获得控制权,我们使用主线程来与服务器管理者交互,使用命令行输入指令就能在服务器启动后与服务器做一些交互,例如代码中的强制关闭服务器,并在强制关闭服务前提前关闭掉这些socket对象。
  1. 在遍历字典来关闭socket对象时,我们使用了锁,要求在这个遍历操作完成前,其他线程无法进行增加或者删除操作,保证了字典遍历时的线程安全。

socket常用的方法

  方法 含义
服务端 s.bind(address) 将套接字绑定到地址,以元组(host,port)的形式表示地址
  s.listen(backlog) 开始监听TCP传入连接。backlog:操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了
  s.accept() 接受TCP连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址,为一个元组
客户端socket函数 s.connect(address) 连接到address处的套接字,格式为元组(hostname,port),如果连接出错,返回socket.error错误
  s.connect_ex(adddress) 功能与connect(address)相同,但是成功返回0,失败返回errno的值
公共socket函数 s.recv(bufsize[,flag]) 从s接受bytes类型的数据,有数据就接受返回,bufsize指定要接收的最大数据量
  s.send(bytes[,flag]) TCP发送数据。将bytes中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于bytes的字节大小
  s.sendall(bytes[,flag]) 发送全部TCP数据。将bytes中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常
  sendfile() 使用os.sendfile()高效的发送文件的方法,必须使用SOCK_STREAM类型的套接字才能使用
  s.recvfrom(bufsize[.flag]) 接受UDP套接字的数据。与recv()类似,但返回值是(data,address)。其中data是包含接收数据的bytes,address是发送方地址
  s.sendto(string[,flag],address) 发送UDP数据。address是形式为(ipaddr,port)的元组。返回值是发送的字节数
     
  s.getpeername() 返回连接套接字的远程地址(ipaddr,port)
  s.getsockname() 返回套接字自己的地址(ipaddr,port)
  s.setsockopt(level,optname,value) 设置给定套接字选项的值
  s.getsockopt(level,optname[.buflen]) 返回套接字选项的值
  s.settimeout(timeout) 设置套接字操作的超时间,值为None表示没有超时期。一般超时期在创建时设置
  s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None
  s.fileno() 返回套接字的文件描述符
  s.setblocking(flag) 设置阻塞模式,非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常
  s.makefile() 创建一个与该套接字相关连的文件,返回一个类文件对象,可是使用文件操作发送和接收数据

sendfile是一个高效的传送方式,文件数据始终处于内核态,在操作系统缓冲区直接发送,不会到应用层缓冲区。

使用makefile方法将返回该socket对应的文件对象(io.TextIOWrapper),该对象的write()等价于send()方法, read方法等价于recv(),还可以使用readline等方法。这样我们可以使用文件的接口去收发信息,客户端将使用这种方式与服务器交互。

sock = socket.socket()
file = sock.makefile("rw")  # mode="rw" 可读可写

data = file.read()   # 等价于socket.recv()

data = file.read(10) # 指定读取字符大小长度,满10个字符才会返回。
data = file.readlin()   # 每次读取一行,遇到换行符才返回。
# 写入数据
msg = "hello world"
file.write(msg)
file.flush()      # 手动flush,否则在缓冲区满或者退出时自动才写入socket。同文件写入操作

TCP客户端

相比于服务端,客户端只需要连接服务器后发送和接受消息即可,相对更容易实现。

客户端需要同时接受和发送消息,而这两个操作均会阻塞,所以两个功能需要在不同的线程。下面代码使用了socket的makefile()方法,使用文件对象进行收发数据。

import socket
import threading
import datetime

class Client:
    def __init__(self, rip, rport):  # 服务器ip 和 端口
        self._raddr = rip, rport
        self._sock = socket.socket()
        self._connect()

    def _connect(self):
        self._sock.connect(self._raddr)   # 尝试连接指定的地址
        self.f = self._sock.makefile("rw")
        self.f.write("i am client at {}\n".format(self._sock.getsockname()))
        self.f.flush()
        threading.Thread(target=self.recv, name="recv", daemon=True).start()  # 一个进程接收消息
        self.send()   # 主进程发送消息

    def send(self):
        while True:
            msg = input(">>>").strip()
            self.f.write(msg)
            self.f.flush()
            if msg == "quit":
                self.stop()
                break

    def recv(self):
        while True:
            msg = self.f.readline()
            print("server:{}{:%Y/%m/%d %H:%M:%S}\n\t{}".format(self._sock.getpeername(), datetime.datetime.now(), msg))

    def stop(self):
        self.f.close()
        self._sock.close()

c = Client("127.0.0.1", 8000)

客户端使用connect()方法将会尝试连接服务器(这个服务必须存在,否则无法连接),由于服务基于TCP协议,所以在connect()连接时候,实际上会进行TCP三次握手的连接,但是我们在应用层面无法感知到这个下层行为。同样的在进行close关闭socket时,在断开连接前将会进行四次挥手操作。

 

使用makefile后会得到该socket的文件对象,在进行read和write时会先将数据放入缓冲区暂存,write方法对应一个发送缓冲区,将需要发送到对方的数据暂存到该缓冲区,在调用flush时才会将数据发送,当写入缓冲区满了而没有及时发送数据,发送数据没有缓存空间可用,将会发生阻塞等待。同样read方法对应一个读取缓冲区,每次从读取缓冲区中读取数据,缓冲区没有数据可读取将会发生阻塞等待。


推荐阅读
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • centos 7.0 lnmp成功安装过程(很乱)
    下载nginx[rootlocalhostsrc]#wgethttp:nginx.orgdownloadnginx-1.7.9.tar.gz--2015-01-2412:55:2 ... [详细]
  • 本文介绍了如何使用Python爬取妙笔阁小说网仙侠系列中所有小说的信息,并将其保存为TXT和CSV格式。主要内容包括如何构造请求头以避免被网站封禁,以及如何利用XPath解析HTML并提取所需信息。 ... [详细]
  • Python多线程详解与示例
    本文介绍了Python中的多线程编程,包括僵尸进程和孤儿进程的概念,并提供了具体的代码示例。同时,详细解释了0号进程和1号进程在系统中的作用。 ... [详细]
  • iOS snow animation
    CTSnowAnimationView.hCTMyCtripCreatedbyalexon1614.Copyright©2016年ctrip.Allrightsreserved.# ... [详细]
  • packagecom.panchan.tsmese.utils;importjava.lang.reflect.ParameterizedType;importjava.lang. ... [详细]
  • 本文详细介绍了 Java 网站开发的相关资源和步骤,包括常用网站、开发环境和框架选择。 ... [详细]
  • 普通树(每个节点可以有任意数量的子节点)级序遍历 ... [详细]
  • 本文详细介绍了如何使用Python的多进程技术来高效地分块读取超大文件,并将其输出为多个文件。通过这种方式,可以显著提高读取速度和处理效率。 ... [详细]
  • C#实现文件的压缩与解压
    2019独角兽企业重金招聘Python工程师标准一、准备工作1、下载ICSharpCode.SharpZipLib.dll文件2、项目中引用这个dll二、文件压缩与解压共用类 ... [详细]
  • 机器学习算法:SVM(支持向量机)
    SVM算法(SupportVectorMachine,支持向量机)的核心思想有2点:1、如果数据线性可分,那么基于最大间隔的方式来确定超平面,以确保全局最优, ... [详细]
  • 过去查询Mysql的时候,都见3306对所有端口开放着,感觉不安全。netstat -anlp | grep mysqltcp 0&am ... [详细]
  • Python 数据可视化实战指南
    本文详细介绍如何使用 Python 进行数据可视化,涵盖从环境搭建到具体实例的全过程。 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • PBO(PixelBufferObject),将像素数据存储在显存中。优点:1、快速的像素数据传递,它采用了一种叫DMA(DirectM ... [详细]
author-avatar
忠讧_136
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有