2012年7月12日 星期四

[ NetworkPrg ] TCP Client/Server 範例代碼


前言 :
使用 TCP 執行通訊時, 會有 Server 與 Client 差別. Server 是經由開啟特定的 Port 來等待來自 Client 端地連線要求 ; 而 Client 則是向 Server 提出連線請求. 可以使用 bind()listen() 與accept() 這三種系統呼叫來使 Server 端接受 Client 的連線要求 :


bind() 會連結到所建立 socket 指定的網路介面上的連接埠, 接著透過 listen(), 伺服器進入被動接受狀態. 針對進入接受狀態的 Server, Client 會透過 connect() 提出連線要求. 在 Server 接受來自用戶端的 TCP 連線要求後, 被占用的 accept() 呼叫會回傳連線成功, 然後在 Server 端產生一個新的 socket 的檔案描述索引值. 這個 socket 表示 Client 成功地與 Server 建立 TCP 連線. 然後 Server 會再次使用 accept() 呼叫來等待下一次來自 Client 端的 TCP 連線.

簡單的 TCP Server 端代碼 :
下面我會撰寫簡單的 Server 端代碼, 這個代碼會針對已連線的 Client 傳送 "HELLO" 字串後結束. 底下是撰寫 Server 端的 TCP 代碼必須要經過以下的幾個步驟 :
* 產生 socket
* 設定等待連線的 IP/Port
* 給定 socket 一個名稱 (設定 bind())
* 等待連線
* 接受從 Client 端來的連線
* 執行通訊

一旦連線後, 使用 socket 的 Server/Client 端通訊是雷同的, 任一方都可以進行傳送與接收資料. 另外任何一端使用 close() 呼叫就可以結束通訊. 下面即是上面步驟說明的 Server 端 TCP 代碼 :
- List 2-5 : 簡單 Server TCP 代碼 (ex2_5.c) 
  1. #include <stdio.h>  
  2. #include <unistd.h>  
  3. #include <sys/types.h>  
  4. #include <sys/socket.h>  
  5. #include <netinet/in.h>  
  6. #include <arpa/inet.h>  
  7.   
  8. int main()  
  9. {  
  10.   int sock0;  
  11.   struct sockaddr_in addr;  
  12.   struct sockaddr_in client;  
  13.   socklen_t len;  
  14.   int sock_client;  
  15.   
  16.   /* 製作 socket */  
  17.   sock0 = socket(AF_INET, SOCK_STREAM, 0);  
  18.   
  19.   /* 設定 socket */  
  20.   addr.sin_family = AF_INET;  
  21.   addr.sin_port = htons(12345);  
  22.   addr.sin_addr.s_addr = INADDR_ANY;  
  23.   bind(sock0, (struct sockaddr*)&addr, sizeof(addr));  
  24.   printf("\t[Info] binding...\n");  
  25.   
  26.   /* 設定成等待來自 TCP 用戶端的連線要求狀態 */  
  27.   listen(sock0, 5);  
  28.   printf("\t[Info] listening...\n");  
  29.   
  30.   /* 接受來自 TCP 用戶端地連線要求*/  
  31.   printf("\t[Info] wait for connection...\n");  
  32.   len = sizeof(client);  
  33.   sock_client = accept(sock0, (struct sockaddr *)&client, &len);  
  34.   printf("\t[Info] Testing...\n");  
  35.   char *paddr_str = inet_ntoa(client.sin_addr);  
  36.   printf("\t[Info] Receive connection from %s...\n", paddr_str);  
  37.   
  38.   /* 傳送 5 個字元 */  
  39.   printf("\t[Info] Say hello back...\n");  
  40.   write(sock_client, "HELLO\n"6);  
  41.   
  42.   /* 結束 TCP 對話 */  
  43.   printf("\t[Info] Close client connection...\n");  
  44.   close(sock_client);  
  45.   
  46.   /* 結束 listen 的 socket */  
  47.   printf("\t[Info] Close self connection...\n");  
  48.   close(sock0);  
  49.   return 0;  
  50. }  
接著你可以如下編譯程式並執行程式 : 
$ gcc ex2_5.c -o server # 編譯 ex2_5.c 並產生執行檔 server
$ ./server
[Info] binding...
[Info] listening...
[Info] wait for connection...

簡單 Client 端 TCP 代碼 :
Client 程式設計的步驟如下 :
* 設定 socket
* 設定連線端
* 連線
* 執行通訊

在用戶端對於特定 IP 與 Port 提出 "連線要求". 取得來自 Server 端的回覆並連線成功後就可以開始通訊. Client TCP 代碼如下 :
- List 2-6 : 簡單的 Client TCP 代碼 (ex2_6.c) 
  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <unistd.h>  
  4. #include <sys/types.h>  
  5. #include <sys/socket.h>  
  6. #include <netinet/in.h>  
  7. #include <arpa/inet.h>  
  8.   
  9.   
  10. int main()  
  11. {  
  12.   struct sockaddr_in server;  
  13.   int sock;  
  14.   char buf[32];  
  15.   int n;  
  16.   
  17.   /* 製作 socket */  
  18.   sock = socket(AF_INET, SOCK_STREAM, 0);  
  19.   
  20.   /* 準備連線端指定用的 struct 資料 */  
  21.   server.sin_family = AF_INET;  
  22.   server.sin_port = htons(12345);  
  23.   
  24.   /* 127.0.0.1 是 localhost 本機位址 */  
  25.   inet_pton(AF_INET, "127.0.0.1", &server.sin_addr.s_addr);  
  26.   
  27.   /* 與 server 端連線 */  
  28.   connect(sock, (struct sockaddr *)&server, sizeof(server));  
  29.   
  30.   /* 從伺服器接受資料 */  
  31.   memset(buf, 0, sizeof(buf));  
  32.   n = read(sock, buf, sizeof(buf));  
  33.   
  34.   printf("\t[Info] Receive %d bytes: %s\n", n, buf);  
  35.   
  36.   /* 結束 socket */  
  37.   close(sock);  
  38.   return 0;  
  39. }  
接著使用 gcc ex2_6.c -o client 編譯後如下執行程式 : 


在 Client 端編碼中的 "127.0.0.1" 是表示 "localhost" (本機位址), 因此 Server 與 Client 是在同一個主機上執行. 想要結束 TCP 通訊時使用 close() 呼叫, 此時另一端的 read() 呼叫會傳回表示 "EOF" (Eod of File) 的數值 0 ; 但如果是發生不明原因的錯誤通訊, 由 read() 讀回的數值為 -1, 並會將錯誤內容記述於 errno 中.

補充說明 :
Linux / Unix Command: int inet_pton(int af, const char *src, void *dst);
net_pton - Create a network address structure

htons(3) - Linux man page
The htons() function converts the unsigned short integer hostshort from host byte order to network byte order.

This message was edited 17 times. Last update was at 12/07/2012 22:07:38

11 則留言:

  1. 非常感謝您 提供的知識
    花了兩天時間
    卡在 WINDOWS 防火牆 與 LINUX 防火牆

    (使用 WIN7 + Hercules SETUP utility 失敗)
    (研究 WIN7 防火牆 太難 放棄)

    (改用 WINXP + Hercules SETUP utility OK)
    但是一直回報 10065 (有反應就是好事) = . =

    最後 參考其他前輩的資料 得到以下兩個 LINUX 命令 執行後 OK

    netstat -anp|grep 12345
    用來檢查 PORT 是否開啟

    iptables -I INPUT 1 -p TCP --dport 12345 -j ACCEPT
    用來開啟防火牆 讓 PORT 12345 通過

    非常感謝 您願意 在 TCP SERVER/CLIENT 範例程式 加上註解
    讓我仍夠很容易完成我的程式
    感謝您
    YUFO 敬上

    回覆刪除
  2. 如果 iptables -I INPUT 1 -p TCP --dport 12345 -j ACCEPT 還不行

    可用 iptables -F
    前提是 這台機器 只是測試用
    要更深入了解 就要詢問專家了

    回覆刪除
  3. 產生segment fault 錯誤,另外你的include沒顯示

    回覆刪除
    回覆
    1. include 資訊因為 "<", ">" 與 HTML 語法衝突, 故無法顯視. 已更新. 至於 "SEGMENT FAULT" 就要 Case by case 了....

      刪除
  4. 請問是在哪個OS下做這項工作的呢?
    win10上面是否也可以模擬

    回覆刪除
    回覆
    1. Linux/Unix like OS should be ok with above sample code. I don't have Windows 10 environment. But you can google "Windows TCP C programming socket" for more reference tutorial pages. e.g.:
      https://causeyourestuck.io/2016/01/17/socket-cc-windows/
      https://www.codeproject.com/Articles/13071/Programming-Windows-TCP-Sockets-in-C-for-the-Begin

      刪除
    2. thanks a lot for anserwing my question
      by the way your blog is really great for programmer who want to learn socket program

      刪除
    3. 作者已經移除這則留言。

      刪除
    4. 作者已經移除這則留言。

      刪除
    5. 作者已經移除這則留言。

      刪除

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...