网络编程
Socket 介绍
Socket套接字
python 中提供了socket.py标准库,非常底层的接口
Socket是一种通用的网络编程接口,和网络层次没有一一对应的关系
协议族
AF表示Address Family,用于socket()第一个参数
名称 | 含义 |
---|---|
AF_INET | IPV4 |
AF_INET6 | IPV6 |
AF_UNIX | Unix 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)发送数据
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
应用
写一个群聊程序
服务器应该具有的功能:
- 启动服务,包括绑定地址和端口,并监听
- 建立连接,能和多个客户端建立连接
- 接收不同用户的信息
- 分发,将接收的某个用户的信息转发到已连接的所有客户端
- 停止服务
- 记录连接的客户端
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方法看做写方法