linux網路編程

來源:互聯網
上載者:User

標籤:style   color   os   io   使用   ar   資料   sp   代碼   

資料存放區優先順序的轉換

電腦資料存放區有兩種位元組優先順序:高位位元組優先(稱為大端模式)和低位位元組優先(稱為小端模式)。記憶體的低地址儲存資料的低位元組,高地址儲存資料的高位元組的方式叫小端模式。記憶體的高地址儲存資料的低位元組,低地址儲存資料高位元組的方式稱為大端模式。

eg:對於記憶體中存放的數0x12345678來說(注意,對於資料而言,此處12是高位元組,78是低位元組;對於地址而言,左邊是低地址,右邊是高地址)

如果是採用大端模式存放的,則其真實的數是:0x12345678

如果是採用小端模式存放的,則其真實的數是:0x78563412

綜上:小端模式,先存低位元組;大端模式先存高位元組。

如果稱某個系統所採用的位元組序為主機位元組序,則它可能是小端模式的,也可能是大端模式的。通常我們的電腦存放資料,都是以小端模式存放。而連接埠號碼和IP地址都是以網路位元組序儲存的,不是主機位元組序,網路位元組序都是大端模式。要把主機位元組序和網路位元組序相互對應起來,需要對這兩個位元組儲存優先順序進行相互轉化。

Pra1原題

實現將字串形式存放的ip地址(點分十進位數)轉換為主機位元組序的32位位元值。IP為:“180.97.33.107”。十六進位為b4.61.21.6b

思路

一般我們的主機位元組序均為小端位元組序,也就是選存放資料的低位元組。

1. 將字串中的每個數取出來

2. 分別左移(注意,左移時一定會補0)

         180 : 00 00 00 b4    左移0  位   00 00 00 b4
         97   : 00 00 00 61    左移8  位   00 00 61 00
         33   : 00 00 00 21    左移16位   00 21 00 00
         107 : 00 00 00 6b    左移24位   6b 00 00 00

3. 或操作

 

#include <stdio.h>#include <stdlib.h>#include <string.h>#define IP "180.97.33.107"/* 將ip地址轉換為主機位元組序的位元值,輸出用16進位表示 */static int my_atoh(char *ip){    int arr[4]; /* 用於存放從ip地址中扣出的4個整數 */    int my_ip;    sscanf(IP,"%d.%d.%d.%d",arr,arr+1,arr+2,arr+3);    my_ip = (arr[3] << 24) | (arr[2] << 16) | (arr[1] << 8) | arr[0];    return my_ip;}int main(int argc, char *argv[]){    int my_host = my_atoh(IP);    printf("ip  : %s \n",IP);    printf("host: %x \n",my_host);    return 0;}


Pra2原題

實現將主機位元組序(小端模式存放)轉換為網路位元組序(大端模式存放)。即主機位元組序為6b2161b4 –> b461216b

思路1

將6b與b4交換,21與61交換。

#include <stdio.h>int my_hton(int ip){    /* &ip指向用32個bit表示的整型數ip,將int*類型的指標轉換為char*類型的指標,     * 則ptr指向用8個bit表示(第0-7位)的整型數,     * ptr+1指向用8個bit表示(第8-15位)的整型數,     * ptr+2指向用8個bit表示(第16-23位)的整型數,     * ptr+3指向用8個bit表示(第24-31位)的整型數。*/    
    char *ptr = (char*)&ip;        char tmp;    tmp = ptr[0];    ptr[0] = ptr[3];    ptr[3] = tmp;    tmp = ptr[1];    ptr[1] = ptr[2];    ptr[2] = tmp;    return ip;}int main(int argc, char *argv[]){    int my_host = 0x6b2161b4;    int my_net = my_hton(my_host);    printf("my_host: %x \n",my_host);    printf("my_net : %x \n",my_net);    return 0;}

實際上,ip地址為點分十進位,每個位元組的表示範圍都是0-255。而我們的char類型通常預設為有符號數,也就是說其表示範圍是-128-127。那麼我在代碼中標記為黃色的部分是不是出錯了呢?實際上並沒有錯,因為在此處我們只是關心ip每一位的儲存情況,並不要求用到具體每個位元組的實際十進位表示數值。在用printf格式化輸出時,是根據資料在記憶體中的二進位形式來格式化的,而16進位形式是沒有負數的。

思路2

1. 對於0x6b2161b4,先右移並進行與操作,取出各位元組

例如:要取出6b,則0x6b2161b4右移24位,之後與0xff進行與操作即可

注意:千萬不能先進行與操作,再右移。如,要取出6b,先使0x6b2161b4與0xff000000進行與操作,之後右移24位。這是錯誤的,因為右移會補符號位。(tips:左移補0,並捨棄符號位)

2. 分別再左移,調整到適當位置。 (以下兩步類似於Pra1)

3. 或操作

#include <stdio.h>int my_hton2(int ip){    int my_net;    my_net = ((ip&0xff)<<24) | (((ip>>8)&0xff) << 16) | (((ip>>16)&0xff) << 8) | ((ip>>24)&0xff);    return my_net;}int main(int argc, char *argv[]){    int my_host = 0x6b2161b4;    int my_net = my_hton2(my_host);    printf("my_host: %x \n", my_host);    printf("my_net : %x \n", my_net);    return 0;}

實現將網路位元組序轉換為點分十進位

#include <stdio.h>static char* my_ntoa(int ip){    static char buf[1024] = "";    sprintf(buf,"%d.%d.%d.%d",(ip >> 24) & 0xff,(ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff);    return buf;}int main(int argc,char *argv[]){    int my_net = 0xb461216b;    printf("my_net: %x \n", my_net);    printf("IP    : %s \n", my_ntoa(my_net));    return 0;}

我們的my_atoh,my_hton合并為系統函數inet_aton,而my_ntoa即為系統函數inet_ntoa。
#include <stdio.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>int main(int argc, char *argv[]){    char ip_buf[] = "180.97.33.107";    struct in_addr my_addr;    inet_aton(ip_buf,&my_addr);    printf("ip : %s \n", ip_buf);    printf("net: %x \n", my_addr.s_addr);    return 0;}
ip : 180.97.33.107net: 6b2161b4
照理,網路位元組序是大端儲存,應該返回0xb461216b。實際上調用系統函數inet_aton後,就直接在變數my_addr.s_addr的實際記憶體空間中以二進位形式寫入了0xb461216b(其實用位元運算,就可以直接操作二進位位,上篇博文有具體實現)。之所以運行結果是0x6b2161b4,是因為我們的主機是小端儲存,用printf顯示結果是先取低位元組。

#include <stdio.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>int main(int argc, char *argv[]){    struct in_addr my_addr;    my_addr.s_addr = 0xb461216b;    printf("net: %x \n", my_addr.s_addr);    printf("ip : %s \n", inet_ntoa(my_addr));    return 0;}
net: b461216bip : 107.33.97.180
照理,ip應該輸出的是180.97.33.107。其實道理很簡單,我們的主機是小端模式儲存,而網路位元組序是大端模式,當我們把0xb461216b賦值給my_addr.s_addr 時,實際上在記憶體中儲存形式是0x6b2161b4,而inet_ntoa的具體實現時通過位元運算直接操縱二進位位的,因此結果就自然輸出107.33.97.180。


// 測試大端機還是小端機
#include <stdio.h>#include <stdlib.h>int main(){ int a = 0x61;//97 printf("%x\n",(char*)(&a)[0]);}
結果輸出61,說明是小端機,先存低位元組。
系統函數無論inet_aton還是inet_ntoa,都是直接以位元運算形式實現的,因此其關注的是資料在記憶體中實際的二進位儲存形式。

使用#把宏參數變為一個字串,用##把兩個宏參數貼合在一起。

TCP通訊的基本步驟如下:

服務端:socket---bind---listen---while(1){---accept---recv---send---close---}------close

用戶端:socket------------------------------connect---send---recv-----------------close

 

UDP通訊的基本步驟如下:

服務端:socket---bind---recvfrom---sendto----close

用戶端:socket----------sendto----recvfrom---close

 

TCP#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <string.h>#include <stdio.h>#include <stdlib.h>server:int socket(int domain, int type, int protocol);int bind(int fd_server, struct sockaddr *addr_server, int addrlen); /* 綁定伺服器連絡方式,讓用戶端有明確的連絡方式可以串連 */int listen(int fd_server, int backlog); /* 將fd_server轉換為被動通訊端,同時核心將listen到的請求的連絡方式放入隊列 */int accept(int fd_server, struct sockaddr *addr_client, int *addrlen); /* 返回用戶端socket的另一端,以此建立連線 *//* addr_client為傳出參數,存放請求串連方的連絡方式 ,如不需要刻意置為NULL */client:int connect (int fd_client,struct sockaddr *addr_server, int addrlen); /* 通過伺服器的連絡方式addr_server去串連伺服器 *//* 作為伺服器,一定要綁定連絡方式,不然請求方沒有明確的連絡方式可以串連 ,   作為用戶端,可以不用綁定連絡方式,其連接埠號碼會由系統自動分配。             建立連線的關鍵在於accept函數會返回用戶端socket描述符所對應的另一端socket的描述符,   並且用戶端的連絡方式也可以在accept中使用傳出函數擷取。                              */   after connect:/* 建立連線後,就可以利用socket描述符發送接收資訊了 */int recv(int sockfd,void *buf,int len,unsigned int flags);
int send(int s,const void * msg,int len,unsigned int flags);
/* 會話結束,關閉 */int close(int fd);

UDPserver:int socket(int domain, int type, int protocol);/* 綁定伺服器連絡方式,讓用戶端有明確的連絡方式可以串連 */int bind(int fd_server, struct sockaddr *addr_server, int addrlen); /* 由於fd_server已經綁定連絡方式(addr_server),請求方發送資訊時,會通過addr_server發送訊息至伺服器fd_server,   因此,伺服器可以通過fd_server收到訊息,並且請求方的連絡方式可以通過傳出參數addr_client擷取                */int recvfrom(int fd_server, void *buf, int len, unsigned int flags, struct sockaddr *addr_client, int *addrlen); /* 請求方的連絡方式(addr_client)會由系統自己綁定,伺服器是通過recvfrom的傳出參數擷取addr_client的。   因此伺服器發送訊息時,可以通過用戶端連絡方式addr_client發送至用戶端fd_client。                  */int sendto(int fd_server, const void *msg, int len, unsigned int flags, const struct sockaddr *addr_client, int addrlen);client:/* 用戶端的連絡方式,伺服器可以在recvfrom時擷取,因此沒有必要在一開始就綁定 */int sendto(int fd_server, const void *msg, int len, unsigned int flags, const struct sockaddr *addr_client, int addrlen);int recvfrom(int fd_server, void *buf, int len, unsigned int flags, struct sockaddr *addr_client, int *addrlen); /* 實際上,UTP通訊方式並沒有建立socket連線,雙方均是通過連絡方式(IP和連接埠號碼)進行通訊的。   伺服器需要在一開始綁定連絡方式,不然請求方沒有明確的連絡方式可以串連。   當伺服器recvfrom,不但可以收到用戶端的訊息,同時可以擷取用戶端的連絡方式。            */


1. TCP通訊會建立連線。伺服器的socket連接埠會bind連絡方式,給用戶端一個明確的連絡方式去connect。伺服器會listen到用戶端的connect,並將其連絡方式放入核心維護的工作要求隊列中。之後伺服器accept,擷取與用戶端通訊的對應socket連接埠,從而建立連線!之後雙方通過socket連接埠進行通訊。

2. UDP通訊不會建立連線。伺服器的socket連接埠同樣會bind連絡方式。用戶端通過此連絡方式向伺服器發送(sendto)訊息,由於此連絡方式已綁定到伺服器的socket連接埠,因此伺服器一定可以收到(recvfrom)訊息,與此同時通過recvfrom的傳出參數擷取用戶端的連絡方式,從而可以通過用戶端的連絡方式向用戶端發送訊息。即,UTP的通訊是基於連絡方式(IP和連接埠號碼)的。是不可靠的通訊方式。

3. 為什麼TCP中的accept與UDP中的recvfrom都可以通過傳出參數擷取對方連絡方式?不理解的話,可以類比寄信,別人給你寄一封信時,總會寫上自己的連絡方式吧。

linux網路編程

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.