網絡編程:socket 編程


socket 編程

-客戶端/服務器架構 :即 C/S架構

1,硬件C/S 架構(打印機)

2, 軟件C/S 架構(web服務)

C/S架構與socket的關系:socket就是為了完成C/S架構的開發

 

-osi 七層:

應用層--運輸層--網絡層--鏈路層--物理層

 

 

 socket 抽象層在應用層和運輸層之間

 

 

socket概念(socket也是套接字)

socket是應用層和TCP/IP協議中間通信的軟件層,它是一組接口,在設計模式中,socket其實就是一個門面模式,它把復雜的TCP/IP協議封裝隱藏在socket接口后,讓socket去組織數據,以符合指定協議,所以只需遵循socket規定去編程就可以。

套接字分為2種:

-基於文件型的套接字家族 AF_UNIX

用於一台機器的不同程序之間

linux 一切皆文件,基於文件的套接字調用的是底層的文件系統來取數據,2個套接字進程運行在同一個機器,可以通過訪問同一個文件系統來間接完成通信

-基於網絡類型的套接字家族 AF_INET

用於網絡編程

通過網絡來實現2個程序通訊

 

socket 基於tcp運行流程圖如下:

  

例如:

服務端:

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#socket.SOCK_STREAM 是基於流的通訊方式,也就是TCP
#socket.AF_INET 代表是網絡嵌套家族類型

phone.bind(('192.168.1.4',8000))
#括號內寫IP地址+端口 自己電腦IP是 192.168.1.4 ,監聽端口是8000

phone.listen(5) #代表同時可以接5個電話

conn,addr =phone.accept()
msg=conn.recv(1024)# 收消息 ,1024代表可以接收多少字節的信息
print('客戶端發來的消息是',msg)
conn.send(msg.upper())#發消息

conn.close()
phone.close()

客戶端

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.connect(('192.168.1.4',8000))

phone.send('hello'.encode('utf-8'))#發消息
data =phone.recv(1024) #收消息
print('收到服務端發來的消息',data)

運行結果是:

服務端:客戶端發來的消息是 b'hello'

客戶端:收到服務端發來的消息 b'HELLO'

 

socket 底層工作原理

 

  客戶端服務端循環發送接收消息

服務端代碼:

from socket import *

ip_import =('192.168.1.3',8000)
back_log = 5
buffer_size = 1024
tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_import)
tcp_server.listen(back_log)

conn,add = tcp_server.accept()
print('雙向鏈接是',conn)
print('客戶端的地址是',add)

while True:
msg = conn.recv(buffer_size)
print('客戶發來的是',msg.decode('utf-8'))
conn.send(msg.upper())

conn.close()
tcp_server.close()

 服務端運行結果是:

雙向鏈接是 <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.1.3', 8000), raddr=('192.168.1.3', 52951)>
客戶端地址是 ('192.168.1.3', 52951)
客戶發來的是 nihao
客戶發來的是 hi
客戶發來的是 how are you

 

 

 客戶端代碼:

from socket import *

ip_port=('192.168.1.3',8000)
back_log=5
buffer_size=1024

tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
client_msg = input('>>:').strip()
if not client_msg: continue
tcp_client.send(client_msg.encode('utf-8'))
print('客戶端已發送消息')
data = tcp_client.recv(buffer_size)
print('收到服務端發來的消息',data.decode('utf-8'))

 

 客戶端結果如下:

>>:nihao
收到服務端發來的消息 NIHAO
>>:hi
收到服務端發來的消息 HI
>>:how are you
收到服務端發來的消息 HOW ARE YOU

 注意:如果輸入可以是空格,但不可以是空,是空的話會繼續要求輸入。

 

 socket 收發消息原理刨析

 

客戶端發消息:是從應用程序發送到用戶態內存,然后發送到內核態內存然后再通過網卡發出

客戶端消息:是從網卡進入內核態內存然后發送到用戶態內存

服務端同樣如此。

 

 

 服務端多次接收雙向連接

windows 系統和linux/mac系統下,socket 不同之處:

斷開客戶端,windows 系統下服務端會報錯,而linux/mac系統下,服務端接收的是空

 windows 系統下:

服務端:from socket import *


ip_port =('192.168.1.3',8001)
back_log = 5
buffer_size = 1024

tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
conn,add = tcp_server.accept()
print('接收的鏈接是',conn)
print('地址是',add)

while True:
print('服務端開始運行了')
try:
data = conn.recv(buffer_size)

print('客戶發來的是',data.decode('utf-8'))
conn.send(data.upper())
print('服務端已發送',data.upper())
except Exception:
break
conn.close()
tcp_server.close()

 客戶端

from socket import *

ip_port=('192.168.1.3',8001)
back_log=5
buffer_size=1024

tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
client_msg = input('>>:').strip()
if not client_msg: continue
tcp_client.send(client_msg.encode('utf-8'))
print('客戶端已發送消息')
data = tcp_client.recv(buffer_size)
print('收到服務端發來的消息',data.decode('utf-8'))

tcp_client.close()

 

linux 系統下:

from socket import *

ip_port =('192.168.1.3',8001)
back_log = 5
buffer_size = 1024

tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
#在bind 之前進行socket 設置,使其不卡在time_wait占用地址

tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
conn,add = tcp_server.accept()
print('接收的鏈接是',conn)
print('地址是',add)

while True:
print('服務端開始運行了')
try:
data = conn.recv(buffer_size)
if not data: break
print('客戶發來的是',data.decode('utf-8'))
conn.send(data.upper())
print('服務端已發送',data.upper())
except Exception:
break

tcp_server.close()

 客戶端代碼如上,不變。

 

總結:服務端,客戶端基本要求:

 

 

 

 基於udp 的套接字

udp沒有連接

 服務端

from socket import *
ip_port = ('192.168.1.5',8080)
buffer_size = 1024


udp_server = socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM 數據報
udp_server.bind(ip_port)


while True:
data,addr = udp_server.recvfrom(buffer_size)
print(data.decode('utf-8'))
udp_server.sendto(data.upper(),addr)
udp_server.close()

 

 客戶端

from socket import *
ip_port = ('192.168.1.5',8080)
buffer_size = 1024


udp_client = socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM 數據報

while True:
msg = input('>>:').strip()
udp_client.sendto(msg.encode('utf-8'),ip_port)

data,addr = udp_client.recvfrom(buffer_size)
print(data.decode('utf-8'))
udp_client.close()

 

運用基於udp的套接字,來制作時間服務器(ntp),代碼如下:

時間服務端:

from socket import *
import time
ip_port = ('192.168.1.5',8080)
buffer_size = 1024

time_server = socket(AF_INET,SOCK_DGRAM)
time_server.bind(ip_port)

while True:
data,addr = time_server.recvfrom(buffer_size)
if not data:
fmt = '%Y-%m-%d-%X'
else:
fmt = data.decode('utf-8')

back_time = time.strftime(fmt)
time_server.sendto(back_time.encode('utf-8'),addr)
time_server.close()

時間客戶端:

from socket import *
ip_port = ('192.168.1.5',8080)
buffer_size = 1024


time_client = socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM 數據報

while True:
msg = input('>>:').strip()
time_client.sendto(msg.encode('utf-8'),ip_port)

data,addr = time_client.recvfrom(buffer_size)
print('服務器的標准時間是:',data.decode('utf-8'))
time_client.close()

 recv 在自己這段的緩沖區為空時,會阻塞

recvfrom 在自己這段的緩沖區為空時,就收一個空

 

 

 基於tcp實現遠程命令

 subprocess 模塊

代碼:變量名=subprocess.Popen(命令,shell=True,

            stderr=subprocess.PIPE,

            stdin=subprocess.PIPE,

            stdout=subprocess.PIPE)

 
        

 將命令結果封裝在管道(PIPE)中,stdin 代表輸入,stdout 代表輸出,stderr代表報錯。

想要讀取內容:代碼:變量名.stdrr.read()

 服務端

from socket import *
import subprocess

ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024

tcp_sever = socket(AF_INET,SOCK_STREAM)
tcp_sever.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) #設置socket,使其不卡在time wait,占用地址
tcp_sever.bind(ip_port)
tcp_sever.listen(back_log)

while True:
conn,addr = tcp_sever.accept()
print('新的客戶端連接',addr)

while True:
try:
#收消息
cmd = conn.recv(buffer_size)
if not cmd:break
print('收到客戶端的命令',cmd)

#執行命令,得到命令的結果cmd_res
res = subprocess.Popen(cmd.decode('utf-8'),shell=True,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
err = res.stderr.read()

if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
#發消息
       if not cmd_res:
        cmd_res = '操作成功'.encode('utf-8') #此代碼代表,如果cmd_res 為空的話,也會顯示結果
            conn.send(cmd_res)
except Exception as e:
print(e)
break

tcp_sever.close()

 

客戶端 

 

from socket import *

ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024

tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
cmd = input('>>:').strip()
if not cmd:continue
if cmd == 'quit':break

tcp_client.send(cmd.encode('utf-8'))
cmd_res = tcp_client.recv(buffer_size)
print('命令的執行結果是',cmd_res.decode('utf-8'))

tcp_client.close()

實操結果如下:

 

 粘包

 注意:只有tcp 會粘包,udp不會粘包。

粘包問題主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少字節的數據造成的。

 

 

解決粘包

方式一:比較低端一些

服務端:

from socket import *
import subprocess

ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024

tcp_sever = socket(AF_INET,SOCK_STREAM)
tcp_sever.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
tcp_sever.bind(ip_port)
tcp_sever.listen(back_log)

while True:
conn,addr = tcp_sever.accept()
print('新的客戶端連接',addr)

while True:
try:
#收消息
cmd = conn.recv(buffer_size)
if not cmd:break
print('收到客戶端的命令',cmd)

#執行命令,得到命令的結果cmd_res
res = subprocess.Popen(cmd.decode('utf-8'),shell=True,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
err = res.stderr.read()

if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
#發消息
if not cmd_res:
cmd_res = '操作成功'.encode('utf-8')

length =len(cmd_res)
conn.send(str(length).encode('utf-8'))
client_ready = conn.recv(buffer_size)
if client_ready == b'ready':
conn.send(cmd_res)
except Exception as e:
print(e)
break

tcp_sever.close()

客戶端

from socket import *

ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024

tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
cmd = input('>>:').strip()
if not cmd:continue
if cmd == 'quit':break

tcp_client.send(cmd.encode('utf-8'))

length= tcp_client.recv(buffer_size)
tcp_client.send(b'ready')
length = int(length.decode('utf-8'))

recv_msg = b''
recv_size = 0
while recv_size < length:
recv_msg +=tcp_client.recv(buffer_size)
recv_size = len(recv_msg)

print('命令的執行結果是',recv_msg.decode('utf-8'))

tcp_client.close()

 方式二:比較高端一些 

 服務端:

from socket import *
import subprocess
import struct

ip_port = ('192.168.1.2',8080)
back_log = 5
buffer_size = 1024

tcp_sever = socket(AF_INET,SOCK_STREAM)
tcp_sever.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
tcp_sever.bind(ip_port)
tcp_sever.listen(back_log)

while True:
conn,addr = tcp_sever.accept()
print('新的客戶端連接',addr)

while True:
try:
#收消息
cmd = conn.recv(buffer_size)
if not cmd:break
print('收到客戶端的命令',cmd)

#執行命令,得到命令的結果cmd_res
res = subprocess.Popen(cmd.decode('utf-8'),shell=True,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
err = res.stderr.read()

if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
#發消息
if not cmd_res:
cmd_res = '操作成功'.encode('utf-8')

length =len(cmd_res)
conn.send(struct.pack('i',length))
conn.send(cmd_res)
except Exception as e:
print(e)
break

tcp_sever.close()

 客戶端:

from socket import *
import struct
from functools import partial

ip_port = ('192.168.1.2',8080)
back_log = 5
buffer_size = 1024

tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
cmd = input('>>:').strip()
if not cmd:continue
if cmd == 'quit':break

tcp_client.send(cmd.encode('utf-8'))

length_data= tcp_client.recv(4)
length = struct.unpack('i',length_data)[0]

recv_msg = ''.join(iter(partial(tcp_client.recv, buffer_size), b''))

print('命令的執行結果是',recv_msg.decode('utf-8'))

tcp_client.close()

 

 總結:

 

 

 

 

 

                                                     

socket 並發socketserver

socketserver-tcp套接字 並發

並發:即多個客戶端與服務端的同時交互

 服務端:

import socketserver


class MyServer(socketserver.BaseRequestHandler):
def handle(self):
# print('conn is:', self.request) # self.request相當於conn
# print('addr is:', self.client_address) # self.client_address相當於addr

while True:
try:
# 收消息
data = self.request.recv(1024)
if not data: break
print('收到客戶端的消息是:', data,self.client_address)

# 發消息
self.request.send(data.upper())
except Exception as e:
print(e)
break

if __name__ == '__main__':
s = socketserver.ThreadingTCPServer(('192.168.1.2',8081),MyServer)
s.serve_forever()

 客戶端:

from socket import *

ip_port = ('192.168.1.2', 8081)
buffer_size = 1024

tcp_client = socket(AF_INET, SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
msg = input('>>: ').strip()
if not msg: continue
tcp_client.send(msg.encode('utf-8'))
print('客戶端已經發送消息')

data = tcp_client.recv(buffer_size)
print('收到服務端發來的消息', data.decode('utf-8'))

tcp_client.close()

socketserver-udp套接字 並發

 

對於tcp來說,self.request = conn

但是對於udp來說,self.request =(data,udp的套接字對象

 

服務端:

import socketserver

class MyServer(socketserver.BaseRequestHandler):
def handle(self):
#self.request[0] 是data
#self.request[1]是udp的socket套接字
#self.client_address 是conn
#收消息
print('收到客戶端的消息是',self.request[0],self.client_address)
#發消息
self.request[1].sendto(self.request[0].upper(),self.client_address)


if __name__ == '__main__':
s=socketserver.ThreadingUDPServer(('192.168.1.2',8082),MyServer) #多線程
s.serve_forever()

客戶端:

from socket import *
ip_port=('192.168.1.2',8082)
buffer_size=1024

udp_client=socket(AF_INET,SOCK_DGRAM) #數據報

while True:
msg=input('>>: ').strip()
udp_client.sendto(msg.encode('utf-8'),ip_port)

data,addr=udp_client.recvfrom(buffer_size)
# print(data.decode('utf-8'))
print(data)

 認證客戶端合法性

服務端

from socket import *
import os
import hmac
secret_key = b'ni hao ma'

def conn_auth(conn):
'''
認證客戶端連接
:param conn:
:return:
'''
print('開始驗證連接的合法性')
msg = os.urandom(32)
conn.sendall(msg)
h=hmac.new(secret_key,msg)#加嚴
digest=h.digest()
respone=conn.recv(len(digest))
return hmac.compare_digest(respone,digest)#對比respone 與digest 是否一致



def data_handler(conn, bufize=1024):
if not conn_auth(conn):
print('該連接不合法,關閉')
conn.close()
return
print('連接合法,開始通信')

while True:
data = conn.recv(bufize)
if not data: break
conn.send(data.upper())



def server_handle(ip_port,bufize,backlog=5):
'''
只處理連接
:param ip_port:
:param bufize:
:param backlog:
:return:
'''
tcp_socket_server = socket(AF_INET, SOCK_STREAM)
tcp_socket_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 在bind 之前進行socket 設置,使其不卡在time_wait占用地址
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(backlog)
while True:
conn, addr = tcp_socket_server.accept()
print('conn是%s,addr是%s' % (conn, addr))
data_handler(conn, bufize)
tcp_socket_server.close()

if __name__ == '__main__':
ip_port =('192.168.1.2',8080)
bufize = 1024
server_handle(ip_port,bufize)

 客戶端:

from socket import *
import os,hmac

secret_key = b'ni hao ma'

def conn_auth(conn):

'''
驗證客戶端到服務器的連接合法性
:param conn:
:return:
'''
msg = conn.recv(32)
h = hmac.new(secret_key, msg)
digest = h.digest()
conn.sendall(digest)


def client_handler(ip_port,bufsize=1024):
tcp_socket_client = socket(AF_INET, SOCK_STREAM)
tcp_socket_client.connect(ip_port)
conn_auth(tcp_socket_client)

while True:
#發消息
cmd = input('>>:').strip()
if not cmd: continue
if cmd == 'quit': break
tcp_socket_client.send(cmd.encode('utf-8'))
#收消息
data=tcp_socket_client.recv(bufsize)
print(data.decode('utf-8'))
tcp_socket_client.close()

if __name__ == '__main__':
ip_port = ('192.168.1.2', 8080)
bufize = 1024
client_handler(ip_port,bufize)

 

 作業

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM