傳統的網絡應用設計模式,客戶機(client)/服務器(server)模式。須要在通信兩端各自部署客戶機和服務器來完成數據通訊。linux
瀏覽器(Browser)/服務器(server)模式。只需在一端部署服務器,而另一端使用每臺PC都默認配置的瀏覽器便可完成數據的傳輸。面試
所以在開發過程當中,模式的選擇由上述各自的特色決定。根據實際需求選擇應用程序設計模式。shell
在網絡通信時,源主機的應用程序知道目的主機的IP地址和端口號,殊不知道目的主機的硬件地址,而數據包首先是被網卡接收到再去處理上層協議的,若是接收到的數據包的硬件地址與本機不符,則直接丟棄。所以在通信前必須得到目的主機的硬件地址。ARP協議就起到這個做用。源主機發出ARP請求,詢問「IP地址是192.168.0.1的主機的硬件地址是多少」,並將這個請求廣播到本地網段(以太網幀首部的硬件地址填FF:FF:FF:FF:FF:FF表示廣播),目的主機接收到廣播的ARP請求,發現其中的IP地址與本機相符,則發送一個ARP應答數據包給源主機,將本身的硬件地址填寫在應答包中。數據庫
每臺主機都維護一個ARP緩存表,能夠用arp -a命令查看。緩存表中的表項有過時時間(通常爲20分鐘),若是20分鐘內沒有再次使用某個表項,則該表項失效,下次還要發ARP請求來得到目的主機的硬件地址。想想,爲何表項要有過時時間而不是一直有效?編程
ARP數據報的格式以下所示:設計模式
源MAC地址、目的MAC地址在以太網首部和ARP請求中各出現一次,對於鏈路層爲以太網的狀況是多餘的,但若是鏈路層是其它類型的網絡則有多是必要的。硬件類型指鏈路層網絡類型,1爲以太網,協議類型指要轉換的地址類型,0x0800爲IP地址,後面兩個地址長度對於以太網地址和IP地址分別爲6和4(字節),op字段爲1表示ARP請求,op字段爲2表示ARP應答。api
請求幀以下(爲了清晰在每行的前面加了字節計數,每行16個字節):數組
以太網首部(14字節) 0000: ff ff ff ff ff ff 00 05 5d 61 58 a8 08 06 ARP幀(28字節) 0000: 00 01 0010: 08 00 06 04 00 01 00 05 5d 61 58 a8 c0 a8 00 37 0020: 00 00 00 00 00 00 c0 a8 00 02 填充位(18字節) 0020: 00 77 31 d2 50 10 0030: fd 78 41 d3 00 00 00 00 00 00 00 00
以太網首部:目的主機採用廣播地址,源主機的MAC地址是00:05:5d:61:58:a8,上層協議類型0x0806表示ARP。瀏覽器
ARP幀:硬件類型0x0001表示以太網,協議類型0x0800表示IP協議,硬件地址(MAC地址)長度爲6,協議地址(IP地址)長度爲4,op爲0x0001表示請求目的主機的MAC地址,源主機MAC地址爲00:05:5d:61:58:a8,源主機IP地址爲c0 a8 00 37(192.168.0.55),目的主機MAC地址全0待填寫,目的主機IP地址爲c0 a8 00 02(192.168.0.2)。緩存
因爲以太網規定最小數據長度爲46字節,ARP幀長度只有28字節,所以有18字節填充位,填充位的內容沒有定義,與具體實現相關。
應答幀以下:
以太網首部 0000: 00 05 5d 61 58 a8 00 05 5d a1 b8 40 08 06 ARP幀 0000: 00 01 0010: 08 00 06 04 00 02 00 05 5d a1 b8 40 c0 a8 00 02 0020: 00 05 5d 61 58 a8 c0 a8 00 37 填充位 0020: 00 77 31 d2 50 10 0030: fd 78 41 d3 00 00 00 00 00 00 00 00
以太網首部:目的主機的MAC地址是00:05:5d:61:58:a8,源主機的MAC地址是00:05:5d:a1:b8:40,上層協議類型0x0806表示ARP。
ARP幀:硬件類型0x0001表示以太網,協議類型0x0800表示IP協議,硬件地址(MAC地址)長度爲6,協議地址(IP地址)長度爲4,op爲0x0002表示應答,源主機MAC地址爲00:05:5d:a1:b8:40,源主機IP地址爲c0 a8 00 02(192.168.0.2),目的主機MAC地址爲00:05:5d:61:58:a8,目的主機IP地址爲c0 a8 00 37(192.168.0.55)。
思考題:若是源主機和目的主機不在同一網段,ARP請求的廣播幀沒法穿過路由器,源主機如何與目的主機通訊?
IP數據報的首部長度和數據長度都是可變長的,但老是4字節的整數倍。對於IPv4,4位版本字段是4。4位首部長度的數值是以4字節爲單位的,最小值爲5,也就是說首部長度最小是4x5=20字節,也就是不帶任何選項的IP首部,4位能表示的最大值是15,也就是說首部長度最大是60字節。8位TOS字段有3個位用來指定IP數據報的優先級(目前已經廢棄不用),還有4個位表示可選的服務類型(最小延遲、最大?吐量、最大可靠性、最小成本),還有一個位老是0。總長度是整個數據報(包括IP首部和IP層payload)的字節數。每傳一個IP數據報,16位的標識加1,可用於分片和從新組裝數據報。3位標誌和13位片偏移用於分片。TTL(Time to live)是這樣用的:源主機爲數據包設定一個生存時間,好比64,每過一個路由器就把該值減1,若是減到0就表示路由已經太長了仍然找不到目的主機的網絡,就丟棄該包,所以這個生存時間的單位不是秒,而是跳(hop)。協議字段指示上層協議是TCP、UDP、ICMP仍是IGMP。而後是校驗和,只校驗IP首部,數據的校驗由更高層協議負責。IPv4的IP地址長度爲32位。
想想,前面講了以太網幀中的最小數據長度爲46字節,不足46字節的要用填充字節補上,那麼如何界定這46字節裏前多少個字節是IP、ARP或RARP數據報然後面是填充字節?
下面分析一幀基於UDP的TFTP協議幀。
以太網首部 0000: 00 05 5d 67 d0 b1 00 05 5d 61 58 a8 08 00 IP首部 0000: 45 00 0010: 00 53 93 25 00 00 80 11 25 ec c0 a8 00 37 c0 a8 0020: 00 01 UDP首部 0020: 05 d4 00 45 00 3f ac 40 TFTP協議 0020: 00 01 'c'':''\''q' 0030: 'w''e''r''q''.''q''w''e'00 'n''e''t''a''s''c''i' 0040: 'i'00 'b''l''k''s''i''z''e'00 '5''1''2'00 't''i' 0050: 'm''e''o''u''t'00 '1''0'00 't''s''i''z''e'00 '0' 0060: 00以太網首部:源MAC地址是00:05:5d:61:58:a8,目的MAC地址是00:05:5d:67:d0:b1,上層協議類型0x0800表示IP。
IP首部:每個字節0x45包含4位版本號和4位首部長度,版本號爲4,即IPv4,首部長度爲5,說明IP首部不帶有選項字段。服務類型爲0,沒有使用服務。16位總長度字段(包括IP首部和IP層payload的長度)爲0x0053,即83字節,加上以太網首部14字節可知整個幀長度是97字節。IP報標識是0x9325,標誌字段和片偏移字段設置爲0x0000,就是DF=0容許分片,MF=0此數據報沒有更多分片,沒有分片偏移。TTL是0x80,也就是128。上層協議0x11表示UDP協議。IP首部校驗和爲0x25ec,源主機IP是c0 a8 00 37(192.168.0.55),目的主機IP是c0 a8 00 01(192.168.0.1)。
UDP首部:源端口號0x05d4(1492)是客戶端的端口號,目的端口號0x0045(69)是TFTP服務的well-known端口號。UDP報長度爲0x003f,即63字節,包括UDP首部和UDP層pay-load的長度。UDP首部和UDP層payload的校驗和爲0xac40。
TFTP是基於文本的協議,各字段之間用字節0分隔,開頭的00 01表示請求讀取一個文件,接下來的各字段是:
c:\qwerq.qwe netascii blksize 512 timeout 10 tsize 0
通常的網絡通訊都是像TFTP協議這樣,通訊的雙方分別是客戶端和服務器,客戶端主動發起請求(上面的例子就是客戶端發起的請求幀),而服務器被動地等待、接收和應答請求。客戶端的IP地址和端口號惟一標識了該主機上的TFTP客戶端進程,服務器的IP地址和端口號惟一標識了該主機上的TFTP服務進程,因爲客戶端是主動發起請求的一方,它必須知道服務器的IP地址和TFTP服務進程的端口號,因此,一些常見的網絡協議有默認的服務器端口,例如HTTP服務默認TCP協議的80端口,FTP服務默認TCP協議的21端口,TFTP服務默認UDP協議的69端口(如上例所示)。在使用客戶端程序時,必須指定服務器的主機名或IP地址,若是不明確指定端口號則採用默認端口,請讀者查閱ftp、tftp等程序的man page瞭解如何指定端口號。/etc/services中列出了全部well-known的服務端口和對應的傳輸層協議,這是由IANA(Internet Assigned Numbers Authority)規定的,其中有些服務既能夠用TCP也能夠用UDP,爲了清晰,IANA規定這樣的服務採用相同的TCP或UDP默認端口號,而另一些TCP和UDP的相同端口號卻對應不一樣的服務。
不少服務有well-known的端口號,然而客戶端程序的端口號卻沒必要是well-known的,每每是每次運行客戶端程序時由系統自動分配一個空閒的端口號,用完就釋放掉,稱爲ephemeral的端口號,想一想這是爲何?
前面提過,UDP協議不面向鏈接,也不保證傳輸的可靠性,例如:
發送端的UDP協議層只管把應用層傳來的數據封裝成段交給IP協議層就算完成任務了,若是由於網絡故障該段沒法發到對方,UDP協議層也不會給應用層返回任何錯誤信息。
接收端的UDP協議層只管把收到的數據根據端口號交給相應的應用程序就算完成任務了,若是發送端發來多個數據包而且在網絡上通過不一樣的路由,到達接收端時順序已經錯亂了,UDP協議層也不保證按發送時的順序交給應用層。
一般接收端的UDP協議層將收到的數據放在一個固定大小的緩衝區中等待應用程序來提取和處理,若是應用程序提取和處理的速度很慢,而發送端發送的速度很快,就會丟失數據包,UDP協議層並不報告這種錯誤。
所以,使用UDP協議的應用程序必須考慮到這些可能的問題並實現適當的解決方案,例如等待應答、超時重發、爲數據包編號、流量控制等。通常使用UDP協議的應用程序實現都比較簡單,只是發送一些對可靠性要求不高的消息,而不發送大量的數據。例如,基於UDP的TFTP協議通常只用於傳送小文件(因此才叫trivial的ftp),而基於TCP的FTP協議適用於 各類文件的傳輸。TCP協議又是如何用面向鏈接的服務來代替應用程序解決傳輸的可靠性問題呢。
在這個例子中,首先客戶端主動發起鏈接、發送請求,而後服務器端響應請求,而後客戶端主動關閉鏈接。兩條豎線表示通信的兩端,從上到下表示時間的前後順序,注意,數據從一端傳到網絡的另外一端也須要時間,因此圖中的箭頭都是斜的。雙方發送的段按時間順序編號爲1-10,各段中的主要信息在箭頭上標出,例如段2的箭頭上標着SYN, 8000(0), ACK1001, ,表示該段中的SYN位置1,32位序號是8000,該段不攜帶有效載荷(數據字節數爲0),ACK位置1,32位確認序號是1001,帶有一個mss(Maximum Segment Size,最大報文長度)選項值爲1024。
創建鏈接(三次握手)的過程:
一、客戶端發送一個帶SYN標誌的TCP報文到服務器。這是三次握手過程當中的段1。
客戶端發出段1,SYN位表示鏈接請求。序號是1000,這個序號在網絡通信中用做臨時的地址,每發一個數據字節,這個序號要加1,這樣在接收端能夠根據序號排出數據包的正確順序,也能夠發現丟包的狀況,另外,規定SYN位和FIN位也要佔一個序號,此次雖然沒發數據,可是因爲發了SYN位,所以下次再發送應該用序號1001。mss表示最大段尺寸,若是一個段太大,封裝成幀後超過了鏈路層的最大幀長度,就必須在IP層分片,爲了不這種狀況,客戶端聲明本身的最大段尺寸,建議服務器端發來的段不要超過這個長度。
二、服務器端迴應客戶端,是三次握手中的第2個報文段,同時帶ACK標誌和SYN標誌。它表示對剛纔客戶端SYN的迴應;同時又發送SYN給客戶端,詢問客戶端是否準備好進行數據通信。
服務器發出段2,也帶有SYN位,同時置ACK位表示確認,確認序號是1001,表示「我接收到序號1000及其之前全部的段,請你下次發送序號爲1001的段」,也就是應答了客戶端的鏈接請求,同時也給客戶端發出一個鏈接請求,同時聲明最大尺寸爲1024。
三、客戶必須再次迴應服務器端一個ACK報文,這是報文段3。
客戶端發出段3,對服務器的鏈接請求進行應答,確認序號是8001。在這個過程當中,客戶端和服務器分別給對方發了鏈接請求,也應答了對方的鏈接請求,其中服務器的請求和應答在一個段中發出,所以一共有三個段用於創建鏈接,稱爲「三方握手(three-way-handshake)」。在創建鏈接的同時,雙方協商了一些信息,例如雙方發送序號的初始值、最大段尺寸等。
在TCP通信中,若是一方收到另外一方發來的段,讀出其中的目的端口號,發現本機並無任何進程使用這個端口,就會應答一個包含RST位的段給另外一方。例如,服務器並無任何進程使用8080端口,咱們卻用telnet客戶端去鏈接它,服務器收到客戶端發來的SYN段就會應答一個RST段,客戶端的telnet程序收到RST段後報告錯誤Connection refused:
$ telnet 192.168.0.200 8080 Trying 192.168.0.200... telnet: Unable to connect to remote host: Connection refused
九、接收端的應用程序在提走所有數據後,決定關閉鏈接,發出段17包含FIN位,發送端應答,鏈接徹底關閉。
上圖在接收端用小方塊表示1K數據,實心的小方塊表示已接收到的數據,虛線框表示接收緩衝區,所以套在虛線框中的空心小方塊表示窗口大小,從圖中能夠看出,隨着應用程序提走數據,虛線框是向右滑動的,所以稱爲滑動窗口。
從這個例子還能夠看出,發送端是一K一K地發送數據,而接收端的應用程序能夠兩K兩K地提走數據,固然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據。也就是說,應用程序所看到的數據是一個總體,或說是一個流(stream),在底層通信中這些數據可能被拆成不少數據包來發送,可是一個數據包有多少字節對應用程序是不可見的,所以TCP協議是面向流的協議。而UDP是面向消息的協議,每一個UDP段都是一條消息,應用程序必須以消息爲單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不一樣的。
CLOSED:表示初始狀態。
LISTEN:該狀態表示服務器端的某個SOCKET處於監聽狀態,能夠接受鏈接。
SYN_SENT:這個狀態與SYN_RCVD遙相呼應,當客戶端SOCKET執行CONNECT鏈接時,它首先發送SYN報文,隨即進入到了SYN_SENT狀態,並等待服務端的發送三次握手中的第2個報文。SYN_SENT狀態表示客戶端已發送SYN報文。
SYN_RCVD: 該狀態表示接收到SYN報文,在正常狀況下,這個狀態是服務器端的SOCKET在創建TCP鏈接時的三次握手會話過程當中的一箇中間狀態,很短暫。此種狀態時,當收到客戶端的ACK報文後,會進入到ESTABLISHED狀態。
ESTABLISHED:表示鏈接已經創建。
FIN_WAIT_2:主動關閉連接的一方,發出FIN收到ACK之後進入該狀態。稱之爲半鏈接或半關閉狀態。該狀態下的socket只能接收數據,不能發。
TIME_WAIT: 表示收到了對方的FIN報文,併發送出了ACK報文,等2MSL後便可回到CLOSED可用狀態。若是FIN_WAIT_1狀態下,收到對方同時帶 FIN標誌和ACK標誌的報文時,能夠直接進入到TIME_WAIT狀態,而無須通過FIN_WAIT_2狀態。
CLOSING: 這種狀態較特殊,屬於一種較罕見的狀態。正常狀況下,當你發送FIN報文後,按理來講是應該先收到(或同時收到)對方的 ACK報文,再收到對方的FIN報文。可是CLOSING狀態表示你發送FIN報文後,並無收到對方的ACK報文,反而卻也收到了對方的FIN報文。什麼狀況下會出現此種狀況呢?若是雙方几乎在同時close一個SOCKET的話,那麼就出現了雙方同時發送FIN報文的狀況,也即會出現CLOSING狀態,表示雙方都正在關閉SOCKET鏈接。
CLOSE_WAIT: 此種狀態表示在等待關閉。當對方關閉一個SOCKET後發送FIN報文給本身,系統會迴應一個ACK報文給對方,此時則進入到CLOSE_WAIT狀態。接下來呢,察看是否還有數據發送給對方,若是沒有能夠 close這個SOCKET,發送FIN報文給對方,即關閉鏈接。因此在CLOSE_WAIT狀態下,須要關閉鏈接。
LAST_ACK: 該狀態是被動關閉一方在發送FIN報文後,最後等待對方的ACK報文。當收到ACK報文後,便可以進入到CLOSED可用狀態。
當TCP連接中A發送FIN請求關閉,B端迴應ACK後(A端進入FIN_WAIT_2狀態),B沒有當即發送FIN給A時,A方處在半連接狀態,此時A能夠接收B發送的數據,可是A已不能再向B發送數據。
從程序的角度,能夠使用API來控制實現半鏈接狀態。
#include <sys/socket.h> int shutdown(int sockfd, int how); sockfd: 須要關閉的socket的描述符 how: 容許爲shutdown操做選擇如下幾種方式: SHUT_RD(0): 關閉sockfd上的讀功能,此選項將不容許sockfd進行讀操做。 該套接字再也不接受數據,任何當前在套接字接受緩衝區的數據將被無聲的丟棄掉。 SHUT_WR(1): 關閉sockfd的寫功能,此選項將不容許sockfd進行寫操做。進程不能在對此套接字發出寫操做。 SHUT_RDWR(2): 關閉sockfd的讀寫功能。至關於調用shutdown兩次:首先是以SHUT_RD,而後以SHUT_WR。
使用close停止一個鏈接,但它只是減小描述符的引用計數,並不直接關閉鏈接,只有當描述符的引用計數爲0時才關閉鏈接。
shutdown不考慮描述符的引用計數,直接關閉描述符。也可選擇停止一個方向的鏈接,只停止讀或只停止寫。
另一個概念叫作incarnation connection,指跟上次的socket pair一摸同樣的新鏈接,叫作incarnation of previous connection。lost uplicate加上incarnation connection,則會對咱們的傳輸形成致命的錯誤。
TCP是流式的,全部包到達的順序是不一致的,依靠序列號由TCP協議棧作順序的拼接;假設一個incarnation connection這時收到的seq=1000, 來了一個lost duplicate爲seq=1000,len=1000, 則TCP認爲這個lost duplicate合法,並存放入了receive buffer,致使傳輸出現錯誤。經過一個2MSL TIME_WAIT狀態,確保全部的lost duplicate都會消失掉,避免對新鏈接形成錯誤。
如何正確對待2MSL TIME_WAIT?
RFC要求socket pair在處於TIME_WAIT時,不能再起一個incarnation connection。但絕大部分TCP實現,強加了更爲嚴格的限制。在2MSL等待期間,socket中使用的本地端口在默認狀況下不能再被使用。
若A 10.234.5.5 : 1234和B 10.55.55.60 : 6666創建了鏈接,A主動關閉,那麼在A端只要port爲1234,不管對方的port和ip是什麼,都不容許再起服務。這甚至比RFC限制更爲嚴格,RFC僅僅是要求socket pair不一致,而實現當中只要這個port處於TIME_WAIT,就不容許起鏈接。這個限制對主動打開方來講是無所謂的,由於通常用的是臨時端口;但對於被動打開方,通常是server,就悲劇了,由於server通常是熟知端口。好比http,通常端口是80,不可能容許這個服務在2MSL內不能起來。
解決方案是給服務器的socket設置SO_REUSEADDR選項,這樣的話就算熟知端口處於TIME_WAIT狀態,在這個端口上依舊能夠將服務啓動。固然,雖然有了SO_REUSEADDR選項,但sockt pair這個限制依舊存在。好比上面的例子,A經過SO_REUSEADDR選項依舊在1234端口上起了監聽,但這時咱們如果從B經過6666端口去連它,TCP協議會告訴咱們鏈接失敗,緣由爲Address already in use.
RFC 793中規定MSL爲2分鐘,實際應用中經常使用的是30秒,1分鐘和2分鐘等。
RFC (Request For Comments),是一系列以編號排定的文件。收集了有關因特網相關資訊,以及UNIX和因特網社羣的軟件文件。
作一個測試,首先啓動server,而後啓動client,用Ctrl-C終止server,立刻再運行server,運行結果:
itcast$ ./server bind error: Address already in use
這是由於,雖然server的應用程序終止了,但TCP協議層的鏈接並無徹底斷開,所以不能再次監聽一樣的server端口。咱們用netstat命令查看一下:
itcast$ netstat -apn |grep 6666
tcp 1 0 192.168.1.11:38103 192.168.1.11:6666 CLOSE_WAIT 3525/client
tcp 0 0 192.168.1.11:6666 192.168.1.11:38103 FIN_WAIT2 -
server終止時,socket描述符會自動關閉併發FIN段給client,client收到FIN後處於CLOSE_WAIT狀態,可是client並無終止,也沒有關閉socket描述符,所以不會發FIN給server,所以server的TCP鏈接處於FIN_WAIT2狀態。
如今用Ctrl-C把client也終止掉,再觀察現象:
itcast$ netstat -apn |grep 6666
tcp 0 0 192.168.1.11:6666 192.168.1.11:38104 TIME_WAIT -
itcast$ ./server
bind error: Address already in use
client終止時自動關閉socket描述符,server的TCP鏈接收到client發的FIN段後處於TIME_WAIT狀態。TCP協議規定,主動關閉鏈接的一方要處於TIME_WAIT狀態,等待兩個MSL(maximum segment lifetime)的時間後才能回到CLOSED狀態,由於咱們先Ctrl-C終止了server,因此server是主動關閉鏈接的一方,在TIME_WAIT期間仍然不能再次監聽一樣的server端口。
MSL在RFC 1122中規定爲兩分鐘,可是各操做系統的實現不一樣,在Linux上通常通過半分鐘後就能夠再次啓動server了。至於爲何要規定TIME_WAIT的時間,可參考UNP 2.7節。
在server的TCP鏈接沒有徹底斷開以前不容許從新監聽是不合理的。由於,TCP鏈接沒有徹底斷開指的是connfd(127.0.0.1:6666)沒有徹底斷開,而咱們從新監聽的是lis-tenfd(0.0.0.0:6666),雖然是佔用同一個端口,但IP地址不一樣,connfd對應的是與某個客戶端通信的一個具體的IP地址,而listenfd對應的是wildcard address。解決這個問題的方法是使用setsockopt()設置socket描述符的選項SO_REUSEADDR爲1,表示容許建立端口號相同但IP地址不一樣的多個socket描述符。
有關setsockopt能夠設置的其它選項請參考UNP第7章。
在TCP網絡通訊中,常常會出現客戶端和服務器之間的非正常斷開,須要實時檢測查詢連接狀態。經常使用的解決方法就是在程序中加入心跳機制。
Heart-Beat線程
這個是最經常使用的簡單方法。在接收和發送數據時我的設計一個守護進程(線程),定時發送Heart-Beat包,客戶端/服務器收到該小包後,馬上返回相應的包便可檢測對方是否實時在線。
該方法的好處是通用,但缺點就是會改變現有的通信協議!你們通常都是使用業務層心跳來處理,主要是靈活可控。
UNIX網絡編程不推薦使用SO_KEEPALIVE來作心跳檢測,仍是在業務層以心跳包作檢測比較好,也方便控制。
SO_KEEPALIVE 保持鏈接檢測對方主機是否崩潰,避免(服務器)永遠阻塞於TCP鏈接的輸入。設置該選項後,若是2小時內在此套接口的任一方向都沒有數據交換,TCP就自動給對方發一個保持存活探測分節(keepalive probe)。這是一個對方必須響應的TCP分節.它會致使如下三種狀況:對方接收一切正常:以指望的ACK響應。2小時後,TCP將發出另外一個探測分節。對方已崩潰且已從新啓動:以RST響應。套接口的待處理錯誤被置爲ECONNRESET,套接 口自己則被關閉。對方無任何響應:源自berkeley的TCP發送另外8個探測分節,相隔75秒一個,試圖獲得一個響應。在發出第一個探測分節11分鐘 15秒後若仍無響應就放棄。套接口的待處理錯誤被置爲ETIMEOUT,套接口自己則被關閉。如ICMP錯誤是「host unreachable(主機不可達)」,說明對方主機並無崩潰,可是不可達,這種狀況下待處理錯誤被置爲EHOSTUNREACH。
根據上面的介紹咱們能夠知道對端以一種非優雅的方式斷開鏈接的時候,咱們能夠設置SO_KEEPALIVE屬性使得咱們在2小時之後發現對方的TCP鏈接是否依然存在。
keepAlive = 1;
setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive));
1.The tcp_keepidle parameter specifies the interval of inactivity that causes TCP to generate a KEEPALIVE transmission for an application that requests them. tcp_keepidle defaults to 14400 (two hours).
/*開始首次KeepAlive探測前的TCP空閉時間 */
2.The tcp_keepintvl parameter specifies the interval between the nine retriesthat are attempted if a KEEPALIVE transmission is not acknowledged. tcp_keep ntvldefaults to 150 (75 seconds).
/* 兩次KeepAlive探測間的時間間隔 */
3.The tcp_keepcnt option specifies the maximum number of keepalive probes tobe sent. The value of TCP_KEEPCNT is an integer value between 1 and n, where n s the value of the systemwide tcp_keepcnt parameter.
/* 斷定斷開前的KeepAlive探測次數*/ int keepIdle = 1000; int keepInterval = 10; int keepCount = 10; Setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle)); Setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval)); Setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
SO_KEEPALIVE設置空閒2小時才發送一個「保持存活探測分節」,不能保證明時檢測。對於判斷網絡斷開時間太長,對於須要及時響應的程序不太適應。
固然也能夠修改時間間隔參數,可是會影響到全部打開此選項的套接口!關聯了完成端口的socket可能會忽略掉該套接字選項。
路由器(Router)是鏈接因特網中各局域網、廣域網的設備,它會根據信道的狀況自動選擇和設定路由,以最佳路徑,按先後順序發送信號的設備。
傳統地,路由器工做於OSI七層協議中的第三層,其主要任務是接收來自一個網絡接口的數據包,根據其中所含的目的地址,決定轉發到下一個目的地址。所以,路由器首先得在轉發路由表中查找它的目的地址,若找到了目的地址,就在數據包的幀格前添加下一個MAC地址,同時IP數據包頭的TTL(Time To Live)域也開始減數, 並從新計算校驗和。當數據包被送到輸出端口時,它須要按順序等待,以便被傳送到輸出鏈路上。
路由器在工做時可以按照某種路由通訊協議查找設備中的路由表。若是到某一特定節點有一條以上的路徑,則基本預先肯定的路由準則是選擇最優(或最經濟)的傳輸路徑。因爲各類網絡段和其相互鏈接狀況可能會因環境變化而變化,所以路由狀況的信息通常也按所使用的路由信息協議的規定而定時更新。
網絡中,每一個路由器的基本功能都是按照必定的規則來動態地更新它所保持的路由表,以便保持路由信息的有效性。爲了便於在網絡間傳送報文,路由器老是先按照預約的規則把較大的數據分解成適當大小的數據包,再將這些數據包分別經過相同或不一樣路徑發送出去。當這些數據包按前後秩序到達目的地後,再把分解的數據包按照必定順序包裝成原有的報文形式。路由器的分層尋址功能是路由器的重要功能之一,該功能能夠幫助具備不少節點站的網絡來存儲尋址信息,同時還能在網絡間截獲發送到遠地網段的報文,起轉發做用;選擇最合理的路由,引導通訊也是路由器基本功能;多協議路由器還能夠鏈接使用不一樣通訊協議的網絡段,成爲不一樣通訊協議網絡段之間的通訊平臺。
路由和交換之間的主要區別就是交換髮生在OSI參考模型第二層(數據鏈路層),而路由發生在第三層,即網絡層。這一區別決定了路由和交換在移動信息的過程 中需使用不一樣的控制信息,因此二者實現各自功能的方式是不一樣的。
以太網交換機是基於以太網傳輸數據的交換機,以太網採用共享總線型傳輸媒體方式的局域網。以太網交換機的結構是每一個端口都直接與主機相連,而且通常都工做在全雙工方式。交換機能同時連通許多對端口,使每一對相互通訊的主機都能像獨佔通訊媒體那樣,進行無衝突地傳輸數據。
以太網交換機工做於OSI網絡參考模型的第二層(即數據鏈路層),是一種基於MAC(Media Access Control,介質訪問控制)地址識別、完成以太網數據幀轉發的網絡設備。
集線器實際上就是中繼器的一種,其區別僅在於集線器可以提供更多的端口服務,因此集線器又叫多口中繼器。
集線器功能是隨機選出某一端口的設備,並讓它獨佔所有帶寬,與集線器的上聯設備(交換機、路由器或服務器等)進行通訊。從Hub的工做方式能夠看出,它在網絡中只起到信號放大和重發做用,其目的是擴大網絡的傳輸範圍,而不具有信號的定向傳送能力,是—個標準的共享式設備。其次是Hub只與它的上聯設備(如上層Hub、交換機或服務器)進行通訊,同層的各端口之間不會直接進行通訊,而是經過上聯設備再將信息廣播到全部端口上。 因而可知,即便是在同一Hub的不一樣兩個端口之間進行通訊,都必需要通過兩步操做:
第一步是將信息上傳到上聯設備;
第二步是上聯設備再將該信息廣播到全部端口上。
DNS 是域名系統 (Domain Name System) 的縮寫,是因特網的一項核心服務,它做爲能夠將域名和IP地址相互映射的一個分佈式數據庫,可以令人更方便的訪問互聯網,而不用去記住可以被機器直接讀取的IP地址串。
它是由解析器以及域名服務器組成的。域名服務器是指保存有該網絡中全部主機的域名和對應IP地址,並具備將域名轉換爲IP地址功能的服務器。
wide area network,一種用來實現不一樣地區的局域網或城域網的互連,可提供不一樣地區、城市和國家之間的計算機通訊的遠程計算機網。
覆蓋的範圍比局域網(LAN)和城域網(MAN)都廣。廣域網的通訊子網主要使用分組交換技術。
四、完善的通訊服務與網絡管理。
如下是一些協議的MTU:
FDDI協議:4352字節 以太網(Ethernet)協議:1500字節 PPPoE(ADSL)協議:1492字節 X.25協議(Dial Up/Modem):576字節 Point-to-Point:4470字節
Socket自己有「插座」的意思,在Linux環境下,用於表示進程間網絡通訊的特殊文件類型。本質爲內核藉助緩衝區造成的僞文件。
既然是文件,那麼理所固然的,咱們能夠使用文件描述符引用套接字。與管道相似的,Linux系統將其封裝成文件的目的是爲了統一接口,使得讀寫套接字和讀寫文件的操做一致。區別是管道主要應用於本地進程間通訊,而套接字多應用於網絡進程間數據的傳遞。
套接字的內核實現較爲複雜,不宜在學習初期深刻學習。
在TCP/IP協議中,「IP地址+TCP或UDP端口號」惟一標識網絡通信中的一個進程。「IP地址+端口號」就對應一個socket。欲創建鏈接的兩個進程各自有一個socket來標識,那麼這兩個socket組成的socket pair就惟一標識一個鏈接。所以能夠用Socket來描述網絡鏈接的一對一關係。
套接字通訊原理以下圖所示:
在網絡通訊中,套接字必定是成對出現的。一端的發送緩衝區對應對端的接收緩衝區。咱們使用同一個文件描述符索發送緩衝區和接收緩衝區。
TCP/IP協議最先在BSD UNIX上實現,爲TCP/IP協議設計的應用層編程接口稱爲socket API。本章的主要內容是socket API,主要介紹TCP協議的函數接口,最後介紹UDP協議和UNIX Domain Socket的函數接口。
咱們已經知道,內存中的多字節數據相對於內存地址有大端和小端之分,磁盤文件中的多字節數據相對於文件中的偏移地址也有大端小端之分。網絡數據流一樣有大端小端之分,那麼如何定義網絡數據流的地址呢?發送主機一般將發送緩衝區中的數據按內存地址從低到高的順序發出,接收主機把從網絡上接到的字節依次保存在接收緩衝區中,也是按內存地址從低到高的順序保存,所以,網絡數據流的地址應這樣規定:先發出的數據是低地址,後發出的數據是高地址。
TCP/IP協議規定,網絡數據流應採用大端字節序,即低地址高字節。例如上一節的UDP段格式,地址0-1是16位的源端口號,若是這個端口號是1000(0x3e8),則地址0是0x03,地址1是0xe8,也就是先發0x03,再發0xe8,這16位在發送主機的緩衝區中也應該是低地址存0x03,高地址存0xe8。可是,若是發送主機是小端字節序的,這16位被解釋成0xe803,而不是1000。所以,發送主機把1000填到發送緩衝區以前須要作字節序的轉換。一樣地,接收主機若是是小端字節序的,接到16位的源端口號也要作字節序的轉換。若是主機是大端字節序的,發送和接收都不須要作轉換。同理,32位的IP地址也要考慮網絡字節序和主機字節序的問題。
爲使網絡程序具備可移植性,使一樣的C代碼在大端和小端計算機上編譯後都能正常運行,能夠調用如下庫函數作網絡字節序和主機字節序的轉換。
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
h表示host,n表示network,l表示32位長整數,s表示16位短整數。
若是主機是小端字節序,這些函數將參數作相應的大小端轉換而後返回,若是主機是大端字節序,這些函數不作轉換,將參數原封不動地返回。
早期:
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int inet_aton(const char *cp, struct in_addr *inp); in_addr_t inet_addr(const char *cp); char *inet_ntoa(struct in_addr in); 只能處理IPv4的ip地址 不可重入函數 注意參數是struct in_addr
如今:
#include <arpa/inet.h> int inet_pton(int af, const char *src, void *dst); const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); 支持IPv4和IPv6 可重入函數 其中inet_pton和inet_ntop不只能夠轉換IPv4的in_addr,還能夠轉換IPv6的in6_addr。 所以函數接口是void *addrptr。
sockaddr數據結構
struct sockaddr { sa_family_t sa_family; /* address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */ };
使用 sudo grep -r "struct sockaddr_in {" /usr 命令可查看到struct sockaddr_in結構體的定義。通常其默認的存儲位置:/usr/include/linux/in.h 文件中。
struct sockaddr_in { __kernel_sa_family_t sin_family; /* Address family */ 地址結構類型 __be16 sin_port; /* Port number */ 端口號 struct in_addr sin_addr; /* Internet address */ IP地址 /* Pad to size of `struct sockaddr'. */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; }; struct in_addr { /* Internet address. */ __be32 s_addr; }; struct sockaddr_in6 { unsigned short int sin6_family; /* AF_INET6 */ __be16 sin6_port; /* Transport layer port # */ __be32 sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ __u32 sin6_scope_id; /* scope id (new in RFC2553) */ }; struct in6_addr { union { __u8 u6_addr8[16]; __be16 u6_addr16[8]; __be32 u6_addr32[4]; } in6_u; #define s6_addr in6_u.u6_addr8 #define s6_addr16 in6_u.u6_addr16 #define s6_addr32 in6_u.u6_addr32 }; #define UNIX_PATH_MAX 108 struct sockaddr_un { __kernel_sa_family_t sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* pathname */ };
Pv4和IPv6的地址格式定義在netinet/in.h中,IPv4地址用sockaddr_in結構體表示,包括16位端口號和32位IP地址,IPv6地址用sockaddr_in6結構體表示,包括16位端口號、128位IP地址和一些控制字段。UNIX Domain Socket的地址格式定義在sys/un.h中,用sock-addr_un結構體表示。各類socket地址結構體的開頭都是相同的,前16位表示整個結構體的長度(並非全部UNIX的實現都有長度字段,如Linux就沒有),後16位表示地址類型。IPv四、IPv6和Unix Domain Socket的地址類型分別定義爲常數AF_INET、AF_INET六、AF_UNIX。這樣,只要取得某種sockaddr結構體的首地址,不須要知道具體是哪一種類型的sockaddr結構體,就能夠根據地址類型字段肯定結構體中的內容。所以,socket API能夠接受各類類型的sockaddr結構體指針作參數,例如bind、accept、connect等函數,這些函數的參數應該設計成void 類型以便接受各類類型的指針,可是sock API的實現早於ANSI C標準化,那時尚未void 類型,所以這些函數的參數都用struct sockaddr *類型表示,在傳遞參數以前要強制類型轉換一下,例如:
struct sockaddr_in servaddr; bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)); /* initialize servaddr */
socket函數
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol); domain: AF_INET 這是大多數用來產生socket的協議,使用TCP或UDP來傳輸,用IPv4的地址 AF_INET6 與上面相似,不過是來用IPv6的地址 AF_UNIX 本地協議,使用在Unix和Linux系統上,通常都是當客戶端和服務器在同一臺及其上的時候使用 type: SOCK_STREAM 這個協議是按照順序的、可靠的、數據完整的基於字節流的鏈接。這是一個使用最多的socket類型,這個socket是使用TCP來進行傳輸。 SOCK_DGRAM 這個協議是無鏈接的、固定長度的傳輸調用。該協議是不可靠的,使用UDP來進行它的鏈接。 SOCK_SEQPACKET該協議是雙線路的、可靠的鏈接,發送固定長度的數據包進行傳輸。必須把這個包完整的接受才能進行讀取。 SOCK_RAW socket類型提供單一的網絡訪問,這個socket類型使用ICMP公共協議。(ping、traceroute使用該協議) SOCK_RDM 這個類型是不多使用的,在大部分的操做系統上沒有實現,它是提供給數據鏈路層使用,不保證數據包的順序 protocol: 傳0 表示使用默認協議。 返回值: 成功:返回指向新建立的socket的文件描述符,失敗:返回-1,設置errno
bind函數
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockfd: socket文件描述符 addr: 構造出IP地址加端口號 addrlen: sizeof(addr)長度 返回值: 成功返回0,失敗返回-1, 設置errno
服務器程序所監聽的網絡地址和端口號一般是固定不變的,客戶端程序得知服務器程序的地址和端口號後就能夠向服務器發起鏈接,所以服務器須要調用bind綁定一個固定的網絡地址和端口號。
bind()的做用是將參數sockfd和addr綁定在一塊兒,使sockfd這個用於網絡通信的文件描述符監聽addr所描述的地址和端口號。前面講過,struct sockaddr *是一個通用指針類型,addr參數實際上能夠接受多種協議的sockaddr結構體,而它們的長度各不相同,因此須要第三個參數addrlen指定結構體的長度。如:
struct sockaddr_in servaddr; bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(6666);
首先將整個結構體清零,而後設置地址類型爲AF_INET,網絡地址爲INADDR_ANY,這個宏表示本地的任意IP地址,由於服務器可能有多個網卡,每一個網卡也可能綁定多個IP地址,這樣設置能夠在全部的IP地址上監聽,直到與某個客戶端創建了鏈接時才肯定下來到底用哪一個IP地址,端口號爲6666。
listen函數
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog); sockfd: socket文件描述符 backlog: 排隊創建3次握手隊列和剛剛創建3次握手隊列的連接數和
查看系統默認backlog
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
典型的服務器程序能夠同時服務於多個客戶端,當有客戶端發起鏈接時,服務器調用的accept()返回並接受這個鏈接,若是有大量的客戶端發起鏈接而服務器來不及處理,還沒有accept的客戶端就處於鏈接等待狀態,listen()聲明sockfd處於監聽狀態,而且最多容許有backlog個客戶端處於鏈接待狀態,若是接收到更多的鏈接請求就忽略。listen()成功返回0,失敗返回-1。
accept函數
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); sockdf: socket文件描述符 addr: 傳出參數,返回連接客戶端地址信息,含IP地址和端口號 addrlen: 傳入傳出參數(值-結果),傳入sizeof(addr)大小,函數返回時返回真正接收到地址結構體的大小 返回值: 成功返回一個新的socket文件描述符,用於和客戶端通訊,失敗返回-1,設置errno
三方握手完成後,服務器調用accept()接受鏈接,若是服務器調用accept()時尚未客戶端的鏈接請求,就阻塞等待直到有客戶端鏈接上來。addr是一個傳出參數,accept()返回時傳出客戶端的地址和端口號。addrlen參數是一個傳入傳出參數(value-result argument),傳入的是調用者提供的緩衝區addr的長度以免緩衝區溢出問題,傳出的是客戶端地址結構體的實際長度(有可能沒有佔滿調用者提供的緩衝區)。若是給addr參數傳NULL,表示不關心客戶端的地址。
咱們的服務器程序結構是這樣的:
while (1) { cliaddr_len = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); n = read(connfd, buf, MAXLINE); ...... close(connfd); }
整個是一個while死循環,每次循環處理一個客戶端鏈接。因爲cliaddr_len是傳入傳出參數,每次調用accept()以前應該從新賦初值。accept()的參數listenfd是先前的監聽文件描述符,而accept()的返回值是另一個文件描述符connfd,以後與客戶端之間就經過這個connfd通信,最後關閉connfd斷開鏈接,而不關閉listenfd,再次回到循環開頭listenfd仍然用做accept的參數。accept()成功返回一個文件描述符,出錯返回-1。
connect函數
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockdf: socket文件描述符 addr: 傳入參數,指定服務器端地址信息,含IP地址和端口號 addrlen: 傳入參數,傳入sizeof(addr)大小 返回值: 成功返回0,失敗返回-1,設置errno
服務器調用socket()、bind()、listen()完成初始化後,調用accept()阻塞等待,處於監聽端口的狀態,客戶端調用socket()初始化後,調用connect()發出SYN段並阻塞等待服務器應答,服務器應答一個SYN-ACK段,客戶端收到後從connect()返回,同時應答一個ACK段,服務器收到後從accept()返回。
在學習socket API時要注意應用程序和TCP協議層是如何交互的: 應用程序調用某個socket函數時TCP協議層完成什麼動做,好比調用connect()會發出SYN段 應用程序如何知道TCP協議層的狀態變化,好比從某個阻塞的socket函數返回就代表TCP協議收到了某些段,再好比read()返回0就代表收到了FIN段
下面經過最簡單的客戶端/服務器程序的實例來學習socket API。
server.c的做用是從客戶端讀字符,而後將每一個字符轉換爲大寫並回送給客戶端。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define MAXLINE 80 #define SERV_PORT 6666 int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int listenfd, connfd; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; int i, n; listenfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(listenfd, 20); printf("Accepting connections ...\n"); while (1) { cliaddr_len = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); n = read(connfd, buf, MAXLINE); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (i = 0; i < n; i++) buf[i] = toupper(buf[i]); write(connfd, buf, n); close(connfd); } return 0; }
client.c的做用是從命令行參數中得到一個字符串發給服務器,而後接收服務器返回的字符串並打印。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; char *str; if (argc != 2) { fputs("usage: ./client message\n", stderr); exit(1); } str = argv[1]; sockfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); write(sockfd, str, strlen(str)); n = read(sockfd, buf, MAXLINE); printf("Response from server:\n"); write(STDOUT_FILENO, buf, n); close(sockfd); return 0; }
因爲客戶端不須要固定的端口號,所以沒必要調用bind(),客戶端的端口號由內核自動分配。注意,客戶端不是不容許調用bind(),只是沒有必要調用bind()固定一個端口號,服務器也不是必須調用bind(),但若是服務器不調用bind(),內核會自動給服務器分配監聽端口,每次啓動服務器時端口號都不同,客戶端要鏈接服務器就會遇到麻煩。
客戶端和服務器啓動後能夠使用netstat命令查看連接狀況:
netstat -apn|grep 6666
上面的例子不只功能簡單,並且簡單到幾乎沒有什麼錯誤處理,咱們知道,系統調用不能保證每次都成功,必須進行出錯處理,這樣一方面能夠保證程序邏輯正常,另外一方面能夠迅速獲得故障信息。
爲使錯誤處理的代碼不影響主程序的可讀性,咱們把與socket相關的一些系統函數加上錯誤處理代碼包裝成新的函數,作成一個模塊wrap.c:
wrap.c
#include <stdlib.h> #include <errno.h> #include <sys/socket.h> void perr_exit(const char *s) { perror(s); exit(1); } int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) { int n; again: if ( (n = accept(fd, sa, salenptr)) < 0) { if ((errno == ECONNABORTED) || (errno == EINTR)) goto again; else perr_exit("accept error"); } return n; } int Bind(int fd, const struct sockaddr *sa, socklen_t salen) { int n; if ((n = bind(fd, sa, salen)) < 0) perr_exit("bind error"); return n; } int Connect(int fd, const struct sockaddr *sa, socklen_t salen) { int n; if ((n = connect(fd, sa, salen)) < 0) perr_exit("connect error"); return n; } int Listen(int fd, int backlog) { int n; if ((n = listen(fd, backlog)) < 0) perr_exit("listen error"); return n; } int Socket(int family, int type, int protocol) { int n; if ( (n = socket(family, type, protocol)) < 0) perr_exit("socket error"); return n; } ssize_t Read(int fd, void *ptr, size_t nbytes) { ssize_t n; again: if ( (n = read(fd, ptr, nbytes)) == -1) { if (errno == EINTR) goto again; else return -1; } return n; } ssize_t Write(int fd, const void *ptr, size_t nbytes) { ssize_t n; again: if ( (n = write(fd, ptr, nbytes)) == -1) { if (errno == EINTR) goto again; else return -1; } return n; } int Close(int fd) { int n; if ((n = close(fd)) == -1) perr_exit("close error"); return n; } ssize_t Readn(int fd, void *vptr, size_t n) { size_t nleft; ssize_t nread; char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; else return -1; } else if (nread == 0) break; nleft -= nread; ptr += nread; } return n - nleft; } ssize_t Writen(int fd, const void *vptr, size_t n) { size_t nleft; ssize_t nwritten; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) nwritten = 0; else return -1; } nleft -= nwritten; ptr += nwritten; } return n; } static ssize_t my_read(int fd, char *ptr) { static int read_cnt; static char *read_ptr; static char read_buf[100]; if (read_cnt <= 0) { again: if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { if (errno == EINTR) goto again; return -1; } else if (read_cnt == 0) return 0; read_ptr = read_buf; } read_cnt--; *ptr = *read_ptr++; return 1; } ssize_t Readline(int fd, void *vptr, size_t maxlen) { ssize_t n, rc; char c, *ptr; ptr = vptr; for (n = 1; n < maxlen; n++) { if ( (rc = my_read(fd, &c)) == 1) { *ptr++ = c; if (c == '\n') break; } else if (rc == 0) { *ptr = 0; return n - 1; } else return -1; } *ptr = 0; return n; }
wrap.h
#ifndef __WRAP_H_ #define __WRAP_H_ void perr_exit(const char *s); int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr); int Bind(int fd, const struct sockaddr *sa, socklen_t salen); int Connect(int fd, const struct sockaddr *sa, socklen_t salen); int Listen(int fd, int backlog); int Socket(int family, int type, int protocol); ssize_t Read(int fd, void *ptr, size_t nbytes); ssize_t Write(int fd, const void *ptr, size_t nbytes); int Close(int fd); ssize_t Readn(int fd, void *vptr, size_t n); ssize_t Writen(int fd, const void *vptr, size_t n); ssize_t my_read(int fd, char *ptr); ssize_t Readline(int fd, void *vptr, size_t maxlen); #endif
server
/* server.c */ #include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #include <sys/types.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 800 void do_sigchild(int num) { while (waitpid(0, NULL, WNOHANG) > 0) ; } int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int listenfd, connfd; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; int i, n; pid_t pid; struct sigaction newact; newact.sa_handler = do_sigchild; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; sigaction(SIGCHLD, &newact, NULL); listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); Listen(listenfd, 20); printf("Accepting connections ...\n"); while (1) { cliaddr_len = sizeof(cliaddr); connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); pid = fork(); if (pid == 0) { Close(listenfd); while (1) { n = Read(connfd, buf, MAXLINE); if (n == 0) { printf("the other side has been closed.\n"); break; } printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (i = 0; i < n; i++) buf[i] = toupper(buf[i]); Write(connfd, buf, n); } Close(connfd); return 0; } else if (pid > 0) { Close(connfd); } else perr_exit("fork"); } Close(listenfd); return 0; }
client
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf)); n = Read(sockfd, buf, MAXLINE); if (n == 0) { printf("the other side has been closed.\n"); break; } else Write(STDOUT_FILENO, buf, n); } Close(sockfd); return 0; }
server
/* server.c */ #include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <pthread.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 struct s_info { struct sockaddr_in cliaddr; int connfd; }; void *do_work(void *arg) { int n,i; struct s_info *ts = (struct s_info*)arg; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; /* 能夠在建立線程前設置線程建立屬性,設爲分離態,哪一種效率高內? */ pthread_detach(pthread_self()); while (1) { n = Read(ts->connfd, buf, MAXLINE); if (n == 0) { printf("the other side has been closed.\n"); break; } printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)), ntohs((*ts).cliaddr.sin_port)); for (i = 0; i < n; i++) buf[i] = toupper(buf[i]); Write(ts->connfd, buf, n); } Close(ts->connfd); } int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int listenfd, connfd; int i = 0; pthread_t tid; struct s_info ts[256]; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); Listen(listenfd, 20); printf("Accepting connections ...\n"); while (1) { cliaddr_len = sizeof(cliaddr); connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); ts[i].cliaddr = cliaddr; ts[i].connfd = connfd; /* 達到線程最大數時,pthread_create出錯處理, 增長服務器穩定性 */ pthread_create(&tid, NULL, do_work, (void*)&ts[i]); i++; } return 0; }
client
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf)); n = Read(sockfd, buf, MAXLINE); if (n == 0) printf("the other side has been closed.\n"); else Write(STDOUT_FILENO, buf, n); } Close(sockfd); return 0; }
主要使用的方法有三種
二、解決1024如下客戶端時使用select是很合適的,但若是連接客戶端過多,select採用的是輪詢模型,會大大下降服務器響應效率,不該在select上投入更多精力
#include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); nfds: 監控的文件描述符集裏最大文件描述符加1,由於此參數會告訴內核檢測前多少個文件描述符的狀態 readfds: 監控有讀數據到達文件描述符集合,傳入傳出參數 writefds: 監控寫數據到達文件描述符集合,傳入傳出參數 exceptfds: 監控異常發生達文件描述符集合,如帶外數據到達異常,傳入傳出參數 timeout: 定時阻塞監控時間,3種狀況 1.NULL,永遠等下去 2.設置timeval,等待固定時間 3.設置timeval裏時間均爲0,檢查描述字後當即返回,輪詢 struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ }; void FD_CLR(int fd, fd_set *set); //把文件描述符集合裏fd清0 int FD_ISSET(int fd, fd_set *set); //測試文件描述符集合裏fd是否置1 void FD_SET(int fd, fd_set *set); //把文件描述符集合裏fd位置1 void FD_ZERO(fd_set *set); //把文件描述符集合裏全部位清0
server
/* server.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { int i, maxi, maxfd, listenfd, connfd, sockfd; int nready, client[FD_SETSIZE]; /* FD_SETSIZE 默認爲 1024 */ ssize_t n; fd_set rset, allset; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; /* #define INET_ADDRSTRLEN 16 */ socklen_t cliaddr_len; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); Listen(listenfd, 20); /* 默認最大128 */ maxfd = listenfd; /* 初始化 */ maxi = -1; /* client[]的下標 */ for (i = 0; i < FD_SETSIZE; i++) client[i] = -1; /* 用-1初始化client[] */ FD_ZERO(&allset); FD_SET(listenfd, &allset); /* 構造select監控文件描述符集 */ for ( ; ; ) { rset = allset; /* 每次循環時都重新設置select監控信號集 */ nready = select(maxfd+1, &rset, NULL, NULL, NULL); if (nready < 0) perr_exit("select error"); if (FD_ISSET(listenfd, &rset)) { /* new client connection */ cliaddr_len = sizeof(cliaddr); connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (i = 0; i < FD_SETSIZE; i++) { if (client[i] < 0) { client[i] = connfd; /* 保存accept返回的文件描述符到client[]裏 */ break; } } /* 達到select能監控的文件個數上限 1024 */ if (i == FD_SETSIZE) { fputs("too many clients\n", stderr); exit(1); } FD_SET(connfd, &allset); /* 添加一個新的文件描述符到監控信號集裏 */ if (connfd > maxfd) maxfd = connfd; /* select第一個參數須要 */ if (i > maxi) maxi = i; /* 更新client[]最大下標值 */ if (--nready == 0) continue; /* 若是沒有更多的就緒文件描述符繼續回到上面select阻塞監聽, 負責處理未處理完的就緒文件描述符 */ } for (i = 0; i <= maxi; i++) { /* 檢測哪一個clients 有數據就緒 */ if ( (sockfd = client[i]) < 0) continue; if (FD_ISSET(sockfd, &rset)) { if ( (n = Read(sockfd, buf, MAXLINE)) == 0) { Close(sockfd); /* 當client關閉連接時,服務器端也關閉對應連接 */ FD_CLR(sockfd, &allset); /* 解除select監控此文件描述符 */ client[i] = -1; } else { int j; for (j = 0; j < n; j++) buf[j] = toupper(buf[j]); Write(sockfd, buf, n); } if (--nready == 0) break; } } } close(listenfd); return 0; }
client
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf)); n = Read(sockfd, buf, MAXLINE); if (n == 0) printf("the other side has been closed.\n"); else Write(STDOUT_FILENO, buf, n); } Close(sockfd); return 0; }
pselect原型以下。此模型應用較少,有須要的同窗可參考select模型自行編寫C/S
#include <sys/select.h> int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask); struct timespec { long tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }; 用sigmask替代當前進程的阻塞信號集,調用返回後還原原有阻塞信號集
poll
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout); struct pollfd { int fd; /* 文件描述符 */ short events; /* 監控的事件 */ short revents; /* 監控事件中知足條件返回的事件 */ }; POLLIN 普通或帶外優先數據可讀,即POLLRDNORM | POLLRDBAND POLLRDNORM 數據可讀 POLLRDBAND 優先級帶數據可讀 POLLPRI 高優先級可讀數據 POLLOUT 普通或帶外數據可寫 POLLWRNORM 數據可寫 POLLWRBAND 優先級帶數據可寫 POLLERR 發生錯誤 POLLHUP 發生掛起 POLLNVAL 描述字不是一個打開的文件 nfds 監控數組中有多少文件描述符須要被監控 timeout 毫秒級等待 -1:阻塞等,#define INFTIM -1 Linux中沒有定義此宏 0:當即返回,不阻塞進程 >0:等待指定毫秒數,如當前系統時間精度不夠毫秒,向上取值
server
/* server.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <poll.h> #include <errno.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 #define OPEN_MAX 1024 int main(int argc, char *argv[]) { int i, j, maxi, listenfd, connfd, sockfd; int nready; ssize_t n; char buf[MAXLINE], str[INET_ADDRSTRLEN]; socklen_t clilen; struct pollfd client[OPEN_MAX]; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); Listen(listenfd, 20); client[0].fd = listenfd; client[0].events = POLLRDNORM; /* listenfd監聽普通讀事件 */ for (i = 1; i < OPEN_MAX; i++) client[i].fd = -1; /* 用-1初始化client[]裏剩下元素 */ maxi = 0; /* client[]數組有效元素中最大元素下標 */ for ( ; ; ) { nready = poll(client, maxi+1, -1); /* 阻塞 */ if (client[0].revents & POLLRDNORM) { /* 有客戶端連接請求 */ clilen = sizeof(cliaddr); connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (i = 1; i < OPEN_MAX; i++) { if (client[i].fd < 0) { client[i].fd = connfd; /* 找到client[]中空閒的位置,存放accept返回的connfd */ break; } } if (i == OPEN_MAX) perr_exit("too many clients"); client[i].events = POLLRDNORM; /* 設置剛剛返回的connfd,監控讀事件 */ if (i > maxi) maxi = i; /* 更新client[]中最大元素下標 */ if (--nready <= 0) continue; /* 沒有更多就緒事件時,繼續回到poll阻塞 */ } for (i = 1; i <= maxi; i++) { /* 檢測client[] */ if ((sockfd = client[i].fd) < 0) continue; if (client[i].revents & (POLLRDNORM | POLLERR)) { if ((n = Read(sockfd, buf, MAXLINE)) < 0) { if (errno == ECONNRESET) { /* 當收到 RST標誌時 */ /* connection reset by client */ printf("client[%d] aborted connection\n", i); Close(sockfd); client[i].fd = -1; } else { perr_exit("read error"); } } else if (n == 0) { /* connection closed by client */ printf("client[%d] closed connection\n", i); Close(sockfd); client[i].fd = -1; } else { for (j = 0; j < n; j++) buf[j] = toupper(buf[j]); Writen(sockfd, buf, n); } if (--nready <= 0) break; /* no more readable descriptors */ } } } return 0; }
client
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf)); n = Read(sockfd, buf, MAXLINE); if (n == 0) printf("the other side has been closed.\n"); else Write(STDOUT_FILENO, buf, n); } Close(sockfd); return 0; }
GNU定義了ppoll(非POSIX標準),能夠支持設置信號屏蔽字,你們可參考poll模型自行實現C/S。
#define _GNU_SOURCE /* See feature_test_macros(7) */ #include <poll.h> int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts, const sigset_t *sigmask);
能夠使用cat命令查看一個進程能夠打開的socket描述符上限。
cat /proc/sys/fs/file-max
若有須要,能夠經過修改配置文件的方式修改該上限值。
sudo vi /etc/security/limits.conf 在文件尾部寫入如下配置,soft軟限制,hard硬限制。以下圖所示。 * soft nofile 65536 * hard nofile 100000
一、建立一個epoll句柄,參數size用來告訴內核監聽的文件描述符的個數,跟內存大小有關。
#include <sys/epoll.h> int epoll_create(int size) size:監聽數目
二、控制某個epoll監控的文件描述符上的事件:註冊、修改、刪除。
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) epfd: 爲epoll_creat的句柄 op: 表示動做,用3個宏來表示: EPOLL_CTL_ADD (註冊新的fd到epfd), EPOLL_CTL_MOD (修改已經註冊的fd的監聽事件), EPOLL_CTL_DEL (從epfd刪除一個fd); event: 告訴內核須要監聽的事件 struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; EPOLLIN : 表示對應的文件描述符能夠讀(包括對端SOCKET正常關閉) EPOLLOUT: 表示對應的文件描述符能夠寫 EPOLLPRI: 表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來) EPOLLERR: 表示對應的文件描述符發生錯誤 EPOLLHUP: 表示對應的文件描述符被掛斷; EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)而言的 EPOLLONESHOT:只監聽一次事件,當監聽完此次事件以後,若是還須要繼續監聽這個socket的話,須要再次把這個socket加入到EPOLL隊列裏
三、等待所監控文件描述符上有事件的產生,相似於select()調用。
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) events: 用來存內核獲得事件的集合, maxevents: 告以內核這個events有多大,這個maxevents的值不能大於建立epoll_create()時的size, timeout: 是超時時間 -1: 阻塞 0: 當即返回,非阻塞 >0: 指定毫秒 返回值: 成功返回有多少文件描述符就緒,時間到時返回0,出錯返回-1
server
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #include <errno.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 #define OPEN_MAX 1024 int main(int argc, char *argv[]) { int i, j, maxi, listenfd, connfd, sockfd; int nready, efd, res; ssize_t n; char buf[MAXLINE], str[INET_ADDRSTRLEN]; socklen_t clilen; int client[OPEN_MAX]; struct sockaddr_in cliaddr, servaddr; struct epoll_event tep, ep[OPEN_MAX]; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); Listen(listenfd, 20); for (i = 0; i < OPEN_MAX; i++) client[i] = -1; maxi = -1; efd = epoll_create(OPEN_MAX); if (efd == -1) perr_exit("epoll_create"); tep.events = EPOLLIN; tep.data.fd = listenfd; res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep); if (res == -1) perr_exit("epoll_ctl"); while (1) { nready = epoll_wait(efd, ep, OPEN_MAX, -1); /* 阻塞監聽 */ if (nready == -1) perr_exit("epoll_wait"); for (i = 0; i < nready; i++) { if (!(ep[i].events & EPOLLIN)) continue; if (ep[i].data.fd == listenfd) { clilen = sizeof(cliaddr); connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (j = 0; j < OPEN_MAX; j++) { if (client[j] < 0) { client[j] = connfd; /* save descriptor */ break; } } if (j == OPEN_MAX) perr_exit("too many clients"); if (j > maxi) maxi = j; /* max index in client[] array */ tep.events = EPOLLIN; tep.data.fd = connfd; res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep); if (res == -1) perr_exit("epoll_ctl"); } else { sockfd = ep[i].data.fd; n = Read(sockfd, buf, MAXLINE); if (n == 0) { for (j = 0; j <= maxi; j++) { if (client[j] == sockfd) { client[j] = -1; break; } } res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); if (res == -1) perr_exit("epoll_ctl"); Close(sockfd); printf("client[%d] closed connection\n", j); } else { for (j = 0; j < n; j++) buf[j] = toupper(buf[j]); Writen(sockfd, buf, n); } } } } close(listenfd); close(efd); return 0; }
client
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf)); n = Read(sockfd, buf, MAXLINE); if (n == 0) printf("the other side has been closed.\n"); else Write(STDOUT_FILENO, buf, n); } Close(sockfd); return 0; }
在這個過程當中,有兩種工做模式:
基於管道epoll ET觸發模式
#include <stdio.h> #include <stdlib.h> #include <sys/epoll.h> #include <errno.h> #include <unistd.h> #define MAXLINE 10 int main(int argc, char *argv[]) { int efd, i; int pfd[2]; pid_t pid; char buf[MAXLINE], ch = 'a'; pipe(pfd); pid = fork(); if (pid == 0) { close(pfd[0]); while (1) { for (i = 0; i < MAXLINE/2; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; for (; i < MAXLINE; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; write(pfd[1], buf, sizeof(buf)); sleep(2); } close(pfd[1]); } else if (pid > 0) { struct epoll_event event; struct epoll_event resevent[10]; int res, len; close(pfd[1]); efd = epoll_create(10); /* event.events = EPOLLIN; */ event.events = EPOLLIN | EPOLLET; /* ET 邊沿觸發 ,默認是水平觸發 */ event.data.fd = pfd[0]; epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event); while (1) { res = epoll_wait(efd, resevent, 10, -1); printf("res %d\n", res); if (resevent[0].data.fd == pfd[0]) { len = read(pfd[0], buf, MAXLINE/2); write(STDOUT_FILENO, buf, len); } } close(pfd[0]); close(efd); } else { perror("fork"); exit(-1); } return 0; }
server
/* server.c */ #include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/epoll.h> #include <unistd.h> #define MAXLINE 10 #define SERV_PORT 8080 int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int listenfd, connfd; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; int i, efd; listenfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(listenfd, 20); struct epoll_event event; struct epoll_event resevent[10]; int res, len; efd = epoll_create(10); event.events = EPOLLIN | EPOLLET; /* ET 邊沿觸發 ,默認是水平觸發 */ printf("Accepting connections ...\n"); cliaddr_len = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); event.data.fd = connfd; epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event); while (1) { res = epoll_wait(efd, resevent, 10, -1); printf("res %d\n", res); if (resevent[0].data.fd == connfd) { len = read(connfd, buf, MAXLINE/2); write(STDOUT_FILENO, buf, len); } } return 0; }
client
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #define MAXLINE 10 #define SERV_PORT 8080 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, i; char ch = 'a'; sockfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (1) { for (i = 0; i < MAXLINE/2; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; for (; i < MAXLINE; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; write(sockfd, buf, sizeof(buf)); sleep(10); } Close(sockfd); return 0; }
server
/* server.c */ #include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/epoll.h> #include <unistd.h> #include <fcntl.h> #define MAXLINE 10 #define SERV_PORT 8080 int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int listenfd, connfd; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; int i, efd, flag; listenfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(listenfd, 20); struct epoll_event event; struct epoll_event resevent[10]; int res, len; efd = epoll_create(10); /* event.events = EPOLLIN; */ event.events = EPOLLIN | EPOLLET; /* ET 邊沿觸發 ,默認是水平觸發 */ printf("Accepting connections ...\n"); cliaddr_len = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); flag = fcntl(connfd, F_GETFL); flag |= O_NONBLOCK; fcntl(connfd, F_SETFL, flag); event.data.fd = connfd; epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event); while (1) { printf("epoll_wait begin\n"); res = epoll_wait(efd, resevent, 10, -1); printf("epoll_wait end res %d\n", res); if (resevent[0].data.fd == connfd) { while ((len = read(connfd, buf, MAXLINE/2)) > 0) write(STDOUT_FILENO, buf, len); } } return 0; }
client
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #define MAXLINE 10 #define SERV_PORT 8080 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, i; char ch = 'a'; sockfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (1) { for (i = 0; i < MAXLINE/2; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; for (; i < MAXLINE; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; write(sockfd, buf, sizeof(buf)); sleep(10); } Close(sockfd); return 0; }
傳輸層主要應用的協議模型有兩種,一種是TCP協議,另一種則是UDP協議。TCP協議在網絡通訊中占主導地位,絕大多數的網絡通訊藉助TCP協議完成數據傳輸。但UDP也是網絡通訊中不可或缺的重要通訊手段。
相較於TCP而言,UDP通訊的形式更像是發短信。不須要在數據傳輸以前創建、維護鏈接。只專心獲取數據就好。省去了三次握手的過程,通訊速度能夠大大提升,但與之伴隨的通訊的穩定性和正確率便得不到保證。所以,咱們稱UDP爲「無鏈接的不可靠報文傳遞」。
那麼與咱們熟知的TCP相比,UDP有哪些優勢和不足呢?因爲無需建立鏈接,因此UDP開銷較小,數據傳輸速度快,實時性較強。多用於對實時性要求較高的通訊場合,如視頻會議、電話會議等。但隨之也伴隨着數據傳輸不可靠,傳輸數據的正確率、傳輸順序和流量都得不到控制和保證。因此,一般狀況下,使用UDP協議進行數據傳輸,爲保證數據的正確性,咱們須要在應用層添加輔助校驗協議來彌補UDP的不足,以達到數據可靠傳輸的目的。
2)藉助setsockopt函數改變接收緩衝區大小。如:
#include <sys/socket.h> int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); int n = 220x1024 setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));
編譯運行server,在兩個終端裏各開一個client與server交互,看看server是否具備併發服務的能力。用Ctrl+C關閉server,而後再運行server,看此時client還可否和server聯繫上。和前面TCP程序的運行結果相比較,體會無鏈接的含義。
server
#include <string.h> #include <netinet/in.h> #include <stdio.h> #include <unistd.h> #include <strings.h> #include <arpa/inet.h> #include <ctype.h> #define MAXLINE 80 #define SERV_PORT 6666 int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int sockfd; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; int i, n; sockfd = socket(AF_INET, SOCK_DGRAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); printf("Accepting connections ...\n"); while (1) { cliaddr_len = sizeof(cliaddr); n = recvfrom(sockfd, buf, MAXLINE,0, (struct sockaddr *)&cliaddr, &cliaddr_len); if (n == -1) perror("recvfrom error"); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (i = 0; i < n; i++) buf[i] = toupper(buf[i]); n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr)); if (n == -1) perror("sendto error"); } close(sockfd); return 0; }
client
#include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <strings.h> #include <ctype.h> #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; int sockfd, n; char buf[MAXLINE]; sockfd = socket(AF_INET, SOCK_DGRAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); while (fgets(buf, MAXLINE, stdin) != NULL) { n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); if (n == -1) perror("sendto error"); n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0); if (n == -1) perror("recvfrom error"); write(STDOUT_FILENO, buf, n); } close(sockfd); return 0; }
組播組能夠是永久的也能夠是臨時的。組播組地址中,有一部分由官方分配的,稱爲永久組播組。永久組播組保持不變的是它的ip地址,組中的成員構成能夠發生變化。永久組播組中成員的數量均可以是任意的,甚至能夠爲零。那些沒有保留下來供永久組播組使用的ip組播地址,能夠被臨時組播組利用。
224.0.0.0~224.0.0.255 爲預留的組播地址(永久組地址),地址224.0.0.0保留不作分配,其它地址供路由協議使用; 224.0.1.0~224.0.1.255 是公用組播地址,能夠用於Internet;欲使用需申請。 224.0.2.0~238.255.255.255 爲用戶可用的組播地址(臨時組地址),全網範圍內有效; 239.0.0.0~239.255.255.255 爲本地管理組播地址,僅在特定的本地範圍內有效。
可以使用ip ad命令查看網卡編號,如:
itcast$ ip ad 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000 link/ether 00:0c:29:0a:c4:f4 brd ff:ff:ff:ff:ff:ff inet6 fe80::20c:29ff:fe0a:c4f4/64 scope link valid_lft forever preferred_lft forever
server
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <net/if.h> #define SERVER_PORT 6666 #define CLIENT_PORT 9000 #define MAXLINE 1500 #define GROUP "239.0.0.2" int main(void) { int sockfd, i ; struct sockaddr_in serveraddr, clientaddr; char buf[MAXLINE] = "itcast\n"; char ipstr[INET_ADDRSTRLEN]; /* 16 Bytes */ socklen_t clientlen; ssize_t len; struct ip_mreqn group; /* 構造用於UDP通訊的套接字 */ sockfd = socket(AF_INET, SOCK_DGRAM, 0); bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; /* IPv4 */ serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); /* 本地任意IP INADDR_ANY = 0 */ serveraddr.sin_port = htons(SERVER_PORT); bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); /*設置組地址*/ inet_pton(AF_INET, GROUP, &group.imr_multiaddr); /*本地任意IP*/ inet_pton(AF_INET, "0.0.0.0", &group.imr_address); /* eth0 --> 編號 命令:ip ad */ group.imr_ifindex = if_nametoindex("eth0"); setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &group, sizeof(group)); /*構造 client 地址 IP+端口 */ bzero(&clientaddr, sizeof(clientaddr)); clientaddr.sin_family = AF_INET; /* IPv4 */ inet_pton(AF_INET, GROUP, &clientaddr.sin_addr.s_addr); clientaddr.sin_port = htons(CLIENT_PORT); while (1) { //fgets(buf, sizeof(buf), stdin); sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr)); sleep(1); } close(sockfd); return 0; }
client
#include <netinet/in.h> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #include <stdlib.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <net/if.h> #define SERVER_PORT 6666 #define MAXLINE 4096 #define CLIENT_PORT 9000 #define GROUP "239.0.0.2" int main(int argc, char *argv[]) { struct sockaddr_in serveraddr, localaddr; int confd; ssize_t len; char buf[MAXLINE]; /* 定義組播結構體 */ struct ip_mreqn group; confd = socket(AF_INET, SOCK_DGRAM, 0); //初始化本地端地址 bzero(&localaddr, sizeof(localaddr)); localaddr.sin_family = AF_INET; inet_pton(AF_INET, "0.0.0.0" , &localaddr.sin_addr.s_addr); localaddr.sin_port = htons(CLIENT_PORT); bind(confd, (struct sockaddr *)&localaddr, sizeof(localaddr)); /*設置組地址*/ inet_pton(AF_INET, GROUP, &group.imr_multiaddr); /*本地任意IP*/ inet_pton(AF_INET, "0.0.0.0", &group.imr_address); /* eth0 --> 編號 命令:ip ad */ group.imr_ifindex = if_nametoindex("eth0"); /*設置client 加入多播組 */ setsockopt(confd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)); while (1) { len = recvfrom(confd, buf, sizeof(buf), 0, NULL, 0); write(STDOUT_FILENO, buf, len); } close(confd); return 0; }
socket API本來是爲網絡通信設計的,但後來在socket的框架上發展出一種IPC機制,就是UNIX Domain Socket。雖然網絡socket也可用於同一臺主機的進程間通信(經過loopback地址127.0.0.1),可是UNIX Domain Socket用於IPC更有效率:不須要通過網絡協議棧,不須要打包拆包、計算校驗和、維護序號和應答等,只是將應用層數據從一個進程拷貝到另外一個進程。這是由於,IPC機制本質上是可靠的通信,而網絡協議是爲不可靠的通信設計的。UNIX Domain Socket也提供面向流和麪向數據包兩種API接口,相似於TCP和UDP,可是面向消息的UNIX Domain Socket也是可靠的,消息既不會丟失也不會順序錯亂。
UNIX Domain Socket是全雙工的,API接口語義豐富,相比其它IPC機制有明顯的優越性,目前已成爲使用最普遍的IPC機制,好比X Window服務器和GUI程序之間就是經過UNIXDomain Socket通信的。
使用UNIX Domain Socket的過程和網絡socket十分類似,也要先調用socket()建立一個socket文件描述符,address family指定爲AF_UNIX,type能夠選擇SOCK_DGRAM或SOCK_STREAM,protocol參數仍然指定爲0便可。
UNIX Domain Socket與網絡socket編程最明顯的不一樣在於地址格式不一樣,用結構體sockaddr_un表示,網絡編程的socket地址是IP地址加端口號,而UNIX Domain Socket的地址是一個socket類型的文件在文件系統中的路徑,這個socket文件由bind()調用建立,若是調用bind()時該文件已存在,則bind()錯誤返回。
對比網絡套接字地址結構和本地套接字地址結構:
struct sockaddr_in { __kernel_sa_family_t sin_family; /* Address family */ 地址結構類型 __be16 sin_port; /* Port number */ 端口號 struct in_addr sin_addr; /* Internet address */ IP地址 }; struct sockaddr_un { __kernel_sa_family_t sun_family; /* AF_UNIX */ 地址結構類型 char sun_path[UNIX_PATH_MAX]; /* pathname */ socket文件名(含路徑) };
如下程序將UNIX Domain socket綁定到一個地址。
size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); #define offsetof(type, member) ((int)&((type *)0)->MEMBER)
server
#include <stdlib.h> #include <stdio.h> #include <stddef.h> #include <sys/socket.h> #include <sys/un.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #define QLEN 10 /* * Create a server endpoint of a connection. * Returns fd if all OK, <0 on error. */ int serv_listen(const char *name) { int fd, len, err, rval; struct sockaddr_un un; /* create a UNIX domain stream socket */ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) return(-1); /* in case it already exists */ unlink(name); /* fill in socket address structure */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, name); len = offsetof(struct sockaddr_un, sun_path) + strlen(name); /* bind the name to the descriptor */ if (bind(fd, (struct sockaddr *)&un, len) < 0) { rval = -2; goto errout; } if (listen(fd, QLEN) < 0) { /* tell kernel we're a server */ rval = -3; goto errout; } return(fd); errout: err = errno; close(fd); errno = err; return(rval); } int serv_accept(int listenfd, uid_t *uidptr) { int clifd, len, err, rval; time_t staletime; struct sockaddr_un un; struct stat statbuf; len = sizeof(un); if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) return(-1); /* often errno=EINTR, if signal caught */ /* obtain the client's uid from its calling address */ len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */ un.sun_path[len] = 0; /* null terminate */ if (stat(un.sun_path, &statbuf) < 0) { rval = -2; goto errout; } if (S_ISSOCK(statbuf.st_mode) == 0) { rval = -3; /* not a socket */ goto errout; } if (uidptr != NULL) *uidptr = statbuf.st_uid; /* return uid of caller */ /* we're done with pathname now */ unlink(un.sun_path); return(clifd); errout: err = errno; close(clifd); errno = err; return(rval); } int main(void) { int lfd, cfd, n, i; uid_t cuid; char buf[1024]; lfd = serv_listen("foo.socket"); if (lfd < 0) { switch (lfd) { case -3:perror("listen"); break; case -2:perror("bind"); break; case -1:perror("socket"); break; } exit(-1); } cfd = serv_accept(lfd, &cuid); if (cfd < 0) { switch (cfd) { case -3:perror("not a socket"); break; case -2:perror("a bad filename"); break; case -1:perror("accept"); break; } exit(-1); } while (1) { r_again: n = read(cfd, buf, 1024); if (n == -1) { if (errno == EINTR) goto r_again; } else if (n == 0) { printf("the other side has been closed.\n"); break; } for (i = 0; i < n; i++) buf[i] = toupper(buf[i]); write(cfd, buf, n); } close(cfd); close(lfd); return 0; }
client
#include <stdio.h> #include <stdlib.h> #include <stddef.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #define CLI_PATH "/var/tmp/" /* +5 for pid = 14 chars */ /* * Create a client endpoint and connect to a server. * Returns fd if all OK, <0 on error. */ int cli_conn(const char *name) { int fd, len, err, rval; struct sockaddr_un un; /* create a UNIX domain stream socket */ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) return(-1); /* fill socket address structure with our address */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid()); len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); /* in case it already exists */ unlink(un.sun_path); if (bind(fd, (struct sockaddr *)&un, len) < 0) { rval = -2; goto errout; } /* fill socket address structure with server's address */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, name); len = offsetof(struct sockaddr_un, sun_path) + strlen(name); if (connect(fd, (struct sockaddr *)&un, len) < 0) { rval = -4; goto errout; } return(fd); errout: err = errno; close(fd); errno = err; return(rval); } int main(void) { int fd, n; char buf[1024]; fd = cli_conn("foo.socket"); if (fd < 0) { switch (fd) { case -4:perror("connect"); break; case -3:perror("listen"); break; case -2:perror("bind"); break; case -1:perror("socket"); break; } exit(-1); } while (fgets(buf, sizeof(buf), stdin) != NULL) { write(fd, buf, strlen(buf)); n = read(fd, buf, sizeof(buf)); write(STDOUT_FILENO, buf, n); } close(fd); return 0; }
過期,僅用於IPv4,且線程不安全。
#include <stdio.h> #include <netdb.h> #include <arpa/inet.h> extern int h_errno; int main(int argc, char *argv[]) { struct hostent *host; char str[128]; host = gethostbyname(argv[1]); printf("%s\n", host->h_name); while (*(host->h_aliases) != NULL) printf("%s\n", *host->h_aliases++); switch (host->h_addrtype) { case AF_INET: while (*(host->h_addr_list) != NULL) printf("%s\n", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str))); break; default: printf("unknown address type\n"); break; } return 0; }
此函數只能獲取域名解析服務器的url和/etc/hosts裏登記的IP對應的域名。
#include <stdio.h> #include <netdb.h> #include <arpa/inet.h> extern int h_errno; int main(int argc, char *argv[]) { struct hostent *host; char str[128]; struct in_addr addr; inet_pton(AF_INET, argv[1], &addr); host = gethostbyaddr((char *)&addr, 4, AF_INET); printf("%s\n", host->h_name); while (*(host->h_aliases) != NULL) printf("%s\n", *host->h_aliases++); switch (host->h_addrtype) { case AF_INET: while (*(host->h_addr_list) != NULL) printf("%s\n", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str))); break; default: printf("unknown address type\n"); break; } return 0; }