1. 程式人生 > >五十四、linux 編程——TCP 編程模型

五十四、linux 編程——TCP 編程模型

獲取 log sin 進制 backlog string idt 主機 ket

54.1 編程模型介紹

54.1.1 TCP 客戶端服務器編程模型

  技術分享圖片

  • 客戶端調用序列
    • 調用 socket 函數創建套接字
    • 調用 connect 連接服務器端
    • 調用 I/O 函數(read/write) 與服務器端通訊
    • 調用 close 關閉套接字
  • 服務器端調用序列
    • 調用 socket 函數創建套接字
    • 調用 bind 綁定本地地址和端口
    • 調用 listen 啟動監聽
    • 調用 accept 從已連接隊列中提取客戶連接
    • 調用 I/O 函數(read/write)與客戶端通訊
    • 調用 close 關閉套接字

54.1.2 套接字與地址綁定

  sockaddr 為自定義的結構體,示例如下:

  技術分享圖片

(1)綁定地址

  技術分享圖片

  • 函數返回值:成功,則返回 0;出錯,則返回 -1

(2)查找綁定到套接字的地址

  技術分享圖片

  • 返回值:成功,則返回 0;出錯,則返回 -1

(3)獲取對方地址

  技術分享圖片

  • 返回值:成功,則返回 0;出錯, 則返回 -1

(4)建立連接

  服務器端:

  技術分享圖片

  • 返回:成功返回0;出錯返回 -1.
  • 說明:backlog 指定進行客戶端連接排隊的隊列長度

  技術分享圖片

  • 函數功能:獲取客戶端的連接
  • 函數參數:
    • address:通用地址,可以存放來源於客戶端的地址信息,若不想獲取客戶端的信息,設置為NULL
  • 返回值:

  客戶端:

  技術分享圖片

  • 返回:成功返回0;出錯返回 -1

54.1.3 特殊 bind 地址

  • 一臺主機可以有多個網絡接口和多個 IP 地址,如果我們只關心某個地址的連接請求,我們可以指定一個具體的本地 IP 地址,如果要響應所有接口上的連接請求,就要使用一個特殊的地址 INADDR_ANY
  • #define INADDR_ANY (uint32_t)0x00000000

  技術分享圖片

54.2 TCP 編程例子

  客戶端連接到服務器端後,服務器端返回給客戶端一個系統時間,客戶端將此時間打印出來

54.2.1 服務器端編程

time_tcp_server.c

  1 #include <netdb.h>
  2 #include <netinet/in.h>
  3 #include <sys/socket.h>
  4
#include <unistd.h> 5 #include <string.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <memory.h> 9 #include <signal.h> 10 #include <time.h> 11 #include <arpa/inet.h> 12 13 14 int sockfd; 15 16 void sig_handler(int signo) 17 { 18 if(signo == SIGINT){ 19 printf("server close\n"); 20 /** 步驟6: 關閉 socket */ 21 close(sockfd); 22 exit(1); 23 } 24 } 25 26 /** 輸出連接上來的客戶端相關信息 */ 27 void out_addr(struct sockaddr_in *clientaddr) 28 { 29 /** 將端口從網絡字節序轉換成主機字節序 */ 30 int port = ntohs(clientaddr->sin_port); 31 char ip[16]; 32 memset(ip, 0, sizeof(ip)); 33 /** 將 ip 地址從網絡字節序轉換成點分十進制 */ 34 inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip)); 35 printf("client: %s(%d) connected\n", ip, port); 36 } 37 38 void do_service(int fd) 39 { 40 /** 獲得系統時間 */ 41 long t = time(0); 42 char *s = ctime(&t); 43 ssize_t size = strlen(s) * sizeof(char); 44 45 /** 將服務器獲得的系統時間寫回到客戶端 */ 46 if(write(fd, s, size) != size){ 47 perror("write error"); 48 } 49 } 50 51 int main(int argc, char *argv[]) 52 { 53 if(argc < 2){ 54 printf("usage: %s #port\n", argv[0]); 55 exit(1); 56 } 57 58 if(signal(SIGINT, sig_handler) == SIG_ERR){ 59 perror("signal sigint error"); 60 exit(1); 61 } 62 63 /** 步驟1: 創建 socket(套接字) 64 * 註: socket 創建在內核中,是一個結構體. 65 * AF_INET: IPV4 66 * SOCK_STREAM: tcp 協議 67 * AF_INET6: IPV6 68 */ 69 sockfd = socket(AF_INET, SOCK_STREAM, 0); 70 71 /** 72 * 步驟2: 調用 bind 函數將 socket 和地址(包括 ip、port)進行綁定 73 */ 74 struct sockaddr_in serveraddr; 75 memset(&serveraddr, 0, sizeof(struct sockaddr_in)); 76 /** 往地址中填入 ip、port、internet 地址族類型 */ 77 serveraddr.sin_family = AF_INET; ///< IPV4 78 serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口 79 serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址 80 if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){ 81 perror("bind error"); 82 exit(1); 83 } 84 85 /** 86 * 步驟3: 調用 listen 函數啟動監聽(指定 port 監聽) 87 * 通知系統去接受來自客戶端的連接請求 88 * (將接受到的客戶端連接請求放置到對應的隊列中) 89 * 第二個參數: 指定隊列的長度 90 */ 91 if(listen(sockfd, 10) < 0){ 92 perror("listen error"); 93 exit(1); 94 } 95 96 /** 97 * 步驟4: 調用 accept 函數從隊列中獲得一個客戶端的請求連接, 並返回新的 98 * socket 描述符 99 * 註意: 若沒有客戶端連接,調用此函數後會足則, 直到獲得一個客戶端的連接 100 */ 101 struct sockaddr_in clientaddr; 102 socklen_t clientaddr_len = sizeof(clientaddr); 103 while(1){ 104 int fd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len); 105 if(fd < 0){ 106 perror("accept error"); 107 continue; 108 } 109 110 /** 111 * 步驟5: 調用 IO 函數(read/write)和連接的客戶端進行雙向的通信 112 */ 113 out_addr(&clientaddr); 114 do_service(fd); 115 116 /** 步驟6: 關閉 socket */ 117 close(fd); 118 } 119 120 return 0; 121 }

  編譯測試:

  技術分享圖片

  可以看到,另一個終端返回了系統時間。

54.2.2 客戶端編程

  time_tcp_client.c

 1 #include <sys/types.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <memory.h>
 5 #include <unistd.h>
 6 #include <sys/socket.h>
 7 #include <netdb.h>
 8 #include <signal.h>
 9 #include <string.h>
10 #include <time.h>
11 #include <arpa/inet.h>
12 
13 
14 int main(int argc, char *argv[])
15 {
16     if(argc < 3){
17         printf("usage: %s ip port\n", argv[0]);
18         exit(1);
19     }
20 
21     /** 步驟1: 創建 socket */
22     int sockfd = socket(AF_INET, SOCK_STREAM, 0);
23     if(sockfd < 0){
24         perror("socket error");
25         exit(1);
26     }
27 
28     /** 往 serveraddr 中填入 ip、port 和地址族類型(ipv4) */
29     struct sockaddr_in serveraddr;
30     memset(&serveraddr, 0, sizeof(struct sockaddr_in));
31     serveraddr.sin_family = AF_INET;
32     serveraddr.sin_port = htons(atoi(argv[2]));
33     /** 將 ip 地址轉換成網絡字節序後填入 serveraddr 中  */
34     inet_pton(AF_INET, argv[1], &serveraddr.sin_addr.s_addr);
35 
36     /**
37      *  步驟2: 客戶端調用 connect 函數連接到服務器端
38      */
39     if(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < 0){
40         perror("connect error");
41         exit(1);
42     }
43 
44     /** 步驟3: 調用 IO 函數(read/write)和服務器端進行雙向通信 */
45     char buffer[1024];
46     memset(buffer, 0, sizeof(buffer));
47     ssize_t size;
48     if((size = read(sockfd, buffer, sizeof(buffer))) < 0){
49         perror("read error");
50     }
51     if(write(STDIN_FILENO, buffer, size) != size){
52         perror("write error");
53     }
54 
55     /** 步驟4: 關閉 socket */
56     close(sockfd);
57 
58     return 0;
59 }

  編譯在兩個終端上,一個打開服務器,一個打開客戶端測試:

  技術分享圖片

五十四、linux 編程——TCP 編程模型