1. 程式人生 > >raw_socket(原始套接字)以及普通socket使用終極總結

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

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

可參考本人以下部落格:

(1)建立

socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//TCP
//或者
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)

可以參考本人一下部落格或程式碼:

(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)傳送

/*
	如果IP_HDRINCL未開啟,由程序讓核心傳送的資料是從IP首部之後的第一個位元組開始的,核心會自動構造合適的IP
	如果IP_HDRINGL開啟,程序需要自行構造IP包
	*/
	/*
	if (setsockopt(sd, IPPROTO_IP, IP_HDRINCL, val, sizeof(int)))
	{
		perror("setsockopt() error");
		exit(-1);
	}
	*/
sendto(sd, buffer, request_length, 0, (sockaddr *)&client_addr, addrlen);

三、資料鏈路層scoket(二層socket)

程式碼可以參考:

(1)建立

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

(2)接受

struct sockaddr_ll client;
	socklen_t addr_length = sizeof(sockaddr_ll);
recvfrom(sock, buffer, 2048, 0, (sockaddr *)&client, &addr_length);//此時的地址是資料鏈路層的地址


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

(3)傳送

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

四、小結

     網絡卡對該資料幀進行硬過濾(根據網絡卡的模式不同會有不同的動作,如果設定了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的套接字,因為校驗和都出錯了,資料肯定有問題的包括所有資訊都沒有意義了.