chapter12.一、socket編程,tcp實現通信

網絡編程

Socket編程

socket套接字

端到端編程,創建了一條數據傳輸的通道編程

網絡編程的基礎,全部的操做系統都支持socketwindows

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編程,socket

服務器編程步驟tcp

一、建立Socket對象ide

二、綁定IP地址和端口,IP負責地址,端口管應用程序性能

端口是進程的,不是線程的,只是線程共享進程的資源ui

三、監聽,暴露端口,將指定的IP端口開始監聽spa

四、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()
ChatTCPServer

 

 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} {}:{}\n{}".format(datetime.datetime.now(),*self.raddr,data.strip())
27             logging.info(msg)
28 
29     def send(self,msg:str):
30         data = "{}\n".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()
View Code
相關文章
相關標籤/搜索