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
TCP服务端编程
服务器端编程步骤
创建Socket对象
绑定IP地址Address和端口Port。bind()方法
IPv4地址为一个二元组(‘IP地址字符串’, Port)
开始监听,将在指定的IP的端口上监听。listen()方法
获取用于传送数据的Socket对象 socket.accept() -> (socket object, address info)
accept方法阻塞等待客户端建立连接,返回一个新的Socket对象和客户端地址的二元组
地址是远程客户端的地址,IPv4中它是一个二元组(clientaddr, port)
查看监听端口
1 2 3 4 5 6
| windows 命令 # netstat -anp tcp | findstr 9999
linux命令 # netstat -tanl | grep 9999 # ss -tanl | grep 9999
|
练习——写一个一对多通信
需求分析
聊天工具是CS程序,C是每一个客户端,S是服务器端。
服务器应该具有的功能:
启动服务,包括绑定地址和端口,并监听
建立连接,能和多个客户端建立连接
接收不同用户的信息
分发,将接收的某个用户的信息转发到已连接的所有客户端
停止服务
记录连接的客户端
代码实现
服务端应该对应一个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| import logging import socket import threading import datetime
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(thread)d %(message)s")
class ChatServer: def __init__(self, ip='127.0.0.1', port=9999): self.sock = socket.socket() self.addr = (ip, port) self.clients = {} self.event = threading.Event() def start(self): self.sock.bind(self.addr) self.sock.listen() threading.Thread(target=self.accept).start()
def accept(self): while not self.event.is_set(): sock, client = self.sock.accept() self.clients[client] = sock threading.Thread(target=self.recv, args=(sock, client)).start()
def recv(self, sock:socket.socket, client): while not self.event.is_set(): data = sock.recv(1024) msg = data.decode().strip() if msg == 'quit' or msg == '': self.clients.pop(client) sock.close() logging.info('{} quits'.format(client)) break msg = "{:%Y/%m/%d %H:%M:%S} {}:{}\n{}\n".format(datetime.datetime.now(), *client,data.decode()) logging.info(msg) msg = msg.encode() for s in self.clients.values(): s.send(msg) def stop(self): for s in self.clients.values(): s.close() self.sock.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())
|
socket常用的方法
名称 |
含义 |
socket.recv(bufsize[, flags]) |
获取数据。默认是阻塞的方式 |
socket.recvfrom(bufsize[, flags]) |
获取数据,返回一个二元组(bytes, address) |
socket.recv_into(buffer[, nbytes[,flags]]) |
获取到nbytes的数据后,存储到buffer中。如果nbytes没有指定或0,将buffer大小的数据存入buffer中。返回接收的字节数。 |
socket.recvfrom_into(buffer[,nbytes[, flags]]) |
获取数据,返回一个二元组(bytes, address)到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
socket.makefile(mode='r', buffering=None, *, encoding=None, errors=None, newline=None)
创建一个与该套接字相关连的文件对象,将recv方法看做读方法,将send方法看做写方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| 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": 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)
|
makefile练习
使用makefile改写一对多通信
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| import logging import socket import threading import datetime
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(thread)d %(message)s")
class ChatServer: def init(self, ip='127.0.0.1', port=9999): self.sock = socket.socket() self.addr = (ip, port) self.clients = {} self.event = threading.Event() def start(self): self.sock.bind(self.addr) self.sock.listen() threading.Thread(target=self.accept).start() def accept(self): while not self.event.is_set(): sock, client = self.sock.accept() f = sock.makefile('rw') self.clients[client] = f 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' msg = data.strip() if msg == 'quit': self.clients.pop(client) f.close() logging.info('{} quits'.format(client)) break msg = "{:%Y/%m/%d %H:%M:%S} {}:{}\n{}\n".format(datetime.datetime.now(), *client,data) logging.info(msg) for s in self.clients.values(): s.write(msg) s.flush() def stop(self): for s in self.clients.values(): s.close() self.sock.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()
|
TCP客户端编程
客户端编程步骤
- 创建Socket对象
- 连接到远端服务端的IP和PORT、connect()方法
- 传输数据
- 关闭连接、释放资源
1 2 3 4 5 6 7 8
| import socket client = socket.socket() ipaddr = ('127.0.0.1', 9999) client.connect(ipaddr) client.send(b'abcd\n') data = client.recv(1024) print(data) client.close()
|
编写聊天的客户类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import socket import threading import datetime import logging
FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s" logging.basicConfig(format=FORMAT, level=logging.INFO)
class ChatClient: def init(self, ip='127.0.0.1', port=9999): self.sock = socket.socket() self.addr = (ip, port) self.event = threading.Event() def start(self): self.sock.connect(self.addr) self.send("I'm ready.") threading.Thread(target=self.recv, name="recv").start() def recv(self): while not self.event.is_set(): try: data = self.sock.recv(1024) except Exception as e: logging.error(e) break msg = "{:%Y/%m/%d %H:%M:%S} {}:{}\n{}\n".format(datetime.datetime.now(), *self.addr,data.strip()) logging.info(msg) def send(self, msg:str): data = "{}\n".format(msg.strip()).encode() self.sock.send(data) def stop(self): self.sock.close() self.event.wait(3) self.event.set() logging.info('Client stops.') def main(): cc = ChatClient() cc.start() while True: cmd = input('>>>') if cmd.strip() == 'quit': cc.stop() break cc.send(cmd)
if name == 'main': main()
|