week7:網路程式設計之基礎
一、OSI模型
二、網路通訊要素
2.1、IP地址
2.2、埠號
2.3、傳輸協議
三、socket程式設計
socket即是一種特殊的檔案,一些socket函式就是對其進行的操作(讀/寫IO、開啟、關閉)。
Socket 是任何一種計算機網路通訊中最基礎的內容。例如當你在瀏覽器位址列中輸入 http://www.cnblogs.com/ 時,你會開啟一個套接字,然後連線到 http://www.cnblogs.com/ 並讀取響應的頁面然後顯示出來。而其他一些聊天客戶端如 gtalk 和 skype 也是類似。任何網路通訊都是通過 Socket 來完成的。Python 官方關於 Socket 的函式請看
socket和file的區別:
1、file模組是針對某個指定檔案進行【開啟】【讀寫】【關閉】
2、socket模組是針對 伺服器端 和 客戶端Socket 進行【開啟】【讀寫】【關閉】
Python 提供了兩個基本的 socket 模組。
第一個是 Socket,它提供了標準的 BSD Sockets API。
第二個是 SocketServer, 它提供了伺服器中心類,可以簡化網路伺服器的開發。
3.1、Socket 型別
套接字格式:
socket(family,type[,protocal]) 使用給定的地址族、套接字型別、協議編號(預設為0)來建立套接字。
socket型別 |
描述 |
socket.AF_UNIX |
只能夠用於單一的Unix系統程序間通訊 |
socket.AF_INET |
伺服器之間網路通訊 |
socket.AF_INET6 |
IPv6 |
socket.SOCK_STREAM |
流式socket , for TCP |
socket.SOCK_DGRAM |
資料報式socket , for UDP |
socket.SOCK_RAW |
原始套接字,普通的套接字無法處理ICMP、IGMP等網路報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由使用者構造IP頭。 |
socket.SOCK_SEQPACKET |
可靠的連續資料包服務 |
建立TCP Socket: |
|
建立UDP Socket: |
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) |
3.2、Socket 函式
注意點:
1)TCP傳送資料時,已建立好TCP連線,所以不需要指定地址。UDP是面向無連線的,每次傳送要指定是發給誰。
2)服務端與客戶端不能直接傳送列表,元組,字典。需要字串化repr(data)。
socket函式 |
描述 |
|
服務端socket函式 |
||
s.bind(address) |
將套接字繫結到地址, 在AF_INET下,以元組(host,port)的形式表示地址. |
|
s.listen(backlog) |
開始監聽TCP傳入連線。backlog指定在拒絕連線之前,作業系統可以掛起的最大連線數量。該值至少為1,大部分應用程式設為5就可以了。 |
|
s.accept() |
接受TCP連線並返回(conn,address),其中conn是新的套接字物件,可以用來接收和傳送資料。address是連線客戶端的地址。 |
|
客戶端socket函式 |
||
s.connect(address) |
連線到address處的套接字。一般address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。 |
|
s.connect_ex(adddress) |
功能與connect(address)相同,但是成功返回0,失敗返回errno的值。 |
|
公共socket函式 |
||
s.recv(bufsize[,flag]) |
接受TCP套接字的資料。資料以字串形式返回,bufsize指定要接收的最大資料量。flag提供有關訊息的其他資訊,通常可以忽略。 |
|
s.send(string[,flag]) |
傳送TCP資料。將string中的資料傳送到連線的套接字。返回值是要傳送的位元組數量,該數量可能小於string的位元組大小。 |
|
s.sendall(string[,flag]) |
完整發送TCP資料。將string中的資料傳送到連線的套接字,但在返回之前會嘗試傳送所有資料。成功返回None,失敗則丟擲異常。 |
|
s.recvfrom(bufsize[.flag]) |
接受UDP套接字的資料。與recv()類似,但返回值是(data,address)。其中data是包含接收資料的字串,address是傳送資料的套接字地址。 |
|
s.sendto(string[,flag],address) |
傳送UDP資料。將資料傳送到套接字,address是形式為(ipaddr,port)的元組,指定遠端地址。返回值是傳送的位元組數。 |
|
s.close() |
關閉套接字。 |
|
s.getpeername() |
返回連線套接字的遠端地址。返回值通常是元組(ipaddr,port)。 |
|
s.getsockname() |
返回套接字自己的地址。通常是一個元組(ipaddr,port) |
|
s.setsockopt(level,optname,value) |
設定給定套接字選項的值。 |
|
s.getsockopt(level,optname[.buflen]) |
返回套接字選項的值。 |
|
s.settimeout(timeout) |
設定套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛建立套接字時設定,因為它們可能用於連線的操作(如connect()) |
|
s.gettimeout() |
返回當前超時期的值,單位是秒,如果沒有設定超時期,則返回None。 |
|
s.fileno() |
返回套接字的檔案描述符。 |
|
s.setblocking(flag) |
如果flag為0,則將套接字設為非阻塞模式,否則將套接字設為阻塞模式(預設值)。非阻塞模式下,如果呼叫recv()沒有發現任何資料,或send()呼叫無法立即傳送資料,那麼將引起socket.error異常。 |
|
s.makefile() |
建立一個與該套接字相關連的檔案 |
四、socket程式設計思路
TCP服務端:
1 建立套接字,繫結套接字到本地IP與埠
# socket.socket(socket.AF_INET,socket.SOCK_STREAM) , s.bind()
2 開始監聽連線 #s.listen()
3 進入迴圈,不斷接受客戶端的連線請求 #s.accept()
4 然後接收傳來的資料,併發送給對方資料 #s.recv() , s.sendall()
5 傳輸完畢後,關閉套接字 #s.close()
TCP客戶端:
1 建立套接字,連線遠端地址
# socket.socket(socket.AF_INET,socket.SOCK_STREAM) , s.connect()
2 連線後傳送資料和接收資料 # s.sendall(), s.recv()
3 傳輸完畢後,關閉套接字 #s.close()
故事1:一次會話
######服務端##########
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')
conn, addr = sk.accept()
client_data = conn.recv(1024)
print(str(client_data, "utf8"))
inp = input('>>>')
conn.send(bytes(inp, 'utf8'))
sk.close()
#######客戶端##############
import socket
sk = socket.socket()
print(sk)
address = ('127.0.0.1', 8000)
sk.connect(address)
inp = input('>>>')
sk.send(bytes(inp, 'utf8'))
data = sk.recv(1024)
print(str(data, 'utf8'))
sk.close()
故事2:持久會話
##############服務端##############
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')
conn, addr = sk.accept()
while 1:
client_data = conn.recv(1024)
print(str(client_data, "utf8"))
inp = input('>>>')
conn.send(bytes(inp, 'utf8'))
sk.close()
#############客戶端##############
import socket
sk = socket.socket()
print(sk)
address = ('127.0.0.1', 8000)
sk.connect(address)
while True:
inp = input('>>>')
sk.send(bytes(inp, 'utf8'))
data = sk.recv(1024)
print(str(data, 'utf8'))
sk.close()
故事3:持久會話,然後再結束
##############服務端##############
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')
conn, addr = sk.accept()
while 1:
client_data = conn.recv(1024)
if not client_data: break
print('.........', str(client_data, "utf8"))
inp = input('>>>')
conn.send(bytes(inp, 'utf8'))
sk.close()
import socket
sk = socket.socket()
print(sk)
address = ('127.0.0.1', 8000)
sk.connect(address)
while True:
inp = input('>>>')
if inp == 'exit':
break
sk.send(bytes(inp, 'utf8'))
data = sk.recv(1024)
print(str(data, 'utf8'))
sk.close()
故事4:持久會話,然後再結束,客戶端只發空,然後會出現什麼情況
##############服務端##############
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')
conn, addr = sk.accept()
while 1:
client_data = conn.recv(1024)
if not client_data: break
print('.........', str(client_data, "utf8"))
inp = input('>>>')
conn.send(bytes(inp, 'utf8'))
sk.close()
import socket
sk = socket.socket()
print(sk)
address = ('127.0.0.1', 8000)
sk.connect(address)
while True:
inp = input('>>>')
if inp == 'exit':
break
sk.send(bytes(inp, 'utf8'))
data = sk.recv(1024)
print(str(data, 'utf8'))
sk.close()
這樣如果客戶端發的資料就是空資料的話豈不是也意外退出,那不就bug啦?
其實不用擔心,如果客戶端send了一個空資料後客戶端繼續向下執行,而server端的recv方法會繼續阻塞,直到接收到一個非空資料才會繼續向下執行。
故事5:持久會話,然後再結束,服務多個客戶端
##############服務端##############
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')
while 1:
conn, addr = sk.accept()
while 1:
client_data = conn.recv(1024)
print('.........', str(client_data, "utf8"))
if not client_data:break
inp = input('>>>')
conn.send(bytes(inp, 'utf8'))
sk.close()
#############客戶端##############
import socket
sk = socket.socket()
print(sk)
address = ('127.0.0.1', 8000)
sk.connect(address)
while True:
inp = input('>>>')
if inp == 'exit':
break
sk.send(bytes(inp, 'utf8'))
data = sk.recv(1024)
print(str(data, 'utf8'))
sk.close()
另外一種方法:
##############服務端##############
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')
conn, addr = sk.accept()
while 1:
client_data = conn.recv(1024)
print('.........', str(client_data, "utf8"))
if not client_data:
conn,addr = sk.accept()
continue
inp = input('>>>')
conn.send(bytes(inp, 'utf8'))
sk.close()
#############客戶端##############
import socket
sk = socket.socket()
print(sk)
address = ('127.0.0.1', 8000)
sk.connect(address)
while True:
inp = input('>>>')
if inp == 'exit':
break
sk.send(bytes(inp, 'utf8'))
data = sk.recv(1024)
print(str(data, 'utf8'))
sk.close()
故事6:持久會話,然後再結束,服務多個客戶端,如果按住紅色stop_button按鈕的退出處理
##############服務端##############
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')
while 1:
conn, addr = sk.accept()
print(addr)
while 1:
try:
client_data = conn.recv(1024)
except Exception:
break
print('.........', str(client_data, "utf8"))
if not client_data:break
inp = input('>>>')
conn.send(bytes(inp, 'utf8'))
sk.close()
#############客戶端##############
import socket
sk = socket.socket()
print(sk)
address = ('127.0.0.1', 8000)
sk.connect(address)
while True:
inp = input('>>>')
if inp == 'exit':
break
sk.send(bytes(inp, 'utf8'))
data = sk.recv(1024)
print(str(data, 'utf8'))
sk.close()
五、遠端執行
#############客戶端##############
import socket
sk = socket.socket()
print(sk)
address = ('127.0.0.1', 8000)
sk.connect(address)
while True:
inp = input('>>>')
if inp == 'exit':
break
sk.send(bytes(inp, 'utf8'))
result_len = int(str(sk.recv(1024)), 'utf8')
print(result_len)
data = bytes()
while len(data) != result_len:
recv = sk.recv(1024)
data += recv
print(str(data, 'gbk'))
sk.close()
##############服務端##############
import subprocess
# subprocess.Popen()
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')
while 1:
conn, addr = sk.accept()
print(addr)
while 1:
try:
client_data = conn.recv(1024)
except Exception:
break
print('.........', str(client_data, "utf8"))
if not client_data:break
obj = subprocess.Popen(str(client_data,'utf8'), shell=True, stdout=subprocess.PIPE)
cmd_result = obj.stdout.read()
result_len = bytes(str(len(cmd_result)), 'utf8')
print('>>>>>>>', result_len)
conn.sendall(result_len)
conn.sendall(cmd_result)
sk.close()
conclusion:
sendall會把資料直接全部發送到客戶端,客戶端將所有的資料都放到緩衝區,每次recv多少位元組取決於recv內的引數,理論不應該超過8k。
所以,並不能一次recv()無限大資料,所以這裡我們應該通過迴圈去接收。
六、解決大資料傳送和粘包問題
##############服務端##############
import subprocess
# subprocess.Popen()
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')
while 1:
conn, addr = sk.accept()
print(addr)
while 1:
try:
client_data = conn.recv(1024)
except Exception:
break
print('.........', str(client_data, "utf8"))
if not client_data:break
obj = subprocess.Popen(str(client_data, 'utf8'), shell=True, stdout=subprocess.PIPE)
cmd_result = obj.stdout.read()
result_len = bytes(str(len(cmd_result)), 'utf8')
print('>>>>>>>', result_len)
conn.sendall(result_len) #粘包現象
conn.recv(1024)#解決粘包
# import time
# time.sleep(1)
conn.sendall(cmd_result)
sk.close()
#############客戶端##############
import socket
sk = socket.socket()
print(sk)
address = ('127.0.0.1', 8000)
sk.connect(address)
while True:
inp = input('>>>')
if inp == 'exit':
break
sk.send(bytes(inp, 'utf8'))
result_len = int(str(sk.recv(1024)), 'utf8')
sk.sendall('111')
print(result_len)
data = bytes()
while len(data) != result_len:
recv = sk.recv(1024)
data += recv
print(str(data, 'gbk'))
sk.close()
七、檔案傳送
post_serve
import subprocess
import os
# subprocess.Popen()
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
while 1:
conn, addr = sk.accept()
# print(addr)
while 1:
data = conn.recv(1024)
cmd, filename, filesize = str(data, 'utf8').split('|')
path = os.path.join(BASE_DIR, 'ma', filename)
filesize = int(filesize)
f = open(path, 'ab')
has_receive = 0
while has_receive != filesize:
data = conn.recv(1024)
f.write(data)
has_receive += len(data)
f.close()
post_client
import socket
import os
sk = socket.socket()
print(sk)
address = ('127.0.0.1', 8000)
sk.connect(address)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
while True:
inp = input('>>>').strip() # post\11.png
cmd,path = inp.split('|')
path = os.path.join(BASE_DIR, path)
filename = os.path.basename(path)
filesize = os.stat(path).st_size
file_info = 'post|%s|%s' % (filename, filesize)
sk.sendall(bytes(file_info, 'utf8'))
f = open(path, 'rb')
has_sent = 0
while has_sent != filesize:
data = f.read(1024)
sk.sendall(data)
has_sent += len(data)
f.close()
print('上傳成功')
八、serve端實現併發聊天
簡單例子
##############服務端##############
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
print("服務端啟動...")
while True:
conn = self.request
print(self.client_address)
while True:
client_data=conn.recv(1024)
print(str(client_data,"utf8"))
print("waiting...")
conn.sendall(client_data)
conn.close()
if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('127.0.0.1', 8091), MyServer)
server.serve_forever()
#############客戶端##############
import socket
ip_port = ('127.0.0.1', 8091)
sk = socket.socket()
sk.connect(ip_port)
print("客戶端啟動:")
while True:
inp = input('>>>')
sk.sendall(bytes(inp,"utf8"))
if inp == 'exit':
break
server_response=sk.recv(1024)
print(str(server_response,"utf8"))
sk.close()
聊天併發例項
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
print("服務端啟動...")
while True:
conn = self.request
print(self.client_address)
while True:
client_data=conn.recv(1024)
print(str(client_data,"utf8"))
print("waiting...")
server_response=input(">>>")
conn.sendall(bytes(server_response,"utf8"))
# conn.sendall(client_data)
conn.close()
# print self.request,self.client_address,self.server
if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('127.0.0.1', 8098), MyServer)
server.serve_forever()
import socket
ip_port = ('127.0.0.1', 8098)
sk = socket.socket()
sk.connect(ip_port)
print ("客戶端啟動:")
while True:
inp = input('>>>')
sk.sendall(bytes(inp,"utf8"))
server_response=sk.recv(1024)
print (str(server_response,"utf8"))
if inp == 'exit':
break
sk.close()
九、socketserver模組
它包含了種五種server類,BaseServer(不直接對外服務)。TCPServer使用TCP協議,UDPServer使用UDP協議,還有兩個不常使用的,即UnixStreamServer和UnixDatagramServer,這兩個類僅僅在unix環境下有用(AF_unix)。
建立一個socketserver 至少分以下幾步
- First, you must create a request handler class by subclassing the
BaseRequestHandler
class and overriding itshandle()
method; this method will process incoming requests. - Second, you must instantiate one of the server classes, passing it the server’s address and the request handler class.
- Then call the
handle_request()
orserve_forever()
method of the server object to process one or many requests. - Finally, call
server_close()
to close the socket.
讓你的socketserver併發起來, 必須選擇使用以下一個多併發的類
1 2 3 4 5 6 7 |
|