Python网络编程基础

网络编程

Socket 介绍

Socket套接字
python 中提供了socket.py标准库,非常底层的接口
Socket是一种通用的网络编程接口,和网络层次没有一一对应的关系

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

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

Socket 类型

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

Socket编程,需要两端,一般来说需要一个服务端,一个客户端,服务端称为Server,客户端称为Client.
这种模式称为cs编程.

TCP服务端编程

服务端编程步骤

  • 创建Socket对象
  • 绑定ip地址Address和端口Port,用bind()方法,IPV4地址和端口为一个二元组(‘ip地址’,Port)
  • 开始监听,将在指定的IP的端口上监听,用listen()方法
  • 获取用于传送数据的Socket对象
    • socket.accept()->(socketobject,addressinfo)
    • accept阻塞,等待客户端建立连接,返回一个新的Socket对象,和客户端地址端口的二元组,地址是远程客户端的地址,IPV4中,它是一个二元组(clientaddr,port).(例如:newsock,clientinfo = server.accept(),clientinfo是客户端的IP地址和端口的二元组)
    • 接收数据:recv(bufsizd[,flags]),使用缓冲区接收数据
    • 发送数据,send(bytes)发送数据

2019-6-12 20-25-37.png

import socket

s = socket.socket()   #创建socket对象
s.bind(('127.0.0.1',9999)) #一个地址和端口的二元组
s.listen()  #开始监听,等待客户端连接到来

s1,info1 = s.accept()  #阻塞,直到和客户端成功建立连接,返回一个新的socket对象和客户端地址

data = s1.recv(1024) #阻塞,新socket对象等待获取数据
print(data)

s1.send(b'asd')
s1.close()

#查看监听端口  netstat-tan1 tcp |findstr 9999


应用

写一个群聊程序
服务器应该具有的功能:

  1. 启动服务,包括绑定地址和端口,并监听
  2. 建立连接,能和多个客户端建立连接
  3. 接收不同用户的信息
  4. 分发,将接收的某个用户的信息转发到已连接的所有客户端
  5. 停止服务
  6. 记录连接的客户端
import socket
import threading
import logging

logging.basicConfig(level=logging.INFO, format="%(message)s")

class ChatServer:
    def __init__(self,ip='127.0.0.1',port=9999):
        self.sock = socket.socket()
        self.ip = ip
        self.port = port
        self.clients = {}             #建立客户端字典
        self.event = threading.Event()
        self.lock = threading.Lock()

    def start(self):
        self.sock.bind((self.ip,self.port))            #实例绑定
        self.sock.listen()                             #实例开始监听
        threading.Thread(target=self.accept).start()   #创建新线程并启动

    def accept(self):
        while not self.event.is_set():
            s1,clientinfo = self.sock.accept()
            f = s1.makefile('rw')               #创建一个与该套接字相关连的文件对象,将recv方法看做读方法,将send方法看做写方法
            with self.lock:
                self.clients[clientinfo] = f,s1
            threading.Thread(target=self.recv,args=(f,clientinfo)).start()
            logging.info((clientinfo[0], '已上线'))

    def recv(self,f,client):
        while not self.event.is_set():
            try:                                #捕获异常,客户端主动掉线将会产生异常,需要处理
                data = f.read(5)
            except Exception as e:
                logging.error(e)
                data = 'quit'
            with self.lock:
                if data == '' or data =='quit':
                    _,s1 = self.clients.pop(client)
                    f.close()
                    s1.close()
                    print('{}已下线'.format(client[0]))
                    break
                for i,_ in self.clients.values():
                    i.write(data)
                    i.flush()
                logging.info(data)

    def stop(self):
        self.event.set()
        with self.lock:
            for i,j in self.clients.values():
                i.close()
                j.close()
        # print('-------')
        self.sock.close()
        # print('~~~~~~~~~')

chat = ChatServer()
chat.start()

while True:
    cmd = input('...').strip()
    if cmd =='quit':
        chat.stop()
        threading.Event().wait(3)
        break


客户端主动断开带来的问题服务端知道自己何时断开,如果客户端断开,服务器不知道。(客户端主动断开,服务端recv会得到一个空串)所以,好的做法是,客户端断开发出特殊消息通知服务器端断开连接。但是,如果客户端主动断开,服务端主动发送一个空消息,超时返回异常,捕获异常并清理连接。即使为客户端提供了断开命令,也不能保证客户端会使用它断开连接。但是还是要增加这个退出功能


socket 的常用方法

名称含义
socket.recv(bufsize[,flags])获取数据,默认是阻塞的方式
socket.recvfrom(bufsize[,flags])获取数据,返回一个二元组(bytes,address)
socket.recv_into(buffer[,nbytes[,flags]])获取到nbytes的数据后,储存到buffer中,如果bbytes没有指定或0,将buffer大小的数据存入buffer中,返回接收到的字节
socket.send(bytes[,flags])TCP发送数据
socket.sendall(bytes[,flags])TCP发送全部数据,成功返回None
socket.sendto(srting[,flag],address)UDP发送数据
socket.sendfile(file,offset=0,count=None)发送一个文件直到EOF,使用高性能的os.sendfile机制,返回发送的字节数,如果win下不支持sendfile,或者不是普通文件,使用send()发送文件,offset告诉起始位置,
socket.getpeername()返回链接套接字的远程地址,返回值通常是元组(ipaddr,port)
socket.getsockname()返回套接字自己的地址。通常是一个元组(ipaddr,port)
socke.setblocking(flag)如果flag为0 则将套接字设置为非阻塞魔术,否则将套接字设置为阻塞魔术(默认值),非阻塞魔术下如果调用recv()没有返现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常
socket.settimeout(value)设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
ocket.setsockopt(level,optname,value)设置套接字选项的值。比如缓冲区大小。太多了,去看文档。不同系统,不同版本都不尽相同
MakeFile

socket.makefile(mode=‘r’,buffering=None,*,encoding=None,errors=None,newline=None)
创建一个与该套接字相关连的文件对象,将recv方法看做读方法,将send方法看做写方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值