在學(xué)習(xí)網(wǎng)絡(luò)編程之前還有許多的知識(shí)需要普及。socket就是很重要的一環(huán)。今天來(lái)看一看套接字。
1.服務(wù)器端與客戶端
BS架構(gòu) (騰訊通軟件:server+client)
CS架構(gòu) (web網(wǎng)站)
C/S架構(gòu)與socket的關(guān)系:
我們學(xué)習(xí)socket就是為了完成C/S架構(gòu)的開(kāi)發(fā)
2.OSI七層模型
互聯(lián)網(wǎng)協(xié)議按照功能不同分為osi七層或tcp/ip五層或tcp/ip四層

每層運(yùn)行常見(jiàn)物理設(shè)備

詳細(xì)參考:
http://www.cnblogs.com/linhaifeng/articles/5937962.html#_label4
學(xué)習(xí)socket一定要先學(xué)習(xí)互聯(lián)網(wǎng)協(xié)議:
1.首先:本節(jié)課程的目標(biāo)就是教會(huì)你如何基于socket編程,來(lái)開(kāi)發(fā)一款自己的C/S架構(gòu)軟件
2.其次:C/S架構(gòu)的軟件(軟件屬于應(yīng)用層)是基于網(wǎng)絡(luò)進(jìn)行通信的
3.然后:網(wǎng)絡(luò)的核心即一堆協(xié)議,協(xié)議即標(biāo)準(zhǔn),你想開(kāi)發(fā)一款基于網(wǎng)絡(luò)通信的軟件,就必須遵循這些標(biāo)準(zhǔn)。
4.最后:就讓我們從這些標(biāo)準(zhǔn)開(kāi)始研究,開(kāi)啟我們的socket編程之旅

TCP/IP協(xié)議族包括運(yùn)輸層、網(wǎng)絡(luò)層、鏈路層。
?3.socket層
Socket是介于應(yīng)用層和傳輸層之間。

4.socket是什么
Socket是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層,它是一組接口。在設(shè)計(jì)模式中,Socket其實(shí)就是一個(gè)門(mén)面模式,它把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面,對(duì)用戶來(lái)說(shuō),一組簡(jiǎn)單的接口就是全部,讓Socket去組織數(shù)據(jù),以符合指定的協(xié)議。
所以,我們無(wú)需深入理解tcp/udp協(xié)議,socket已經(jīng)為我們封裝好了,我們只需要遵循socket的規(guī)定去編程,寫(xiě)出的程序自然就是遵循tcp/udp標(biāo)準(zhǔn)的。
?掃盲篇:
1 將socket說(shuō)成ip+port,ip是用來(lái)標(biāo)識(shí)互聯(lián)網(wǎng)中的一臺(tái)主機(jī)的位置,而port是用來(lái)標(biāo)識(shí)這臺(tái)機(jī)器上的一個(gè)應(yīng)用程序,ip地址是配置到網(wǎng)卡上的,而port是應(yīng)用程序開(kāi)啟的,ip與port的綁定就標(biāo)識(shí)了互聯(lián)網(wǎng)中獨(dú)一無(wú)二的一個(gè)應(yīng)用程序
2
3 而程序的pid是同一臺(tái)機(jī)器上不同進(jìn)程或者線程的標(biāo)識(shí)(Google Chrome會(huì)有多個(gè)PID)
?5.套接字的發(fā)展歷程
套接字起源于 20 世紀(jì) 70 年代加利福尼亞大學(xué)伯克利分校版本的 Unix,即人們所說(shuō)的 BSD Unix。 因此,有時(shí)人們也把套接字稱為“伯克利套接字”或“BSD 套接字”。一開(kāi)始,套接字被設(shè)計(jì)用在同 一臺(tái)主機(jī)上多個(gè)應(yīng)用程序之間的通訊。這也被稱進(jìn)程間通訊,或 IPC。套接字有兩種(或者稱為有兩個(gè)種族),分別是基于文件型的和基于網(wǎng)絡(luò)型的。?
1、基于文件類型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字調(diào)用的就是底層的文件系統(tǒng)來(lái)取數(shù)據(jù),兩個(gè)套接字進(jìn)程運(yùn)行在同一機(jī)器,可以通過(guò)訪問(wèn)同一個(gè)文件系統(tǒng)間接完成通信
2、基于網(wǎng)絡(luò)類型的套接字家族
套接字家族的名字:AF_INET
(還有AF_INET6被用于ipv6,還有一些其他的地址家族,不過(guò),他們要么是只用于某個(gè)平臺(tái),要么就是已經(jīng)被廢棄,或者是很少被使用,或者是根本沒(méi)有實(shí)現(xiàn),所有地址家族中,AF_INET是使用最廣泛的一個(gè),python支持很多種地址家族,但是由于我們只關(guān)心網(wǎng)絡(luò)編程,所以大部分時(shí)候我么只使用AF_INET)
?6.套接字的工作流程
生活中的場(chǎng)景,你要打電話給一個(gè)朋友,先撥號(hào),朋友聽(tīng)到電話鈴聲后提起電話,這時(shí)你和你的朋友就建立起了連接,就可以講話了。等交流結(jié)束,掛斷電話結(jié)束此次交談。????
生活中的場(chǎng)景就解釋了這工作原理,也許TCP/IP協(xié)議族就是誕生于生活中,這也不一定。


先從服務(wù)器端說(shuō)起。服務(wù)器端先初始化Socket,然后與端口綁定(bind),對(duì)端口進(jìn)行監(jiān)聽(tīng)(listen),調(diào)用accept阻塞,等待客戶端連接。在這時(shí)如果有個(gè)客戶端初始化一個(gè)Socket,然后連接服務(wù)器(connect),如果連接成功,這時(shí)客戶端與服務(wù)器端的連接就建立了??蛻舳税l(fā)送數(shù)據(jù)請(qǐng)求,服務(wù)器端接收請(qǐng)求并處理請(qǐng)求,然后把回應(yīng)數(shù)據(jù)發(fā)送給客戶端,客戶端讀取數(shù)據(jù),最后關(guān)閉連接,一次交互結(jié)束。
一、socket模塊發(fā)送和接收消息
示例:模擬發(fā)送消息和接收消息的過(guò)程
tcp服務(wù)端(server)

1#!/usr/bin/env python 2# -*- coding:utf-8 -*-? ? ? 3 4import socket 5 6phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#買手機(jī) 7phone.bind(('127.0.0.1',8000))#綁定手機(jī)卡? #改成服務(wù)端網(wǎng)卡IP地址和端口 8phone.listen(5)#開(kāi)機(jī)? 5的作用是最大掛起連接數(shù)? #backlog連接池(也叫半鏈接) 9print('------------->')10conn,addr=phone.accept()#等電話1112msg=conn.recv(1024)#收消息13print('客戶端發(fā)來(lái)的消息是:',msg)14conn.send(msg.upper())#發(fā)消息1516conn.close()17phone.close()
執(zhí)行結(jié)果:
1 ------------->
tcp客戶端(client)
1#!/usr/bin/env python 2# -*- coding:utf-8 -*-? ? ? 3 4import socket 5 6phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 7 8phone.connect(('127.0.0.1',8000))#拔通電話? #改成服務(wù)端網(wǎng)卡IP地址和端口 910phone.send('hello'.encode('utf-8'))#發(fā)消息11data=phone.recv(1024)12print('收到服務(wù)端的發(fā)來(lái)的消息: ',data)1314phone.close()
執(zhí)行結(jié)果:
1 收到服務(wù)端的發(fā)來(lái)的消息:? b'HELLO'
二、功能介紹
①?server = socket.socket()
1參數(shù)一:地址簇 2 3 socket.AF_INET IPv4(默認(rèn)) 4 socket.AF_INET6 IPv6 5 6 socket.AF_UNIX 只能夠用于單一的Unix系統(tǒng)進(jìn)程間通信 7 8參數(shù)二:類型 910socket.SOCK_STREAM 流式socket ,for TCP (默認(rèn))11socket.SOCK_DGRAM 數(shù)據(jù)報(bào)式socket ,for UDP1213 socket.SOCK_RAW 原始套接字,普通的套接字無(wú)法處理ICMP、IGMP等網(wǎng)絡(luò)報(bào)文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報(bào)文;此外,利用原始套接字,可以通過(guò)IP_HDRINCL套接字選項(xiàng)由用戶構(gòu)造IP頭。14 socket.SOCK_RDM 是一種可靠的UDP形式,即保證交付數(shù)據(jù)報(bào)但不保證順序。SOCK_RAM用來(lái)提供對(duì)原始協(xié)議的低級(jí)訪問(wèn),在需要執(zhí)行某些特殊操作時(shí)使用,如發(fā)送ICMP報(bào)文。SOCK_RAM通常僅限于高級(jí)用戶或管理員運(yùn)行的程序使用。15 socket.SOCK_SEQPACKET 可靠的連續(xù)數(shù)據(jù)包服務(wù)1617參數(shù)三:協(xié)議18?。J(rèn))與特定的地址家族相關(guān)的協(xié)議,如果是 0 ,則系統(tǒng)就會(huì)根據(jù)地址格式和套接類別,自動(dòng)選擇一個(gè)合適的協(xié)議1920詳情
1# 服務(wù)端 2import socket 3ip_port = ('127.0.0.1',9999) 4sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0) 5sk.bind(ip_port) 6 7while True: 8data,(host,port) = sk.recvfrom(1024) 9print(data,host,port)10sk.sendto(bytes('ok', encoding='utf-8'), (host,port))111213#客戶端14import socket15ip_port = ('127.0.0.1',9999)1617sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)18while True:19inp = input('數(shù)據(jù):').strip()20ifinp =='exit':21break22sk.sendto(bytes(inp, encoding='utf-8'),ip_port)23data = sk.recvfrom(1024)24print(data)2526sk.close()2728UDP Demo
②server.bind(address)
server.bind(address) 將套接字綁定到地址。address地址的格式取決于地址族。在AF_INET下,以元組(host,port)的形式表示地址
③server.listen(backlog)
開(kāi)始監(jiān)聽(tīng)傳入連接。backlog指定在拒絕連接之前,可以掛起的最大連接數(shù)量。backlog等于5,表示內(nèi)核已經(jīng)接到了連接請(qǐng)求,但服務(wù)器還沒(méi)有調(diào)用accept進(jìn)行處理的連接個(gè)數(shù)最大為5,這個(gè)值不能無(wú)限大,因?yàn)橐趦?nèi)核中維護(hù)連接隊(duì)列
④server.setblocking(bool)
是否阻塞(默認(rèn)True),如果設(shè)置False,那么accept和recv時(shí)一旦無(wú)數(shù)據(jù),則報(bào)錯(cuò)
⑤conn,addr =server.accept()
接受連接并返回(conn,address),其中conn是新的套接字對(duì)象,可以用來(lái)接收和發(fā)送數(shù)據(jù)。address是連接客戶端的地址。接收TCP 客戶的連接(阻塞式)等待連接的到來(lái)
⑥?client.connect(address)
連接到address處的套接字。一般,address的格式為元組(hostname,port),如果連接出錯(cuò),返回socket.error錯(cuò)誤。
⑦client.connect_ex(address)
同上,只不過(guò)會(huì)有返回值,連接成功時(shí)返回 0 ,連接失敗時(shí)候返回編碼,例如:10061
⑧client.close()
關(guān)閉套接字
⑨client.recv(bufsize[,flag])
接受套接字的數(shù)據(jù)。數(shù)據(jù)以字符串形式返回,bufsize指定最多可以接收的數(shù)量。flag提供有關(guān)消息的其他信息,通常可以忽略
⑩client.recvfrom(bufsize[.flag])
與recv()類似,但返回值是(data,address)。其中data是包含接收數(shù)據(jù)的字符串,address是發(fā)送數(shù)據(jù)的套接字地址
?server.send(string[,flag])
將string中的數(shù)據(jù)發(fā)送到連接的套接字。返回值是要發(fā)送的字節(jié)數(shù)量,該數(shù)量可能小于string的字節(jié)大小。即:可能未將指定內(nèi)容全部發(fā)送
?server.sendall(string[,flag])
將string中的數(shù)據(jù)發(fā)送到連接的套接字,但在返回之前會(huì)嘗試發(fā)送所有數(shù)據(jù)。成功返回None,失敗則拋出異常;
? ? ? 內(nèi)部通過(guò)遞歸調(diào)用send,將所有內(nèi)容發(fā)送出去
?server.sendto(string[,flag],address)
將數(shù)據(jù)發(fā)送到套接字,address是形式為(ipaddr,port)的元組,指定遠(yuǎn)程地址。返回值是發(fā)送的字節(jié)數(shù)。該函數(shù)主要用于UDP協(xié)議
?sk.settimeout(timeout)
設(shè)置套接字操作的超時(shí)期,timeout是一個(gè)浮點(diǎn)數(shù),單位是秒。值為None表示沒(méi)有超時(shí)期。一般,超時(shí)期應(yīng)該在剛創(chuàng)建套接字時(shí)設(shè)置,因?yàn)樗鼈兛赡苡糜谶B接的操作(如 client 連接最多等待5s )
?sk.getpeername()
返回連接套接字的遠(yuǎn)程地址。返回值通常是元組(ipaddr,port)
?sk.getsockname()
返回套接字自己的地址。通常是一個(gè)元組(ipaddr,port)
?sk.fileno()
套接字的文件描述符
三、ssh程序
整合下上面的代碼,做個(gè)ssh連接的客戶端,實(shí)現(xiàn)基本xshell功能
7.粘包
須知:只有TCP有粘包現(xiàn)象,UDP永遠(yuǎn)不會(huì)粘包。(原因詳見(jiàn)第3點(diǎn))
1、socket收發(fā)消息的原理

? ? ?socket發(fā)送原理圖
2、為什么會(huì)出現(xiàn)所謂的粘包
原因:接收方不知道消息之間的界限,不知道一次性提取多少字節(jié)的數(shù)據(jù)所造成的。
此外,發(fā)送方引起的粘包是由TCP協(xié)議本身造成的,TCP為提高傳輸效率,發(fā)送方往往要收集到足夠多的數(shù)據(jù)后才發(fā)送一個(gè)TCP段。若連續(xù)幾次需要send的數(shù)據(jù)都很少,通常TCP會(huì)根據(jù)優(yōu)化算法把這些數(shù)據(jù)合成一個(gè)TCP段后一次發(fā)送出去,這樣接收方就收到了粘包數(shù)據(jù)。
TCP(transport control protocol,傳輸控制協(xié)議)是面向連接的,面向流的,提供高可靠性服務(wù)。收發(fā)兩端(客戶端和服務(wù)器端)都要有一一成對(duì)的socket,因此,發(fā)送端為了將多個(gè)發(fā)往接收端的包,更有效的發(fā)到對(duì)方,使用了優(yōu)化方法(Nagle算法),將多次間隔較小且數(shù)據(jù)量小的數(shù)據(jù),合并成一個(gè)大的數(shù)據(jù)塊,然后進(jìn)行封包。這樣,接收端,就難于分辨出來(lái)了,必須提供科學(xué)的拆包機(jī)制。 即面向流的通信是無(wú)消息保護(hù)邊界的。
UDP(user datagram protocol,用戶數(shù)據(jù)報(bào)協(xié)議)是無(wú)連接的,面向消息的,提供高效率服務(wù)。不會(huì)使用塊的合并優(yōu)化算法,, 由于UDP支持的是一對(duì)多的模式,所以接收端的skbuff(套接字緩沖區(qū))采用了鏈?zhǔn)浇Y(jié)構(gòu)來(lái)記錄每一個(gè)到達(dá)的UDP包,在每個(gè)UDP包中就有了消息頭(消息來(lái)源地址,端口等信息),這樣,對(duì)于接收端來(lái)說(shuō),就容易進(jìn)行區(qū)分處理了。即面向消息的通信是有消息保護(hù)邊界的。
tcp是基于數(shù)據(jù)流的,于是收發(fā)的消息不能為空,這就需要在客戶端和服務(wù)端都添加空消息的處理機(jī)制,防止程序卡住,而udp是基于數(shù)據(jù)報(bào)的,即便是你輸入的是空內(nèi)容(直接回車),那也不是空消息,udp協(xié)議會(huì)幫你封裝上消息頭。
3、tcp會(huì)發(fā)生粘包的兩種情況如下:
1、發(fā)送端多次send間隔較短,并且數(shù)據(jù)量較小,tcp會(huì)通過(guò)Nagls算法,封裝成一個(gè)包,發(fā)送到接收端,接收端不知道這個(gè)包由幾部分組成,所以就會(huì)產(chǎn)生粘包。
2、數(shù)據(jù)量發(fā)送的大,接收端接收的小,再接一次,還會(huì)出現(xiàn)上次沒(méi)有接收完成的數(shù)據(jù)。就會(huì)出現(xiàn)粘包。