1,簡述socket 通信原理
如上圖,socket通信建立在應用層與TCP/IP協議組通信(運輸層)的中間軟件抽象層,它是一組接口,在設計模式中,socket其實就是一個門面模式,它把復雜的TCP/IP協議組隱藏在Socket接口后面,對於用戶來說,一組簡單的接口就是全部,讓socket去組織數據,以符合指定的協議。
所以,經常對用戶來講,socket就是ip+prot 即IP地址(識別互聯網中主機的位置)+port是程序開啟的端口號
socket通信如下:
客戶端
# _*_ coding: utf-8 _*_ import socket ip_port = ('127.0.0.1',9696) link = socket.socket(socket.AF_INET,socket.SOCK_STREAM) link.connect(ip_port) print("開始發送數據") cmd = input("client請輸入要發送的數據>>>>").strip() link.send(cmd.encode('utf-8')) recv_data = link.recv(1024) print("這是受到的消息:",recv_data) link.close()
服務端
# _*_ coding: utf-8 _*_ import socket ip_port = ('127.0.0.1',9696) link = socket.socket(socket.AF_INET,socket.SOCK_STREAM) link.bind(ip_port) link.listen(5) conn,addr = link.accept() #這里,因為我們知道自己寫的少,所以1024夠用 recv_data = conn.recv(1024) print("這是受到的消息:",recv_data) cmd = input("server請輸入要發送的數據>>>>").strip() conn.send(cmd.encode('utf-8')) conn.close() link.close()
2,粘包的原因和解決方法?
TCP是面向流的協議,發送文件內容是按照一段一段字節流發送的,在接收方看來不知道文件的字節流從和開始,從何結束。
UDP是面向消息的協議,每個UDP段都是一個消息,
直接原因: 所謂粘包問題主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所造成的 根本原因: 發送方引起的粘包是由TCP協議本身造成的,TCP為了提高傳送效率,發送方往往要收集到足夠多的數據 才發送一個TCP段,若連續幾次需要send的數據都很少,通常TCP會根據優化算法把這些數據合成到一個TCP 段后一次發送過去,這樣接收方就受到了粘包數據。
如果需要一直收發消息,加一個while True即可。但是這里有個1024的問題,即粘包,
粘包的根源在於:接收端不知道發送端將要發的字節流的長度,所以解決粘包問題的方法就是圍繞如何讓發送端在發送數據前,把自己將要發送的字節流大小讓接收段知道,然后接收端來一個死循環,接收完所有的數據即可。
粘包解決的具體做法:為字節流加上自定義固定長度報頭,報頭中包含字節流長度,然后依次send到對端,對端在接受時,先從緩存中取出定長的報頭,然后再取真是數據。
客戶端
# _*_ coding: utf-8 _*_ import socket import struct import json phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) #連接服務器 while True: # 發收消息 cmd = input('請你輸入命令>>:').strip() if not cmd:continue phone.send(cmd.encode('utf-8')) #發送 #先收報頭的長度 header_len = struct.unpack('i',phone.recv(4))[0] #吧bytes類型的反解 #在收報頭 header_bytes = phone.recv(header_len) #收過來的也是bytes類型 header_json = header_bytes.decode('utf-8') #拿到json格式的字典 header_dic = json.loads(header_json) #反序列化拿到字典了 total_size = header_dic['total_size'] #就拿到數據的總長度了 #最后收數據 recv_size = 0 total_data=b'' while recv_size<total_size: #循環的收 recv_data = phone.recv(1024) #1024只是一個最大的限制 recv_size+=len(recv_data) #有可能接收的不是1024個字節,或許比1024多呢, # 那么接收的時候就接收不全,所以還要加上接收的那個長度 total_data+=recv_data #最終的結果 print('返回的消息:%s'%total_data.decode('gbk')) phone.close()
服務端
# _*_ coding: utf-8 _*_ import socket import subprocess import struct import json phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #買手機 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1',8080)) #綁定手機卡 phone.listen(5) #阻塞的最大數 print('start runing.....') while True: #鏈接循環 coon,addr = phone.accept()# 等待接電話 print(coon,addr) while True: #通信循環 # 收發消息 cmd = coon.recv(1024) #接收的最大數 print('接收的是:%s'%cmd.decode('utf-8')) #處理過程 res = subprocess.Popen(cmd.decode('utf-8'),shell = True, stdout=subprocess.PIPE, #標准輸出 stderr=subprocess.PIPE #標准錯誤 ) stdout = res.stdout.read() stderr = res.stderr.read() # 制作報頭 header_dic = { 'total_size': len(stdout)+len(stderr), # 總共的大小 'filename': None, 'md5': None } header_json = json.dumps(header_dic) #字符串類型 header_bytes = header_json.encode('utf-8') #轉成bytes類型(但是長度是可變的) #先發報頭的長度 coon.send(struct.pack('i',len(header_bytes))) #發送固定長度的報頭 #再發報頭 coon.send(header_bytes) #最后發命令的結果 coon.send(stdout) coon.send(stderr) coon.close() phone.close()
3,TCP/IP協議詳情
TCP和UDP協議在傳輸層
4,簡述3次握手,四次揮手?
三次握手: client發送請求建立通道; server收到請求並同意,同時也發送請求建通道; client收到請求並同意,建立完成 四次揮手: client發送請求斷開通道; server收到請求並同意,同時還回復client上一條消息; server也發送請求斷開通道; client受到消息結束
5,定義一個學生類,然后。。。
__init__被稱為構造方法或者初始化方法,在例實例化過程中自動執行,目的是初始化實例的一些屬性,每個實例通過__init__初始化的屬性都是獨有的。
self就是實例本身,你實例化時候python解釋器就會自動把這個實例本身通過self參數傳進去。
這個object,就是經典類與新式類的問題了。
1.只有在python2中才分新式類和經典類,python3中統一都是新式類 2.在python2中,沒有顯式的繼承object類的類,以及該類的子類,都是經典類 3.在python2中,顯式地聲明繼承object的類,以及該類的子類,都是新式類 4.在python3中,無論是否繼承object,都默認繼承object,即python3中所有類均為新式類
# _*_ coding: utf-8 _*_ class Student(object): def __init__(self,name,age,sex): self.name = name self.sex = sex self.age = age def talk(self): print("hello,my name is %s "%self.name) p = Student('james',12,'male') p.talk() print(p.__dict__) # 結果: # hello,my name is james # {'name': 'james', 'sex': 'male', 'age': 12}
6,繼承
繼承: 繼承就是類與類的關系,是一種創建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類可以稱為基類或超類,新建的類稱為派生類或子類。
python中類的繼承分為:單繼承和多繼承
class ParentClass1: #定義父類 pass class ParentClass2: #定義父類 pass class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類 pass
查看繼承:
>>> SubClass1.__bases__ #__base__只查看從左到右繼承的第一個子類,__bases__則是查看所有繼承的父類 (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
7,多態
多態指一種事物有多種形態,那為什么要使用多態呢?
1.增加了程序的靈活性 以不變應萬變,不論對象千變萬化,使用者都是同一種形式去調用,如func(animal) 2.增加了程序額可擴展性 通過繼承animal類創建了一個新的類,使用者無需更改自己的代碼,還是用func(animal)去調用
舉個例子:
>>> class Cat(Animal): #屬於動物的另外一種形態:貓 ... def talk(self): ... print('say miao') ... >>> def func(animal): #對於使用者來說,自己的代碼根本無需改動 ... animal.talk() ... >>> cat1=Cat() #實例出一只貓 >>> func(cat1) #甚至連調用方式也無需改變,就能調用貓的talk功能 say miao ''' 這樣我們新增了一個形態Cat,由Cat類產生的實例cat1,使用者可以在完全不需要修改 自己代碼的情況下。使用和人、狗、豬一樣的方式調用cat1的talk方法,即func(cat1) '''
8,封裝
首先說一下隱藏,在python中用雙下划線開頭的方式將屬性隱藏起來(即設置成私有屬性)
#其實這僅僅這是一種變形操作 #類中所有雙下划線開頭的名稱如__x都會自動變形成:_類名__x的形式: class A: __N=0 #類的數據屬性就應該是共享的,但是語法上是可以把類的數據屬性設置 成私有的如__N,會變形為_A__N def __init__(self): self.__X=10 #變形為self._A__X def __foo(self): #變形為_A__foo print('from A') def bar(self): self.__foo() #只有在類內部才可以通過__foo的形式訪問到. #A._A__N是可以訪問到的,即這種操作並不是嚴格意義上的限制外部訪問, 僅僅只是一種語法意義上的變形
封裝不是單純意義上的隱藏
1,封裝數據
將數據隱藏起來這不是目的。隱藏起來然后對外提供操作該數據的接口,然后我們可以在接口附加上對該數據操作的限制,以此完成對數據屬性操作的嚴格控制
class Teacher: def __init__(self,name,age): self.__name=name self.__age=age def tell_info(self): print('姓名:%s,年齡:%s' %(self.__name,self.__age)) def set_info(self,name,age): if not isinstance(name,str): raise TypeError('姓名必須是字符串類型') if not isinstance(age,int): raise TypeError('年齡必須是整型') self.__name=name self.__age=age t=Teacher('egon',18) t.tell_info() t.set_info('egon',19) t.tell_info()
2,封裝方法,目的是隔離復雜度
#取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、打印賬單、取錢 #對使用者來說,只需要知道取款這個功能即可,其余功能我們都可以隱藏起來,很明顯這么做 #隔離了復雜度,同時也提升了安全性 class ATM: def __card(self): print('插卡') def __auth(self): print('用戶認證') def __input(self): print('輸入取款金額') def __print_bill(self): print('打印賬單') def __take_money(self): print('取款') def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() a=ATM() a.withdraw()
9,元類? 使用元類定義一個對象
元類是類的類,是類的模板 元類是用來控制如何創建類的,正如類是創建對象的模板一樣,而元類的主要目的是為了控制類的創建行為 元類的實例化的結果為我們用class定義的類,正如類的實例為對象(f1對象是Foo類的一個實例, Foo類是 type 類的一個實例)
10,說一下__new__和__init__的區別
根據官方文檔:
-
__init__是當實例對象創建完成后被調用的,然后設置對象屬性的一些初始值。
-
__new__是在實例創建之前被調用的,因為它的任務就是創建實例然后返回該實例,是個靜態方法。
也就是,__new__在__init__之前被調用,__new__的返回值(實例)將傳遞給__init__方法的第一個參數,然后__init__給這個實例設置一些參數。
在python2.x中,從object繼承得來的類稱為新式類(如class A(object))不從object繼承得來的類稱為經典類(如class A()
新式類跟經典類的差別主要是以下幾點:
1. 新式類對象可以直接通過__class__屬性獲取自身類型:type
2. 繼承搜索的順序發生了改變,經典類多繼承時屬性搜索順序: 先深入繼承樹左側,再返回,開始找右側(即深度優先搜索);新式類多繼承屬性搜索順序: 先水平搜索,然后再向上移動
例子:
經典類: 搜索順序是(D,B,A,C)
>>> class A: attr = 1 ... >>> class B(A): pass ... >>> class C(A): attr = 2 ... >>> class D(B,C): pass ... >>> x = D() >>> x.attr 1
新式類繼承搜索程序是寬度優先
新式類:搜索順序是(D,B,C,A)
>>> class A(object): attr = 1 ... >>> class B(A): pass ... >>> class C(A): attr = 2 ... >>> class D(B,C): pass ... >>> x = D() >>> x.attr 2
3. 新式類增加了__slots__內置屬性, 可以把實例屬性的種類鎖定到__slots__規定的范圍之中。
4. 新式類增加了__getattribute__方法
5.新式類內置有__new__方法而經典類沒有__new__方法而只有__init__方法
注意:Python 2.x中默認都是經典類,只有顯式繼承了object才是新式類
而Python 3.x中默認都是新式類(也即object類默認是所有類的祖先),不必顯式的繼承object(可以按照經典類的定義方式寫一個經典類並分別在python2.x和3.x版本中使用dir函數檢驗下。
例如:
class A(): pass print(dir(A))
會發現在2.x下沒有__new__方法而3.x下有。
接下來說下__new__方法和__init__的區別:
在python中創建類的一個實例時,如果該類具有__new__方法,會先調用__new__方法,__new__方法接受當前正在實例化的類作為第一個參數(這個參數的類型是type,這個類型在c和python的交互編程中具有重要的角色,感興趣的可以搜下相關的資料),其返回值是本次創建產生的實例,也就是我們熟知的__init__方法中的第一個參數self。那么就會有一個問題,這個實例怎么得到?
注意到有__new__方法的都是object類的后代,因此如果我們自己想要改寫__new__方法(注意不改寫時在創建實例的時候使用的是父類的__new__方法,如果父類沒有則繼續上溯)可以通過調用object的__new__方法類得到這個實例(這實際上也和python中的默認機制基本一致),如:
class display(object): def __init__(self, *args, **kwargs): print("init") def __new__(cls, *args, **kwargs): print("new") print(type(cls)) return object.__new__(cls, *args, **kwargs) a=display()
運行上述代碼會得到如下輸出:
new <class 'type'> init
因此我們可以得到如下結論:
在實例創建過程中__new__方法先於__init__方法被調用,它的第一個參數類型為type。
如果不需要其它特殊的處理,可以使用object的__new__方法來得到創建的實例(也即self)。
於是我們可以發現,實際上可以使用其它類的__new__方法類得到這個實例,只要那個類或其父類或祖先有__new__方法。
class another(object): def __new__(cls,*args,**kwargs): print("newano") return object.__new__(cls, *args, **kwargs) class display(object): def __init__(self, *args, **kwargs): print("init") def __new__(cls, *args, **kwargs): print("newdis") print(type(cls)) return another.__new__(cls, *args, **kwargs) a=display()
上面的輸出是:
newdis <class 'type'> newano init
所有我們發現__new__和__init__就像這么一個關系,__init__提供生產的原料self(但並不保證這個原料來源正宗,像上面那樣它用的是另一個不相關的類的__new__方法類得到這個實例),而__init__就用__new__給的原料來完善這個對象(盡管它不知道這些原料是不是正宗的)
11,說一下深度優先和廣度優先的區別
只有在python2中才分新式類和經典類,python3中統一都是新式類 2.在python2中,沒有顯式的繼承object類的類,以及該類的子類,都是經典類 3.在python2中,顯式地聲明繼承object的類,以及該類的子類,都是新式類 4.在python3中,無論是否繼承object,都默認繼承object,即python3中所有類均為新式類
在Java和C#中子類只能繼承一個父類,而Python中子類可以同時繼承多個父類,如果繼承了多個父類,那么屬性的查找方式有兩種,分別是:深度優先和廣度優先
示范代碼
class A(object): def test(self): print('from A') class B(A): def test(self): print('from B') class C(A): def test(self): print('from C') class D(B): def test(self): print('from D') class E(C): def test(self): print('from E') class F(D,E): # def test(self): # print('from F') pass f1=F() f1.test() print(F.__mro__) #只有新式才有這個屬性可以查看線性列表,經典類沒有這個屬性 #新式類繼承順序:F->D->B->E->C->A #經典類繼承順序:F->D->B->A->E->C #python3中統一都是新式類 #pyhon2中才分新式類與經典類
12,說一下反射的原理
反射就是通過字符串映射到對象的屬性,python的一切事物都是對象(都可以使用反射)
1,hasattr(object,name) 判斷object中有沒有對應的方法和屬性
判斷object中有沒有一個name字符串對應的方法或屬性
2,getattr(object, name, default=None) 獲取object中有沒有對應的方法和屬性
3,setattr(x, y, v) 設置對象及其屬性
4,delattr(x, y) 刪除類或對象的屬性
13,編寫程序, 在元類中控制把自定義類的數據屬性都變成大寫.
class Mymetaclass(type): def __new__(cls,name,bases,attrs): update_attrs={} for k,v in attrs.items(): if not callable(v) and not k.startswith('__'): update_attrs[k.upper()]=v else: update_attrs[k]=v return type.__new__(cls,name,bases,update_attrs) class Chinese(metaclass=Mymetaclass): country='China' tag='Legend of the Dragon' #龍的傳人 def walk(self): print('%s is walking' %self.name) print(Chinese.__dict__) ''' {'__module__': '__main__', 'COUNTRY': 'China', 'TAG': 'Legend of the Dragon', 'walk': <function Chinese.walk at 0x0000000001E7B950>, '__dict__': <attribute '__dict__' of 'Chinese' objects>, '__weakref__': <attribute '__weakref__' of 'Chinese' objects>, '__doc__': None} '''
14,編寫程序, 在元類中控制自定義的類無需init方法.
1.元類幫其完成創建對象,以及初始化操作;
2.要求實例化時傳參必須為關鍵字形式,否則拋出異常TypeError: must use keyword argument
3.key作為用戶自定義類產生對象的屬性,且所有屬性變成大寫
class Mymetaclass(type): # def __new__(cls,name,bases,attrs): # update_attrs={} # for k,v in attrs.items(): # if not callable(v) and not k.startswith('__'): # update_attrs[k.upper()]=v # else: # update_attrs[k]=v # return type.__new__(cls,name,bases,update_attrs) def __call__(self, *args, **kwargs): if args: raise TypeError('must use keyword argument for key function') obj = object.__new__(self) #創建對象,self為類Foo for k,v in kwargs.items(): obj.__dict__[k.upper()]=v return obj class Chinese(metaclass=Mymetaclass): country='China' tag='Legend of the Dragon' #龍的傳人 def walk(self): print('%s is walking' %self.name) p=Chinese(name='egon',age=18,sex='male') print(p.__dict__)
15,簡述靜態方法和類方法
1:綁定方法(綁定給誰,誰來調用就自動將它本身當作第一個參數傳入):
綁定方法分為綁定到類的方法和綁定到對象的方法,具體如下:
1. 綁定到類的方法:用classmethod裝飾器裝飾的方法。 為類量身定制 類.boud_method(),自動將類當作第一個參數傳入 (其實對象也可調用,但仍將類當作第一個參數傳入) 2. 綁定到對象的方法:沒有被任何裝飾器裝飾的方法。 為對象量身定制 對象.boud_method(),自動將對象當作第一個參數傳入 (屬於類的函數,類可以調用,但是必須按照函數的規則來,沒有自動傳值那么一說)
2:非綁定方法:用staticmethod裝飾器裝飾的方法
1. 不與類或對象綁定,類和對象都可以調用,但是沒有自動傳值那么一說。就是一個普通工具而已 注意:與綁定到對象方法區分開,在類中直接定義的函數,沒有被任何裝飾器 裝飾的,都是綁定到對象的方法,可不是普通函數,對象調用該方法會自動傳值,而 staticmethod裝飾的方法,不管誰來調用,都沒有自動傳值一說
具體見:http://www.cnblogs.com/wj-1314/p/8675548.html
3,類方法與靜態方法說明
1:self表示為類型為類的object,而cls表示為類也就是class
2:在定義普通方法的時候,需要的是參數self,也就是把類的實例作為參數傳遞給方法,如果不寫self的時候,會發現報錯TypeError錯誤,表示傳遞的參數多了,其實也就是調用方法的時候,將實例作為參數傳遞了,在使用普通方法的時候,使用的是實例來調用方法,不能使用類來調用方法,沒有實例,那么方法將無法調用。
3:在定義靜態方法的時候,和模塊中的方法沒有什么不同,最大的不同就是在於靜態方法在類的命名空間之間,而且在聲明靜態方法的時候,使用的標記為@staticmethod,表示為靜態方法,在叼你用靜態方法的時候,可以使用類名或者是實例名來進行調用,一般使用類名來調用
4:靜態方法主要是用來放一些方法的,方法的邏輯屬於類,但是有何類本身沒有什么交互,從而形成了靜態方法,主要是讓靜態方法放在此類的名稱空間之內,從而能夠更加有組織性。
5:在定義類方法的時候,傳遞的參數為cls.表示為類,此寫法也可以變,但是一般寫為cls。類的方法調用可以使用類,也可以使用實例,一般情況使用的是類。
6:在重載調用父類方法的時候,最好是使用super來進行調用父類的方法。靜態方法主要用來存放邏輯性的代碼,基本在靜態方法中,不會涉及到類的方法和類的參數。類方法是在傳遞參數的時候,傳遞的是類的參數,參數是必須在cls中進行隱身穿
7:python中實現靜態方法和類方法都是依賴python的修飾器來實現的。靜態方法是staticmethod,類方法是classmethod。