网络编程
Socket编程
socket套接字
端到端编程,建立了一条数据传输的通道
网络编程的基础,所有的操作系统都支持socket
socket是一种通用的网路编程接口,不需要关心协议层,只是打通了一条通道,和网络层次没有一一对应关系。
IPC,RPC,最底层也是socket实现
协议族
名称 | 含义 |
AF_INET | IPV4 |
AF_INET6 | IPV6 |
AF_UNIX | Unix Domain Socket,windows没有 |
socket 类型
名称 | 含义 |
SOCK_STREAM | 面向连接的流套接字,默认值,TCP协议 |
SOCK_DGRAM | 无连接的数据报文套接字,UDP协议,效率高,易产生乱序,丢包问题 |
TCP编程
服务端server,客户端client,cs编程,
服务器编程步骤
1、创建Socket对象
2、绑定IP地址和端口,IP负责地址,端口管应用程序
端口是进程的,不是线程的,只是线程共享进程的资源
3、监听,暴露端口,将指定的IP端口开始监听
4、socket accept 获取传送数据的socket对象
用于阻塞等待客户端建立连接,返回一个新的socket对象和客户端地址的二元组
地址是远程客户端地址,IPv4中是一个二元组(clientaddr,port)
-
recv(bufsize[,flags]) 使用缓冲区接收数据
-
send(bytes) 发送数据
注意:两次绑定同一个端口会报错,操作系统级别的错误
Windows报错: 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
#1 socket sevrer = socket.socket() #2 bind ip = '127.0.0.1' port = 9999 addr = (ip,port) server.bind(addr) #3 listen server.listen() #4 accept s1, info = server.accept() #阻塞等待client连接 #5 recv,send data = server.recv(1024) s1.send("hello, I'm server") #6 close server.close()
netstat Windows使用 b 显示进程,要用管理员权限 windows下
ss -tanl l表示监听,t tcp协议,a全部,n数字显示端口 Linux下
windows下的管道也可以使用,findstr是过滤方法
群聊程序TCP协议服务器端简单实现
TCPChatserver
1 import socket 2 import threading 3 import logging 4 5 FORMAT = "%(thread)d %(threadName)s %(asctime)s %(message)s" 6 logging.basicConfig(format=FORMAT,level=logging.INFO) 7 8 class ChatServer: 9 def __init__(self,ip="127.0.0.1",port=9999): 10 self.sock = socket.socket() 11 self.addr = (ip,port) 12 self.clients = {} 13 self.event = threading.Event() 14 15 def start(self):#开始监听 16 self.sock.bind(self.addr) 17 self.sock.listen() 18 #accept会阻塞主线程,开线程跑 19 threading.Thread(target=self.accept,name="accept").start() 20 21 def accept(self):#多人连接 22 while not self.event.is_set(): 23 try: 24 sock,raddr = self.sock.accept()#阻塞等待 25 except Exception: 26 self.stop() 27 break 28 self.clients[raddr] = sock#记录客户端 29 logging.info(raddr) 30 #开启新线程,一个新的socket对象处理单个client 31 threading.Thread(target=self.recv,args=(sock,raddr),name="r-{}".format(raddr)).start() 32 33 34 def recv(self,sock,raddr):#接受数据 35 while not self.event.is_set(): 36 try: 37 data = sock.recv(1024).strip()#阻塞等待 38 except Exception: 39 break 40 logging.info(":{}".format(data.decode())) 41 #client退出机制 42 if data.strip() == b"quit" or data ==b"": 43 print(data,"~~~~~~~~~~") 44 self.clients.pop(raddr) 45 sock.close() 46 break 47 48 msg = "{}".format(data) 49 msg = msg.encode() 50 for client in self.clients.values(): 51 client.sendall(msg) 52 53 def stop(self):#停止服务 54 for c in self.clients.values(): 55 c.close() 56 self.sock.close() 57 self.event.set() 58 59 def main(): 60 server = ChatServer() 61 server.start() 62 63 while True: 64 cmd = input(">>>") 65 if cmd == "quit": 66 server.stop() 67 threading.Event().wait(3) 68 break 69 logging.info(threading.enumerate())#观察线程变化 70 71 if __name__ == '__main__': 72 main()
只是实现主要功能,且现在不用该方法了
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会占文件描述符,不用要归还
import socket import threading import logging FORMAT = "%(asctime)s %(thread)d %(message)s" logging.basicConfig(format=FORMAT,level=logging.INFO) class ChatTCPServer: def __init__(self,ip="127.0.0.1",port=9999): self.sock = socket.socket() self.addr = ip,port self.event = threading.Event() self.clients = {} def start(self): self.sock.bind(self.addr) self.sock.listen() threading.Thread(target=self.accept,name="accept").start() def accept(self): while not self.event.is_set(): try: s, client = self.sock.accept() f = s.makefile("rw") except: self.stop() break self.clients[client] = f threading.Thread(target=self.recv,args=(f, client,s),name="client-{}".format(client[1])).start() logging.info("{} login".format(client)) def recv(self,f,raddr,s): while not self.event.is_set(): try: msg = f.readline().strip() except Exception as e: logging.error(e) self.clients.pop(raddr) f.close() break logging.info(msg) if msg == "quit" or "": self.clients.pop(raddr) f.close() break for f1 in self.clients.values(): f1.write(msg) f1.flush() def stop(self): for client in self.clients.values(): client.close() self.sock.close() self.event.set() def main(): server = ChatTCPServer() server.start() while True: cmd = input(">>>") if cmd =="quit": server.stop() threading.Event().wait(3) break logging.info(threading.enumerate()) if __name__ == '__main__': main()
client 客户端实现
创建socket对象
连接到远程服务端的ip,port,connect方法
传输数据
使用send,recv方法接受,发送数据
关闭连接,释放资源
1 import socket 2 import threading 3 import logging 4 import datetime 5 6 logging.basicConfig(format="%(asctime)s %(thread)s %(message)s",level=logging.INFO) 7 8 class ChatTcpClient: 9 def __init__(self,ip="127.0.0.1",port=9999): 10 self.sock = socket.socket() 11 self.event = threading.Event() 12 self.raddr = ip,port 13 14 def start(self): 15 self.sock.connect(self.raddr) 16 self.sock.send(b"I'm ready") 17 threading.Thread(target=self.recv,name='recv').start() 18 19 def recv(self): 20 while not self.event.is_set(): 21 try: 22 data = self.sock.recv(1024).decode() 23 except Exception as e: 24 logging.info(e) 25 break 26 msg = "{:%Y%m%d %H:%M:%S} {}:{} {}".format(datetime.datetime.now(),*self.raddr,data.strip()) 27 logging.info(msg) 28 29 def send(self,msg:str): 30 data = "{} ".format(msg.strip()).encode() 31 self.sock.send(data) 32 33 def stop(self): 34 self.sock.close() 35 self.event.wait(3) 36 self.event.set() 37 logging.info("Client stop") 38 39 def main(): 40 cc = ChatTcpClient() 41 cc.start() 42 while True: 43 cmd = input(">>>") 44 if cmd == "quit" or "": 45 cc.stop() 46 break 47 cc.send(cmd) 48 49 if __name__ == '__main__': 50 main()