日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無先后,達者為師

網(wǎng)站首頁 編程語言 正文

Python網(wǎng)絡(luò)編程之socket與socketserver_python

作者:springsnow ? 更新時間: 2022-07-28 編程語言

一、基于TCP協(xié)議的socket套接字編程

1、套接字工作流程

先從服務(wù)器端說起。服務(wù)器端先初始化Socket,然后與端口綁定(bind),對端口進行監(jiān)聽(listen),調(diào)用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然后連接服務(wù)器(connect),如果連接成功,這時客戶端與服務(wù)器端的連接就建立了。客戶端發(fā)送數(shù)據(jù)請求,服務(wù)器端接收請求并處理請求,然后把回應(yīng)數(shù)據(jù)發(fā)送給客戶端,客戶端讀取數(shù)據(jù),最后關(guān)閉連接,一次交互結(jié)束,使用以下Python代碼實現(xiàn):

import socket
# socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默認值為 0
socket.socket(socket_family, socket_type, protocal=0)
# 獲取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 獲取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

1、 服務(wù)端套接字函數(shù)

  • s.bind():綁定(主機,端口號)到套接字
  • s.listen():開始TCP監(jiān)聽
  • s.accept():被動接受TCP客戶的連接,(阻塞式)等待連接的到來

2、 客戶端套接字函數(shù)

  • s.connect():主動初始化TCP服務(wù)器連接
  • s.connect_ex():connect()函數(shù)的擴展版本,出錯時返回出錯碼,而不是拋出異常

3、 公共用途的套接字函數(shù)

  • s.recv():接收TCP數(shù)據(jù)
  • s.send():發(fā)送TCP數(shù)據(jù)(send在待發(fā)送數(shù)據(jù)量大于己端緩存區(qū)剩余空間時,數(shù)據(jù)丟失,不會發(fā)完)
  • s.sendall():發(fā)送完整的TCP數(shù)據(jù)(本質(zhì)就是循環(huán)調(diào)用send,sendall在待發(fā)送數(shù)據(jù)量大于己端緩存區(qū)剩余空間時,數(shù)據(jù)不丟失,循環(huán)調(diào)用send直到發(fā)完)
  • s.recvfrom():接收UDP數(shù)據(jù)
  • s.sendto():發(fā)送UDP數(shù)據(jù)
  • s.getpeername():連接到當前套接字的遠端的地址
  • s.getsockname():當前套接字的地址
  • s.getsockopt():返回指定套接字的參數(shù)
  • s.setsockopt():設(shè)置指定套接字的參數(shù)
  • s.close():關(guān)閉套接字

4、 面向鎖的套接字方法

  • s.setblocking():設(shè)置套接字的阻塞與非阻塞模式
  • s.settimeout():設(shè)置阻塞套接字操作的超時時間
  • s.gettimeout():得到阻塞套接字操作的超時時間

5、 面向文件的套接字的函數(shù)

  • s.fileno():套接字的文件描述符
  • s.makefile():創(chuàng)建一個與該套接字相關(guān)的文件

2、基于TCP協(xié)議的套接字編程

可以通過netstat -an | findstr 8080查看套接字狀態(tài)

1、 服務(wù)端

import socket
# 1、買手機
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # tcp稱為流式協(xié)議,udp稱為數(shù)據(jù)報協(xié)議SOCK_DGRAM
# print(phone)
# 2、插入/綁定手機卡
# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1', 8080))
# 3、開機
phone.listen(5) # 半連接池,限制的是請求數(shù)
# 4、等待電話連接
print('start....')
while True: # 連接循環(huán)
conn, client_addr = phone.accept() # (三次握手建立的雙向連接,(客戶端的ip,端口))
# print(conn)
print('已經(jīng)有一個連接建立成功', client_addr)
# 5、通信:收\發(fā)消息
while True: # 通信循環(huán)
try:
print('服務(wù)端正在收數(shù)據(jù)...')
data = conn.recv(1024) # 最大接收的字節(jié)數(shù),沒有數(shù)據(jù)會在原地一直等待收,即發(fā)送者發(fā)送的數(shù)據(jù)量必須>0bytes
# print('===>')
if len(data) == 0: break # 在客戶端單方面斷開連接,服務(wù)端才會出現(xiàn)收空數(shù)據(jù)的情況
print('來自客戶端的數(shù)據(jù)', data)
conn.send(data.upper())
except ConnectionResetError:
break
# 6、掛掉電話連接
 conn.close()
# 7、關(guān)機
phone.close()
# start....
# 已經(jīng)有一個連接建立成功 ('127.0.0.1', 4065)
# 服務(wù)端正在收數(shù)據(jù)...
# 來自客戶端的數(shù)據(jù) b'\xad'
# 服務(wù)端正在收數(shù)據(jù)...

2、 客戶端

import socket
# 1、買手機
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# print(phone)
# 2、撥電話
phone.connect(('127.0.0.1', 8080)) # 指定服務(wù)端ip和端口
# 3、通信:發(fā)\收消息
while True: # 通信循環(huán)
msg = input('>>: ').strip() # msg=''
if len(msg) == 0: continue
phone.send(msg.encode('utf-8'))
# print('has send----->')
data = phone.recv(1024)
# print('has recv----->')
print(data)
# 4、關(guān)閉
phone.close()
# >>: 啊
# b'a'
# >>: 啊啊
# b'\xb0\xa1\xb0\xa1'
# >>:

3、地址占用問題

這個是由于你的服務(wù)端仍然存在四次揮手的time_wait狀態(tài)在占用地址(如果不懂,請深入研究1.tcp三次握手,四次揮手 2.syn洪水攻擊 3.服務(wù)器高并發(fā)情況下會有大量的time_wait狀態(tài)的優(yōu)化方法)

1、 方法一:加入一條socket配置,重用ip和端口

# 

phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))

2、 方法二:通過調(diào)整linux內(nèi)核參數(shù)

發(fā)現(xiàn)系統(tǒng)存在大量TIME_WAIT狀態(tài)的連接,通過調(diào)整linux內(nèi)核參數(shù)解決,
vi /etc/sysctl.conf
編輯文件,加入以下內(nèi)容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
然后執(zhí)行 /sbin/sysctl -p 讓參數(shù)生效。
net.ipv4.tcp_syncookies = 1 表示開啟SYN Cookies。當出現(xiàn)SYN等待隊列溢出時,啟用cookies來處理,可防范少量SYN攻擊,默認為0,表示關(guān)閉;
net.ipv4.tcp_tw_reuse = 1 表示開啟重用。允許將TIME-WAIT sockets重新用于新的TCP連接,默認為0,表示關(guān)閉;
net.ipv4.tcp_tw_recycle = 1 表示開啟TCP連接中TIME-WAIT sockets的快速回收,默認為0,表示關(guān)閉。
net.ipv4.tcp_fin_timeout 修改系統(tǒng)默認的 TIMEOUT 時間

4、模擬ssh遠程執(zhí)行命令

服務(wù)端通過subprocess執(zhí)行該命令,然后返回命令的結(jié)果。

服務(wù)端:

from socket import *
import subprocess
server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8000))
server.listen(5)
print('start...')
while True:
conn, client_addr = server.accept()
while True:
print('from client:', client_addr)
cmd = conn.recv(1024)
if len(cmd) == 0: break
print('cmd:', cmd)
obj = subprocess.Popen(cmd.decode('utf8'), # 輸入的cmd命令
shell=True, # 通過shell運行
stderr=subprocess.PIPE, # 把錯誤輸出放入管道,以便打印
stdout=subprocess.PIPE) # 把正確輸出放入管道,以便打印

stdout = obj.stdout.read() # 打印正確輸出
stderr = obj.stderr.read() # 打印錯誤輸出

conn.send(stdout)
conn.send(stderr)
conn.close()
server.close()

客戶端

import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8000))
while True:
data = input('please enter your data')
client.send(data.encode('utf8'))
data = client.recv(1024)
print('from server:', data)
client.close()

輸入dir命令,由于服務(wù)端發(fā)送字節(jié)少于1024字節(jié),客戶端可以接受。

輸入tasklist命令,由于服務(wù)端發(fā)送字節(jié)多于1024字節(jié),客戶端只接受部分數(shù)據(jù),并且當你再次輸入dir命令的時候,客戶端會接收dir命令的結(jié)果,但是會打印上一次的剩余未發(fā)送完的數(shù)據(jù),這就是粘包問題。

5、粘包

1、發(fā)送端需要等緩沖區(qū)滿才發(fā)送出去,造成粘包

發(fā)送數(shù)據(jù)時間間隔很短,數(shù)據(jù)量很小,會合到一起,產(chǎn)生粘包。

服務(wù)端

# _*_coding:utf-8_*_
from socket import *
ip_port = ('127.0.0.1', 8080)
TCP_socket_server = socket(AF_INET, SOCK_STREAM)
TCP_socket_server.bind(ip_port)
TCP_socket_server.listen(5)
conn, addr = TCP_socket_server.accept()

data1 = conn.recv(10)
data2 = conn.recv(10)
print('----->', data1.decode('utf-8'))
print('----->', data2.decode('utf-8'))
conn.close()

客戶端

# _*_coding:utf-8_*_
import socket
BUFSIZE = 1024
ip_port = ('127.0.0.1', 8080)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
res = s.connect_ex(ip_port)
s.send('hello'.encode('utf-8'))
s.send('world'.encode('utf-8'))

# 服務(wù)端一起收到b'helloworld'

2、接收方不及時接收緩沖區(qū)的包,造成多個包接收

客戶端發(fā)送了一段數(shù)據(jù),服務(wù)端只收了一小部分,服務(wù)端下次再收的時候還是從緩沖區(qū)拿上次遺留的數(shù)據(jù),產(chǎn)生粘包。

服務(wù)端

# _*_coding:utf-8_*_
from socket import *
ip_port = ('127.0.0.1', 8080)
TCP_socket_server = socket(AF_INET, SOCK_STREAM)
TCP_socket_server.bind(ip_port)
TCP_socket_server.listen(5)
conn, addr = TCP_socket_server.accept()
data1 = conn.recv(2) # 一次沒有收完整
data2 = conn.recv(10) # 下次收的時候,會先取舊的數(shù)據(jù),然后取新的
print('----->', data1.decode('utf-8'))
print('----->', data2.decode('utf-8'))
conn.close()

客戶端

# _*_coding:utf-8_*_
import socket
BUFSIZE = 1024
ip_port = ('127.0.0.1', 8080)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
res = s.connect_ex(ip_port)
s.send('hello feng'.encode('utf-8'))

6、解決粘包問題

1、先發(fā)送的字節(jié)流總大小(low版)

問題的根源在于,接收端不知道發(fā)送端將要傳送的字節(jié)流的長度,所以解決粘包的方法就是圍繞,如何讓發(fā)送端在發(fā)送數(shù)據(jù)前,把自己將要發(fā)送的字節(jié)流總大小讓接收端知曉,然后接收端來一個死循環(huán)接收完所有數(shù)據(jù)。

為何low:程序的運行速度遠快于網(wǎng)絡(luò)傳輸速度,所以在發(fā)送一段字節(jié)前,先用send去發(fā)送該字節(jié)流長度,這種方式會放大網(wǎng)絡(luò)延遲帶來的性能損耗。

服務(wù)端:

import socket, subprocess
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8000))
server.listen(5)
while True:
conn, addr = server.accept()
print('start...')
while True:
cmd = conn.recv(1024)
print('cmd:', cmd)
obj = subprocess.Popen(cmd.decode('utf8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
stdout = obj.stdout.read()
if stdout:
ret = stdout
else:
stderr = obj.stderr.read()
ret = stderr
ret_len = len(ret)
 conn.send(str(ret_len).encode('utf8'))
data = conn.recv(1024).decode('utf8')
if data == 'recv_ready':
conn.sendall(ret)
conn.close()
server.close()

客戶端:

import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8000))
while True:
msg = input('please enter your cmd you want>>>').strip()
if len(msg) == 0: continue
client.send(msg.encode('utf8'))
length = int(client.recv(1024))
client.send('recv_ready'.encode('utf8'))
send_size = 0
recv_size = 0
data = b''
while recv_size < length:
data = client.recv(1024)
recv_size += len(data)
print(data.decode('utf8'))

2、自定義固定長度報頭(struct模塊)

struct模塊解析

import struct
import json
# 'i'是格式
try:
obj = struct.pack('i', 1222222222223)
except Exception as e:
print(e)
obj = struct.pack('i', 1222)
print(obj, len(obj))
# 'i' format requires -2147483648 <= number <= 2147483647
# b'\xc6\x04\x00\x00' 4

res = struct.unpack('i', obj)
print(res[0])
# 1222

解決粘包問題的核心就是:為字節(jié)流加上自定義固定長度報頭,報頭中包含字節(jié)流長度,然后一次send到對端,對端在接收時,先從緩存中取出定長的報頭,然后再取真實數(shù)據(jù)。

1、 使用struct模塊創(chuàng)建報頭:

import json
import struct
header_dic = {
'filename': 'a.txt',
'total_size':111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222223131232,
'hash': 'asdf123123x123213x'
}
header_json = json.dumps(header_dic)
header_bytes = header_json.encode('utf-8')
print(len(header_bytes))# 223
# 'i'是格式
obj = struct.pack('i', len(header_bytes))
print(obj, len(obj))
# b'\xdf\x00\x00\x00' 4

res = struct.unpack('i', obj)
print(res[0])
# 223

2、服務(wù)端:

from socket import *
import subprocess
import struct
import json
server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8000))
server.listen(5)
print('start...')
while True:
conn, client_addr = server.accept()
print(conn, client_addr)
while True:
cmd = conn.recv(1024)
obj = subprocess.Popen(cmd.decode('utf8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
stderr = obj.stderr.read()
stdout = obj.stdout.read()
# 制作報頭
header_dict = {
'filename': 'a.txt',
'total_size': len(stdout) + len(stderr),
'hash': 'xasf123213123'
}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode('utf8')
# 1. 先把報頭的長度len(header_bytes)打包成4個bytes,然后發(fā)送
conn.send(struct.pack('i', len(header_bytes)))
# 2. 發(fā)送報頭
 conn.send(header_bytes)
# 3. 發(fā)送真實的數(shù)據(jù)
 conn.send(stdout)
conn.send(stderr)
conn.close()
server.close()

3、 客戶端:

from socket import *
import json
import struct
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8000))
while True:
cmd = input('please enter your cmd you want>>>')
if len(cmd) == 0: continue
client.send(cmd.encode('utf8'))
# 1. 先收4個字節(jié),這4個字節(jié)中包含報頭的長度
header_len = struct.unpack('i', client.recv(4))[0]
# 2. 再接收報頭
header_bytes = client.recv(header_len)
# 3. 從包頭中解析出想要的東西
header_json = header_bytes.decode('utf8')
header_dict = json.loads(header_json)
total_size = header_dict['total_size']
# 4. 再收真實的數(shù)據(jù)
recv_size = 0
res = b''
while recv_size < total_size:
data = client.recv(1024)
res += data
recv_size += len(data)
print(res.decode('utf8'))
client.close()

二、基于UDP協(xié)議的socket套接字編程

  • UDP是無鏈接的,先啟動哪一端都不會報錯,并且可以同時多個客戶端去跟服務(wù)端通信

  • UDP協(xié)議是數(shù)據(jù)報協(xié)議,發(fā)空的時候也會自帶報頭,因此客戶端輸入空,服務(wù)端也能收到。

  • UPD協(xié)議一般不用于傳輸大數(shù)據(jù)。

  • UPD套接字無粘包問題,但是不能替代TCP套接字,因為UPD協(xié)議有一個缺陷:如果數(shù)據(jù)發(fā)送的途中,數(shù)據(jù)丟失,則數(shù)據(jù)就丟失了,而TCP協(xié)議則不會有這種缺陷,因此一般UPD套接字用戶無關(guān)緊要的數(shù)據(jù)發(fā)送,例如qq聊天。

UDP套接字簡單示例

1、服務(wù)端

import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 數(shù)據(jù)報協(xié)議-》UDP
server.bind(('127.0.0.1', 8080))
while True:
data, client_addr = server.recvfrom(1024)
print('===>', data, client_addr)
server.sendto(data.upper(), client_addr)
server.close()

2、客戶端

import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 數(shù)據(jù)報協(xié)議-》UDP
while True:
msg = input('>>: ').strip() # msg=''
client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080))
data, server_addr = client.recvfrom(1024)
print(data)
client.close()

三、基于socketserver實現(xiàn)并發(fā)的socket編程

1、基于TCP協(xié)議

基于tcp的套接字,關(guān)鍵就是兩個循環(huán),一個鏈接循環(huán),一個通信循環(huán)

socketserver模塊中分兩大類:server類(解決鏈接問題)和request類(解決通信問題)。

1、 server類

2、 request類

基于tcp的socketserver我們自己定義的類中的。

  • self.server即套接字對象

  • self.request即一個鏈接
  • self.client_address即客戶端地址

3、 服務(wù)端

import socketserver
class MyHandler(socketserver.BaseRequestHandler):
def handle(self):
# 通信循環(huán)
while True:
# print(self.client_address)
# print(self.request) #self.request=conn
try:
data = self.request.recv(1024)
if len(data) == 0: break
self.request.send(data.upper())
except ConnectionResetError:
break
if __name__ == '__main__':
s = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyHandler, bind_and_activate=True)
s.serve_forever() # 代表連接循環(huán)
# 循環(huán)建立連接,每建立一個連接就會啟動一個線程(服務(wù)員)+調(diào)用Myhanlder類產(chǎn)生一個對象,調(diào)用該對象下的handle方法,專門與剛剛建立好的連接做通信循環(huán)

4、 客戶端

import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080)) # 指定服務(wù)端ip和端口
while True:
# msg=input('>>: ').strip() #msg=''
msg = 'client33333' # msg=''
if len(msg) == 0: continue
phone.send(msg.encode('utf-8'))
data = phone.recv(1024)
print(data)
phone.close()

2、基于UDP協(xié)議

基于udp的socketserver我們自己定義的類中的

  • self.request是一個元組(第一個元素是客戶端發(fā)來的數(shù)據(jù),第二部分是服務(wù)端的udp套接字對象),如(b'adsf',?)
  • self.client_address即客戶端地址

1、 服務(wù)端

import socketserver
class MyHandler(socketserver.BaseRequestHandler):
def handle(self):
# 通信循環(huán)
print(self.client_address)
print(self.request)
data = self.request[0]
print('客戶消息', data)
self.request[1].sendto(data.upper(), self.client_address)
if __name__ == '__main__':
s = socketserver.ThreadingUDPServer(('127.0.0.1', 8080), MyHandler)
s.serve_forever()

2、 客戶端

import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 數(shù)據(jù)報協(xié)議-》udp
while True:
# msg=input('>>: ').strip() #msg=''
msg = 'client1111'
client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080))
data, server_addr = client.recvfrom(1024)
print(data)
client.close()

四、Python Internet 模塊

以下列出了 Python 網(wǎng)絡(luò)編程的一些重要模塊:

協(xié)議 功能用處 端口號 Python 模塊
HTTP 網(wǎng)頁訪問 80 httplib, urllib, xmlrpclib
NNTP 閱讀和張貼新聞文章,俗稱為"帖子" 119 nntplib
FTP 文件傳輸 20 ftplib, urllib
SMTP 發(fā)送郵件 25 smtplib
POP3 接收郵件 110 poplib
IMAP4 獲取郵件 143 imaplib
Telnet 命令行 23 telnetlib
Gopher 信息查找 70 gopherlib, urllib

原文鏈接:https://www.cnblogs.com/springsnow/p/12011327.html

欄目分類
最近更新