1,什麼是C/S架構?
C指的是client(用戶端軟體),S指的是Server(服務端軟體)
一個C/S架構就是,實作服務端軟體與用戶端軟體基于網絡通信。
網際網路中處處是C/S架構
如12306網站是服務端,你的浏覽器是用戶端(B/S架構也是C/S架構的一種)
騰訊作為服務端為你提供視訊,你得下個騰訊視訊用戶端才能看它的視訊)
C/S架構與socket的關系:
我們學習socket就是為了完成C/S架構的開發
2,網際網路協定是什麼?分别介紹五層協定中每一層的功能?
英語成為世界上所有人通信的統一标準,計算機之間的通信也應該有一個像英語一樣的通信标準,
這個标準稱之為網際網路協定, 可以很明确地說:網際網路協定就是計算機界的英語,網絡就是實體連結
媒體+網際網路協定。 我們需要做的是,讓全世界的計算機都學會網際網路協定,這樣任意一台計算機在
發消息時都嚴格按照協定規定的格式去組織資料,接收方就可以按照相同的協定解析出結果了,這就
實作了全世界的計算機都能無障礙通信。 按照功能不同,人們将網際網路協定分為osi七層或tcp/ip五
層或tcp/ip四層(我們隻需要掌握tcp/ip五層協定即可),這種分層就好比是學習英語的幾個階段,
每個階段應該掌握專門的技能或者說完成特定的任務,比如:1、學音标 2、學單詞 3、學文法 4、寫作文
簡單說,計算機之間的通信标準,就稱為網際網路協定
按照功能不同,人們将網際網路協定分為osi七層或tcp/ip五層或tcp/ip四層
tcp/ip四層:應用層,傳輸層,網絡層,網絡接口層
tcp/ip五層:應用層,傳輸層,網絡層,資料鍊路層,實體層
osi七層:應用層,表示層,會話層,傳輸層,網絡層,資料鍊路層,實體層
3,基于tcp協定通信,為何建立連結需要三次握手,而斷開連結卻需要四次揮手
為什麼要三次揮手?
在隻有兩次“握手”的情形下,假設Client想跟Server建立連接配接,但是卻因為中途連接配接請求的
資料報丢失了,故Client端不得不重新發送一遍;這個時候Server端僅收到一個連接配接請求,是以
可以正常的建立連接配接。但是,有時候Client端重新發送請求不是因為資料報丢失了,而是有可能
資料傳輸過程因為網絡并發量很大在某結點被阻塞了,這種情形下Server端将先後收到2次請求,
并持續等待兩個Client請求向他發送資料...問題就在這裡,Cient端實際上隻有一次請求,而
Server端卻有2個響應,極端的情況可能由于Client端多次重新發送請求資料而導緻Server端
最後建立了N多個響應在等待,因而造成極大的資源浪費!是以,“三次握手”很有必要!
為什麼要四次揮手?
試想一下,假如現在你是用戶端你想斷開跟Server的所有連接配接該怎麼做?
第一步,你自己先停止向Server端發送資料,并等待Server的回複。但事情還沒有完,
雖然你自身不往Server發送資料了,但是因為你們之前已經建立好平等的連接配接了,
是以此時他也有主動權向你發送資料;故Server端還得終止主動向你發送資料,
并等待你的确認。其實,說白了就是保證雙方的一個合約的完整執行!
三次握手:client發送請求建立通道;server收到請求并同意,同時也發送請求建通道;client收到請求并同意,建立完成
四次揮手:client發送請求斷開通道;server收到請求并同意,同時還回複client上一條消息;server也發送請求斷開通道;client受到消息結束
為什麼TCP協定終止連結要四次?
1、當主機A确認發送完資料且知道B已經接受完了,想要關閉發送資料口(當然确認信号還是可以發),
就會發FIN給主機B。
2、主機B收到A發送的FIN,表示收到了,就會發送ACK回複。
3、但這是B可能還在發送資料,沒有想要關閉資料口的意思,是以FIN與ACK不是同時發送的,
而是等到B資料發送完了,才會發送FIN給主機A。
4、A收到B發來的FIN,知道B的資料也發送完了,回複ACK, A等待2MSL以後,沒有收到B傳來的
任何消息,知道B已經收到自己的ACK了,A就關閉連結,B也關閉連結了。
4,為何基于tcp協定的通信比基于udp協定的通信更可靠?
tcp:可靠 對方給了确認收到資訊,才發下一個,如果沒收到确認資訊就重發
udp:不可靠 一直發資料,不需要對方回應
(1)UDP:user datagram protocol(使用者資料報協定)
特點:
——1:面向無連接配接:傳輸資料之前源端和目的端不需要建立連接配接
——2:每個資料報的大小都限制在64k(8個位元組)以内
——3:面向封包的不可靠協定(即發出去的資料不一定會接收到)
——4:傳輸速率快,效率高
——5:現實生活執行個體:郵局寄件,實時線上聊天,視訊協定等等
(2)TCP:transmission control protocol(傳輸控制協定)
特點:
——1:面向連接配接:傳輸資料之前需要建立連接配接
——2:在連接配接過程中進行大量的資料傳輸
——3:通過“三次握手”的方式完成連接配接,是安全可靠的協定
——4:傳輸效率低,速度慢
5,流式協定指的是什麼協定,資料報協定指的是什麼協定?
TCP協定,可靠傳輸
資料報協定: UDP協定,不可傳輸
也就是TCP和UDP的差別:
TCP是面向連接配接的,可靠的位元組流服務
UDP是面向無連接配接的資料報服務
6,什麼是socket?簡述基于tcp協定的套接字通信流程流式協定:
Socket是應用層與TCP/IP協定族通信的中間軟體抽象層,它是一組接口。在設計模式中,
Socket其實就是一個門面模式,它把複雜的TCP/IP協定族隐藏在Socket接口後面,
對使用者來說,一組簡單的接口就是全部。
服務端:建立socket對象,綁定ip端口bind(), 設定最大連結數listen(), accept()與用戶端
的connect()建立雙向管道, send(), recv(),close()
用戶端:建立socket對象,connect()與服務端accept()建立雙向管道 , send(), recv(),close()
7,什麼是粘包? socket 中造成粘包的原因是什麼? 哪些情況會發生粘包現象?
粘包:資料粘在一起,主要因為:接收方不知道消息之間的界限,不知道一次性提取
多少位元組的資料造成的資料量比較小,時間間隔比較短,就合并成了一個包,
這是底層的一個優化算法(Nagle算法)
8,基于socket開發一個聊天程式,實作兩端互相發送和接收消息
服務端:
# _*_ coding: utf-8 _*_
# 8,基于socket開發一個聊天程式,實作兩端互相發送和接收消息
import socket
ip_port = ('127.0.0.1',8088)
link = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
link.bind(ip_port)
link.listen(5)
print("等待資料連接配接:。。》》")
# 阻塞直到有連接配接為止,有了一個新連接配接進來後,就會為這個請求生成一個連接配接對象
conn, addr = link.accept()
client_data = conn.recv(1024)
print("這是收到的消息:",client_data.decode('utf-8'))
conn.send(client_data.upper())
conn.close()
link.close()
用戶端
# _*_ coding: utf-8 _*_
# 8,基于socket開發一個聊天程式,實作兩端互相發送和接收消息/
import socket
ip_port = ('127.0.0.1',8088)
link = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
link.connect(ip_port)
print("開始發送資料")
cmd = input("請輸入:>>").strip()
link.send(cmd.encode('utf-8'))
data = link.recv(1028)
print(data)
link.close()
9,基于tcp socket,開發簡單的遠端指令執行程式,允許使用者執行指令,并傳回結果
服務端:
# _*_ coding: utf-8 _*_
# 9,基于tcp socket,開發簡單的遠端指令執行程式,允許使用者執行指令,并傳回結果
import socket
import struct
import subprocess
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.bind(ip_port)
sk.listen(5)
while True: #連接配接循環
conn,addr = sk.accept()
print(conn,addr)
while True: #通信循環
client_data = conn.recv(1024)
#處理過程
res = subprocess.Popen(client_data.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout = res.stdout.read()
stderr = res.stderr.read()
# 先發報頭(轉成固定長度的bytes類型,那麼怎麼轉呢?就用到了struct子產品)
length = len(stdout)+len(stderr)
header = struct.pack('i',length)
conn.send(header)
conn.send(stderr)
conn.send(stdout)
conn.close()
sk.close()
conn.close()
sk.close()
# _*_ coding: utf-8 _*_
# 9,基于tcp socket,開發簡單的遠端指令執行程式,允許使用者執行指令,并傳回結果
import socket
import struct
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.connect(ip_port)
while True:
cmd = input("請輸入指令:>>").strip()
if not cmd:continue
sk.send(cmd.encode('utf-8'))
header_struct = sk.recv(1024)
unpack_res = struct.unpack('i',header_struct)
total_size = unpack_res[0]
recv_size = 0
total_data = b''
while recv_size < total_size:
recv_data = sk.recv(1024)
recv_size += len(recv_data)
total_data += recv_data
print("傳回的消息:%s"%total_data.decode('gbk'))
sk.close()
10,基于tcp協定編寫簡單FTP程式,實作上傳、下載下傳檔案功能,并解決粘包問題
用戶端
# _*_ coding: utf-8 _*_
import socket
import struct
import json
downlaod_dir = r'D:\檔案傳輸\client\download'
ip_port = ('127.0.0.1',8808)
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(ip_port)
while True:
cmd = input(">>>").strip() #get D:\檔案傳輸\server\a.avi
if not cmd:continue
phone.send(cmd.encode('utf-8'))
#接受檔案的内容,以寫的方式打開一個新檔案,接受服務端發來的檔案内容,并寫入用戶端的新檔案
#第一步,先收報頭的長度,然後解包
obj = phone.recv(1024)
header_size = struct.unpack('i',obj)[0]
#第二部 再收報頭
header_bytes = phone.recv(header_size)
#第三部,從報頭中解析除對真實資料的描述資訊
header_json = header_bytes.decode('utf-8')
header_dic = json.loads(header_json)
'''
header_dic = {
'filename':filename, #a.avi
'md5':'dsdsd',
'file_size':os.path.getsize(filename)
}'''
print(header_dic)
total_size = header_dic['file_size']
filename = header_dic['filename']
#第四步,接受真實的資料
with open('%s/%s'%(downlaod_dir,filename),'wb') as f:
recv_size = 0
# recv_data = b''
while recv_size <total_size:
res = phone.recv(1024)
# recv_data += res
f.write(res)
recv_size += len(res)
print("總大小: %s \n已經下載下傳大小 :%s"%(total_size,recv_size))
# print(recv_data.decode('utf-8'))
phone.close()
服務端
# _*_ coding: utf-8 _*_
import subprocess
import socket
import struct
import json
import os
share_dir =r'/檔案傳輸/server/share'
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port = ('127.0.0.1',8808)
phone.bind(ip_port)
phone.listen(5)
print("starting....")
while True: #連結循環
conn,client_addr = phone.accept()
print(client_addr)
while True: #通信循環
try:
#收指令
res = conn.recv(1024) #b'get a.txt'
if not res :continue
#解析指令,提取相應的參數
cmds = res.decode('utf-8').split()
filename = cmds[1]
#以讀的方式打開檔案,讀取檔案内容發送給用戶端
# with open(filename,'rb') as f:
# conn.s
#制定固定長度的報頭
header_dic = {
'filename':filename, #a.avi
'md5':'dsdsd',
'file_size':os.path.getsize(r"%s/%s"%(share_dir,filename))
}
header_json = json.dumps(header_dic)
header_bytes = header_json.encode('utf-8')
#先發送報頭的長度
conn.send(struct.pack('i',len(header_bytes)))
#再發報頭
conn.send(header_bytes)
#再發真實的資料
with open('%s/%s'%(share_dir,filename),'rb') as f:
# conn.send(f.read())
for line in f:
conn.send(line)
except ConnectionResetError:
break
conn.close()
phone.close()
函數版本服務端
# _*_ coding: utf-8 _*_
import socket
import os
import struct
import pickle
dirname = os.path.dirname(os.path.abspath(__file__))
filepath = os.path.join(dirname, 'share')
def get(cmds,conn):
filename = cmds[1]
file_path = os.path.join(filepath, filename)
if os.path.isfile(file_path):
header = {
'filename': filename,
'md5': 'xxxxxx',
'file_size': os.path.getsize(file_path)
}
header_bytes = pickle.dumps(header)
conn.send(struct.pack('i', len(header_bytes)))
conn.send(header_bytes)
with open(file_path, 'rb') as f:
for line in f:
conn.send(line)
else:
conn.send(struct.pack('i', 0))
def put(cmds,conn):
pass
def run():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)
print('starting...')
while True:
conn, client_addr = server.accept()
print(client_addr)
while True:
try:
res = conn.recv(1024)
if not res: continue
cmds = res.decode('utf-8').split()
if cmds[0] == 'get':
get(cmds,conn)
elif cmds[0] == 'put':
put(cmds,conn)
except ConnectionResetError:
break
conn.close()
server.close()
if __name__ == '__main__':
run()
函數版本用戶端
# _*_ coding: utf-8 _*_
import socket
import struct
import pickle
import os
dirname = os.path.dirname(os.path.abspath(__file__))
filepath = os.path.join(dirname,'download')
def get(client):
obj = client.recv(4)
header_size = struct.unpack('i', obj)[0]
if header_size == 0:
print('檔案不存在')
else:
header_types = client.recv(header_size)
header_dic = pickle.loads(header_types)
print(header_dic)
file_size = header_dic['file_size']
filename = header_dic['filename']
with open('%s/%s' % (filepath, filename), 'wb') as f:
recv_size = 0
while recv_size < file_size:
res = client.recv(1024)
f.write(res)
recv_size += len(res)
print('總大小:%s 已下載下傳:%s' % (file_size, recv_size))
def put():
pass
def run():
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8080))
while True:
msg = input(">>>:").strip() # get a.txt
if not msg:continue
client.send(msg.encode('utf-8'))
cmds = msg.split()
if cmds[0] == 'get':
get(client)
elif cmds[0] == 'put':
put()
client.close()
if __name__ == '__main__':
run()
面向對象版本服務端
# _*_ coding: utf-8 _*_
import socket
import os
import struct
import pickle
class TCPServer:
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
listen_count = 5
max_recv_bytes = 8192
coding = 'utf-8'
allow_reuse_address = False
# 下載下傳的檔案存放路徑
down_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'share')
# 上傳的檔案存放路徑
upload_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'upload')
def __init__(self,server_address,bind_and_listen=True):
self.server_address = server_address
self.socket = socket.socket(self.address_family,self.socket_type)
if bind_and_listen:
try:
self.server_bind()
self.server_listen()
except Exception:
self.server_close()
def server_bind(self):
if self.allow_reuse_address:
self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
self.socket.bind(self.server_address)
def server_listen(self):
self.socket.listen(self.listen_count)
def server_close(self):
self.socket.close()
def server_accept(self):
return self.socket.accept()
def conn_close(self,conn):
conn.close()
def run(self):
print('starting...')
while True:
self.conn,self.client_addr = self.server_accept()
print(self.client_addr)
while True:
try:
res = self.conn.recv(self.max_recv_bytes)
if not res:continue
cmds = res.decode(self.coding).split()
if hasattr(self,cmds[0]):
func = getattr(self,cmds[0])
func(cmds)
except Exception:
break
self.conn_close(self.conn)
def get(self,cmds):
""" 下載下傳
1.找到下載下傳的檔案
2.發送 header_size
3.發送 header_bytes file_size
4.讀檔案 rb 發送 send(line)
5.若檔案不存在,發送0 client提示:檔案不存在
:param cmds: 下載下傳的檔案 eg:['get','a.txt']
:return:
"""
filename = cmds[1]
file_path = os.path.join(self.down_filepath, filename)
if os.path.isfile(file_path):
header = {
'filename': filename,
'md5': 'xxxxxx',
'file_size': os.path.getsize(file_path)
}
header_bytes = pickle.dumps(header)
self.conn.send(struct.pack('i', len(header_bytes)))
self.conn.send(header_bytes)
with open(file_path, 'rb') as f:
for line in f:
self.conn.send(line)
else:
self.conn.send(struct.pack('i', 0))
def put(self,cmds):
""" 上傳
1.接收4個bytes 得到檔案的 header_size
2.根據 header_size 得到 header_bytes header_dic
3.根據 header_dic 得到 file_size
3.以寫的形式 打開檔案 f.write()
:param cmds: 下載下傳的檔案 eg:['put','a.txt']
:return:
"""
obj = self.conn.recv(4)
header_size = struct.unpack('i', obj)[0]
header_bytes = self.conn.recv(header_size)
header_dic = pickle.loads(header_bytes)
print(header_dic)
file_size = header_dic['file_size']
filename = header_dic['filename']
with open('%s/%s' % (self.upload_filepath, filename), 'wb') as f:
recv_size = 0
while recv_size < file_size:
res = self.conn.recv(self.max_recv_bytes)
f.write(res)
recv_size += len(res)
tcp_server = TCPServer(('127.0.0.1',8080))
tcp_server.run()
tcp_server.server_close()
服務端
面向對象版本用戶端
# _*_ coding: utf-8 _*_
import socket
import struct
import pickle
import os
class FTPClient:
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
# 下載下傳的檔案存放路徑
down_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'download')
# 上傳的檔案存放路徑
upload_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'share')
coding = 'utf-8'
max_recv_bytes = 8192
def __init__(self, server_address, connect=True):
self.server_address = server_address
self.socket = socket.socket(self.address_family, self.socket_type)
if connect:
try:
self.client_connect()
except Exception:
self.client_close()
def client_connect(self):
self.socket.connect(self.server_address)
def client_close(self):
self.socket.close()
def run(self):
while True:
# get a.txt 下載下傳 put a.txt 上傳
msg = input(">>>:").strip()
if not msg: continue
self.socket.send(msg.encode(self.coding))
cmds = msg.split()
if hasattr(self,cmds[0]):
func = getattr(self,cmds[0])
func(cmds)
def get(self, cmds):
""" 下載下傳
1.得到 header_size
2.得到 header_types header_dic
3.得到 file_size file_name
4.以寫的形式 打開檔案
:param cmds: 下載下傳的内容 eg: cmds = ['get','a.txt']
:return:
"""
obj = self.socket.recv(4)
header_size = struct.unpack('i', obj)[0]
if header_size == 0:
print('檔案不存在')
else:
header_types = self.socket.recv(header_size)
header_dic = pickle.loads(header_types)
print(header_dic)
file_size = header_dic['file_size']
filename = header_dic['filename']
with open('%s/%s' % (self.down_filepath, filename), 'wb') as f:
recv_size = 0
while recv_size < file_size:
res = self.socket.recv(self.max_recv_bytes)
f.write(res)
recv_size += len(res)
print('總大小:%s 已下載下傳:%s' % (file_size, recv_size))
else:
print('下載下傳成功!')
def put(self, cmds):
""" 上傳
1.檢視上傳的檔案是否存在
2.上傳檔案 header_size
3.上傳檔案 header_bytes
4.以讀的形式 打開檔案 send(line)
:param cmds: 上傳的内容 eg: cmds = ['put','a.txt']
:return:
"""
filename = cmds[1]
file_path = os.path.join(self.upload_filepath, filename)
if os.path.isfile(file_path):
file_size = os.path.getsize(file_path)
header = {
'filename': os.path.basename(filename),
'md5': 'xxxxxx',
'file_size': file_size
}
header_bytes = pickle.dumps(header)
self.socket.send(struct.pack('i', len(header_bytes)))
self.socket.send(header_bytes)
with open(file_path, 'rb') as f:
send_bytes = b''
for line in f:
self.socket.send(line)
send_bytes += line
print('總大小:%s 已上傳:%s' % (file_size, len(send_bytes)))
else:
print('上傳成功!')
else:
print('檔案不存在')
ftp_client = FTPClient(('127.0.0.1',8080))
ftp_client.run()
ftp_client.client_close()
用戶端
11,基于udp協定編寫程式,實作功能
服務端:
# _*_ coding: utf-8 _*_
import socket
ip_port = ('127.0.0.1',8808)
udp_server_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_server_client.bind(ip_port)
while True:
conn,addr = udp_server_client.recvfrom(1024)
print(conn,addr)
udp_server_client.sendto(conn.upper(),addr)
用戶端:
# _*_ coding: utf-8 _*_
import socket
ip_port = ('127.0.0.1',8808)
udp_server_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
cmd = input(">>>>").strip()
if not cmd:
continue
udp_server_client.sendto(cmd.encode('utf-8'),ip_port)
back_cmd,addr = udp_server_client.recvfrom(1024)
print(back_cmd.decode('utf-8'))
UDP不會發生粘包現象,下面舉例說明
# _*_ coding: utf-8 _*_
import socket
ip_port = ('127.0.0.1',8989)
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
client.sendto('hello'.encode('utf-8'),ip_port)
client.sendto('james'.encode('utf-8'),ip_port)
client.close()
# _*_ coding: utf-8 _*_
import socket
ip_port = ('127.0.0.1',8989)
server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
server.bind(ip_port)
res1 = server.recvfrom(5)
print("res1:",res1)
res2 = server.recvfrom(5)
print("res2:",res2)
server.close()
12,執行指定的指令,讓用戶端可以檢視服務端的時間
13,執行指定的指令,讓用戶端可以與服務的的時間同步
# _*_ coding: utf-8 _*_
import socket
import subprocess
import time
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8080))
while True:
data, client_addr = server.recvfrom(1024)
print(data, client_addr)
obj = subprocess.Popen(data.decode('utf-8'),shell=True, # time 指令在windows 下不能用
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
print(stdout+stderr)
server.sendto(stdout+stderr,client_addr)
if data.decode('utf-8') == 'time':
str_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
# str_time = '2017-01-01 00:00:00'
server.sendto(str_time.encode('gbk'), client_addr)
server.close()
# _*_ coding: utf-8 _*_
import socket
import os
import time
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
msg = input('>>>:').strip()
client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
data,server_addr = client.recvfrom(1024)
print(data.decode('utf-8'),server_addr)
localtime = time.localtime()
os.system("date %d-%d-%d" % (localtime.tm_year, localtime.tm_mon, localtime.tm_mday)) # 設定日期
os.system("time %d:%d:%d.0" % (localtime.tm_hour, localtime.tm_min, localtime.tm_sec)) # 設定時間
client.close()
不經一番徹骨寒 怎得梅花撲鼻香