您好,登錄后才能下訂單哦!
?
?
?
目錄
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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。