网络编程Socket介绍套接字Python中提供标准库

网络编程Socket简介

套接字套接字

Python 中提供了socket.py 标准库,尤其是底层的socket 库。

Socket是通用的网络编程套接字,与网络层没有一一对应的关系。

契约族

AF表示AddressFamily,作为socket()的第一个参数。

名字含义

AF_INET

IPv4

AF_INET6

IPv6

AF_UNIX

UnixDomainSocket,不是 Windows

套接字类型

名字含义

SOCK_STREAM

面向链接的流套接字。默认,TCP 合约

SOCK_DGRAM

未连接的数据报套接字。 UDP 合约

TCP编程(CS编程)

Socket编程需要两端,通常需要一个服务器,一个客户端称为Server,客户端称为Client

TCP 服务器

服务器编程步骤

import socket
ipaddr = ("127.0.0.1", 9999)
with socket.socket() as server:
    server.bind(ipaddr)
    server.listen()
    sl, ip = server.accept()
    data = sl.recv(1024)
    sl.send(data)

单向聊天的简单实现

import socket
ipaddr = ("127.0.0.1", 9999)
with socket.socket() as server:
    server.bind(ipaddr)
    server.listen()
    s, raddr = server.accept()     # 等待对方连接
    with s as se:
        while True:
            try:
                data = s.recv(1024)            # 获取数据 等待数据
                print('已接收到对方数据,信息如下')
                print(data.decode(encoding='gbk'))
                if data.decode('gbk') == 'exit':
                    break
                data = input('回应对方数据:')
                for i in range(2):
                    s.send(bytes(data, encoding='gbk'))                   # 回应数据
            except ConnectionResetError:
                print('对方已断开连接')
                break

问题

如果你绑定同一个端口两次会发生什么

import socket
with socket.socket as server:
    server.bind(('127.0.0.1', 9999))
    server.listen()
    s1, info = server.accept()
    with s1:
        data = s1.recv(1024)
        print(data, info)
        s1.send(b'okay1')
    s2, info = server.accept()
    with s2:
        data = s2.recv(1024)
        print(data, info)
        s2.send(b'okay2')

上面accept和recv的列表被阻塞,逐渐从被阻塞到无法工作逐渐

练习

编写群聊程序

需求分析

聊天基础是CS程序,C是每个客户端,S是服务器端。

服务器应具备的功能:

服务器对应一个类

import socket
import threading
import logging
import datetime
FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=FORMAT, level=logging.INFO)
# TCP Server
class GlobalChatServer:
    def __init__(self, ip: str = '127.0.0.1', port: int = 9999):
        self.addr = (ip, port)
        self.sock = socket.socket()
        self.clients = {}
        self.event = threading.Event()
    def start(self):
        self.sock.bind(self.addr)
        self.sock.listen()  # 服务启动
        threading.Thread(target=self.accept(), name="accept").start()
    def accept(self):
        while not self.event.is_set():
            s, raddr = self.sock.accept()  # 阻塞
            self.clients[raddr] = s
            logging.info(s)
            logging.info(raddr)
            threading.Thread(target=self.recv, name="recv", args=(s,)).start()
    def recv(self, sock):
        while not self.event.is_set():
            try:
                data = sock.recv(1024)    # 阻塞
            except Exception as e:
                logging.info(e)
                data = b'quit'
            if data == b'quit':
                self.clients.pop(sock.getpeername())
                break
            logging.info(data)
            msg = 'ack{} {} {}'.format(
                sock.getpeername(),
                datetime.datetime.now().strftime("%Y/%m/%d-%H:%M:%S"),
                data.decode()).encode('gbk')
            for s in self.clients.values():
                s.send(msg)
    def stop(self):
        for i in self.clients.values():
            i.close()
        self.sock.close()
        self.event.set()
cs = GlobalChatServer()
cs.start()
while True:
    cmd = input(">>>")
    if cmd.strip() == 'exit':
        cs.stop()
        threading.Event.wait(3)
    logging.info(threading.enumerate())

其他表示名称含义

socket.recv(bufsize[,flags])

获取数据,默认为阻塞方式

socket.recvfrom(bufsize[,flags])

获取数据,返回一个2元组(字节,地址)

socket.recv_into(buffer)[,nbytes[,flags]]

获取nbytes数据后,存入缓冲区。如果未指定 nbytes 或为 0应用编程接口和套接字,则缓冲区大小的数据存储在缓冲区中。返回接收到的字节数。

socket.recvfrom_into(buffer[,nbytes[,flags]])

获取数据并返回一个 2 元组(字节,地址)到缓冲区

socket.send(bytes[,flags])

TCP 发送数据

socket.sendall(bytes[,flags])

TCP发送所有数据,成功返回None

socket.sendfile(file,offset=0,count=None)

发送一个文件直到 EOF,使用高性能的 os.sendfile 机制,返回发送的字节数。如果win下不支持sendfile,或者不是普通文件,使用send()发送文件。偏移量告诉起始位置。 3.第 5 版开始

发送文件:

socket.makefile(mode='r', buffering=None, *, encofing=None,  errors=None, newline=None)

创建一个与socket关联的文件对象,把recv当作读应用编程接口和套接字,把send当作写

# 使用makefile
import socket
sockserver = socket.socket()
ip = '127.0.0.1'
port = 9999
addr = (ip, port)
sockserver.bind(addr)
sockserver.listen()
print('-'*30)
s, _ = sockserver.accept()
print('-'*30)
f = s.makefile(mode='rw') # 读发文件
line = f.read(10) # 阻塞等
print('-'*30)
print(line)
f.write('Return your msg:{}'.format(line))
f.flush()

以上列表不能循环消息

import socket
import threading
sockserver = socket.socket()
ip = '127.0.0.1'
port = 9999
addr = (ip, port)
sockserver.bind(addr)
sockserver.listen()
print('-'*30)
event = threading.Event()
def accept(sock: socket.socket, e: threading.Event):
    s, _ =  sock.accept()
    f = s.makefile(mode='rw')
    
    while True:
        line = f.readline()
        print(line)
        if line.strip() == 'quit':  # 注意要发quitn
            break
        f.write('Return your msg: {}'.format(line))
        f.flush()
    f.close()
    sock.close()
    e.wait(3)
t = threading.Thread(target=accept, args=(sockserver,  event))
t.start()
t.join()
print(sockserver)

名字含义

socket.getpeername()

返回连接socket的远程地址,返回值通常是一个元组(ipaddr,port)

socket.getsockname()

返回套接字自己的地址。通常是一个元组(ipaddr, port)

socket.setblocking(标志)

如果flag为0,则设置socket为非阻塞模式,否则设置socket为阻塞模式(默认)

在非阻塞模式下,如果调用recv()没有找到数据或者调用send()没有立即发送数据,会导致socket.error异常

socket.settimeout(值)

设置socket的超时时间,timeout是以秒为单位的浮点数。 None 值表示没有超时期限。一般来说,应该在第一次创建套接字时设置超时,因为它们可能与连接操作一起使用(例如 connect())

socket.setsockopt(level,optname,value)

设置套接字选项的值。例如缓冲区大小。更详细的查看相关文档,不同的系统,不同的版本不一样。

练习

使用makefile编译群聊

import socket
import threading
import logging
import datetime
FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=FORMAT, level=logging.INFO)
# TCP Server
class GlobalChatServer:
    def __init__(self, ip: str = '127.0.0.1', port: int = 9999):
        self.addr = (ip, port)
        self.sock = socket.socket()
        self.clients = {}
        self.event = threading.Event()
    def start(self):
        self.sock.bind(self.addr)
        self.sock.listen()  # 服务启动
        threading.Thread(target=self.accept(), name="accept").start()
    def accept(self):
        while not self.event.is_set():
            s, raddr = self.sock.accept()  # 阻塞
            f = s.makefile(mode='rw')
            self.clients[raddr] = f
            logging.info(f)
            logging.info(s)
            logging.info(raddr)
            threading.Thread(target=self.recv, name="recv", args=(f, raddr)).start()
    def recv(self, f, raddr):
        while not self.event.is_set():
            try:
                # data = f.recv(1024)    # 阻塞
                data = f.readline()     # string, 会等待换行符n
                logging.info(data)
            except Exception as e:
                logging.error(e)
                data = b'quit'
            if data == b'quit':
                self.clients.pop(raddr)
                break
            msg = 'ack{} {} {}'.format(
                raddr,
                datetime.datetime.now().strftime("%Y/%m/%d-%H:%M:%S"),
                data)
            for s in self.clients.values():
                f.write(msg)
                f.flush()
    def stop(self):
        for i in self.clients.values():
            i.close()
        self.sock.close()
        self.event.set()
# cmd = input(">>>")
cs = GlobalChatServer()
cs.start()
while True:
    cmd = input(">>>")
    if cmd.strip() == 'exit':
        cs.stop()
        threading.Event.wait(3)
    logging.info(threading.enumerate())

上面列出了基本功能,如果客户主动断开或者readline异常,无效的socket不会从客户中移除,可以通过异常处理来解决这个问题

全球聊天服务器

注意,这段代码是实验用的,代码还是有很多缺陷的。 Socket太低级了,实际开发中很少用到这么低级的socket。

添加一些异常处理

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 网站程序 网络编程Socket介绍套接字Python中提供标准库 https://www.wkzy.net/game/8249.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务