目录
socket,套接字:... 1
TCP编程:... 2
TCP服务器端编程步骤:... 3
群聊程序,TCP实现:... 5
makefile:... 7
TCP客户端编程步骤:... 10
py中提供socket.py标准库,非常底层的接口库;
socket是一种通用的网络编程接口;
协议族:
AF,address family,用于sock=socket.socket()第一个参数;
AF_NET,ipv4;
AF_NET6,ipv6;
AF_UNIX,unix domain socket,win没有这个;
socket类型:
socket.SOCK_STREAM,面向连接的流套接字,默认值TCP协议,如sock=socket.socket(type=socket.SOCK_STREAM或sock=socket.socket())均取默认;
socket.SOCK_DGRAM,无连接的数据报文套接字,UDP协议,如sock=socket.socket(type=socket.SOCK_DGRAM);
socket编程,需要两端,一般需要一个服务端server,一个客户端client;
C/S模型,C/S编程;
B/S,B/S编程,本质上是C/S,是种特殊的C/S,要支持http、html(h6)、css、js、声音、视频等;只做B即web前端开发;
创建socket对象,socket(family=AF_INET, type=socket.SOCK_STREAM, proto=0, fileno=None);
绑定ip和port,bind()方法;ipv4地址为一个二元组('ip',port);
开始监听,将在指定的ip和port上监听,listen()方法;
获取用于传送数据的socket对象:
accept(),阻塞等待客户端建立连接,返回一个新的socket对象和客户端地址(ipv4为二元组地址,远程客户端的地址),如accept() -> (socket object, address info);
recv(bufsize[,flags]),接收数据,使用缓冲区接收数据;
send(bytes),发送数据;
生产中编程时不会用这么底层的模块,当前使用是用于理解socket;
sock=socket.socket()
ip='127.0.0.1'
port=9999
addr=(ip,port)
sock.bind(addr)
sock.lienten()
conn,addrinfo=sock.accept()
conn.recv(bufsize[,flag]),获取数据,默认是阻塞方式,TCP接收数据;
conn.recvfrom(bufsize[,flag]),获取数据,返回一个二元组(bytes,address),UDP接收数据;
conn.recv_into(buffer[,nbytes][,flag]]),获取到nbytes的数据后,存储到buffer中;如果nbytes没有指定或0,将buffer大小的数据存入buffer中;返回接收的字节数;
conn.recvfrom_into(buffer[,nbytes[,flags]]),获取数据,返回一个二元组(bytes,address)到buffer中;
conn.send(bytes[,flags]),TCP发送数据;
conn.sendall(bytes[,flags]),TCP发送全部数据,成功返回None,本质上调的是send();
conn.sendto(string[,flag],address),UDP发送数据;
conn.sendfile(file,offset=0,count=None),发送一个文件直至EOF,使用高性能的os的sendfile机制,返回发送的字节数;win不支持sendfile,不是普通文件时,用send()发送文件;offset指起始位置;3.5版开始;
conn.getpeername(),返回连接套接字的远程地址,返回值通常是元组(ipaddr,port);
conn.getsockname(),返回套接字自己的地址,通常是元组(ipaddr,port);
conn.setblocking(flag),默认值True阻塞;False或0非阻塞,通常将套接字设为非阻塞,非阻塞模式下,如果调用recv()没有发现任何数据,或调用send()无法立即发送数据,将引起socket.error异常;Set the socket to blocking (flag is true) or non-blocking (false). setblocking(True) is equivalent to settimeout(None);setblocking(False) is equivalent to settimeout(0.0).;
conn.settimeout(value),设置套接字操作的超时期,timeout是一个浮点数,单位s,值为None表示没有超时期;一般,超时期应在刚创建套接字时设置,因为它们可能用于连接的操作,如connect();
conn.setsockopt(level,optname,value),设置套接字选项的值,如缓冲区大小等;具体看help有很多选项,不同OS不同version都不尽相同;
例:
sock = socket.socket() #步骤1,均取默认,family=AF_NET,type=SOCK_STREAM
ip = '192.168.7.144' #点分四段表示
port = 9999
addr = (ip, port)
sock.bind(addr) #步骤2
sock.listen() #步骤3
# s1 = socket.socket()
# s1.bind(addr) #OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次
# s1.listen()
time.sleep(3)
logging.info(sock)
conn, addrinfo = sock.accept() #步骤4
# while True: #不能这样写,接收进1个client请求后,之后的连接请求接收不到,解决:多线程
# conn, addrinfo = sock.accept()
logging.info('{} {}'.format(conn, addrinfo))
for i in range(3):
data = conn.recv(1024)
logging.info(data) #字节码
logging.info(data.decode()) #字符串
msg = 'ask {}'.format(data.decode()) #要发送字节码,若用data则抛TypeError异常
conn.send(msg.encode())
conn.close()
sock.close()
输出:
2018-08-08-11:28:03 Thread info: 13528 MainThread <socket.socket fd=236, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.7.144', 9999)>
2018-08-08-11:28:04 Thread info: 13528 MainThread <socket.socket fd=240, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.7.144', 9999), raddr=('192.168.7.144', 7536)> ('192.168.7.144', 7536)
2018-08-08-11:28:47 Thread info: 13528 MainThread b'fuck'
2018-08-08-11:28:47 Thread info: 13528 MainThread fuck
2018-08-08-11:28:52 Thread info: 13528 MainThread b'fuck again'
2018-08-08-11:28:52 Thread info: 13528 MainThread fuck again
2018-08-08-11:28:58 Thread info: 13528 MainThread b'fuck again again'
2018-08-08-11:28:58 Thread info: 13528 MainThread fuck again again
例:
群聊程序:
需求分析:聊天工具CS程序;
服务器应具有的功能:
启动服务,包括绑定地址端口、监听;
建立连接,能和多个client建立连接;
接收不同用户信息;
分发,将接收的某个用户的信息转发到已连接的所有client;
停止服务;
记录连接的client;
class ChatServer:
def __init__(self, ip='127.0.0.1', port=9999): #生产中用ini文件配置
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, name='accept').start()
def stop(self):
for conn in self.clients.values():
conn.close()
self.sock.close()
self.event.wait(3) #TCP资源回收要些时间;UDP很快
self.event.set()
def _accept(self):
# while True:
while not self.event.is_set():
conn, client = self.sock.accept()
self.clients[client] = conn
threading.Thread(target=self._recv, args=(conn, client), name='recv').start()
def _recv(self, conn, client):
# while True:
while not self.event.is_set():
data = conn.recv(1024) #data为bytecode,此句可能有异常,建议放在try...except中
data = data.strip().decode() #data为string
logging.info(data)
if data == 'quit':
logging.info('...quit')
self.clients.pop(client)
conn.close()
break
msg = 'ack {}'.format(data) #msg为string
# conn.send(msg.encode())
for c in self.clients.values(): #注意此处c不能写为conn,否则会将上面的conn覆盖;一般循环次数用一个字符i,有意义的变量用多个字符
c.send(msg.encode()) #send的数据要为bytecode
cs = ChatServer()
cs.start()
e = threading.Event()
def showthread():
while not e.wait(5):
logging.info(threading.enumerate())
# showthread()
# cs.stop()
threading.Thread(target=showthread, daemon=True).start() #用于自己监控看,可随时关闭
while True:
cmd = input('>>> ').strip()
if cmd == 'quit':
cs.stop()
break
注:
仍有问题:各种异常处理;client主动退出后server不知道;
conn,clientinfo=sock.accept(1024)
f=conn.makefile(mode='r',buffering=None,*,encoding=None,errors=None,newline=None) #创建一个与该套接字相关联的文件对象;
高级接口用的是makefile;
例:
sock = socket.socket()
ip = '127.0.0.1'
port = 9999
addr = (ip,port)
sock.bind(addr)
sock.listen()
conn, addrinfo = sock.accept()
f = conn.makefile(mode='rw')
line = f.read(10) #等价conn.recv(1024),两者差不多,makefile在处理字符串上容易些(不用decode()或encode())
print(line)
f.write('return your msg:{}'.format(line)) #同conn.send(msg)
f.flush()
例,使用makefile循环接收消息:
sock = socket.socket()
ip = '127.0.0.1'
port = 9999
addr = (ip,port)
sock.bind(addr)
sock.listen()
event = threading.Event()
def accept(sock, event:threading.Event):
conn, addrinfo = sock.accept()
f = conn.makefile(mode='rw')
while True:
line = f.readline() #注意此处按行读取,遇到\n才接收;类似conn.recv(1024)
print(line)
if line.strip() == 'quit':
break
f.write('return your msg:{}'.format(line)) #conn.send(msg)
f.flush()
f.close()
sock.close()
event.wait(3)
print('end')
event.set()
threading.Thread(target=accept, args=(sock, event)).start()
while not event.wait(5):
print(sock)
例,将ChatServer改为makefile;
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, name='accept').start()
def stop(self):
for f in self.clients.values():
f.close()
self.sock.close()
self.event.wait(3)
self.event.set()
def _accept(self):
while not self.event.is_set():
conn, client = self.sock.accept()
f = conn.makefile(mode='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.info(e)
data = 'quit'
data = data.strip()
logging.info(data)
if data == 'quit':
logging.info('{}: ...quit'.format(client))
self.clients.pop(client)
f.close()
break
msg = 'ack: {}'.format(data)
for f in self.clients.values():
f.writelines(msg)
f.flush()
cs = ChatServer()
cs.start()
myutils.show_threads()
e = threading.Event()
while not e.wait(5):
cmd = input('>>> ').strip()
if cmd == 'quit':
cs.stop()
break
创建socket对象;
连接到远端服务器的ip和port,connect()方法;
传输数据,使用send()、recv()方法;
关闭连接、释放资源;
TCP、UDP的客户端是随机的port,而服务器端是绑定死的(提供服务的场所要固定,如银行);
例:
sock = socket.socket() #step 1
ip = '127.0.0.1'
port = 9999
addr = (ip, port)
sock.connect(addr) #step 2
sock.send(b'hello\n') #step3
data = sock.recv(1024)
print(data)
sock.close() #step4,好习惯,fd资源有限
例,ChatClient:
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)
threading.Thread(target=self._recv, name='recv').start()
def stop(self):
self.sock.close()
self.event.wait(3)
self.event.set()
def _recv(self):
while not self.event.is_set():
try:
data = self.sock.recv(1024)
except Exception as e:
logging.info(e)
break
logging.info(data.decode())
def send(self):
self.sock.send(data.encode())
def main():
cc = ChatClient()
cc.start()
myutils.show_threads()
while True:
cmd = input('>>> ').strip()
if cmd == 'quit':
cc.send(cmd)
cc.stop()
break
cc.send(cmd)
if __name__ == '__main__':
main()
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。