raw_socket(原始套接字)以及普通socket使用終極總結


 

一、傳輸層socket(四層socket,普通socket)

可參考本人以下博客:

Windows Socket編程之UDP實現大文件的傳輸:http://blog.csdn.net/luchengtao11/article/details/71016222

Windows Socket編程之TCP實現大文件的傳輸:http://blog.csdn.net/luchengtao11/article/details/71012580

(1)創建

 

  1.  
    socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //TCP
  2.  
    //或者
  3.  
    socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); //UDP

AF_INEF表示TCP/IP族

 

第三個參數可以為0,由操作系統自行選擇

(2)發送

 

  sendto(sd,buffer,BUFSIZ,0,(SOCKADDR*)&addrServ,sizeof(SOCKADDR));//UDP
 
  send(sd, buffer, BUFSIZ, 0);  //TCP
 

 

(3)接收

recvfrom(sd,buffer,BUFSIZ,0,(SOCKADDR*)&addrClient,sizeof(SOCKADDR));//UDP
 
recv(sd, buffer, BUFSIZ, 0);//TCP
 

 

 

sd為socket標識符,buffer為接受/發送緩沖區,BUFSIZ為接受/發送緩沖區。后兩個參數為發送或接受的對方地址,可以為NULL

二、網絡層socket(三層socket)

可以參考本人一下博客或代碼:

Linux下的raw Socket(原始套接字)編程:http://blog.csdn.net/luchengtao11/article/details/73878760

網絡層多線程收發:https://github.com/Wuchenwcf/MyCode/blob/master/C%2B%2B/Linux/computer%20network/raw_socket_%E7%BD%91%E7%BB%9C%E5%B1%82%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%94%B6%E5%8F%91.cpp

(1)創建

 

socket(AF_INET, SOCK_RAW, IPPROTO_UDP );//第三個參數可以是UDP,TCP或者ICMP
 

(2)接收

 

recvfrom(sd, buffer, sizeof(buffer), 0,(struct sockaddr *)&client_addr, &addrlen));// 
 

 

后兩個參數可以為null

接受的報文是從IP數據報的第一個字節開始的。

當內核有一個需要傳遞到原始套接字的IP數據報時,它將檢查所有進程上的原始套接字,以尋找所有匹配的套接字。每個匹配懂得套接字將被遞送以該iP數據報的一個副本。(事實證明,如果進程過多,匹配的套接字過多,內核會忙於數據報的軟過濾和分發,而實際的套接字卻空閑,導致性能下降
內核對每個原始套接字均執行如下3個測試,只有這三個測試為真,內核才把接收到的數據報遞送到這個套接字。

【1】如果創建這個套接字時制訂了非0的協議參數(socket的第三個參數),那么接受到的數據報的協議字段必須匹配該值,否則數據報不遞送到這個套接字

【2】如果這個原始套接字已由bind調用綁定了某個本地IP地址,那么接受到的數據報的目的IP地址必須匹配這個綁定的地址,否則該數據報不遞送到這個套接字。

【3】如果這個原始套接字已由connect調用指定了某個外地IP地址,那么接受到的數據報的源IP地址必須匹配這個已連接地址,否則該數據報不遞送到這個套接字。

注意,如果一個原始套接字以0值協議參數創建的,而且沒有bind和connect,那么該套接字將接收可由內核傳遞到原始套接字的每個原始數據報的一個副本。

(3)發送

 

  1.  
    /*
  2.  
    如果IP_HDRINCL未開啟,由進程讓內核發送的數據是從IP首部之后的第一個字節開始的,內核會自動構造合適的IP
  3.  
    如果IP_HDRINGL開啟,進程需要自行構造IP包
  4.  
    */
  5.  
    /*
  6.  
    if (setsockopt(sd, IPPROTO_IP, IP_HDRINCL, val, sizeof(int)))
  7.  
    {
  8.  
    perror("setsockopt() error");
  9.  
    exit(-1);
  10.  
    }
  11.  
    */
  12.  
    sendto(sd, buffer, request_length, 0, (sockaddr *)&client_addr, addrlen);

 

 

三、數據鏈路層scoket(二層socket)

代碼可以參考:

raw_socket_數據鏈路層收發實例:https://github.com/Wuchenwcf/MyCode/blob/master/C%2B%2B/Linux/computer%20network/raw_socket_%E6%95%B0%E6%8D%AE%E9%93%BE%E8%B7%AF%E5%B1%82%E6%94%B6%E5%8F%91%E5%AE%9E%E4%BE%8B.cpp
 

(1)創建

 

 

sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));//第三個參數可以為ETH_P_ALL ETH_P_IP ETH_P_ARP等
 

 

(2)接受

 

 

  1.  
    struct sockaddr_ll client;
  2.  
    socklen_t addr_length = sizeof(sockaddr_ll);
  3.  
    recvfrom(sock, buffer, 2048, 0, (sockaddr *)&client, &addr_length); //此時的地址是數據鏈路層的地址


接受的報文是從以太網的幀開始的

 

(3)發送

 

sendto(sock, sendbuffer, n, 0, (struct sockaddr *) &client, sizeof(client));
 

 

四、小結

 

引用:http://blog.csdn.net/firefoxbug/article/details/7561159

     網卡對該數據幀進行硬過濾(根據網卡的模式不同會有不同的動作,如果設置了promisc混雜模式的話,則不做任何過濾直接交給下一層輸入例程,否則非本機mac或者廣播mac會被直接丟棄).按照上面的例子,如果成功的話,會進入ip輸入例程.但是在進入ip輸入例程之前,系統會檢查系統中是否有通過socket(AF_PACKET, SOCK_RAW, ..)創建的套接字.如果有的話並且協議相符,在這個例子中就是需要ETH_P_IP或者ETH_P_ALL類型.系統就給每個這樣的socket接收緩沖區發送一個數據幀拷貝.然后進入下一步.

  其次,進入了ip輸入例程(ip層會對該數據包進行軟過濾,就是檢查校驗或者丟棄非本機ip或者廣播ip的數據包等,具體要參考源代碼),例子中就是如果成功的話會進入udp輸入例程.但是在交給udp輸入例程之前,系統會檢查系統中是否有通過socket(AF_INET, SOCK_RAW, ..)創建的套接字.如果有的話並且協議相符,在這個例子中就是需要IPPROTO_UDP類型.系統就給每個這樣的socket接收緩沖區發送一個數據幀拷貝.然后進入下一步。

最后,進入udp輸入例程 ...

ps:如果校驗和出錯的話,內核會直接丟棄該數據包的.而不會拷貝給sock_raw的套接字,因為校驗和都出錯了,數據肯定有問題的包括所有信息都沒有意義了.

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM