歡迎您光臨本站 註冊首頁

學習Linux網路編程(3)

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  6. 高級套接字函數
在前面的幾個部分裡面,我們已經學會了怎麼樣從網路上讀寫信息了.前面的一些函數(read,write)是網路程序裡面最基本的函數.也是最原始的通信函數.在這一章裡面,我們一起來學習網路通信的高級函數.這一章我們學習另外幾個讀寫函數.

6.1 recv和send
recv和send函數提供了和read和write差不多的功能.不過它們提供 了第四個參數來控制讀寫操作.

int recv(int sockfd,void *buf,int len,int flags)
int send(int sockfd,void *buf,int len,int flags)

前面的三個參數和read,write一樣,第四個參數可以是0或者是以下的組合
_______________________________________________________________
| MSG_DONTROUTE | 不查找路由表 |
| MSG_OOB | 接受或者發送帶外數據 |
| MSG_PEEK | 查看數據,並不從系統緩衝區移走數據 |
| MSG_WAITALL | 等待所有數據 |
|--------------------------------------------------------------|

MSG_DONTROUTE:是send函數使用的標誌.這個標誌告訴IP協議.目的主機在本地網路上面,沒有必要查找路由表.這個標誌一般用網路診斷和路由程序裡面.
MSG_OOB:表示可以接收和發送帶外的數據.關於帶外數據我們以後會解釋的.

MSG_PEEK:是recv函數的使用標誌,表示只是從系統緩衝區中讀取內容,而不清楚系統緩衝區的內容.這樣下次讀的時候,仍然是一樣的內容.一般在有多個進程讀寫數據時可以使用這個標誌.

MSG_WAITALL是recv函數的使用標誌,表示等到所有的信息到達時才返回.使用這個標誌的時候recv回一直阻塞,直到指定的條件滿足,或者是發生了錯誤. 1)當讀到了指定的位元組時,函數正常返回.返回值等於len 2)當讀到了文件的結尾時,函數正常返回.返回值小於len 3)當操作發生錯誤時,返回-1,且設置錯誤為相應的錯誤號(errno)

如果flags為0,則和read,write一樣的操作.還有其它的幾個選項,不過我們實際上用的很少,可以查看 Linux Programmers Manual得到詳細解釋.

6.2 recvfrom和sendto
這兩個函數一般用在非套接字的網路程序當中(UDP),我們已經在前面學會了.

6.3 recvmsg和sendmsg
recvmsg和sendmsg可以實現前面所有的讀寫函數的功能.

int recvmsg(int sockfd,struct msghdr *msg,int flags)
int sendmsg(int sockfd,struct msghdr *msg,int flags)

struct msghdr
{
void *msg_name;
int msg_namelen;
struct iovec *msg_iov;
int msg_iovlen;
void *msg_control;
int msg_controllen;
int msg_flags;
}

struct iovec
{
void *iov_base; /* 緩衝區開始的地址 */
size_t iov_len; /* 緩衝區的長度 */
}

msg_name和 msg_namelen當套接字是非面向連接時(UDP),它們存儲接收和發送方的地址信息.msg_name實際上是一個指向struct sockaddr的指針,msg_name是結構的長度.當套接字是面向連接時,這兩個值應設為NULL. msg_iov和msg_iovlen指出接受和發送的緩衝區內容.msg_iov是一個結構指針,msg_iovlen指出這個結構數組的大小. msg_control和msg_controllen這兩個變數是用來接收和發送控制數據時的 msg_flags指定接受和發送的操作選項.和recv,send的選項一樣
6.4 套接字的關閉
關閉套接字有兩個函數close和shutdown.用close時和我們關閉文件一樣.

6.5 shutdown

int shutdown(int sockfd,int howto)

TCP連接是雙向的(是可讀寫的),當我們使用close時,會把讀寫通道都關閉,有時侯我們希望只關閉一個方向,這個時候我們可以使用shutdown.針對不同的howto,系統回採取不同的關閉方式.
howto=0這個時候系統會關閉讀通道.但是可以繼續往接字描述符寫.

howto=1關閉寫通道,和上面相反,著時候就只可以讀了.

howto=2關閉讀寫通道,和close一樣 在多進程程序裡面,如果有幾個子進程共享一個套接字時,如果我們使用shutdown, 那麼所有的子進程都不能夠操作了,這個時候我們只能夠使用close來關閉子進程的套接字描述符.

7. TCP/IP協議
你也許聽說過TCP/IP協議,那麼你知道到底什麼是TCP,什麼是IP嗎?在這一章裡面,我們一起來學習這個目前網路上用最廣泛的協議.


7.1 網路傳輸分層
如果你考過計算機等級考試,那麼你就應該已經知道了網路傳輸分層這個概念.在網路上,人們為了傳輸數據時的方便,把網路的傳輸分為7個層次.分別是:應用層,表示層,會話層,傳輸層,網路層,數據鏈路層和物理層.分好了層以後,傳輸數據時,上一層如果要數據的話,就可以直接向下一層要了,而不必要管數據傳輸的細節.下一層也只向它的上一層提供數據,而不要去管其它東西了.如果你不想考試,你沒有必要去記這些東西的.只要知道是分層的,而且各層的作用不同.

7.2 IP協議
IP協議是在網路層的協議.它主要完成數據包的發送作用. 下面這個表是IP4的數據包格式

0 4 8 16 32
--------------------------------------------------
|版本 |首部長度|服務類型| 數據包總長 |
--------------------------------------------------
| 標識 |DF |MF| 碎片偏移 |
--------------------------------------------------
| 生存時間 | 協議 | 首部較驗和 |
------------------------------------------------
| 源IP地址 |
------------------------------------------------
| 目的IP地址 |
-------------------------------------------------
| 選項 |
=================================================
| 數據 |
-------------------------------------------------

下面我們看一看IP的結構定義

struct ip
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ip_hl:4; /* header length */
unsigned int ip_v:4; /* version */
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
unsigned int ip_v:4; /* version */
unsigned int ip_hl:4; /* header length */
#endif
u_int8_t ip_tos; /* type of service */
u_short ip_len; /* total length */
u_short ip_id; /* identification */
u_short ip_off; /* fragment offset field */
#define IP_RF 0x8000 /* reserved fragment flag */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
u_int8_t ip_ttl; /* time to live */
u_int8_t ip_p; /* protocol */
u_short ip_sum; /* checksum */
struct in_addr ip_src, ip_dst; /* source and dest address */
};

ip_vIP協議的版本號,這裡是4,現在IPV6已經出來了

ip_hlIP包首部長度,這個值以4位元組為單位.IP協議首部的固定長度為20個位元組,如果IP包沒有選項,那麼這個值為5.

ip_tos服務類型,說明提供的優先權.

ip_len說明IP數據的長度.以位元組為單位.

ip_id標識這個IP數據包.

ip_off碎片偏移,這和上面ID一起用來重組碎片的.

ip_ttl生存時間.沒經過一個路由的時候減一,直到為0時被拋棄.

ip_p協議,表示創建這個IP數據包的高層協議.如TCP,UDP協議.

ip_sum首部校驗和,提供對首部數據的校驗.

ip_src,ip_dst發送者和接收者的IP地址

關於IP協議的詳細情況,請參考 RFC791

7.3 ICMP協議
ICMP是消息控制協議,也處於網路層.在網路上傳遞IP數據包時,如果發生了錯誤,那麼就會用ICMP協議來報告錯誤.

ICMP包的結構如下:

0 8 16 32
---------------------------------------------------------------------
| 類型 | 代碼 | 校驗和 |
--------------------------------------------------------------------
| 數據 | 數據 |
--------------------------------------------------------------------

ICMP在中的定義是
struct icmphdr
{
u_int8_t type; /* message type */
u_int8_t code; /* type sub-code */
u_int16_t checksum;
union
{
struct
{
u_int16_t id;
u_int16_t sequence;
} echo; /* echo datagram */
u_int32_t gateway; /* gateway address */
struct
{
u_int16_t __unused;
u_int16_t mtu;
} frag; /* path mtu discovery */
} un;
};

關於ICMP協議的詳細情況可以查看 RFC792

7.4 UDP協議
UDP協議是建立在IP協議基礎之上的,用在傳輸層的協議.UDP和IP協議一樣是不可靠的數據報服務.UDP的頭格式為:


0 16 32
---------------------------------------------------
| UDP源埠 | UDP目的埠 |
---------------------------------------------------
| UDP數據報長度 | UDP數據報校驗 |
---------------------------------------------------

UDP結構在中的定義為:
struct udphdr {
u_int16_t source;
u_int16_t dest;
u_int16_t len;
u_int16_t check;
};

關於UDP協議的詳細情況,請參考 RFC768
7.5 TCP
TCP協議也是建立在IP協議之上的,不過TCP協議是可靠的.按照順序發送的.TCP的數據結構比前面的結構都要複雜.

0 4 8 10 16 24 32
-------------------------------------------------------------------
| 源埠 | 目的埠 |
-------------------------------------------------------------------
| 序列號 |
------------------------------------------------------------------
| 確認號 |
------------------------------------------------------------------
| | |U|A|P|S|F| |
|首部長度| 保留 |R|C|S|Y|I| 窗口 |
| | |G|K|H|N|N| |
-----------------------------------------------------------------
| 校驗和 | 緊急指針 |
-----------------------------------------------------------------
| 選項 | 填充位元組 |
-----------------------------------------------------------------

TCP的結構在中定義為:
struct tcphdr
{
u_int16_t source;
u_int16_t dest;
u_int32_t seq;
u_int32_t ack_seq;
#if __BYTE_ORDER == __LITTLE_ENDIAN
u_int16_t res1:4;
u_int16_t doff:4;
u_int16_t fin:1;
u_int16_t syn:1;
u_int16_t rst:1;
u_int16_t psh:1;
u_int16_t ack:1;
u_int16_t urg:1;
u_int16_t res2:2;
#elif __BYTE_ORDER == __BIG_ENDIAN
u_int16_t doff:4;
u_int16_t res1:4;
u_int16_t res2:2;
u_int16_t urg:1;
u_int16_t ack:1;
u_int16_t psh:1;
u_int16_t rst:1;
u_int16_t syn:1;
u_int16_t fin:1;
#endif
u_int16_t window;
u_int16_t check;
u_int16_t urg_prt;
};

source發送TCP數據的源埠
dest接受TCP數據的目的埠

seq標識該TCP所包含的數據位元組的開始序列號

ack_seq確認序列號,表示接受方下一次接受的數據序列號.

doff數據首部長度.和IP協議一樣,以4位元組為單位.一般的時候為5

urg如果設置緊急數據指針,則該位為1

ack如果確認號正確,那麼為1

psh如果設置為1,那麼接收方收到數據后,立即交給上一層程序

rst為1的時候,表示請求重新連接

syn為1的時候,表示請求建立連接

fin為1的時候,表示親戚關閉連接

window窗口,告訴接收者可以接收的大小

check對TCP數據進行較核

urg_ptr如果urg=1,那麼指出緊急數據對於歷史數據開始的序列號的偏移值

關於TCP協議的詳細情況,請查看 RFC793


7.6 TCP連接的建立
TCP協議是一種可靠的連接,為了保證連接的可靠性,TCP的連接要分為幾個步驟.我們把這個連接過程稱為"三次握手".

下面我們從一個實例來分析建立連接的過程.

第一步客戶機向伺服器發送一個TCP數據包,表示請求建立連接. 為此,客戶端將數據包的SYN位設置為1,並且設置序列號seq=1000(我們假設為1000).

第二步伺服器收到了數據包,並從SYN位為1知道這是一個建立請求的連接.於是伺服器也向客戶端發送一個TCP數據包.因為是響應客戶機的請求,於是伺服器設置ACK為1,sak_seq=1001(1000+1)同時設置自己的序列號.seq=2000(我們假設為2000).

第三步客戶機收到了伺服器的TCP,並從ACK為1和ack_seq=1001知道是從伺服器來的確認信息.於是客戶機也向伺服器發送確認信息.客戶機設置ACK=1,和ack_seq=2001,seq=1001,發送給伺服器.至此客戶端完成連接.

最後一步伺服器受到確認信息,也完成連接.

通過上面幾個步驟,一個TCP連接就建立了.當然在建立過程中可能出現錯誤,不過TCP協議可以保證自己去處理錯誤的.


說一說其中的一種錯誤.
聽說過DOS嗎?(可不是操作系統啊).今年春節的時候,美國的五大網站一起受到攻擊.攻擊者用的就是DOS(拒絕式服務)方式.概括的說一下原理.
客戶機先進行第一個步驟.伺服器收到后,進行第二個步驟.按照正常的TCP連接,客戶機應該進行第三個步驟.
不過攻擊者實際上並不進行第三個步驟.因為客戶端在進行第一個步驟的時候,修改了自己的IP地址,就是說將一個實際上不存在的IP填充在自己IP數據包的發送者的IP一欄.這樣因為伺服器發的IP地址沒有人接收,所以服務端會收不到第三個步驟的確認信號,這樣服務務端會在那邊一直等待,直到超時.
這樣當有大量的客戶發出請求后,服務端會有大量等待,直到所有的資源被用光,而不能再接收客戶機的請求.
這樣當正常的用戶向伺服器發出請求時,由於沒有了資源而不能成功.於是就出現了春節時所出現的情況.


[火星人 ] 學習Linux網路編程(3)已經有543次圍觀

http://coctec.com/docs/program/show-post-72413.html