哈工大計算機網絡Week2-網絡應用數據交換

目錄java

網絡應用數據交換

P2P應用:原理與文件分發

純P2P架構

Peer-to-peer
沒有服務器
任意端系統之間直接通訊
節點階段性接入Internet
節點可能更換IP地址python

文件分發:客戶機/服務器 vs. P2P

問題 : 從一個服務器向N個節點分發一個文件須要多長時間?linux

==如下速度計算所有是創建在假設:因特網核心速度無限制;服務器客戶端帶寬所有被運用到文件傳輸。並且所有是一個下限!==git

us: 服務器上傳帶寬(upload)
ui: 節點i的上傳帶寬
di: 節點i的下載帶寬程序員

CS

服務器串行地發送N個副本
 時間: NF/us
客戶機i須要F/di時間下載github

==對cs,能夠看到時間在N大的時候是和N呈線性的!==算法

爲何是這樣的?不該該傳送和發出難道是並行的???

P2P

服務器必須發送一個副本
 時間: F/us
客戶機i須要F/di時間下載
總共須要下載NF比特
最快的可能上傳速率:us + ui編程

==這解釋了p2p的自拓展性:對等方除了是bit的消費者仍是從新分發者。==windows

P2P文件分發典型例子:BitTorrent(bit激流)

tracker: 跟蹤參與torrent的節點,每一個torrent都有一個trackerapi

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,從而創建新的鏈接。

思考題

BitTorrent技術對網絡性能有哪些潛在的危害?

這個欠考慮。

BT三大指控:高溫、重複讀寫、扇區斷塊。

Bittorrent下載是寬帶時代新興的P2P交換文件模式,各用戶之間共享資源,互至關種子和中繼站,俗稱BT下載。因爲每一個用戶的下載和上傳幾乎是同時進行,所以下載的速度很是快。不過,開發BT的人由於缺少對維護硬盤的考慮,使用了不好的HASH算法,它會將下載的數據直接寫進硬盤(不像FlashGet等下載工具能夠調整緩存,到指定的數據量後才寫入硬盤),所以形成硬盤損害,提前結束硬盤的壽命。

此外,BT下載事先要申請硬盤空間,在下載較大的文件的時候,通常會有2~3分鐘時間整個系統優先權所有被申請空間的任務佔用,其餘任務反應極慢。有些人爲了充分利用帶寬,還會同時進行幾個BT下載任務,此時就很是容易出現因爲磁盤佔用率太高而致使的死機故障。

由於BT對硬盤的重複讀寫動做會產生高溫,令硬盤的溫度升高,直接影響硬盤的壽命。而當下載人數愈多,同一時間讀取你的硬盤的人亦愈多,硬盤大量進行重複讀寫的動做,加速消耗。基於對硬盤工做原理的分析能夠知道,硬盤的磁頭壽命是有限的,頻繁的讀寫會加快磁頭臂及磁頭電機的磨損,頻繁的讀寫磁盤某個區域更會使該區溫度升高,將影響該區磁介質的穩定性還會導至讀寫錯誤,高溫還會使該區因熱膨漲而使磁頭和碟面更近了(正常狀況下磁頭和碟面只有幾個微米,高溫膨脹會讓磁頭更靠近碟面),並且也會影響薄膜式磁頭的數據讀取靈敏度,會使晶體振盪器的時鐘主頻發生改變,還會形成硬盤電路元件失靈。任務繁多也會導至ide硬盤過早損壞,因爲ide硬盤自身的不足,過多任務請求是會使尋道失敗率上升導至磁頭頻繁複位(復位就是磁頭回復到 0磁道,以便從新尋道)加速磁頭臂及磁頭電機磨損。所以有些人形容,BT就像把單邊燃燒的柴枝折開兩、三段一塊兒燃燒,大量的讀寫動做會大大加速硬盤的消耗,燃燒硬盤的生命。

其次,同時由於下載太多東西,使扇區的編排混亂,讀寫數據時要在不一樣扇區中讀取,增長讀寫次數,加速硬盤消耗。
二、對網絡帶寬的損害

當前,以BitTorrent(如下簡稱BT)爲表明的P2P下載軟件流量佔用了寬帶接入的大量帶寬,據統計已經超過了50%。這對於以太網接入等共享帶寬的寬帶接入方式提出了很大的挑戰,大量的使接入層交換機的端口長期工做在線速狀態,嚴重影響了用戶使用正常的Web、E-mail以及視頻點播等業務,並可能形成重要數據沒法及時傳輸而給企業帶來損失。所以,運營商、企業用戶以及教育等行業的用戶都有對這類流量進行限制的要求。BT將會佔用太多的網絡資源,從而有可能在接入網、傳輸網、骨幹網等不一樣層面造成瓶頸,形成資源緊張,這彷佛也是目前運營商包括網通、長寬等封掉BT端口的最大理由。

三、滋長了病毒的傳播

2005年11月17日,公安部公共信息網絡安全監察處許劍卓處長在天津AVAR2005大會上作了《中國網絡犯罪現狀》的報告,報告指出,經過計算機病毒和木馬進行的黑客行爲是計算機網絡犯罪的主要根源。調查狀況代表,計算機病毒除了經過常規的電子郵件等途徑傳播外,目前網絡上盛行的P2P軟件成爲計算機病毒和木馬傳播的主要途徑。這些病毒和木馬對企業的安全造成巨大的挑戰。
四、可能面臨着版權侵害的風險

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處請求文件
  • 集中式索引的問題
    • 單點失效問題
    • 性能瓶頸
    • 版權問題(集中目錄容易被發現版權問題)

全分佈非結構:洪泛式查詢(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)。

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抽象

  • 相似於文件的抽象,像文件同樣管理socket
  • 當應用進程建立套接字時,操做系統分配一個數據結構存儲該套接字相關信息
  • 進程調用api通知操做系統建立套接字,該函數由操做系統返回套接字描述符給進程。
  • 都是經過該socket描述符來引用、訪問套接字。
  • 每個進程都管理一個soket描述符表,管理其建立的socket,這個表相似一個結構體指針數組,每一個指針指向一個socket數據結構。
  • ==由系統使用來提供api。==

soket描述符存儲原理

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的程序代碼段
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
wVersionRequested = MAKEWORD( 2, 1 );
err = WSAStartup( wVersionRequested, &wsaData );

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地址
      • 已是網絡字節順序

解析服務器(熟知)端口號

  •  客戶端可能不使用端口號而是使用服務名(如HTTP)標識服務器端口
  •  須要將服務名轉換爲標準的熟知端口號
  •  函數getservbyname( )
    • • 返回一個指向結構servent的指針,該結構包含端口號

解析協議號

  • socket使用協議號來標識協議
  • 客戶端可能使用協議名(如:TCP)指定協議,須要將協議名轉換爲協議號(如:6)
  • 函數getprotobyname ( ) 實現協議名到協議號的轉換
    • 返回一個指向結構protoent的指針

TCP客戶端軟件流程

  1. 肯定服務器IP地址與端口號
  2. 建立套接字
  3. 分配本地端點地址(IP地址+端口號)(==顯示的建立socket的同時,系統自動完成本地端點地址分配==)
  4. 鏈接服務器(套接字)
  5. 遵循應用層協議進行通訊
  6. 關閉/釋放鏈接

UDP客戶端軟件流程

  1. 肯定服務器IP地址與端口號
  2. 建立套接字
  3. 分配本地端點地址(IP地址+端口號)
  4. 指定服務器端點地址,構造UDP數據報併發送(注意,這裏並無通知服務器,而是直接發送)
  5. 遵循應用層協議進行通訊
  6. 關閉/釋放套接字

客戶端軟件的實現- 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版須要==客戶端發送一個數據報==

/* TCPdtc.cpp - main, TCPdaytime */
#include <stdlib.h>
#include <stdio.h>
#include <winsock.h>
void TCPdaytime(const char *, const char *);
void errexit(const char *, ...);
SOCKET connectTCP(const char *, const char *);
#define LINELEN 128
#define WSVERS MAKEWORD(2, 0)
/*--------------------------------------------------------
* main - TCP 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: TCPdaytime [host [port]]\n");
exit(1);
}
if (WSAStartup(WSVERS, &wsadata) != 0)
errexit("WSAStartup failed\n");
TCPdaytime(host, service);
WSACleanup();
return 0; /* exit */
}
/*-----------------------------------------------------
* TCPdaytime - invoke Daytime on specified host and print results
*-----------------------------------------------------
*/
void TCPdaytime(const char *host, const char *service)
{
char buf[LINELEN+1]; /* buffer for one line of text */
SOCKET s; /* socket descriptor */
int cc; /* recv character count */
 s = connectTCP(host, service);
cc = recv(s, buf, LINELEN, 0);
while( cc != SOCKET_ERROR && cc > 0)
{
buf[cc] = '\0'; /* ensure null-termination */
(void) fputs(buf, stdout);
cc = recv(s, buf, LINELEN, 0);
}
closesocket(s);
}

例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

  1. 循環無鏈接(Iterative connectionless)服務器
  2. 循環面向鏈接(Iterative connection-oriented)服務器
  3. 併發無鏈接(Concurrent connectionless)服務器
  4. 併發面向鏈接(Concurrent connection-oriented)服務器

循環無鏈接服務器基本流程

  1. 建立套接字
  2. 綁定端點地址(==INADDR_ANY+端口號==
  3. 反覆接收來自客戶端的請求
  4. 遵循應用層協議,構造響應報文,發送給客戶

數據發送

服務器端不能使用connect()函數,connect()是客戶端專用
無鏈接服務器使用sendto()函數發送數據報

獲取客戶端點地址

調用recvfrom()函數接收數據時,自動提取客戶進程端點地址

循環面向鏈接服務器基本流程

  1. 建立(主)套接字,並綁定熟知端口號;
  2. 設置(主)套接字爲被動監聽模式,準備用於服務器;
  3. 調用accept()函數接收下一個鏈接請求(經過主套接字),建立新套接字用於與該客戶創建鏈接;(==主套接字要用與接受鏈接申請==
  4. 遵循應用層協議,反覆接收客戶請求,構造併發送響應(經過新套接字);
  5. 完成爲特定客戶服務後,關閉與該客戶之間的鏈接,返回步驟3.

併發無鏈接服務器基本流程

這裏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 許可協議。轉載請註明出處!

相關文章
相關標籤/搜索