您好,登錄后才能下訂單哦!
?
?
?
目錄
socket,套接字:... 1
TCP編程:... 2
TCP服務(wù)器端編程步驟:... 3
群聊程序,TCP實現(xiàn):... 5
makefile:... 7
TCP客戶端編程步驟:... 10
?
?
?
py中提供socket.py標(biāo)準(zhǔn)庫,非常底層的接口庫;
socket是一種通用的網(wǎng)絡(luò)編程接口;
?
協(xié)議族:
AF,address family,用于sock=socket.socket()第一個參數(shù);
AF_NET,ipv4;
AF_NET6,ipv6;
AF_UNIX,unix domain socket,win沒有這個;
?
socket類型:
socket.SOCK_STREAM,面向連接的流套接字,默認(rèn)值TCP協(xié)議,如sock=socket.socket(type=socket.SOCK_STREAM或sock=socket.socket())均取默認(rèn);
socket.SOCK_DGRAM,無連接的數(shù)據(jù)報文套接字,UDP協(xié)議,如sock=socket.socket(type=socket.SOCK_DGRAM);
?
?
?
socket編程,需要兩端,一般需要一個服務(wù)端server,一個客戶端client;
?
C/S模型,C/S編程;
B/S,B/S編程,本質(zhì)上是C/S,是種特殊的C/S,要支持http、html(h6)、css、js、聲音、視頻等;只做B即web前端開發(fā);
?
創(chuàng)建socket對象,socket(family=AF_INET, type=socket.SOCK_STREAM, proto=0, fileno=None);
綁定ip和port,bind()方法;ipv4地址為一個二元組('ip',port);
開始監(jiān)聽,將在指定的ip和port上監(jiān)聽,listen()方法;
獲取用于傳送數(shù)據(jù)的socket對象:
???????? accept(),阻塞等待客戶端建立連接,返回一個新的socket對象和客戶端地址(ipv4為二元組地址,遠(yuǎn)程客戶端的地址),如accept() -> (socket object, address info);
???????? recv(bufsize[,flags]),接收數(shù)據(jù),使用緩沖區(qū)接收數(shù)據(jù);
???????? send(bytes),發(fā)送數(shù)據(jù);
?
生產(chǎn)中編程時不會用這么底層的模塊,當(dāng)前使用是用于理解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]),獲取數(shù)據(jù),默認(rèn)是阻塞方式,TCP接收數(shù)據(jù);
conn.recvfrom(bufsize[,flag]),獲取數(shù)據(jù),返回一個二元組(bytes,address),UDP接收數(shù)據(jù);
conn.recv_into(buffer[,nbytes][,flag]]),獲取到nbytes的數(shù)據(jù)后,存儲到buffer中;如果nbytes沒有指定或0,將buffer大小的數(shù)據(jù)存入buffer中;返回接收的字節(jié)數(shù);
conn.recvfrom_into(buffer[,nbytes[,flags]]),獲取數(shù)據(jù),返回一個二元組(bytes,address)到buffer中;
conn.send(bytes[,flags]),TCP發(fā)送數(shù)據(jù);
conn.sendall(bytes[,flags]),TCP發(fā)送全部數(shù)據(jù),成功返回None,本質(zhì)上調(diào)的是send();
conn.sendto(string[,flag],address),UDP發(fā)送數(shù)據(jù);
conn.sendfile(file,offset=0,count=None),發(fā)送一個文件直至EOF,使用高性能的os的sendfile機(jī)制,返回發(fā)送的字節(jié)數(shù);win不支持sendfile,不是普通文件時,用send()發(fā)送文件;offset指起始位置;3.5版開始;
?
conn.getpeername(),返回連接套接字的遠(yuǎn)程地址,返回值通常是元組(ipaddr,port);
conn.getsockname(),返回套接字自己的地址,通常是元組(ipaddr,port);
conn.setblocking(flag),默認(rèn)值True阻塞;False或0非阻塞,通常將套接字設(shè)為非阻塞,非阻塞模式下,如果調(diào)用recv()沒有發(fā)現(xiàn)任何數(shù)據(jù),或調(diào)用send()無法立即發(fā)送數(shù)據(jù),將引起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),設(shè)置套接字操作的超時期,timeout是一個浮點(diǎn)數(shù),單位s,值為None表示沒有超時期;一般,超時期應(yīng)在剛創(chuàng)建套接字時設(shè)置,因為它們可能用于連接的操作,如connect();
conn.setsockopt(level,optname,value),設(shè)置套接字選項的值,如緩沖區(qū)大小等;具體看help有很多選項,不同OS不同version都不盡相同;
?
例:
sock = socket.socket()?? #步驟1,均取默認(rèn),family=AF_NET,type=SOCK_STREAM
?
ip = '192.168.7.144'?? #點(diǎn)分四段表示
port = 9999
addr = (ip, port)
sock.bind(addr)?? #步驟2
?
sock.listen()?? #步驟3
?
# s1 = socket.socket()
# s1.bind(addr)?? #OSError: [WinError 10048] 通常每個套接字地址(協(xié)議/網(wǎng)絡(luò)地址/端口)只允許使用一次
# s1.listen()
?
time.sleep(3)
logging.info(sock)
?
conn, addrinfo = sock.accept()?? #步驟4
# while True:?? #不能這樣寫,接收進(jìn)1個client請求后,之后的連接請求接收不到,解決:多線程
#???? conn, addrinfo = sock.accept()
?
logging.info('{} {}'.format(conn, addrinfo))
?
for i in range(3):
??? data = conn.recv(1024)
??? logging.info(data)?? #字節(jié)碼
??? logging.info(data.decode())?? #字符串
??? msg = 'ask {}'.format(data.decode())?? #要發(fā)送字節(jié)碼,若用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程序;
服務(wù)器應(yīng)具有的功能:
啟動服務(wù),包括綁定地址端口、監(jiān)聽;
建立連接,能和多個client建立連接;
接收不同用戶信息;
分發(fā),將接收的某個用戶的信息轉(zhuǎn)發(fā)到已連接的所有client;
停止服務(wù);
記錄連接的client;
?
class ChatServer:
??? def __init__(self, ip='127.0.0.1', port=9999):?? #生產(chǎn)中用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覆蓋;一般循環(huán)次數(shù)用一個字符i,有意義的變量用多個字符
??????????????? c.send(msg.encode())?? #send的數(shù)據(jù)要為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()?? #用于自己監(jiān)控看,可隨時關(guān)閉
?
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)?? #創(chuàng)建一個與該套接字相關(guān)聯(lián)的文件對象;
?
高級接口用的是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循環(huán)接收消息:
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
?
?
創(chuàng)建socket對象;
連接到遠(yuǎn)端服務(wù)器的ip和port,connect()方法;
傳輸數(shù)據(jù),使用send()、recv()方法;
關(guān)閉連接、釋放資源;
?
TCP、UDP的客戶端是隨機(jī)的port,而服務(wù)器端是綁定死的(提供服務(wù)的場所要固定,如銀行);
?
?
例:
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,好習(xí)慣,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()
?
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。