哈工大计算机网络Week2-网络应用数据交换
目錄
- 網絡應用數據交換
- P2P應用:原理與文件分發
- 純P2P架構
- 文件分發:客戶機/服務器 vs. P2P
- CS
- 為什么是這樣的?不應該傳送和發出難道是并行的???
- P2P
- P2P文件分發典型例子:BitTorrent(bit激流)
- 思考題
- P2P應用:索引技術
- P2P: 搜索信息
- 集中式索引
- 全分布非結構:洪泛式查詢(Query flooding)
- 如何查詢?
- 層次式覆蓋網絡
- P2P案例應用:Skype(最成功的之一)
- 課后作業
- Socket編程API
- 網絡程序設計接口主要類型
- 理解應用編程接口API
- 幾種典型的應用編程接口
- Socket編程-Socket API概述
- Socket API
- 如何標識socket?
- 對內的Socket抽象
- socket地址結構
- Socket API
- Socket編程-Socket API函數
- Socket API函數(以WinSock為例)
- WSAStartup
- WSACleanup
- socket
- Socket面向TCP/IP的服務類型
- closesocket(linux版為close)
- bind(服務器)
- listen(服務器)
- connect(客戶端,tcp/udp差異)
- accept(服務器,tcp)
- send, sendto(服務器客戶機都用)
- recv, recvfrom(服務器客戶機都用)
- setsockopt, getsockopt(不具體講了)
- Socket API函數小結
- win獨占
- 多平臺通用
- 關于網絡字節順序
- 網絡應用的Socket API(TCP)調用基本流程
- Socket API函數(以WinSock為例)
- Socket編程-客戶端軟件設計
- 解析服務器IP地址
- 解析服務器(熟知)端口號
- 解析協議號
- TCP客戶端軟件流程
- UDP客戶端軟件流程
- 客戶端軟件的實現- connectsock()
- 客戶端軟件的實現-UDP客戶端
- 客戶端軟件的實現-TCP客戶端
- 客戶端軟件的實現-異常處理
- 例1:訪問DAYTIME服務的客戶端(TCP)
- 例2:訪問DAYTIME服務的客戶端(UDP)
- Socket編程-服務器軟件設計
- 4種類型基本服務器
- 循環無連接服務器基本流程
- 數據發送
- 獲取客戶端點地址
- 循環面向連接服務器基本流程
- 并發無連接服務器基本流程
- 并發面向連接服務器基本流程
- 服務器的實現
- 服務器的實現-passivesock()
- 服務器的實現-passiveUDP()
- 服務器的實現-passiveTCP()
- 例1:無連接循環DAYTIME服務器
- 例2:面向連接并發DAYTIME服務器
網絡應用數據交換
P2P應用:原理與文件分發
純P2P架構
Peer-to-peer
?沒有服務器
?任意端系統之間直接通信
?節點階段性接入Internet
?節點可能更換IP地址
文件分發:客戶機/服務器 vs. P2P
問題 : 從一個服務器向N個節點分發一個文件需要多長時間?
==以下速度計算全部是建立在假設:因特網核心速度無限制;服務器客戶端帶寬全部被運用到文件傳輸。而且全部是一個下限!==
us: 服務器上傳帶寬(upload)
ui: 節點i的上傳帶寬
di: 節點i的下載帶寬
CS
?服務器串行地發送N個副本
? 時間: NF/us
?客戶機i需要F/di時間下載
==對cs,可以看到時間在N大的時候是和N呈線性的!==
為什么是這樣的?不應該傳送和發出難道是并行的???
P2P
?服務器必須發送一個副本
? 時間: F/us
?客戶機i需要F/di時間下載
?總共需要下載NF比特
?最快的可能上傳速率:us + ?ui
==這解釋了p2p的自拓展性:對等方除了是bit的消費者還是重新分發者。==
P2P文件分發典型例子:BitTorrent(bit激流)
tracker: 跟蹤參與torrent的節點,每個torrent都有一個tracker
torrent: 交換同一個文件的文件塊的節點組
- ?文件劃分為256KB的chunk(塊)
- ?節點加入torrent
- ? 沒有chunk,但是會逐漸積累
- ? 向tracker注冊以獲得節點清單,與某些節點(“鄰居”)建立連接
- ?下載的同時,節點需要向其他節點上傳chunk(從網絡下載的或者是節點自身有的)
- ? 節點自由的加入或離開
- ?一旦節點獲得完整的文件,它可能(自私地)離開或(無私地)留下
- ? 獲取chunk
- ? 給定任一時刻,不同的節點持有文件的不同chunk集合
- ? 節點(Alice)定期查詢每個鄰居所持有的chunk列表
- ? 請求原則(請求獲取缺失的chunk)
- ? 稀缺優先的請求建立原則:先去拿自己沒有的chunk中最稀少的
- ? 發(送原則)
- tit-for-tat(以牙還牙)
- ? Alice向4個鄰居發送chunk:正在向Alice發送Chunk的鄰居中速率最快的4個,每10秒重新評估top 4
- 上傳速率高,則能夠找到更好的交易伙伴,從而更快地獲取文件。
- ? 每30秒隨機選擇一個其他節點,向其發送chunk,則alice可能成為chunk的top4,從而建立新的連接。
- ? Alice向4個鄰居發送chunk:正在向Alice發送Chunk的鄰居中速率最快的4個,每10秒重新評估top 4
- tit-for-tat(以牙還牙)
思考題
BitTorrent技術對網絡性能有哪些潛在的危害?
這個欠考慮。
BT三大指控:高溫、重復讀寫、扇區斷塊。
Bittorrent下載是寬帶時代新興的P2P交換文件模式,各用戶之間共享資源,互相當種子和中繼站,俗稱BT下載。由于每個用戶的下載和上傳幾乎是同時進行,因此下載的速度非常快。不過,開發BT的人因為缺乏對維護硬盤的考慮,使用了很差的HASH算法,它會將下載的數據直接寫進硬盤(不像FlashGet等下載工具可以調整緩存,到指定的數據量后才寫入硬盤),因此造成硬盤損害,提早結束硬盤的壽命。
此外,BT下載事先要申請硬盤空間,在下載較大的文件的時候,一般會有2~3分鐘時間整個系統優先權全部被申請空間的任務占用,其他任務反應極慢。有些人為了充分利用帶寬,還會同時進行幾個BT下載任務,此時就非常容易出現由于磁盤占用率過高而導致的死機故障。
因為BT對硬盤的重復讀寫動作會產生高溫,令硬盤的溫度升高,直接影響硬盤的壽命。而當下載人數愈多,同一時間讀取你的硬盤的人亦愈多,硬盤大量進行重復讀寫的動作,加速消耗。基于對硬盤工作原理的分析可以知道,硬盤的磁頭壽命是有限的,頻繁的讀寫會加快磁頭臂及磁頭電機的磨損,頻繁的讀寫磁盤某個區域更會使該區溫度升高,將影響該區磁介質的穩定性還會導至讀寫錯誤,高溫還會使該區因熱膨漲而使磁頭和碟面更近了(正常情況下磁頭和碟面只有幾個微米,高溫膨脹會讓磁頭更靠近碟面),而且也會影響薄膜式磁頭的數據讀取靈敏度,會使晶體振蕩器的時鐘主頻發生改變,還會造成硬盤電路元件失靈。任務繁多也會導至ide硬盤過早損壞,由于ide硬盤自身的不足,過多任務請求是會使尋道失敗率上升導至磁頭頻繁復位(復位就是磁頭回復到 0磁道,以便重新尋道)加速磁頭臂及磁頭電機磨損。因此有些人形容,BT就像把單邊燃燒的柴枝折開兩、三段一起燃燒,大量的讀寫動作會大大加速硬盤的消耗,燃燒硬盤的生命。
其次,同時因為下載太多東西,使扇區的編排混亂,讀寫數據時要在不同扇區中讀取,增加讀寫次數,加速硬盤消耗。
2、對網絡帶寬的損害
當前,以BitTorrent(以下簡稱BT)為代表的P2P下載軟件流量占用了寬帶接入的大量帶寬,據統計已經超過了50%。這對于以太網接入等共享帶寬的寬帶接入方式提出了很大的挑戰,大量的使接入層交換機的端口長期工作在線速狀態,嚴重影響了用戶使用正常的Web、E-mail以及視頻點播等業務,并可能造成重要數據無法及時傳輸而給企業帶來損失。因此,運營商、企業用戶以及教育等行業的用戶都有對這類流量進行限制的要求。BT將會占用太多的網絡資源,從而有可能在接入網、傳輸網、骨干網等不同層面形成瓶頸,造成資源緊張,這似乎也是目前運營商包括網通、長寬等封掉BT端口的最大理由。
3、助長了病毒的傳播
2005年11月17日,公安部公共信息網絡安全監察處許劍卓處長在天津AVAR2005大會上做了《中國網絡犯罪現狀》的報告,報告指出,通過計算機病毒和木馬進行的黑客行為是計算機網絡犯罪的主要根源。調查情況表明,計算機病毒除了通過常規的電子郵件等途徑傳播外,目前網絡上盛行的P2P軟件成為計算機病毒和木馬傳播的主要途徑。這些病毒和木馬對企業的安全形成巨大的挑戰。
4、可能面臨著版權侵害的風險
Fred Lawrence是一個美國普通老人,今年67歲,因為自己孫子的緣故惹來了美國電影協會(MPAA)的大麻煩。Lawrence的孫子通過iMesh P2P服務在家中的電腦下載并分享了4部電影,美國電影協會通過IP地址找到了他和他的電腦,并以侵犯版權為由要求老人為此在18個月中付出4000美元的罰金……;
現在國內外都在嚴厲打擊盜版,不排除版權作者或機構通過各種網絡跟蹤技術來找到非法進行P2P下載的用戶,并提起訴訟或者其他賠償要求;如果企業員工進行了這些行為,可能由此對企業的形象造成極大負面影響,并可能使得企業遭受其他損失。此外,員工可能通過BT等下載一些色情、反動、暴力的等違法的信息,這些信息可能被公安機關檢測到,由此可能給員工和企業帶來法律風險。
P2P應用:索引技術
P2P: 搜索信息
- ?P2P系統的索引:信息到節點位置(IP地址+端口號)的映射
- ?文件共享(電驢)
- ? 利用索引動態跟蹤、存儲節點所共享的文件的位置
- ? 節點需要告訴索引它擁有哪些文件
- ? 節點搜索索引,從而獲知能夠得到哪些文件
- ?即時消息(QQ)
- ? 索引負責將用戶名映射到位置
- ? 當用戶開啟IM應用時,需要通知索引它的位置
- ? 節點檢索索引,確定用戶的IP地址
集中式索引
- ?Napster最早采用這種設計
- ? 1) 節點加入時,通知中央服務器:
- ? IP地址
- ? 內容
- ? 2) Alice查找“Hey Jude”
- ? 3) Alice從Bob處請求文件
- ? 1) 節點加入時,通知中央服務器:
- 集中式索引的問題
- ?單點失效問題
- ?性能瓶頸
- ?版權問題(集中目錄容易被發現版權問題)
全分布非結構:洪泛式查詢(Query flooding)
- ?完全分布式架構
- ?每個節點對它共享的文件進行索引,且只對它共享的文件進行索引
如何查詢?
由覆蓋網絡(overlay network): Graph來組織查詢和連接
- ?節點X與Y之間如果有TCP連接,那么構成一個邊
- ?所有的活動節點和邊構成覆蓋網絡
- ?邊:虛擬鏈路
- ?節==點一般鄰居數少于10個???==
在覆蓋網絡上進行廣播來完成查詢。
- ?查詢消息通過已有的TCP連接發送
- ?節點轉發查詢消息
?如果查詢命中,則利用反向路徑發回查詢節點
- 問題
- 信息泛濫,帶寬壓力,無效的垃圾信息
- 網絡形成之初,難以形成網絡,而成為各個獨立分支,造成信息缺失。需要特殊手段處理
層次式覆蓋網絡
- ?介于集中式索引和洪泛查詢之間的方法
- ?每個節點或者是一個超級節點,或者被分配一個超級節點
- ? 節點和超級節點間維持TCP連接(集中式索引)
- ? 某些超級節點對之間維持TCP連接(超級節點之間是洪泛式)
- ?超級節點負責跟蹤子節點的內容
P2P案例應用:Skype(最成功的之一)
?通話時是P2P的:==用戶/節點對之間直接通信。但是索引階段是層次式覆蓋網絡架構==
?私有應用層協議
?采用層次式覆蓋網絡架構
?索引負責維護用戶名與IP地址間的映射
?索引分布在超級節點上
課后作業
查閱Skype應用的相關資料,就其架構、協議、算法等撰寫一篇調研報告,長度在5000字以上。
Socket編程API
網絡程序設計接口主要類型
開發網絡應用程序關聯的API類型。
按5層結構觀察,除物理層外,所有層次,包括應用層本身,都能提供網絡程序設計的api。
- 直接面向網卡編程-大部分不需要也難以掌握。==網卡是數據鏈路層==
- 網卡之上的,數據鏈路層的編程,屏蔽網卡細節,適用所有網卡。
- 特定操作系統的開發api。
- 基于庫的。
- socket:應用層、傳輸層之間的。==屬于傳輸層==
理解應用編程接口API
- 應用層協議組構應用進程之間的邏輯連接。
- API通常是從傳輸層開始封裝。
幾種典型的應用編程接口
- Berkeley UNIX 操作系統定義了一種 API,稱為套接字接口(socket interface),簡稱套接字(socket)。
- 微軟公司在其操作系統中采用了套接字接口 API,形成了一個稍有不同的 API,并稱之為Windows Socket Interface,WINSOCK。
- AT&T (美國電話電報公司)為其 UNIX 系統 V 定義了一種 API,簡寫為 TLI (Transport Layer Interface)。
- UNIX,一種計算機操作系統,具有多任務、多用戶的特征。于1969年,在美國AT&T公司的貝爾實驗室開發類UNIX(UNIX-like)
Socket編程-Socket API概述
Socket API
抽象通信機制。是一種門面模式,為應用層封裝傳輸層協議,為應用層提供抽象鏈路。
- 最初設計
- 面向BSD UNIX-Berkley
- 面向TCP/IP協議棧接口
- 目前
- Internet網絡應用最典型的API接口,事實上的工業標準
- 絕大多數操作系統都支持
- 通信模型
- 面向客戶/服務器(C/S),==p2p也使用,p2p微觀上也是cs==
- ==由進程或者操作系統創建==
- 本質上是操作系統提供api,進程調用該api通知操作系統創建。
如何標識socket?
- 對外
- 標識通信端點:
- IP地址+端口號(==16位整數端口號,0-65535,即16位二進制無符號數==)
- 標識通信端點:
- 對內
- 操作系統/進程如何管理套接字(對內)?
- 套接字描述符(socket descriptor)
- ==小整數==
- 當應用程序要創建一個套接字時,操作系統就返回一個小整數作為描述符,應用程序則使用這個描述符來引用該套接字。
- 套接字描述符(socket descriptor)
- 操作系統/進程如何管理套接字(對內)?
對內的Socket抽象
- 類似于文件的抽象,像文件一樣管理socket
- 當應用進程創建套接字時,操作系統分配一個數據結構存儲該套接字相關信息
- 進程調用api通知操作系統創建套接字,該函數由操作系統返回套接字描述符給進程。
- 都是通過該socket描述符來引用、訪問套接字。
- 每一個進程都管理一個soket描述符表,管理其創建的socket,這個表類似一個結構體指針數組,每個指針指向一個socket數據結構。
- ==由系統使用來提供api。==
socket地址結構
- ==sin:socket internet==
- IP地址、本地端口號這兩個必需。
- socket提供多協議支持,不僅僅是TCP/IP
- 地址族:表述所使用的傳輸層協議
- AF_INEF:TCP/IP使用的地址族
- 只需知道,windows下tcpip要用的地址族是AF_INEF就夠了
- sin_zero是為了讓sockaddr與sockaddr_in兩個數據結構保持大小相同而保留的空字節。
- 使用TCP/IP協議簇的網絡應用程序聲明端點地址變量時,使用結構sockaddr_in
sockaddr與sockaddr_in:
注釋中標明了屬性的含義及其字節大小,這兩個結構體一樣大,都是16個字節,而且都有family屬性,不同的是:
sockaddr用其余14個字節來表示sa_data,而sockaddr_in把14個字節拆分成sin_port, sin_addr和sin_zero
分別表示端口、ip地址。sin_zero用來填充字節使sockaddr_in和sockaddr保持一樣大小。
sockaddr和sockaddr_in包含的數據都是一樣的,但他們在使用上有區別:
程序員不應操作sockaddr,sockaddr是給操作系統用的
程序員應使用sockaddr_in來表示地址,sockaddr_in區分了地址和端口,使用更方便。
==思考:為何這里要維護地址長度?==
Socket編程-Socket API函數
Socket API函數(以WinSock為例)
winsock實現機制是動態連接庫,所以要初始化、釋放動態連接庫,使用api開始和結束要分別調用
- WSAStartup(初始化Windows Sockets API)
- WSACleanup(釋放所使用的WindowsSockets DLL)
- ==wsa表示Windows Sockets API==
WSAStartup
使用Socket的應用程序在使用Socket之前必須首先調用WSAStartup函數
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);- WORD wVersionRequested
- 指明程序請求使用的WinSock版本,其中高位字節指明副版本、低位字節指明主版本
- 十六進制整數,例如0x102表示2.1版(==WORD表示雙字節==)
- LPWSADATA lpWSAData
- 返回實際的WinSock的版本信息,指向WSADATA結構的指針
- 例:使用2.1版本的WinSock的程序代碼段
WSACleanup
應用程序在完成對請求的Socket庫的使用,最后要調用WSACleanup函數。
int WSACleanup (void);- 解除與Socket動態庫的綁定。
- 釋放Socket庫所占用的系統資源。
下面的不帶wsa的api是多系統通用的,上面的wsa api是win專用的
socket
創建套接字
sd = socket(protofamily,type,proto);sd
- 操作系統返回的套接字描述符,應用層使用該描述符操作、引用套接字。
protofamily協議族(說明面向哪個協議族)
- protofamily = PF_INET(TCP/IP)
==type套接字類型==(每種協議族不同,下面的例子是)
- type = SOCK_STREAM,SOCK_DGRAM or SOCK_RAW(TCP/IP)
proto(協議號,訪問的是哪種協議):0表示缺省,可以使用對應數字表示所選協議族和套接字類型的支持的協議號
例:創建一個流套接字的代碼段
struct protoent *p; p=getprotobyname("tcp"); SOCKET sd=socket(PF_INET,SOCK_STREAM,p->p_proto);
==思考:為何這里擁PF——INEF?==
Socket面向TCP/IP的服務類型
stream(傳輸流,使用tcp):可靠、面向連接、字節流傳輸、點對點(一個連接只能連接兩點)
dgram(datagram數據報,使用udp):不可靠、無連接、數據報傳輸
raw(raw:生的,這里指不加傳輸層處理的原始套接字)
closesocket(linux版為close)
關閉一個描述符為sd的套接字。
int closesocket(SOCKET sd);- 如果多個進程共享一個套接字,有計數。
- 調用closesocket將把調用進程的描述符表中的引用刪除,然后該socket的數據結構的reference counting減1,減至0才釋放該socket數據結構空間。
- 一個進程中的多線程對一個套接字的使用無計數
- 如果進程中的一個線程調用closesocket將一個套接字關閉,則該進程的描述符表中無該socket數據結構的引用,該進程中的其他線程也將不能訪問該套接字。
- ==描述符表是一個進程一個,而不是一個線程一個!==
- 返回值
- 0:成功
- SOCKET_ERROR:失敗
bind(服務器)
綁定(填寫)套接字的本地端點地址(IP地址+==16進制==端口號)
int bind(sd,localaddr,addrlen);- 參數:
- 套接字描述符(sd)
- 端點地址(localaddr)
- 結構sockaddr_in
- 客戶程序一般不必調用bind函數,因為這個工作一般是由操作系統來完成的。
- 服務器端需要調用
- 綁定設置熟知端口號:80 25 等等
- IP地址?服務器運行主機的ip地址可以嗎?
- 綁定問題:服務器應該綁定哪個地址?
- 解決方案
- 地址通配符:INADDR_ANY,把ip地址附成該值,表示主機上任意一個有效ip地址都是可以來訪問該socket。
- 服務器應該綁定INADDR_ANY:端口號,而是saddrlen不具體IP:端口號
- INADDR_ANY一般是0.0.0.0
==思考:為什么要加地址長度?localaddr里面已經包含長度==
listen(服務器)
置流套接字處于監聽狀態,listen只用于服務器端,僅面向tcp連接的流類型。
int listen(sd,queuesize);- 設置連接==請求緩存隊列==大小(queuesize)
- 返回值:
- 0:成功
- SOCKET_ERROR:失敗
connect(客戶端,tcp/udp差異)
連接建立不等同數據請求發送,還需要send
connect(sd,saddr,saddrlen);- 客戶程序調用connect函數來使本地套接字(sd)與特定計算機的特定端口(saddr)的套接字(服務)進行連接
- 僅用于客戶端
- 可用于TCP客戶端也可以用于UDP客戶端
- 用于TCP客戶端:建立TCP連接
- 用于UDP客戶端:只是簡單的本地指定了服務器端點地址,在之后發送數據報和接受數據時使用該地址。
accept(服務器,tcp)
newsock = accept(sd,caddr,caddrlen);- 服務程序調用accept函數從處于監聽狀態的流套接字sd的客戶連接請求隊列中取出排在最前的一個客戶請求,并且創建一個新的套接字來與客戶套接字創建連接通道。
- 注意這里接==受的是建立連接的請求,而不是對數據的請求==,后者由recv, recvfrom負責,數據由send, sendto發送。
- ==僅用于TCP套接字==
- ==僅用于服務器==
- 利用新創建的套接字
- 使用新套接字(newsock)與客戶通信,why?
- tcp是點對點的,socket2socket的,單對單的,如果不這么做,就不能并發的提供服務。這里把接受服務和提供服務的socket區分開了。
send, sendto(服務器客戶機都用)
send(sd,*buf,len,flags); sendto(sd,*buf,len,flags,destaddr,addrlen);- send函數TCP套接字(客戶與服務器)或調用了connect函數的UDP客戶端套接字
- sendto函數用于==UDP服務器==端套接字與==未調用connect函數的UDP客戶端==套接字
recv, recvfrom(服務器客戶機都用)
recv(sd,*buffer,len,flags); recvfrom(sd,*buf,len,flags,senderaddr,saddrlen);- recv函數從TCP連接的另一端接收數據,或者從調用了connect函數的UDP客戶端套接字接收服務器發來的數據。
- recvfrom函數用于從==UDP服務器==端套接字與==未調用connect函數的UDP客戶端==套接字接收對端數據。
setsockopt, getsockopt(不具體講了)
int setsockopt(int sd, int level, int optname, *optval, int optlen); int getsockopt(int sd, int level, int optname,*optval, socklen_t *optlen);- setsockopt()函數用來設置套接字sd的選項參數
- getsockopt()函數用于獲取任意類型、任意狀態套接口的選項當前值,并把結果存入optval
Socket API函數小結
win獨占
- ? WSAStartup: 初始化socket庫(僅對WinSock)
- ? WSACleanup: 清楚/終止socket庫的使用 (僅對WinSock)
多平臺通用
==按照服務器、客戶機、tcp、udp去理解==
- ? socket: 創建套接字
- ? connect:“連接”遠端服務器 (僅用于客戶端)
- ? closesocket: 釋放/關閉套接字
- ? bind: 綁定套接字的本地IP地址和端口號(通常客戶端不需要)
- ? listen: 置服務器端TCP套接字為監聽模式,并設置隊列大小 (僅用于服務器端TCP套接字)
- ? accept: 接受/提取一個連接請求,創建新套接字,通過新套接 (僅用于服務器端的TCP套接字)
- ? recv: 接收數據(用于TCP套接字或連接模式的客戶端UDP套接字)
- ? recvfrom: 接收數據報(用于非連接模式的UDP套接字)
- ? send: 發送數據(用于TCP套接字或連接模式的客戶端UDP套接字)
- ? sendto:發送數據報(用于非連接模式的UDP套接字)
- ? setsockopt: 設置套接字選項參數(用的時候查)
- ? getsockopt: 獲取套接字選項參數(用的時候查)
關于網絡字節順序
osi7層模型才有表示層來兼容字節順序,5層中是沒有的,需要協議輔助完成該功能。
- ?TCP/IP定義了標準的用于協議頭中的二進制整數表示:網絡字節順序(network byte order)
- ?某些Socket API函數的參數需要存儲為網絡字節順序而不是本地字節順序(如IP地址、端口號等)。
- ?==可以實現本地字節順序與網絡字節順序間轉換的函數==
- ? htons: 本地字節順序→網絡字節順序(16bits)(host2net s)
- ? ntohs: 網絡字節順序→本地字節順序(16bits)
- ? htonl: 本地字節順序→網絡字節順序(32bits)
- ? ntohl: 網絡字節順序→本地字節順序(32bits)
網絡應用的Socket API(TCP)調用基本流程
注意,這張圖并不完整,注意區分tcp udp
listen并不阻塞,listen只是開啟listen狀態。
recv、accept是真正對連接的反饋需要循環開啟,也是后續的操作的前提,所以要阻塞accept、recv。
阻塞過程,表示當函數未成功則一直等待。
左右兩邊都有2個阻塞函數。
==ns:newsocket==
注意兩邊close的區別,服務器只是關閉了ns。
Socket編程-客戶端軟件設計
socket服務器按上述流程要:選擇服務器ip、端口號,網絡字節轉化,選擇協議。
具體實現上要求:4位ip/域名:服務名轉化為32位ip:端口號,協議名轉化為服務號
解析服務器IP地址
- ? 客戶端可能使用域名(如:study.163.com)或IP地址(如:123.58.180.121)標識服務器
- ? IP協議需要使用32位二進制IP地址
- ? 需要將域名或IP地址轉換為32位IP地址
- ? 函數inet_addr( ) 實現點分十進制IP地址到32位IP地址轉換
- 如果正確執行將返回一個無符號長整數型數。如果傳入的字符串不是一個合法的IP地址,將返回INADDR_NONE。
- 已經是網絡字節順序
- ? 函數gethostbyname( ) 實現域名到32位IP地址轉換
- 返回一個指向結構hostent 的指針,該結構中包含32位ip地址
- 已經是網絡字節順序
- ? 函數inet_addr( ) 實現點分十進制IP地址到32位IP地址轉換
解析服務器(熟知)端口號
- ? 客戶端可能不使用端口號而是使用服務名(如HTTP)標識服務器端口
- ? 需要將服務名轉換為標準的熟知端口號
- ? 函數getservbyname( )
- ? 返回一個指向結構servent的指針,該結構包含端口號
解析協議號
- socket使用協議號來標識協議
- 客戶端可能使用協議名(如:TCP)指定協議,需要將協議名轉換為協議號(如:6)
- 函數getprotobyname ( ) 實現協議名到協議號的轉換
- 返回一個指向結構protoent的指針
TCP客戶端軟件流程
UDP客戶端軟件流程
客戶端軟件的實現- connectsock()
設計一個connectsock過程封裝底層代碼,這部分代碼在udp和tcp中都可能用到。
/* consock.cpp - connectsock */ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <winsock.h> #ifndef INADDR_NONE #define INADDR_NONE 0xffffffff #endif /* INADDR_NONE */ void errexit(const char *, ...); /*------------------------------------------------------- * connectsock - allocate & connect a socket using TCP or UDP *------------------------------------------------------ *///transport指的是所用協議名稱 SOCKET connectsock(const char *host, const char *service, const char *transport ) { struct hostent *phe; /* pointer to host information entry */ struct servent *pse; /* pointer to service information entry */ struct protoent *ppe; /* pointer to protocol information entry */ struct sockaddr_in sin;/* an Internet endpoint address */ int s, type; /* socket descriptor and socket type */ memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; /* Map service name to port number */ if ( pse = getservbyname(service, transport) ) sin.sin_port = pse->s_port; else if ( (sin.sin_port = htons((u_short)atoi(service))) == 0 ) errexit("can't get \"%s\" service entry\n", service);/* Map host name to IP address, allowing for dotted decimal */ if ( phe = gethostbyname(host) ) memcpy(&sin.sin_addr, phe->h_addr, phe->h_length); else if ( (sin.sin_addr.s_addr = inet_addr(host))==INADDR_NONE) errexit("can't get \"%s\" host entry\n", host);/* Map protocol name to protocol number */ if ( (ppe = getprotobyname(transport)) == 0) errexit("can't get \"%s\" protocol entry\n", transport); /* Use protocol to choose a socket type */ if (strcmp(transport, "udp") == 0) type = SOCK_DGRAM; else type = SOCK_STREAM;/* Allocate a socket */ s = socket(PF_INET, type, ppe->p_proto); if (s == INVALID_SOCKET) errexit("can't create socket: %d\n", GetLastError());/* Connect the socket */ if (connect(s, (struct sockaddr *)&sin, sizeof(sin))==SOCKET_ERROR) errexit("can't connect to %s.%s: %d\n", host, service, GetLastError()); return s; }客戶端軟件的實現-UDP客戶端
設計connectUDP過程用于創建連接模式客戶端UDP套接字
/* conUDP.cpp - connectUDP */ #include <winsock.h> SOCKET connectsock(const char *, const char *, const char *); /*-------------------------------------------------------* connectUDP - connect to a specified UDP service* on a specified host*----------------------------------------------------- */ SOCKET connectUDP(const char *host, const char *service ) { return connectsock(host, service, "udp"); }客戶端軟件的實現-TCP客戶端
設計connectTCP過程,用于創建客戶端TCP套接字
/* conTCP.cpp - connectTCP */ #include <winsock.h> SOCKET connectsock(const char *, const char *, const char *); /*----------------------------------------------------* connectTCP - connect to a specified TCP service* on a specified host*---------------------------------------------------*/ SOCKET connectTCP(const char *host, const char *service ) { return connectsock( host, service, "tcp"); }客戶端軟件的實現-異常處理
/* errexit.cpp - errexit */ #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <winsock.h> /*---------------------------------------------------------- * errexit - print an error message and exit *---------------------------------------------------------- */ /*VARARGS1*/ void errexit(const char *format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); WSACleanup(); exit(1);}例1:訪問DAYTIME服務的客戶端(TCP)
?DAYTIME服務
? 獲取日期和時間
? 雙協議服務(TCP、 UDP),端口號13
? TCP版利用TCP連接請求觸發服務(==不需要發送任何數據,只需要發送連接建立請求==)
? UDP版需要==客戶端發送一個數據報==
例2:訪問DAYTIME服務的客戶端(UDP)
/* UDPdtc.cpp - main, UDPdaytime */ #include <stdlib.h> #include <stdio.h> #include <winsock.h> void UDPdaytime(const char *, const char *); void errexit(const char *, ...); SOCKET connectUDP(const char *, const char *); #define LINELEN 128 #define WSVERS MAKEWORD(2, 0) #define MSG “what daytime is it?\n" /*-------------------------------------------------------- * main - UDP client for DAYTIME service *-------------------------------------------------------- */ int main(int argc, char *argv[]) { char *host = "localhost"; /* host to use if none supplied */ char *service = "daytime"; /* default service port */ WSADATA wsadata; switch (argc) { case 1: host = "localhost"; break; case 3: service = argv[2]; /* FALL THROUGH */ case 2: host = argv[1]; break; default: fprintf(stderr, "usage: UDPdaytime [host [port]]\n"); exit(1); } if (WSAStartup(WSVERS, &wsadata) != 0) errexit("WSAStartup failed\n"); UDPdaytime(host, service); WSACleanup(); return 0; /* exit */ } /*----------------------------------------------------- * UDPdaytime - invoke Daytime on specified host and print results *----------------------------------------------------- */ void UDPdaytime(const char *host, const char *service) { char buf[LINELEN+1]; /* buffer for one line of text */ SOCKET s; /* socket descriptor */ int n; /* recv character count */ s = connectUDP(host, service); (void) send(s, MSG, strlen(MSG), 0); /* Read the daytime */ n = recv(s, buf, LINELEN, 0); if (n == SOCKET_ERROR) errexit("recv failed: recv() error %d\n", GetLastError()); else { buf[cc] = '\0'; /* ensure null-termination */ (void) fputs(buf, stdout); } closesocket(s); return 0; /* exit */ }Socket編程-服務器軟件設計
4種類型基本服務器
循環:同時處理一個用戶的請求
并發:同時處理多個用戶請求
無連接:基于udp
面向連接:基于tcp
循環無連接服務器基本流程
數據發送
?服務器端不能使用connect()函數,connect()是客戶端專用
?無連接服務器使用sendto()函數發送數據報
獲取客戶端點地址
?調用recvfrom()函數接收數據時,自動提取客戶進程端點地址
循環面向連接服務器基本流程
并發無連接服務器基本流程
這里123指的是第一步第二步
主線程1: 創建套接字,并綁定熟知端口號;
主線程2: 反復調用recvfrom()函數,接收下一個客戶請求并創建==新線程處理(為了并發)==該客戶響應;
子線程1: 接收一個特定請求;
子線程2: 依據應用層協議構造響應報文,并調用sendto()發送;
子線程3: 退出(一個子線程處理一個請求后即終止)。
并發面向連接服務器基本流程
這里123指的是第一步第二步
主線程1: 創建(主)套接字,并綁定熟知端口號;
主線程2: 設置(主)套接字為被動監聽模式,準備用于服務器;
主線程3: 反復調用accept()函數接收下一個連接請求(通過主套接字),并創建一個新子線程處理該客戶響應;
子線程1: 接收一個客戶的服務請求(通過新創建的套接字);
子線程2: 遵循應用層協議與特定客戶進行交互;
子線程3: 關閉/釋放連接并退出(線程終止).
服務器的實現
實現的一種設計,可以不這么設計。
?設計一個底層過程隱藏底層代碼:passivesock()
?兩個高層過程分別用于創建服務器端UDP套接字和TCP套接字(調用passivesock()函數):
? passiveUDP()
? passiveTCP()
服務器的實現-passivesock()
/* passsock.cpp - passivesock */ #include <stdlib.h> #include <string.h> #include <winsock.h> void errexit(const char *, ...); /*----------------------------------------------------------------------- * passivesock - allocate & bind a server socket using TCP or UDP *------------------------------------------------------------------------ */ SOCKET passivesock(const char *service, const char *transport, int qlen) { struct servent *pse; /* pointer to service information entry */ struct protoent *ppe; /* pointer to protocol information entry */ struct sockaddr_in sin;/* an Internet endpoint address */ SOCKET s; /* socket descriptor */ int type; /* socket type (SOCK_STREAM, SOCK_DGRAM)*/ memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY;/* Map service name to port number */ if ( pse = getservbyname(service, transport) ) sin.sin_port = (u_short)pse->s_port; else if ( (sin.sin_port = htons((u_short)atoi(service))) == 0 ) errexit("can't get \"%s\" service entry\n", service); /* Map protocol name to protocol number */ if ( (ppe = getprotobyname(transport)) == 0) errexit("can't get \"%s\" protocol entry\n", transport);/* Use protocol to choose a socket type */ if (strcmp(transport, "udp") == 0) type = SOCK_DGRAM; else type = SOCK_STREAM;/* Allocate a socket */ s = socket(PF_INET, type, ppe->p_proto); if (s == INVALID_SOCKET) errexit("can't create socket: %d\n", GetLastError());/* Bind the socket */ if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == SOCKET_ERROR) errexit("can't bind to %s port: %d\n", service, GetLastError()); if (type == SOCK_STREAM && listen(s, qlen) == SOCKET_ERROR) errexit("can't listen on %s port: %d\n", service, GetLastError()); return s;}服務器的實現-passiveUDP()
/* passUDP.cpp - passiveUDP */ #include <winsock.h> SOCKET passivesock(const char *, const char *, int); /*------------------------------------------------------------------------------------- * passiveUDP - create a passive socket for use in a UDP server *------------------------------------------------------------------------------------- */ SOCKET passiveUDP(const char *service) { return passivesock(service, "udp", 0); }服務器的實現-passiveTCP()
/* passTCP.cpp - passiveTCP */ #include <winsock.h> SOCKET passivesock(const char *, const char *, int); /*------------------------------------------------------------------------------------ * passiveTCP - create a passive socket for use in a TCP server *------------------------------------------------------------------------------------ */ SOCKET passiveTCP(const char *service, int qlen) { return passivesock(service, "tcp", qlen);例1:無連接循環DAYTIME服務器
/* UDPdtd.cpp - main, UDPdaytimed */ #include <stdlib.h> #include <winsock.h> #include <time.h> void errexit(const char *, ...); SOCKET passiveUDP(const char *); #define WSVERS MAKEWORD(2, 0) /*------------------------------------------------------------------------ * main - Iterative UDP server for DAYTIME service *------------------------------------------------------------------------ */ void main(int argc, char *argv[]) { struct sockaddr_in fsin; /* the from address of a client */ char *service = "daytime"; /* service name or port number */ SOCKET sock; /* socket */ int alen; /* from-address length */ char * pts; /* pointer to time string */ time_t now; /* current time */ WSADATA wsadata; switch (argc) {case 1: break;case 2: service = argv[1]; break;default: errexit("usage: UDPdaytimed [port]\n"); } if (WSAStartup(WSVERS, &wsadata) != 0) errexit("WSAStartup failed\n"); sock = passiveUDP(service); while (1) { alen = sizeof(struct sockaddr); if (recvfrom(sock, buf, sizeof(buf), 0,(struct sockaddr *)&fsin, &alen) == SOCKET_ERROR) errexit("recvfrom: error %d\n", GetLastError()); (void) time(&now); pts = ctime(&now); (void) sendto(sock, pts, strlen(pts), 0, (struct sockaddr *)&fsin, sizeof(fsin)); } return 1; /* not reached */ }例2:面向連接并發DAYTIME服務器
/* TCPdtd.cpp - main, TCPdaytimed */ #include <stdlib.h> #include <winsock.h> #include <process.h> #include <time.h> void errexit(const char *, ...); void TCPdaytimed(SOCKET); SOCKET passiveTCP(const char *, int); #define QLEN 5 #define WSVERS MAKEWORD(2, 0) /*------------------------------------------------------------------------ * main - Concurrent TCP server for DAYTIME service *------------------------------------------------------------------------ */ void main(int argc, char *argv[]) { struct sockaddr_in fsin; /* the from address of a client */ char *service = "daytime"; /* service name or port number*/ SOCKET msock, ssock; /* master & slave sockets */ int alen; /* from-address length */ WSADATA wsadata; switch (argc) { case1: break; case2: service = argv[1]; break; default: errexit("usage: TCPdaytimed [port]\n"); } if (WSAStartup(WSVERS, &wsadata) != 0) errexit("WSAStartup failed\n"); msock = passiveTCP(service, QLEN); while (1) { alen = sizeof(struct sockaddr); ssock = accept(msock, (struct sockaddr *)&fsin, &alen); if (ssock == INVALID_SOCKET) errexit("accept failed: error number %d\n", GetLastError()); if (_beginthread((void (*)(void *)) TCPdaytimed, 0,(void *)ssock) < 0) { errexit("_beginthread: %s\n", strerror(errno)); } } return 1; /* not reached */ } /*---------------------------------------------------------------------- * TCPdaytimed - do TCP DAYTIME protocol *----------------------------------------------------------------------- */ void TCPdaytimed(SOCKET fd) { char * pts; /* pointer to time string */ time_t now; /* current time */ (void) time(&now); pts = ctime(&now); (void) send(fd, pts, strlen(pts), 0); (void) closesocket(fd); }Markdown文本:https://github.com/ArrogantL/BlogData/tree/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9Cspoc/W2
本文作者: ArrogantL (arrogant262@gmail.com)
版權聲明: 本博客所有文章除特別聲明外,均采用 CC BY-NC-SA 4.0 許可協議。轉載請注明出處!
轉載于:https://www.cnblogs.com/gjl-blog/p/9665048.html
總結
以上是生活随笔為你收集整理的哈工大计算机网络Week2-网络应用数据交换的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Palette优化界面色彩搭配
- 下一篇: 阿里云域名备案