<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    TCP套接字編程模型及實例

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀

    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    摘要:

        本文講述了TCP套接字編程模塊,包括服務器端的創建套接字、綁定、監聽、接受、讀/寫、終止連接,客戶端的創建套接字、連接、讀/寫、終止連接。先給出實例,進而結合代碼分析。

     

    PS:本文權當復習套接字編程的讀書筆記。

     

    一、TCP套接字編程模型

        同一臺計算機上運行的進程可以利用管道、消息隊列、信號量、共享內存等進行相互通信,不同計算機上運行的進程可以通過套接字網絡IPC接口進行相互通信。套接字編程基本步驟如下圖所示:

    RFID設備管理軟件

    圖 TCP套接字編程模型[1]

    二、源代碼

        本實例旨在實現簡單的echo服務,客戶端發送數據給服務端,在服務端打印出來并且回發給客戶端,并在客戶端顯示。

    TCP_socket_programming_example源文件RFID設備管理軟件 TCP_socket_programming_example.rar   

    2.1 TCP服務端

     

    1. //filename:TCPserver.c

    2. #include 

    3. #include 

    4. #include 

    5. #include 

    6.  

    7. #define BACKLOG 10

    8. #define BUFFER_SIZE 1024

    9.  

    10. int main(int argc, char *argv[])

    11. {

    12.   if(2 != argc)

    13.   {

    14.     printf("Usage:%s portnumber\n", argv[0]);

    15.     return - 1;

    16.   }

    17.  

    18.   /***1.create a socket***/

    19.   int fd_server = socket(AF_INET, SOCK_STREAM, 0); //TCP

    20.   if( - 1 == fd_server)

    21.   {

    22.     printf("%s\n", strerror(errno));

    23.     return - 1;

    24.   }

    25.  

    26.   /***2.bind the socket***/

    27.   int listen_port = atoi(argv[1]);

    28.   struct sockaddr_in addr_server;

    29.   //memset(&addr_server, 0, sizeof(addr_server));

    30.   addr_server.sin_family = AF_INET;

    31.   addr_server.sin_port = htons(listen_port);

    32.   addr_server.sin_addr.s_addr = htonl(INADDR_ANY);

    33.  

    34.   if(bind(fd_server, (struct sockaddr*) &addr_server, sizeof(addr_server)) == - 1)

    35.   {

    36.     printf("%s\n", strerror(errno));

    37.     return - 1;

    38.   }

    39.  

    40.   /***3.listen the socket***/

    41.   if(listen(fd_server, BACKLOG) == - 1)

    42.   {

    43.     printf("%s\n", strerror(errno));

    44.     return - 1;

    45.   }

    46.  

    47.   /***4.accept the requirement of some client***/

    48.   struct sockaddr_in addr_client;

    49.   int len_addr_client = sizeof(addr_client);

    50.   int fd_client = accept(fd_server, (struct sockaddr*) &addr_client, &len_addr_client);

    51.   if( - 1 == fd_client)

    52.   {

    53.     printf("%s\n", strerror(errno));

    54.     return - 1;

    55.   }

    56.  

    57.   /****5.serve the client******/

    58.   char buf[BUFFER_SIZE];

    59.   int size;

    60.   while(1)

    61.   {

    62.     /***read from client***/

    63.     size = recv(fd_client, buf, sizeof(buf), 0);

    64.     buf[size] = '\0';

    65.     printf("%s\n", buf);

    66.  

    67.     /***write to client***/

    68.     size = send(fd_client, buf, strlen(buf), 0);

    69.   }

    70.  

    71.   /****6.close the socket******/

    72.   close(fd_server);

    73.   close(fd_client);

    74. }

     

     

    2.2 TCP客戶端

     

    點擊(此處)折疊或打開

    1. //filename:TCPclient.c

    2. #include 

    3. #include 

    4. #include 

    5. #include 

    6.  

    7. #define BUFFER_SIZE 1024

    8.  

    9. int main(int argc, char *argv[])

    10. {

    11.   if(3 != argc)

    12.   {

    13.     printf("Usage:%s hostname portnumber\n", argv[0]);

    14.     return - 1;

    15.   }

    16.  

    17.   /***1.create a socket***/

    18.   int fd_client = socket(AF_INET, SOCK_STREAM, 0); //TCP

    19.   if( - 1 == fd_client)

    20.   {

    21.     printf("%s\n", strerror(errno));

    22.     return - 1;

    23.   }

    24.   /***2.connect to the server***/

    25.   int portnumber = atoi(argv[2]);

    26.   struct sockaddr_in addr_server;

    27.   addr_server.sin_family = AF_INET;

    28.   addr_server.sin_port = htons(portnumber);

    29.   if(0 == inet_pton(AF_INET, argv[1], (void*) &addr_server.sin_addr.s_addr))

    30.   {

    31.     printf("Invalid address.\n");

    32.     return - 1;

    33.   }

    34.  

    35.   if(connect(fd_client, (struct sockaddr*) &addr_server, sizeof(addr_server)) == - 1)

    36.   {

    37.     printf("%s\n", strerror(errno));

    38.     return - 1;

    39.   }

    40.  

    41.  

    42.   /****3.get the server******/

    43.   char buf[BUFFER_SIZE];

    44.   int size;

    45.   while(1)

    46.   {

    47.     /***write to server***/

    48.     scanf("%s", buf);

    49.     size = send(fd_client, buf, strlen(buf), 0);

    50.  

    51.     /***read from server***/

    52.     size = recv(fd_client, buf, BUFFER_SIZE, 0);

    53.  

    54.     buf[size] = '\0';

    55.     printf("%s\n", buf);

    56.  

    57.   }

    58.   /****4.close the socket******/

    59.   close(fd_client);

    60. }

     

    2.3 測試結果

    $ ./TCPserver 2000

    RFID設備管理軟件

    $ ./TCPclient 127.0.0.1 2000

    RFID設備管理軟件

    三、源碼分析

    3.1 創建套接字

     

    1. int socket(int domain, int type, int protocol);//成功返回套接字描述符.出錯返回-1

     

        這一步事實上是確定通信特征,各個域domain有自己的格式表示地址,以AF_開頭(address family);type確定套接字類型,如數據報、字節流;協議protocol對同一個域和套接字類型支持的多個協議進行選擇,通常為0,即按給定的域和套接字類型選擇默認的協議。典型的TCP、UDP如下:

     

    1. TCP:(AF_INET, SOCK_DGRAM, 0)


    2. UDP:(AF_INET, SOCK_STREAM, 0)

     

    注:

        盡管套接字本質是文件描述符,但不是所有用于文件操作的函數都能用于套接字操作,比如lseek,套接字不支持文件偏移量。

    3.2 綁定

     

    1. int bind(int sockfd, const struct sockaddr *addr, socklen_t len);//成功返回0.出錯返回-1

     

        bind函數用于將地址綁定到一個套接字。服務器需要給一個接收客戶端請求套接字綁定一個眾所周知的地址,而客戶端可以讓系統選一個默認地址綁定(無須綁定)。

    (1) 套接字地址sockaddr_in

    在IPv4因特網域AF_INET中,套接字地址用結構sockaddr_in表示,如下:

     

    1. struct sockaddr_in

    2. {

    3.   sa_family_t sin_family; //unsigned short 地址族

    4.   in_port_t sin_sport; //uint16_t

    5.   struct in_addr sin_addr; //IPv4

    6. };


    7. struct in_addr

    8. {

    9.   in_addr_t s_addr; //uint32_t

    10. };

     

    注:

        初始化sockaddr_in結構體時,因為sin_port和sin_addr被封裝在網絡傳輸,所以端口號和地址必須用網絡字節序;而sin_family只是被內核用來決定數據結構包含什么類型的地址,沒有發送到到網絡,應該是本機字節順序。處理器與網絡字節序之間轉換函數為htonl、htons、ntohl、ntohs(h指host主機,n指network網絡,l指long32位,s指short16位)。

        理論上,端口號可以是0~65535,但1~1023已由IANA管理,綁定時端口號不少于1024[2]。

        此處的地址s_addr是二進制地址格式,如果參數是點分十進制字符串表示,則需通過函數inet_ntop(將網絡字節序的二進制地址轉換成點分十進制字符串表示)、inet_pton進行相互轉換。其轉換過程如下:

     

    1. 127.0.0.1 --> 7F.0.0.1 --> 100007F=16777343(網絡字節序為大端)

     

        如果地址s_addr為ANADDR_ANY,套接字端點可以被綁定到所有系統網絡接口,即可以收到這個系統所安裝的所有網卡的數據包。

    (2) 通用地址格式sockaddr

        地址格式與特定的通信域有關(如AF_INET、AF_INET6),為使不同地址格式地址能夠傳入套接字函數,地址被強制轉換成通用的地址結構sockaddr,如下(以Linux為例):

     

    1. struct sockaddr

    2. {

    3.   unsigned short sa_family; /* address family, AF_xxx */

    4.   char sa_data[14]; /* 14 bytes of protocol address */

    5. };

     

    3.3 監聽listen

     

    1. int listen(int sockfd, int backlog);//成功返回0,出錯返回-1

     

        一旦服務器調用listen,套接字就能接收連接請求。backlog用于表示該進程所要入隊的連接請求數量,實際值由系統決定,但上限由SOMAXCONN指定。一旦隊列滿,系統會拒絕多余連接請求。

    3.4 接受連接請求accept

     

    1. //成功返回套接字描述符,出錯返回-1

    2. int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);

     

        使用accept獲得連接請求并建立連接,新的套接字描述符連接到調用connect的客戶端。傳給accept的原始套接字(sockfd)沒有關聯到這個連接,而是接收保持可用狀態并接受其他請求連接,這樣做是為了使新的套接字描述符和原始套接字具有相同的地址族domain和套接字類型type。

        如果服務器調用accept并且當前沒有連接請求,服務器會阻塞直到一個請求到來。如果不關心客戶端標識,可以將參數addr和len設為NULL。

    注:

        關鍵字restrict是C99新引入的,所有修改該指針所指向內容的操作全部都是基于(base on)該指針的,即不存在其它進行修改操作的途徑;從而幫助編譯器進行更好的代碼優化,生成更有效率的匯編代碼[4]。

    3.5 建立連接connect

     

    1. //成功返回0,出錯返回-1

    2. int connect(int sockfd, const struct sockaddr *addr, socklen_t len);

     

        addr是想與之通信的服務器地址,如果sockfd沒有綁定到一個地址,connect會給調用者綁定一個默認的地址。成功連接需要以下條件:要連接的機器開啟且正在運行,服務器綁定到一個想與之連接的地址,服務器的等待連接隊列有足夠的空間。

    3.6 讀取數據

     

    1. ssize_t read(int fd, void *buf, size_t nbytes); //成功返回讀到的字節數,已到文件末尾返回0,出錯返回-1


    2. ssize_t recv(int sockfd, const void *buf, size_t nbytes, int flags); //成功返回字節計數的消息長度,無可用消息或對方已經按序結束返回0,出錯返回-1


    3. ssize_t recvfrom(int sockfd,void *restrict buf, size_t len, int flags, struct sockaddr *restrict addr, socketlen_t *restruct addrlen); //成功返回字節計數的消息長度,無可用消息或對方已經按序結束返回0,出錯返回-1


    4. ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);//成功返回字節計數的消息長度,無可用消息或對方已經按序結束返回0,出錯返回-1

     

        可以使用read通過套接字通信,但read只能交換數據,若想指定選項、從多個客戶端接收數據包,則需選擇套接字函數recv(指定標志控制接收數據的方式)、recvfrom(得到數據發送者的源地址)、resvmsg(將接收到數據送入多個緩沖區或接收輔助數據)。

    RFID設備管理軟件

    3.7 寫入數據

     

    1. ssize_t write(int fd, void *buf, size_t count); //成功返回寫入字節數,出錯返回-1


    2. ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags); //成功返回發送的字節數,出錯返回-1


    3. ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags, 

    4.               const struct sockaddr *destaddr, socklen_t destlen); //成功返回發送的字節數,出錯返回-1


    5. ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); //成功返回發送的字節數,出錯返回-1

     

    注:send與sendto的flags含義相同,sendmsg的flags與前兩者不同

        可以使用write通過套接字通信,但write只能交換數據,若想指定選項、發送帶外數據,則需選擇套接字函數send(指定標志改變處理傳輸數據的方式)、sendto(允許無連接的套接字上指定一個目標地址)、sendmsg(指定多重緩沖傳輸數據)。

    RFID設備管理軟件

    3.8 終止連接

     

    1. int close(int fd); //成功返回0,出錯返回-1


    2. int shutdown(int sockfd, int how);//成功返回0,出錯返回-1

     

        關閉套接字close只有在最后一個活動引用被關閉后才釋放網絡端點,而shutdown提供更精細的控制,套接字通信是雙向的,可以用shutdown禁止套接字上的輸入/輸出,即how為SHUT_RD、SHUT_WR、SHUT_RDWR。除此之外,shutdown允許使一個套接字處于不活動狀態(不管引用它的文件描述符數目多少),便于復制一個套接字(如dup)。

     

    參考資料:

    [1] 百度文庫《互聯網絡程序設計第3章

    [2] 《UNIX環境高級編程》[M].

    [3] 維基百科詞條:TCP/UDP端口列表

    [4] 博文《C99中的restrict關鍵字

    套接字調用標志源文件RFID設備管理軟件 套接字調用標志.xls  

    from:http://blog.chinaunix.net/uid-9112803-id-3199813.html

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成

    最近免费观看高清韩国日本大全