python网络编程

socket

socket

客户端/服务器架构
C/S(Client/Server)架构
B/S(Browser/Server)架构

OSI(Open System Interconnect)七层模型

在这里插入图片描述
在这里插入图片描述
Socket
●socket (套接字)起源于Unix , socket即是一 种特殊的文件,一些socket函数就是对其
进行的操作(读/写IO、打开、关闭)
●它是TCP/IP网络环境下应用程序与底层通信驱动程序之间运行的开发接口,它可以将应
用程序与具体的TCP/IP隔离开来,使得应用程序不需要了解TCP/IP的具体细节, 就能够
实现数据传输。
●网络应用程序中, Socket通信是基于客户端/服务器结构。
在这里插入图片描述

服务端

"""
服务器监听的方式
1.监听IP+端口
2.监听套接字 -> socket
监听文件,Server端-> socket 发送接收数据,client端 -> socket发送接收数据
Server -> socket <- Client
本地服务

服务端:
1.创建socket
2.绑定ip+port <- 客户端来连 接的时候
3.监听客户端的连按
4.接收客户端的请求-> 阻塞状态
客户端与服务端进行交互=>接收数据/发送数据
5.关闭socket

客户端:
1.创建socket
2.诖接服务端=> ip+port
客户端与服务端进行交互=>接收数据/发送数据
3.关闭socket

"""
import socket
#创建一个套接字
server = socket.socket()

#绑定监听ip和端口
#0.0.0.0表示监听本机所有ip地址
server.bind(("0.0.0.0",8888))

#监听,设置可以有多个客户端连接进来
server.listen(2)

#接收客户端,如果进行accept时,程序进入阻塞状态
#返回两个数据,一个是客户端连接,一个是ip和端口
print("start.....")
conn,addr = server.accept()
print("有一个客户端连接进来了",conn,addr)

#接收客户端数据
#python3在网络上进行传输的格式都是bytes格式的
accept_data = conn.recv(1024)
print("接收的数据为:",str(accept_data,encoding="utf8"))

#给客户端发送一个数据
conn.sendall(bytes("thankyou!",encoding="utf8"))

conn.close()
print("end......")


"""
如果服务端建立连接之后,第一件事情是recv
那么客户端第一件事情一定要send

所以在编写cs服务时,注意数据发送和接收一定要一一对应
"""


客户端

import socket
client = socket.socket()

#参数是一个元组,连接器
client.connect(("127.0.0.1",8888))
send_data = input(">>>")
client.sendall(bytes(send_data,encoding="utf8"))
accept_data = client.recv(1024)
print(str(accept_data,encoding="utf8"))
client.close()

优化-客户端和服务端进行多次数据交互

#服务端
import socket
#创建一个套接字
server = socket.socket()

#绑定监听ip和端口
#0.0.0.0表示监听本机所有ip地址
server.bind(("0.0.0.0",8888))

#监听,设置可以有多个客户端连接进来
server.listen(2)

#接收客户端,如果进行accept时,程序进入阻塞状态
#返回两个数据,一个是客户端连接,一个是ip和端口
print("start.....")
conn,addr = server.accept()
print("有一个客户端连接进来了",conn,addr)

while True:
    #接收客户端数据
    #python3在网络上进行传输的格式都是bytes格式的
    accept_data = conn.recv(1024)
    print("接收的数据为:",str(accept_data,encoding="utf8"))
    if not accept_data or accept_data == b"bye":
        print("客户端要求断开")
        break
    #给客户端发送一个数据
    conn.sendall(bytes("thankyou!",encoding="utf8"))

conn.close()
server.close()
print("end......")
#客户端
import socket
client = socket.socket()

#参数是一个元组,连接器
client.connect(("127.0.0.1",8888))
while True:
    send_data = input(">>>")
    if not send_data.strip():
        continue
    if send_data == "bye":
        break
    client.sendall(bytes(send_data,encoding="utf8"))
    accept_data = client.recv(1024)
    print(str(accept_data,encoding="utf8"))
client.close()

优化-连接多个客户端

#服务端
import socket
#创建一个套接字
server = socket.socket()

#绑定监听ip和端口
#0.0.0.0表示监听本机所有ip地址
server.bind(("0.0.0.0",8888))

#监听,设置可以有多个客户端连接进来
server.listen(2)

#接收客户端,如果进行accept时,程序进入阻塞状态
#返回两个数据,一个是客户端连接,一个是ip和端口
print("start.....")
while True:
    conn,addr = server.accept()
    print("有一个客户端连接进来了",conn,addr)

    while True:
        #接收客户端数据
        #python3在网络上进行传输的格式都是bytes格式的
        accept_data = conn.recv(1024)
        print("接收的数据为:",str(accept_data,encoding="utf8"))
        if not accept_data or accept_data == b"bye":
            print("客户端要求断开")
            break
        #给客户端发送一个数据
        conn.sendall(bytes("thankyou!",encoding="utf8"))

    conn.close()
server.close()
print("end......")
#客户端
import socket
client = socket.socket()

#参数是一个元组,连接器
client.connect(("127.0.0.1",8888))
while True:
    send_data = input(">>>")
    if not send_data.strip():
        continue
    if send_data == "bye":
        break
    client.sendall(bytes(send_data,encoding="utf8"))
    accept_data = client.recv(1024)
    print(str(accept_data,encoding="utf8"))
client.close()

socket-tcp编程

●socket ( )
s = socket.socket(socket_ family,socket type,protocal=0)
socket
family:
socket.AF
INET IPv4 (默认)
eP)
sockct.AF_ INET6 IPv6
socket.AF_ UNIX 只能够用于单一-的Unix系统进程间通信
socket_ type:
socket.SOCK_ STREAM 流式socket , tor TCP (默认)
socket.SOCK_ DGRAM 数据报式socket,forUDP
socket.SOCK_ RAW原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_ RAW可以;其次,SOCK_ RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_ _HDRINCL套 接字选项由用户构造IP头。
config.py

addr = ("0.0.0.0",8888)

server.py

import socket
import config

with socket.socket(socket.AF_INET,socket.SOCK_DGRAM) as server:
    server.bind(config.addr)
    print("start")
    while True:
        data = server.recv(1024)
        print(data)

client.py

import socket
import config
with socket.socket(socket.AF_INET,socket.SOCK_DGRAM) as client:
    while True:
        data =bytes(input(">>>"),encoding="utf8")
        if data == "exit":
            break
        client.sendto(data,config.addr)

TCP与UDP的区别
TCP =>面向连按,有状态的。确保数据完螯性
UDP =>面向无洼按。有可能会丢包
TCP服务=>需要先启动服务端,再劣客户端连接
ConnectionRefusedError: [Errno 111] Connection refused
ConnectionRefusedError: [WinError 10061]由于日标计算札积极拒绝,无法连接。
UDP => 可以直接启动客户端。不报错,但是肯定也是无法发送数据给服务端的。
可能的原囚:
1.网络连通性=> 192.168.0.110 => ping
2.端口连通性=> telnet
|通」, 马上会断开连接=>服务内部白名单
3.端不通?
a. selinux => Disable
b. firewalld/iptables
c.服务是否启动
d.监听地端几是不是正确

粘包现象

●只有基于tcp的socket
●粘包:发送方发送两个字符串”hello" +”world" ,接收方却一次性接收到
了”helloworld"
●为什么会产生粘包?
应用程序所看到的数据是一个流( stream) , 一条消息有多少字节对应用程序是不可见
的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。
根本原因:两端互相不知道对方发送数据的长度

server.py

import socket
import config
with socket .socket() as server:
    server.bind(config . addr)
    server.listen(2)
    print("start......")
    conn, addr = server. accept( )
    print("有一个客户端连接进米了",conn, addr)
    accept_data = conn. recv(1024)
    print("接收到的数据为:",str(accept_data, encoding="utf-8"))
    accept_data = conn . recv(1024)
    print("接收到的数据为:", str(accept_data, encoding="utf-8"))
    print("end......")

client.py

import socket
import config
with socket.socket() as client:
    client.connect(config. addr)
    client.sendall(bytes("hello",encoding="utf-8"))
    client.sendall(bytes("world",encoding="utf-8"))

解决

"""
TCP粘包的产生
TCP是数据是Stream,一条消息有多少个字节,程序不可知的
发送的时候,两条消息间隔很近
接收的时候,不知道消息的长度,于是就两条消息当作一条消息按收了。
sendall + input + sendall=>不粘包
sendall + print + sendll =〉粘包

如何解决?
1.sendall + time.sleep + sendLL =>不粘包
2.send + recv + send => 
"""
#方式一
import socket
import config
with socket.socket() as client:
    client.connect(config. addr)
    a = input(">>>")
    client.sendall(bytes(a,encoding="utf-8"))
    b = input(">>>")
    client.sendall(bytes(b,encoding="utf-8"))

#方式二
#client.py
import socket
import config
with socket.socket() as client:
    client.connect(config. addr)
    client.sendall(bytes("hello",encoding="utf-8"))
    client.recv(1024)
    client.sendall(bytes("world",encoding="utf-8"))
#server.py
import socket
import config
with socket .socket() as server:
    server.bind(config.addr)
    server.listen(2)
    print("start......")
    conn, addr = server.accept()
    print("有一个客户端连接进米了",conn, addr)
    accept_data = conn.recv(1024)
    print("接收到的数据为:",str(accept_data, encoding="utf-8"))

    conn.sendall(b"ok")
    accept_data = conn.recv(1024)
    print("接收到的数据为:", str(accept_data, encoding="utf-8"))
    print("end......")

ftp服务器
"""
1.启动ftp服务器
2.连接ftp服务器
3.client => filename_path /tmp/a.jpg
    服务端读本地文件内容,发送给客户端
    客户端保存服务器发送过来的内容
4.在本地文件能打开
能为一个客户端提供多次服务即可
"""
"""
服务端
"""
import os
import socket
import config

with socket.socket() as server:
    server.bind(config.ftp_addr)
    server.listen(2)

    while True:
        conn , addr = server.accept()
        print("客户端连接过来了~",addr,conn)

        #为客户端提供服务
        while True:
            file_path = conn.recv(1024)
            if not file_path:break
            if not os.path.exists(file_path):
                conn.sendall("文件目录不存在".encode("utf8"))
                continue
            print("开始传输",file_path)
            with open(file_path,"rb") as f:
                file_data = f.read()
                conn.sendall(str(len(file_data)).encode("utf8"))
                conn.recv(1024)
                conn.sendall(file_data)



"""
ftp客户端
"""

import os
import socket
import config
with socket.socket() as client:
    client.connect(config.ftp_addr)
    #用户可以多次下载文件
    while True:
        data = input("请输入您要下载的文件路径---exit退出")
        if not data.strip():continue
        if data == "exit": break

        filename = os.path.split(data)[-1]
        #将要下载的文件路径发送到服务器
        client.send(bytes(data,encoding="utf-8"))

        #接收服务器发送过来的数据(思考:如果接收的文件数据特别大怎么办?服务端发送数据时,告诉我们文件大小是多少)
        file_length = client.recv(1024)
        client.sendall(b"ok")
        file_data = b""
        while True:
            file_data += client.recv(1024)
            if len(file_data) >= int(file_length):break

        with open(filename,"wb") as f:
            f.write(file_data)


ftp服务器-多客户端
"""
服务端
"""
import os
import socket
import threading

import config


def worker(conn,addr):
    print("客户端连接过来了~",addr,conn)

    #为客户端提供服务
    while True:
        file_path = conn.recv(1024)
        if not file_path:break
        if not os.path.exists(file_path):
            conn.sendall("文件目录不存在".encode("utf8"))
            continue
        print("开始传输",file_path)
        with open(file_path,"rb") as f:
            file_data = f.read()
            conn.sendall(str(len(file_data)).encode("utf8"))
            conn.recv(1024)
            conn.sendall(file_data)



with socket.socket() as server:
    server.bind(config.ftp_addr)
    server.listen(2)

    while True:
        conn , addr = server.accept()
        t = threading.Thread(target=worker,args=(conn,addr))
        t.start()


"""
ftp客户端
"""
import os
import socket
import config
with socket.socket() as client:
    client.connect(config.ftp_addr)
    #用户可以多次下载文件
    while True:
        data = input("请输入您要下载的文件路径---exit退出")
        if not data.strip():continue
        if data == "exit": break

        filename = os.path.split(data)[-1]
        #将要下载的文件路径发送到服务器
        client.send(bytes(data,encoding="utf-8"))

        #接收服务器发送过来的数据(思考:如果接收的文件数据特别大怎么办?服务端发送数据时,告诉我们文件大小是多少)
        file_length = client.recv(1024)
        client.sendall(b"ok")
        file_data = b""
        while True:
            file_data += client.recv(1024)
            if len(file_data) >= int(file_length):break

        with open(filename,"wb") as f:
            f.write(file_data)


socket-udp编程

select

linux io模型

阻塞io
买一件事情的时候,等待回复/资源,如果没有得到回复资源,只会等待,其他任何事情都不做
买票=>老王去火车站买票=>排队->一直等->轮到他
烧水=>烧一壶谁=>等水开(在水开之前,什么都不做)

非阻塞io
做一件事的时候,等待回复,先去干一件其他的事情,等一下再来问情况
买票 => 老王去火车站买票->排队看一下->隔N个小时再来看一下
烧水=>烧一壶谁=>等5分钟去看一眼是不是开了

i0多路复用
nginx的并发,如果一个请求i/o,开启一个进程/线程处理这个请求
如果一下就进来2万个请求,是否直接开启2w个线程/进程处理请求
i/o多路复用模型=> 一个线程通过记录i/o状态来同时管理多个i/o,提升服务器的吞吐能力
一个线程管理1024个连接i/o,2万个20个线程
select,poll,epoll

信号驱动io
买一件事情的时候,等待回复/资源,如果没有得到回复资源,先去干别的,第有资源,会通知
买票->留个信息给管理员->有票->打电话通知->去火车站交钱买票

异步io
买一件事情的时候,等待回复/资源,如果没有得到回复资源,先去干别的,第有资源,自动调用并完成接下来的任务
买票->留个信息给管理员->有票->打电话通知->把票寄给你

io多路复用

Select和poll的区别(select有数量的限制1024,poll没有数量的限制)
select-> 1024个fd ->对所有的描述符进行遍历->找出就绪转态的fd
换尿布=>select=>1024=>定期去检查每个baby是否尿片是不是湿了=>找到所有尿布湿了的小孩
找人=>select=>问每一个人

epoll->等通知型
换尿布=>select=>1024=>高级尿布,感应器=>如果湿了,响…=>去给他换

select/poll:
1、当有io就绪,select会遍历所有的fd,来找到就绪fd
2、每次调用select(), 都会把所有的fd_ set从用户态拷贝到内核态
3、select所监控的fd_ set有限制,一般来说最多 只能监听1024个。
4、poll跟select实现类似,只是poll 没有fd监控数量限制

epoll:
1、没有并发连接的限制
2、效率提升不是采用轮询方式,而是回调(有结果通种)
3、fd在整个过程中只拷贝-次

select模块

Python中的select模块专注于I/O多路复用,提供了select、poll、
epoll三个方法
poll epollI在linux中可用 , windows仅支持select。

进程指定内核监听哪些文件描述符(最多监听1024个fd)的哪些事件,当没有文件描述符事件发生
时,进程被阻塞;
当一个或者多个文件描述符事件发生时1进程被唤醒。
文件描述符:
对于Linux而言,所有对设备或文件的操作都是通过文件描述符进行的。

select方法用来监视文件描述符(当文件描述符条件不满足时, select会阻塞) ,当某个文件描述符状态改变
后,会返回三个列表。
fd_ r_ list, fd_ w_ list, fd_ e_ list = select.select(rlist, wlist, xlist, [timeout])
参数:可接受四个参数(前三个必须)
rlist: wait until ready for reading (所有的输入的data,就是指外部发过来的数据)
当序列中的fd满足“可读”条件时,则获取发生变化的fd并添加到fd r_ list中
wlist: wait until ready for writing ( 监控和接收所有要发出去的data )
●当参数2序列中含有fd时,则将该序列中所有的fd添加到fd_ w_ list中
xlist: wait for an “exceptional condition” ( 监控错误信息)
●当参数3序列中的fd发生错误时,则将该发生错误的fd添加到fd_ e list中
三创教育
timeout:超时时间。当超时时间为空 ,则select会-直阻塞,直到监听的句柄发生变化

小案例

"""
select-server
"""
import socket
import select
import config

with socket.socket() as server:
    server.bind(config.addr)
    server.listen(5)
    rlist = [server]

    while True:
        #当我们执行到这来,程序就会阻塞,等待rlist就绪
        rl , _ , _ = select.select(rlist,[],[])

        #遍历所有操作fd
        for fd in rl:
            #如果fd是server=>有新的连接过来
            if fd == server:
                conn,addr = server.accept()
                print("有客户端连接过来了")

                #将客户端加入到监听列表
                rlist.append(conn)

                #接收客户端发送的数据,并直接返回
                msg = conn.recv(1024)
                conn.sendall(msg)
            else:
                #已经建立连接的客户端
                msg = fd.recv(1024)
                if not msg:
                    rlist.remove(fd)
                    fd.close()
                else:
                    fd.sendall(msg)

"""
select-client
"""
import socket
import config

with socket.socket() as client:
    client.connect(config.addr)
    while True:
        data = input("请输入要发送的数据(exit):")
        if not data.strip():continue
        elif data.strip() == "exit":break
        client.sendall(bytes(data,encoding="utf8"))
        print("recv:",client.recv(1024))

解决bug

"""
select-server
"""
import socket
import select
import config
import threading

def newclient(conn):
    # 接收客户端发送的数据,并直接返回
    msg = conn.recv(1024)
    conn.sendall(msg)

def oldclient(fd):
    # 已经建立连接的客户端
    msg = fd.recv(1024)
    if not msg:
        rlist.remove(fd)
        fd.close()
    else:
        fd.sendall(msg)

with socket.socket() as server:
    server.bind(config.addr)
    server.listen(5)
    rlist = [server]

    while True:
        #当我们执行到这来,程序就会阻塞,等待rlist就绪
        rl , _ , _ = select.select(rlist,[],[])

        #遍历所有操作fd
        for fd in rl:
            #如果fd是server=>有新的连接过来
            if fd == server:
                conn,addr = server.accept()
                print("有客户端连接过来了")

                #将客户端加入到监听列表
                rlist.append(conn)
                t1 = threading.Thread(target=newclient,args=(conn,))
                t1.start()
            else:
                t2 = threading.Thread(target=oldclient,args=(fd,))
                t2.start()


select与poll
1、select采用轮询的方式遍历所有fd ,监控对应事件是否发生
2、select需要在用户空间与内核空间频繁拷贝fd
3、 select最大仅仅支持1024个文件描述符
4、poll与select相差不大, poll无文件描述符监听限制。

epoll
1、注册新事件时,就将fd拷贝进内核,每个fd在整个过程中只会拷贝一次。
2、为每个fd指定一个回调函数 ,当设备就绪,调用这个回调函数。
3、epoll对文件描述符没有额外限制

epoll

select.epoll()创建epoll对象
epoll.close()关闭epoll对象的文件描述符
epoll.closed检测epol对象是否关闭
epoll.fileno()返回epoll对象的文件描述符
epoll.register(fd[, eventmask])向epoll对象中注册fd和对应的事件
epoll.unregister(fd)取消注册
事件:
EPOLLIN Available for read可读状态符为1
EPOLLOUT Available for write可写状态符为4
EPOLLERR Error condition happened on the assoc. fd发生错误状态符为8

"""epoll-server"""
import socket
import select
import config

with socket.socket() as server:
    server.bind(config.addr)
    server.listen(5)

    #创建一个epoll对象
    epoll_obj = select.epoll()

    # 将server注册带epoll_obj,监听读事件
    epoll_obj.register(server,select.EPOLLIN)
    # 存放所有的客户端信息
    connections = {}
    while True:
        # events 就绪的对象
        events = epoll_obj.poll()
        for fd,event in events:
            print(fd,event)

            #区分新连接和已经建立的连接
            #新连接
            if fd == server.fileno():
                conn,addr = server.accept()
                connections[conn.fileno()] = conn
                epoll_obj.register(conn,select.EPOLLIN)
                msg = conn.recv(1024)
                conn.sendall(msg)

            #已经建立的连接
            else:
                conn = connections[fd]
                msg = conn.recv(1024)
                # 客户端断开连接,从epoll中取消注册,并断开连接
                if not msg:
                    epoll_obj.unregister(fd)
                    conn.close()
                    del connections[fd]
                else:
                    conn.sendall(msg)

"""
epoll-client
"""
import socket
import config

with socket.socket() as client:
    client.connect(config.addr)
    while True:
        data = input("请输入要发送的数据(exit):")
        if not data.strip():continue
        elif data.strip() == "exit":break
        client.sendall(bytes(data,encoding="utf8"))
        print("recv:",client.recv(1024))

socketserver

SocketServer内部使用I0多路复用以及“多线程” 和“多进程”, 从而实现并发处理多个客户端请求的Socket服务端。
即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者"进程”专门负责处理当前客户端的所有请求。
socketserver将socket模块和select模块进行了封装,简化网络服务器版的开发。
socketserver最主要的作用:就是实现一个并发处理。

SocketServer类

四个比较主要的类,其中常用的是TCPServer和UDPServer
1、 TCPServer
2、UDPServer
3、UnixStreamServer,类似于TCPServer提供面向数据流的套接字连接,但是旨在UNIX平台上可用;
4、UnixDatagramServer,类似于UDPServer提供面向数据报的套接字连接,但是旨在UNIX平台上可用;
两种支持异步处理的类:
1、ForkingMixIn ,为每一个客户端请求派生一个新的进程去专门处理;
2、ThreadingMixIn ,为每一个客户端请求派生一 个新的线程去专门处理;
继承自这两个类型的服务端在处理新的客户端连接时不会阻塞,而是创建新的进/线程专门处理安户端的请求.

创建SocketServer

1、创建一一个请求处理类)继承BaseRequestHandlerclass类并且重写父类的handle()方
法,该方法将处理传入的请求。
2、必须实例化-个上面类型中的一个类(如TCPServer )传递服务器的地址和你上面创建
的请求处理类给这个TCPServer。
3、调用handle_ request()或者serve_ forever()方法来处理一个或多个请求
4、调用server close()关闭socket

# socketserver-server
import socketserver
import config
class MyRequestHandler(socketserver.BaseRequestHandler):
    """'这个类的功能-> 每一个请求过来, 应该如何处理"""
    def handle(self):
        """核心部分:处理接收的数据"""
        # self.request =>类定义的方法=>表示客户端的请求
        while True:
            data = self.request.recv(1024)
            print(data)
            if not data: break
            # 发送数据回去
            self.request.sendall(data)

if __name__ == "__main__":
    # 一次只能处理一个客户端的请求! !因为这里并没有使用多进程或多线程
    # server = socketserver . TCPServer(config. addr, MyRequestHandLer)
    # 一次处理qqr个客户端的请求! !因为这里并没有使用多进程或多线程
    # server = socketserver.TCPServer(config.addr,MyRewuestHandler)
    server = socketserver.ThreadingTCPServer(config.addr,MyRequestHandler)
    print("start...")
    server.serve_forever()
    server.server_close()

# socketserver-client
import socket
import config
with socket.socket() as client:
    client.connect(config.addr)
    while True:
        data = input(">>>")
        if not data:continue
        if data == "exit":break
        client.sendall(bytes(data,encoding="utf8"))
        recv = client.recv(1024)
        print(str(recv,encoding="utf8"))
    client.close()

案例-聊天

"""
服务器=>启动=>
1.启动服务并监听
2.服务端接收到用户连接
3.接收用户数据
=> @setnome =》返回欢迎您, XXX
=》clientf 与client2通信=>保存起来=> {username: username-socket} =>实例属性/类局烟
=》@username =>发送消息给username => 难点?
=》username => username-socket =》socket.send(消息内容)
=> 如果username找不到对应的socket, 给自己回一个消息-> (当前用户不存在)
=如果断开连接,那么从字典中删除
"""


import socketserver
import config

class MyRequestHandler(socketserver.BaseRequestHandler):
    """这个类的功能 -> 每一个请求过来,应该如何处理"""
    socket_dict = dict()
    def handle(self):
        """核心部分:处理接收的数据"""
        print("一个新客户端连接来啦!")
        while True:
            print("当前所有的用户有", self.socket_dict)
            # self.request => 类定义的方法 => 表示客户端的请求
            data = str(self.request.recv(1024), encoding="utf-8")
            if not data: break
            print("接收到的数据是:", data)
            if data.startswith("@setname"):
                print("设置用户名") # "@setname lxl abc"
                username = ''.join(data.split()[1:])
                self.request.sendall(bytes(f"欢迎你!{username}!", encoding="utf-8"))
                # 设置当前用户名 => 动态为请求添加了一个socket
                self.username = username
                self.add_client(username, self.request)
            elif data.startswith("@"):
                # "@lihu xxxx xxx2"
                to = data.split()[0][1:]
                message = " ".join(data.split()[1:])
                if to in self.socket_dict:
                    # {"ryc": socket, "lxl": socket}
                    print(f"给{to}发消息")
                    self.socket_dict[to].sendall(bytes(message, encoding="utf-8"))
                    self.request.sendall(bytes("", encoding="utf-8"))
                else:
                    self.request.sendall(bytes(f"您聊天的对象({to})不存在", encoding="utf-8"))
            else:
                self.request.sendall(bytes(f"数据有误!{data}", encoding="utf-8"))

    @classmethod
    def add_client(cls, username, client):
        cls.socket_dict[username] = client
        print("当前所有的用户有",cls.socket_dict)

    @classmethod
    def remove_client(cls, username):
        cls.socket_dict.pop(username)

    def finish(self):
        """一个客户端断开"""
        if hasattr(self, "username"):
            self.remove_client(self.username)
            print(f"{self.username}退出了~")


if __name__ == "__main__":
    # 一次只能处理一个客户端的请求!!因为这里并没有使用多进程或多线程
    # server = socketserver.TCPServer(config.addr, MyRequestHandler)
    # 同时处理多个客户端的请求!
    server = socketserver.ThreadingTCPServer(config.addr, MyRequestHandler)
    print("start....")
    server.serve_forever()
    server.server_close()
"""
客户端=>启动
1.连接服务器
2.输入你的名字发送给服务端 => @setname x0x
3. 与谁聊天(呢称-》Lihu) => @Lihu infomation
4.如果输入的数据是=> @exit =>表示要断开连接
"""
import socket
import config

with socket.socket() as client:
    client.connect(config.addr)
    while True:
        data = input(">>>")
        if not data: continue
        if data == "@exit": break
        client.sendall(bytes(data, encoding="utf-8"))
        recv = client.recv(1024)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值