Server端:python
# Echo server program import socket HOST = '' # Symbolic name meaning all available interfaces PORT = 50007 # Arbitrary non-privileged port sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock_server.bind((HOST, PORT)) sock_server.listen(1) #開始監聽,1表明在容許有一個鏈接排隊,更多的新鏈接連進來時就會被拒絕 conn, addr = sock_server.accept() #阻塞直到有鏈接爲止,有了一個新鏈接進來後,就會爲這個請求生成一個鏈接對象 with conn: print('Connected by', addr) while True: data = conn.recv(1024) #接收1024個字節 if not data: break #收不到數據,就break conn.sendall(data) #把收到的數據再所有返回給客戶端
Client端:linux
# Echo client program import socket HOST = 'localhost' # The remote host PORT = 50007 # The same port as used by the server client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((HOST, PORT)) client.sendall(b'Hello, world') data = client.recv(1024) print('Received',data)
先啓動Server端,再啓動Client端,結果以下:編程
第一次接觸就這麼交待了,之說了一句話,感受不夠過癮,如何實現更多的交互呢?簡單,只須要讓客戶端不斷的發,服務端不斷的收就能夠了,寫個循環搞定。windows
Server端:服務器
# Echo server program import socket HOST = '' # Symbolic name meaning all available interfaces PORT = 50007 # Arbitrary non-privileged port sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock_server.bind((HOST, PORT)) sock_server.listen(1) #開始監聽,1表明在容許有一個鏈接排隊,更多的新鏈接連進來時就會被拒絕 conn, addr = sock_server.accept() #阻塞直到有鏈接爲止,有了一個新鏈接進來後,就會爲這個請求生成一個鏈接對象 with conn: print('Connected by', addr) while True: data = conn.recv(1024) #接收1024個字節 print("server recv:",conn.getpeername(), data.decode()) if not data: break #收不到數據,就break conn.sendall(data) #把收到的數據再所有返回給客戶端
Client端:cookie
# Echo client program import socket HOST = 'localhost' # The remote host PORT = 50007 # The same port as used by the server client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((HOST, PORT)) while True: msg = input(">>>:").strip() if len(msg) == 0:continue client.sendall(msg.encode()) #發送用戶輸入的數據,必須是bytes模式 data = client.recv(1024) print('Received',data.decode()) #收到服務器的響應後,decode一下
上面的例子,服務端只是將客戶端發來的再發送給客戶端,這哪叫聊天啊,這種事須要雙方配合,得讓服務端也能說話。網絡
Server端:併發
import socket HOST = '' # Symbolic name meaning all available interfaces PORT = 50007 # Arbitrary non-privileged port sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock_server.bind((HOST, PORT)) sock_server.listen(1) #開始監聽,1表明在容許有一個鏈接排隊,更多的新鏈接連進來時就會被拒絕 conn, addr = sock_server.accept() #阻塞直到有鏈接爲止,有了一個新鏈接進來後,就會爲這個請求生成一個鏈接對象 with conn: print('Connected by', addr) while True: data = conn.recv(1024) #接收1024個字節 print("recv from Alex:",conn.getpeername(), data.decode()) if not data: break #收不到數據,就break response = input(">>>").strip() conn.send(response.encode()) print("send to alex:",response)
Client不須要作更改,直接看結果:socket
以上的例子仍是有bug,雙方只能一來一往的說話,若是你想來納許發2句話是不行的,會卡住。這是由於你發了一條消息後,就去調用recv方法接收服務器的響應了,再服務器端返回消息以前,這個recv(1024)方法是阻塞的,若是想容許此時還能再發消息給服務器端,就須要再單獨啓動一個線程,只負責發消息。tcp
剛纔在聊天的時候,服務端在服務客戶端的時候,其它人若是也想跟服務端鏈接是處於排隊狀態,而後等正在被服務的客戶端完事並斷開後,下一我的就跟上,但實際狀況是客戶端一斷開,服務端也跟着斷了。
爲何會斷呢?引文服務端如下代碼的意思是,若是收不到數據,就跳出循環,就斷開了。
conn, addr = sock_server.accept() #阻塞直到有鏈接爲止,有了一個新鏈接進來後,就會爲這個請求生成一個鏈接對象 with conn: print('Connected by', addr) while True: data = conn.recv(1024) #接收1024個字節 print("recv from Alex:",conn.getpeername(), data.decode()) if not data: break #收不到數據,就break , 就是它乾的 response = input(">>>").strip() conn.send(response.encode()) print("send to alex:",response)
想實現一個客戶端斷開後,能夠馬上接入另一個客戶端的話,怎麼辦呢?只須要再在外層加個循環。
while True: #最外層loop conn, addr = sock_server.accept() #阻塞直到有鏈接爲止,有了一個新鏈接進來後,就會爲這個請求生成一個鏈接對象 #爲什麼把上面這句話也包含在循環裏? print("來了個新客人",conn.getpeername() ) with conn: print('Connected by', addr) while True: data = conn.recv(1024) #接收1024個字節 print("recv from :",conn.getpeername(), data.decode()) if not data: break #收不到數據,就break conn.send(data.upper()) print("send to alex:",data)
break 跳出後就回到大while那層:
可是,有的人在重啓服務端時可能會遇到:
這是因爲你的服務端仍然存在4次揮手的time_wait狀態,在佔用地址(若是不懂,請深刻研究:一、tcp三次握手,四次揮手。二、sun洪水攻擊。三、服務器高併發狀況下會有大量的time_wait狀態的優化方法)
解決方法1:
sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #一行代碼搞定,寫在bind以前 sock_server.bind((HOST, PORT))
解決方法2(用於Linux系統):
發現系統存在大量TIME_WAIT狀態的鏈接,經過調整linux內核參數解決, vi /etc/sysctl.conf 編輯文件,加入如下內容: net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_fin_timeout = 30 而後執行 /sbin/sysctl -p 讓參數生效。 net.ipv4.tcp_syncookies = 1 表示開啓SYN Cookies。當出現SYN等待隊列溢出時,啓用cookies來處理,可防範少許SYN攻擊,默認爲0,表示關閉; net.ipv4.tcp_tw_reuse = 1 表示開啓重用。容許將TIME-WAIT sockets從新用於新的TCP鏈接,默認爲0,表示關閉; net.ipv4.tcp_tw_recycle = 1 表示開啓TCP鏈接中TIME-WAIT sockets的快速回收,默認爲0,表示關閉。 net.ipv4.tcp_fin_timeout 修改系統默認的 TIMEOUT 時間
UDP不須要通過3次握手和4次揮手,不須要提早創建鏈接,直接發數據就行。
Server端:
import socket ip_port=('127.0.0.1',9000) BUFSIZE=1024 udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #udp類型 udp_server_client.bind(ip_port) while True: msg,addr=udp_server_client.recvfrom(BUFSIZE) print("recv ",msg,addr) udp_server_client.sendto(msg.upper(),addr)
Client端:
import socket ip_port = ('127.0.0.1',9000) BUFSIZE = 1024 udp_server_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) while True: msg=input('>>: ').strip() if not msg:continue udp_server_client.sendto(msg.encode('utf-8'),ip_port) back_msg,addr = udp_server_client.recvfrom(BUFSIZE) print(back_msg.decode('utf-8'),addr)
結果:
一、TCP基於連接通訊
二、UDP無連接