Python:网络编程TCP、MackfFile、聊天编写

目录

Socket介绍

TCP编程

TCP服务端编程【服务端编程步骤】

 练习----写一个群聊程序

Socket常用方法

MakeFile

ChatServer实验用完整代码


​​​​​​​Socket介绍

socket套接字:Python中提供socket.py标准库,非常底层的接口库
Socket是一种通用的网络编程接口,和网络层次没有一一对应的关系
Socket文件本身就是传输用, 传输的话就要有一个东西来缓冲,说到地就是BUffer,BUffer其实就是个队列

协议族:AF表示Address Family,用于socket() 第一个参数

名称含义
AF_INETIPV4
AF_INET6IPV6
AF_UNIXUnix Domain Socket,window没有

Socket类型

名称含义
SOCK_STREAM面向连接的流套接字。默认值,TCP协议
SOCK_DGRAM无连接的数据报文套接字。UDP协议

TCP编程

Socket编程,需要两端,一般来说需要一个服务端,一个客户端,服务端称为Server,客户端称为Client

TCP服务端编程【服务端编程步骤】

  • 创建Socket对象
  • 绑定IP地址Address和端口Port。bind()方法;IPv4地址为一个二元组("IP地址字符串",Port)
  • 开始监听,将在指定的IP的端口上监听。listen()方法
  • 获取用户传送数据的Socket对象
    • socket.accept()     ---->  (socket objcet,address info);accept方法阻塞等待客户端建立连接,返回一个新的Socket对象和客户端地址的二元组,地址是远程客户端的地址,IPv4中它是一个二元组(clientaddr,port)
    • 接受数据:recv(bufsize,[, flags])  使用缓冲区接收数据
    • 发送数据:send(bytes)   发送数据

问题:两次绑定同一个监听端口会怎么样?

  • accept,recv是阻塞的,主线程经常被阻塞而不能工作
import logging
import socket
import time
#-*- coding:utf-8 -*-


FORMAT='%(asctime)-15s\t [%(processName)s:%(threadName)s,%(process)d:%(thread)8d] %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)

socket_ob = socket.socket()   #创建socket对象
socket_ob.bind(('127.0.0.1',9999))   #创建一个二元组
socket_ob.listen()   #开始监听
#开启一个连接
socket_cob,info=socket_ob.accept()  #阻塞直到和客户端成功建立连接,返回一个socket对象和客户端地址

#适用一个缓冲区获取数据
data_buff = socket_cob.recv(1024)   #1024的倍数
logging.info(data_buff.decode("gbk"))
socket_cob.send(b"day day up up")

#开启另外一个连接
socket_cob2 ,info =socket_ob.accept()
data_buff2 =socket_cob2.recv(1024)
logging.info(data_buff2.decode("gbk"))
socket_cob2.send(b"hello python")

socket_ob.close()


结果:
2021-06-28 01:11:57,044	 [MainProcess:MainThread,28864:   28184] 我有来了
2021-06-28 01:11:57,044	 [MainProcess:MainThread,28864:   28184] 我来了


结果:


 练习----写一个群聊程序

需求分析:

  • 聊天工具是CS程序,C是每一个客户端,S是服务端
  • 服务器应具有的功能
    • 启动服务,包括绑定端口,监听
    • 建立连接,能和多个客户端建立连接
    • 接受不同用户的信息
    • 分发,将接受的某个用户的信息转发到已连接的所有客户端
    • 停止分发
    • 记录连接的客户端

代码实现

1、基本框架实现

class ChatServer:

    def __init__(self):

    def start(self):
        pass

    def accept(self):
        pass

    def recv(self):
        pass

    def stop(self):
        pass

2、扩展代码,实现基本功能

import socket
import threading
import logging
import datetime


FORMAT ='%(asctime)s %(thread)d %(message)s'
logging.basicConfig(level=logging.INFO)

class ChatServer:

    def __init__(self,ip='127.0.0.1',port=9999):  #启动服务
        self.socket_server =socket.socket()
        self.addr_server = (ip,port)
        self.client = {}   #客户端


    def start(self):  #启动监听
        self.socket_server.bind(self.addr_server)  #启动服务
        self.socket_server.listen()                 #监听服务
        #accept会阻塞主线程,所以开一个新线程
        threading.Thread(target=self.accept,name="accept").start()


    def accept(self):   #多人连接
        while True:
            client_socket ,client=self.socket_server.accept()  #阻塞
            self.client[client] = client_socket  #添加到客户端字典
            #准备接受数据,recv 是阻塞的,开启新的线程
            threading.Thread(target=self.recv,args=(client_socket,client),name="recv").start()


    def recv(self,clent_socket:socket.socket,client):
        while True:
            recv_data = clent_socket.recv(1024)  #阻塞接受
            recv_msg = '{:%Y%m%d %H:%M:%S} {}:{}\n{}\n  '.format(datetime.datetime.now(),*client,recv_data.decode("gbk"))
            logging.info(recv_msg)
            logging.info(threading.enumerate())
            recv_msg_en = recv_msg.encode()
            for s in self.client.values():
                s.send(recv_msg_en)

    def stop(self):
        for s in self.client.values():
            s.close()
        self.socket_server.close()


cs = ChatServer()
cs.start()

结果:
D:\Python3.7.5\python.exe D:/rsa_code/tvp_chart_server.py
INFO:root:20210703 17:45:39 127.0.0.1:52012
no1
  
INFO:root:[<_MainThread(MainThread, stopped 7288)>, <Thread(accept, started 5584)>, <Thread(recv, started 10264)>]
INFO:root:20210703 17:45:58 127.0.0.1:52013
no2
  
INFO:root:[<_MainThread(MainThread, stopped 7288)>, <Thread(accept, started 5584)>, <Thread(recv, started 10264)>, <Thread(recv, started 25544)>]
INFO:root:20210703 17:48:18 127.0.0.1:52034
no3
  
INFO:root:[<_MainThread(MainThread, stopped 7288)>, <Thread(accept, started 5584)>, <Thread(recv, started 10264)>, <Thread(recv, started 25544)>, <Thread(recv, started 6136)>]
INFO:root:20210703 17:49:31 127.0.0.1:52034

 中途有一个客户端退出,所有的都会报错
INFO:root:[<_MainThread(MainThread, stopped 7288)>, <Thread(accept, started 5584)>, <Thread(recv, started 10264)>, <Thread(recv, started 25544)>, <Thread(recv, started 6136)>]
Exception in thread recv:
Traceback (most recent call last):
  File "D:\Python3.7.5\lib\threading.py", line 926, in _bootstrap_inner
    self.run()
  File "D:\Python3.7.5\lib\threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "D:/rsa_code/tvp_chart_server.py", line 35, in recv
    recv_data = clent_socket.recv(1024)  #阻塞接受
ConnectionAbortedError: [WinError 10053] 你的主机中的软件中止了一个已建立的连接。

INFO:root:20210703 17:49:43 127.0.0.1:52012
no1
  
INFO:root:[<_MainThread(MainThread, stopped 7288)>, <Thread(accept, started 5584)>, <Thread(recv, started 10264)>, <Thread(recv, started 25544)>]
Exception in thread recv:
Traceback (most recent call last):
  File "D:\Python3.7.5\lib\threading.py", line 926, in _bootstrap_inner
    self.run()
  File "D:\Python3.7.5\lib\threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "D:/rsa_code/tvp_chart_server.py", line 41, in recv
    s.send(recv_msg_en)
ConnectionAbortedError: [WinError 10053] 你的主机中的软件中止了一个已建立的连接。

3、基本功能的完成, 但是有问题,适用Event改进【用Evetn确保程序退出的时候,全部退出】

import socket
import threading
import logging
import datetime


FORMAT ='%(asctime)s %(thread)d %(message)s'
logging.basicConfig(level=logging.INFO)

class ChatServer:

    def __init__(self,ip='127.0.0.1',port=9999):  #启动服务
        self.socket_server =socket.socket()
        self.addr_server = (ip,port)
        self.client = {}   #客户端
        self.event=threading.Event()

    def start(self):  #启动监听
        self.socket_server.bind(self.addr_server)  #启动服务
        self.socket_server.listen()                 #监听服务
        #accept会阻塞主线程,所以开一个新线程
        threading.Thread(target=self.accept,name="accept").start()


    def accept(self):   #多人连接
        while not self.event.is_set():
            client_socket ,client=self.socket_server.accept()  #阻塞
            self.client[client] = client_socket  #添加到客户端字典
            #准备接受数据,recv 是阻塞的,开启新的线程
            threading.Thread(target=self.recv,args=(client_socket,client),name="recv").start()


    def recv(self,clent_socket:socket.socket,client):
        while not self.event.is_set():
            recv_data = clent_socket.recv(1024)  #阻塞接受
            recv_msg = '{:%Y%m%d %H:%M:%S} {}:{}\n{}\n  '.format(datetime.datetime.now(),*client,recv_data.decode("gbk"))
            logging.info(recv_msg)
            logging.info(threading.enumerate())
            recv_msg_en = recv_msg.encode()
            for s in self.client.values():
                s.send(recv_msg_en)

    def stop(self):
        for s in self.client.values():
            s.close()
        self.socket_server.close()
        self.event.set()


cs = ChatServer()
cs.start()

while True:
    cmd = input(">>>").strip()
    if cmd == "quit":
        cs.stop()
        threading.Event().wait(3)
        break

4、这一版本基本能用了,测试通过,但是还需要完善的地方:增加客户端退出命令

  • 例如:各种异常的处理,客户端断开后字典中的移除客户端数据等
  • 客户端主动断开带来的问题
  • 服务端知道合适断开,如果客户端断开,服务器不知道
  • 所以好的做法是,客户端开发出特殊消息通知服务器端,断开连接,但是,如果客户端主动断开,服务端主动发出空的消息,超时发出异常,捕获异常并处理异常
  • 即使为客户端提供断开命令,也不能保证客户端会使用它断开连接,但是还是要增加这个退出功能
  • 程序还有瑕疵,但是业务能力基本完成了​​​​​​​
import socket
import threading
import logging
import datetime


FORMAT ='%(asctime)s %(thread)d %(message)s'
logging.basicConfig(level=logging.INFO)

class ChatServer:

    def __init__(self,ip='127.0.0.1',port=9999):  #启动服务
        self.socket_server =socket.socket()
        self.addr_server = (ip,port)
        self.client = {}   #客户端
        self.event=threading.Event()

    def start(self):  #启动监听
        self.socket_server.bind(self.addr_server)  #启动服务
        self.socket_server.listen()                 #监听服务
        #accept会阻塞主线程,所以开一个新线程
        threading.Thread(target=self.accept,name="accept").start()


    def accept(self):   #多人连接
        while not self.event.is_set():
            client_socket ,client=self.socket_server.accept()  #阻塞
            self.client[client] = client_socket  #添加到客户端字典
            #准备接受数据,recv 是阻塞的,开启新的线程
            threading.Thread(target=self.recv,args=(client_socket,client),name="recv").start()


    def recv(self,clent_socket:socket.socket,client):
        while not self.event.is_set():
            recv_data = clent_socket.recv(1024).decode("gbk").strip()  #阻塞接受

            #客户端退出
            if recv_data == "quit":
                self.client.pop(client)
                clent_socket.close()
                logging.info("{} quits".format(client))
                break
            recv_msg = '{:%Y%m%d %H:%M:%S} {}:{}\n{}\n'.format(datetime.datetime.now(),*client,recv_data)
            logging.info(recv_msg)
            logging.info(threading.enumerate())
            for s in self.client.values():
                s.send(recv_msg.encode())

    def stop(self):     #停止服务
        for s in self.client.values():
            s.close()
        self.socket_server.close()
        self.event.set()


cs = ChatServer()
cs.start()

while True:
    cmd = input(">>>").strip()
    if cmd == "quit":
        cs.stop()
        threading.Event().wait(3)
        break
    logging.info(threading.enumerate()) #用来观察断开后线程的变化

结果:
D:\Python3.7.5\python.exe D:/rsa_code/tvp_chart_server.py
>>>INFO:root:20210703 22:31:36 127.0.0.1:62110
no1

INFO:root:[<_MainThread(MainThread, started 24548)>, <Thread(accept, started 5840)>, <Thread(recv, started 26420)>]
INFO:root:20210703 22:31:46 127.0.0.1:62112
no2

INFO:root:[<_MainThread(MainThread, started 24548)>, <Thread(accept, started 5840)>, <Thread(recv, started 26420)>, <Thread(recv, started 24008)>]
INFO:root:('127.0.0.1', 62112) quits
INFO:root:20210703 22:32:13 127.0.0.1:62113
no3

INFO:root:[<_MainThread(MainThread, started 24548)>, <Thread(accept, started 5840)>, <Thread(recv, started 26420)>, <Thread(recv, started 26196)>]
INFO:root:('127.0.0.1', 62113) quits

Socket常用方法

名称含义
socket.recv(bufsize [ ,flags]) 获取数据。默认是阻塞方式
socket.recvfrom(bufsize[, flags])获取数据,返回一个二元组(bytes,address)
sockt.recv_into(buffer[,nbytes[,flags]])获取到nbytes的数据后,存储到buffer中,如果nbytes没有指定或0,将buffer大小的数据存入buffer中,返回接受到字节数
socket.recvfrom_into(buffer[,nbytes[,flags]])获取数据,返回一个二元组(bytes,addres)到buffer中
socket.send(bytes,[, flags])TCP发送数据
socket.sendall(bytes[,flags])TCP发送全部数据,成功返回None
socket.sendto(string[,flag],address)UDP发送数据
socket.sendfile(file,offset=0,count=None)发送一个文件直到EOF,适用高性能的os.sendfile机制,返回发送的字节数,如果win下不支持sendfile,或者不是普通文件,使用send() 发送文件,offset高速起始位置,。3.5版本开始
socket.getpeername()返回连接套接字的远程地址,返回值通常是元组(ipaddr,port)
socket.getsockname()     返回套接字自己的地址,通常是一个元组(ipaddr,port)
socket.setblocking(flag)

如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)

非阻塞模式下,如果调用recv() 没有发现任何数据,或send() 调用无法立即发送数据,那么将引起socket.error异常

socket.settimeout(value)设置套接字的朝时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,朝时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect() )
socket.setsockopt(level,optname,value)设置套接字的值,比如缓冲区大小,太多了,去看文档。不同系统,不同版本都不尽相同

MakeFile


    def makefile(self, mode="r", buffering=None, *,encoding=None, errors=None, newline=Non

创建一个与改套接字相关的文件对象,将recv方法看作读方法,将send方法看作写方法

使用makefile 改写群聊

import socket
import threading
import logging
import datetime


FORMAT ='%(asctime)s %(thread)d %(message)s'
logging.basicConfig(level=logging.INFO)

class ChatServer:

    def __init__(self,ip='127.0.0.1',port=9999):  #启动服务
        self.socket_server =socket.socket()
        self.addr_server = (ip,port)
        self.client = {}   #客户端
        self.event=threading.Event()

    def start(self):  #启动监听
        self.socket_server.bind(self.addr_server)  #启动服务
        self.socket_server.listen()                 #监听服务
        #accept会阻塞主线程,所以开一个新线程
        threading.Thread(target=self.accept,name="accept").start()


    def accept(self):   #多人连接
        while not self.event.is_set():
            client_socket ,client=self.socket_server.accept()  #阻塞
            #转杯接受数据,recv时阻塞的,开启新的线程
            f = client_socket.makefile("rw")
            self.client[client] = f  #添加到客户端字典
            #准备接受数据,recv 是阻塞的,开启新的线程
            threading.Thread(target=self.recv,args=(f,client),name="recv").start()


    def recv(self,f,client):
        while not self.event.is_set():
            data = f.readline().strip()   #阻塞换行符
            #客户端退出
            if data == "quit":
                self.client.pop(client)
                f.close()
                logging.info("{} quits".format(client))
                break
            recv_msg = '{:%Y%m%d %H:%M:%S} {}:{}\n{}\n'.format(datetime.datetime.now(),*client,data)
            logging.info(recv_msg)
            logging.info(threading.enumerate())
            for s in self.client.values():
                s.write(recv_msg)
                s.flush()

    def stop(self):     #停止服务
        for s in self.client.values():
            s.close()
        self.socket_server.close()
        self.event.set()


cs = ChatServer()
cs.start()

while True:
    cmd = input(">>>").strip()
    if cmd == "quit":
        cs.stop()
        threading.Event().wait(3)
        break
    logging.info(threading.enumerate()) #用来观察断开后线程的变化

上例完成了基本功能,但是如果客户端主动断开,或者readline出现异常,就不会从clients中移除作废的socket,可以使用异常处理解决这个问题 

ChatServer实验用完整代码

import socket
import threading
import logging
import datetime


FORMAT ='%(asctime)s %(thread)d %(message)s'
logging.basicConfig(level=logging.INFO)

class ChatServer:

    def __init__(self,ip='127.0.0.1',port=9999):  #启动服务
        self.socket_server =socket.socket()
        self.addr_server = (ip,port)
        self.client = {}   #客户端
        self.event=threading.Event()

    def start(self):  #启动监听
        self.socket_server.bind(self.addr_server)  #启动服务
        self.socket_server.listen()                 #监听服务
        #accept会阻塞主线程,所以开一个新线程
        threading.Thread(target=self.accept,name="accept").start()


    def accept(self):   #多人连接
        while not self.event.is_set():
            client_socket ,client=self.socket_server.accept()  #阻塞
            #转杯接受数据,recv时阻塞的,开启新的线程
            f = client_socket.makefile("rw")
            self.client[client] = f  #添加到客户端字典
            #准备接受数据,recv 是阻塞的,开启新的线程
            threading.Thread(target=self.recv,args=(f,client),name="recv").start()


    def recv(self,f,client):
        while not self.event.is_set():
            try:
                data = f.readline()  #阻塞换行符
            except Exception as e:
                logging.error(e) #有任何异常退出
                data = "quit"
            data = data.strip()
            #客户端退出
            if data == "quit":
                self.client.pop(client)
                f.close()
                logging.info("{} quits".format(client))
                break
            recv_msg = '{:%Y%m%d %H:%M:%S} {}:{}\n{}\n'.format(datetime.datetime.now(),*client,data)
            logging.info(recv_msg)
            logging.info(threading.enumerate())
            for s in self.client.values():
                s.write(recv_msg)
                s.flush()

    def stop(self):     #停止服务
        for s in self.client.values():
            s.close()
        self.socket_server.close()
        self.event.set()

def main():
    cs = ChatServer()
    cs.start()

    while True:
        cmd = input(">>>").strip()
        if cmd == "quit":
            cs.stop()
            threading.Event().wait(3)
            break
        logging.info(threading.enumerate()) #用来观察断开后线程的变化

if __name__ == '__main__':
    main()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值