1. 程式人生 > 其它 >Linux 系統程式設計 學習:8-基於socket的網路程式設計3:基於 TCP 的通訊

Linux 系統程式設計 學習:8-基於socket的網路程式設計3:基於 TCP 的通訊

知識

TCP(Transmission Control Protoco 傳輸控制協議)。

TCP是一種面向廣域網的通訊協議,目的是在跨越多個網路通訊時,為兩個通訊端點之間提供一條具有下列特點的通訊方式:

  • 基於流的方式;

  • 面向連線;

  • 可靠通訊方式;

  • 在網路狀況不佳的時候儘量降低系統由於重傳帶來的頻寬開銷;

  • 通訊連線維護是面向通訊的兩個端點的,而不考慮中間網段和節點。

為滿足TCP協議的這些特點,TCP協議做了如下的規定:

  • 資料分片:在傳送端對使用者資料進行分片,在接收端進行重組,由TCP確定分片的大小並控制分片和重組;
  • 到達確認:接收端接收到分片資料時,根據分片資料序號向傳送端傳送一個確認;
  • 超時重發:傳送方在傳送分片時啟動超時定時器,如果在定時器超時之後沒有收到相應的確認,重發分片;
  • 滑動視窗:TCP連線每一方的接收緩衝空間大小都固定,接收端只允許另一端傳送接收端緩衝區所能接納的資料,TCP在滑動視窗的基礎上提供流量控制,防止較快主機致使較慢主機的緩衝區溢位;
  • 失序處理:作為IP資料報來傳輸的TCP分片到達時可能會失序,TCP將對收到的資料進行重新排序,將收到的資料以正確的順序交給應用層;
  • 重複處理:作為IP資料報來傳輸的TCP分片會發生重複,TCP的接收端必須丟棄重複的資料;
  • 資料校驗:TCP將保持它首部和資料的檢驗和,這是一個端到端的檢驗和,目的是檢測資料在傳輸過程中的任何變化。如果收到分片的檢驗和有差錯,TCP將丟棄這個分片,並不確認收到此報文段導致對端超時並重發。
ServerClient雙方都建立socket物件socketsocket伺服器一般繫結埠號bind伺服器監聽是否有連線請求listen客戶端請求連結connectaccept收發訊息send/recvsend/recv關閉連線closecloseServerClient

有關函式介紹

根據流程圖,我們知道,在UDP通訊中,使用到了這些函式:socket()bind()sendto()recvfrom()

上面的函式我們在《基於UDP 的通訊》中已經講過,這裡不再重複了。

在TCP中,多了這幾個函式:listen()connect()accept()

伺服器呼叫listen監聽 客戶端的connect

listen成功時,伺服器使用由accept獲取到的新的套接字進行通訊。

當客戶端呼叫connect函式時,將引發三次握手過程:客戶端首先發送SYN請求分組,此時服務端會將請求放入SYN佇列,同時向客戶端傳送ACK確認報文,然後客戶端向服務端再次傳送ACK報文。服務端收到ACK確認報文後,將SYN裡的連線請求移入ACCEPT佇列。此時三次握手結束,即TCP連線成功建立。然後核心通知使用者空間的阻塞的服務程序,服務程序呼叫accept僅僅是從ACCEPT佇列裡取出一個連線而已。也就是說客戶端呼叫connect連線伺服器,與伺服器呼叫accept“接受”連線是兩個獨立的過程。

參考:《服務端不呼叫accept,客戶端connect能否成功?》

listen

c
#include<sys/types.h>          /* See NOTES */
#include<sys/socket.h>

intlisten(int sockfd, int backlog);

描述:將尚未建立連線的socket轉換為被動socket,並監聽發給這個被動socket的connect請求。

引數解析:

sockfd:由socket函式成功返回的值

backlog :核心應該為相應套介面排隊的最大連線個數(不是用來限制socket的最大連線數),一般為以下兩個佇列的大小之和,即未完成三次握手佇列 + 已經完成三次握手佇列。即:TCP模組允許的已完成三次握手過程(TCP模組完成)但還沒來得及被應用程式accept的最大連結數。

核心為任何一個給定的監聽套介面維護兩個佇列:

1、未完成連線佇列(incomplete connection queue),每個這樣的SYN分節對應其中一項:已由某個客戶發出併到達伺服器,而伺服器正在等待完成相應的TCP三次握手過程。這些套介面處於SYN_RCVD狀態。

2、已完成連線佇列(completed connection queue),每個已完成TCP三次握手過程的客戶對應其中一項。這些套介面處於ESTABLISHED狀態。

當來自客戶的SYN到達時,TCP在 未完成連線佇列 中建立一個新項,然後響應以三次握手的第二個分節:伺服器的SYN響應,其中稍帶對客戶SYN的ACK(即SYN+ACK)。這一項一直保留在未完成連線佇列中,直到三路握手的第三個分節(客戶對伺服器SYN的ACK)到達或者該項超時為止(曾經源自Berkeley的實現為這些未完成連線的項設定的超時值為75秒)。如果三路握手正常完成,該項就從未完成連線佇列移到已完成連線佇列的隊尾。當程序呼叫accept時,已完成連線佇列中的隊頭項將返回給程序,或者如果該佇列為空,那麼程序將被投入睡眠,直到TCP在該佇列中放入一項才喚醒它。

返回值:成功返回0,失敗返回-1,置errno:

  • EADDRINUSE:另一個套接字已在同一埠上偵聽。
  • EADDRINUSE:(Internet域套接字)sockfd引用的套接字以前沒有繫結到地址,在嘗試將其繫結到臨時埠時,確定臨時埠範圍中的所有埠號當前都在使用中。
  • EBADF:引數sockfd不是有效的描述符。
  • ENOTSOCK:檔案描述符sockfd沒有引用套接字。
  • EOPNOTSUPP:套接字的型別不支援listen()操作。

主動socket和被動socket

一般來說,使用socket函式建立的socket預設是主動socket,這意味著一個主動的socket可以呼叫connect跟一個被動socket建立一個連線,對主動socket來說,這叫主動開啟。

被動socket是一個通過呼叫listen函式監聽要發起連線的socket,當被動socket接受一個連線通常稱為被動開啟。

在大多數網路程式中,服務端會作為被動socket被動接受連線,而客戶端會作為主動socket主動發起連線。

服務端通過socket函式建立的socket是主動socket,而listen函式就是把這個還未接受連線的主動socket轉換為被動socket,因為服務端只需要被動接受客戶端的連線請求。

Linux系統設定未連線佇列最大數限制

linux系統tcp/ip協議棧有個選項可以設定未連線佇列大小限制tcp_max_syn_backlog

可以通過命令:cat /proc/sys/net/ipv4/tcp_max_syn_backlog檢視

Linux 系統中提供somaxconn這個引數,它定義了系統中每一個埠最大的監聽佇列的長度,這是個全域性的引數,預設值為128

可以通過命令:cat /proc/sys/net/core/somaxconn檢視

connect

c
#include<sys/types.h>          /* See NOTES */
#include<sys/socket.h>

intconnect(int sockfd, const struct sockaddr *addr,
            socklen_t addrlen);

描述:連線一個被動socket

引數解析:

sockfd:主動socket

addr:目的地址

addrlen:地址屬性的長度(addr的大小)

返回值:成功返回0,失敗返回-1,置errno:

  • EAFNOSUPPORT:傳遞的地址在其sau family欄位中沒有正確的地址系列。
  • EAGAIN :路由快取中的條目不足。
  • EALREADY:套接字未阻塞,上一次連線嘗試尚未完成。
    EBADF:檔案描述符不是描述符表中的有效索引。
  • ECONNREFUSED:沒有人監聽遠端地址。
  • EFAULT :套接字結構地址在使用者的地址空間之外。
  • EINPROGRESS:套接字未阻塞,無法立即完成連線。可以通過選擇要寫入的套接字來選擇(2)或輪詢(2)以完成。
  • EINTR :系統呼叫被捕獲的訊號中斷。
  • EISCONN:套接字已連線。
  • ENETUNREACH:無法訪問網路。
  • ENOTSOCK:sockfd不是套接字。
  • EPROTOTYPE:套接字型別不支援請求的通訊協議。例如,在嘗試將UNIX域資料報套接字連線到流套接字時,可能會發生此錯誤。
  • ETIMEDOUT:嘗試連線時超時。伺服器可能太忙,無法接受新連線。請注意,對於IP套接字,當伺服器上啟用Syncookie時,超時可能非常長。

accept

c
#include<sys/types.h>          /* See NOTES */
#include<sys/socket.h>

intaccept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include<sys/socket.h>

intaccept4(int sockfd, struct sockaddr *addr,
            socklen_t *addrlen, int flags);

描述:從核心的ACCEPT佇列中取出對應被動socket的連線,關於連線的有關屬性填入addr中。

引數解析:

sockfd:對應的被動socket

addr:儲存連線方的addr屬性的容器

len:addr屬性的長度

返回值:成功返回可用於連線的新socket,失敗返回-1,置errno:

此外,可能會返回新套接字的網路錯誤以及為協議定義的網路錯誤。各種Linux核心可以返回其他錯誤,例如ENOSR、ESOCKTNOSUPPORT、EPROTONOSUPPORT、ETIMEDOUT。在跟蹤期間可以看到值ERESTARTSYS。

  • EMFILE :已達到開啟的檔案描述符數的每個程序限制

  • ENFILE :已達到系統範圍內開啟檔案總數的限制

  • ENOBUFS, ENOMEM:沒有足夠的可用記憶體。這通常意味著記憶體分配受到套接字緩衝區限制,而不是系統記憶體的限制

  • ENOTSOCK sockfd不是套接字

  • EOPNOTSUPP 引用的套接字不是SOCK_STREAM型別

  • EPROTO :協議錯誤

  • EPERM (Linux) :防火牆規則禁止連線

例程

我們簡單地進行一次TCP對答通訊的實現

server.c

c
/*
#    Copyright By Schips, All Rights Reserved
#    https://gitee.com/schips/
#
#    File Name:  server.c
#    Created  :  Sat 21 Mar 2020 04:43:39 PM CST
*/

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>          /* See NOTES */
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

typedef struct _info {
    char name[10];
    char text[54];
}info;

intmain(int argc, char *argv[]){
    int my_socket; 
    unsigned int len;
    int ret;

    // 建立套接字
    my_socket = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP socket
    if(my_socket == -1) { perror("Socket"); }
    printf("Creat a socket :[%d]\n", my_socket);

    // 用於接收訊息
    info buf ={0};

    // 指定地址
    structsockaddr_inaddr = {0};
    addr.sin_family = AF_INET;  // 地址協議族
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");   //指定 IP地址
        addr.sin_port = htons(12345); //指定埠號

    // 伺服器 繫結
    bind(my_socket, (struct sockaddr *)&addr, sizeof(addr));

    // my_socket 只用於監聽
    ret = listen(my_socket, 10);
    if(-1 == ret) { perror("listen"); }
    printf("Listening\n");

    int new_socket;
    structsockaddr_innew = {0};
    int new_addr_size;
    // accept以後會返回一個新的套接字,用於與客戶端通訊
    new_socket = accept(my_socket, (struct sockaddr*)&new, &new_addr_size);
    printf("New socket is %d\n", new_socket);
    perror("accept");

    // 接收並列印訊息
    //recvfrom(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
    recv(new_socket, &buf, sizeof(buf), 0);
        perror("recvfrom");

    printf("%s: %s\n", buf.name, buf.text);
  
    // 回覆訊息
    sprintf(buf.name, "Server");
    sprintf(buf.text, "Had recvied your message");
    //sendto(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
    send(new_socket, &buf, sizeof(buf), 0);
    perror("sendto");

    // 關閉連線
    //shutdown(my_socket, SHUT_RDWR); perror("shutdown");
    close(new_socket); perror("close");
    return close(my_socket); perror("close");
    printf("%d\n", errno);
    return errno;
}

client.c

c
/*
#    Copyright By Schips, All Rights Reserved
#    https://gitee.com/schips/
#
#    File Name:  client.c
#    Created  :  Sat 21 Mar 2020 04:43:39 PM CST
*/

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>          /* See NOTES */
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

typedef struct _info {
    char name[10];
    char text[54];
}info;

intmain(int argc, char *argv[]){
    int my_socket;
    unsigned int len;
    int ret;

    // 建立套接字
    my_socket = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP socket
    if(my_socket == -1) { perror("Socket"); }
    printf("Creat a socket :[%d]\n", my_socket);

    // 用於接收訊息
    info buf ={0};

    // 指定地址
    structsockaddr_inaddr = {0};
    addr.sin_family = AF_INET;  // 地址協議族
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");   //指定 IP地址
    addr.sin_port = htons(12345); //指定埠號
    
    // 用於連線伺服器
    connect(my_socket, (struct sockaddr *)(&addr), sizeof(struct sockaddr_in));
    if(-1 == ret) { perror("connect"); }
    printf("connected\n");

    // 回覆訊息
    sprintf(buf.name, "Client");
    sprintf(buf.text, "Hello tcp text.");
    //sendto(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
    send(my_socket, &buf, sizeof(buf), 0);
    perror("sendto");

    // 接收並列印訊息
    //recvfrom(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
    recv(my_socket, &buf, sizeof(buf), 0);
    perror("recvfrom");

    printf("%s: %s\n", buf.name, buf.text);

    // 關閉連線
    //shutdown(my_socket, SHUT_RDWR); perror("shutdown");

    return close(my_socket); perror("close");
    printf("%d\n", errno);
    return errno;
}

知識

TCP(Transmission Control Protoco 傳輸控制協議)。

TCP是一種面向廣域網的通訊協議,目的是在跨越多個網路通訊時,為兩個通訊端點之間提供一條具有下列特點的通訊方式:

  • 基於流的方式;

  • 面向連線;

  • 可靠通訊方式;

  • 在網路狀況不佳的時候儘量降低系統由於重傳帶來的頻寬開銷;

  • 通訊連線維護是面向通訊的兩個端點的,而不考慮中間網段和節點。

為滿足TCP協議的這些特點,TCP協議做了如下的規定:

  • 資料分片:在傳送端對使用者資料進行分片,在接收端進行重組,由TCP確定分片的大小並控制分片和重組;
  • 到達確認:接收端接收到分片資料時,根據分片資料序號向傳送端傳送一個確認;
  • 超時重發:傳送方在傳送分片時啟動超時定時器,如果在定時器超時之後沒有收到相應的確認,重發分片;
  • 滑動視窗:TCP連線每一方的接收緩衝空間大小都固定,接收端只允許另一端傳送接收端緩衝區所能接納的資料,TCP在滑動視窗的基礎上提供流量控制,防止較快主機致使較慢主機的緩衝區溢位;
  • 失序處理:作為IP資料報來傳輸的TCP分片到達時可能會失序,TCP將對收到的資料進行重新排序,將收到的資料以正確的順序交給應用層;
  • 重複處理:作為IP資料報來傳輸的TCP分片會發生重複,TCP的接收端必須丟棄重複的資料;
  • 資料校驗:TCP將保持它首部和資料的檢驗和,這是一個端到端的檢驗和,目的是檢測資料在傳輸過程中的任何變化。如果收到分片的檢驗和有差錯,TCP將丟棄這個分片,並不確認收到此報文段導致對端超時並重發。
ServerClient雙方都建立socket物件socketsocket伺服器一般繫結埠號bind伺服器監聽是否有連線請求listen客戶端請求連結connectaccept收發訊息send/recvsend/recv關閉連線closecloseServerClient

有關函式介紹

根據流程圖,我們知道,在UDP通訊中,使用到了這些函式:socket()bind()sendto()recvfrom()

上面的函式我們在《基於UDP 的通訊》中已經講過,這裡不再重複了。

在TCP中,多了這幾個函式:listen()connect()accept()

伺服器呼叫listen監聽 客戶端的connectlisten成功時,伺服器使用由accept獲取到的新的套接字進行通訊。

當客戶端呼叫connect函式時,將引發三次握手過程:客戶端首先發送SYN請求分組,此時服務端會將請求放入SYN佇列,同時向客戶端傳送ACK確認報文,然後客戶端向服務端再次傳送ACK報文。服務端收到ACK確認報文後,將SYN裡的連線請求移入ACCEPT佇列。此時三次握手結束,即TCP連線成功建立。然後核心通知使用者空間的阻塞的服務程序,服務程序呼叫accept僅僅是從ACCEPT佇列裡取出一個連線而已。也就是說客戶端呼叫connect連線伺服器,與伺服器呼叫accept“接受”連線是兩個獨立的過程。

參考:《服務端不呼叫accept,客戶端connect能否成功?》

listen

c
#include<sys/types.h>          /* See NOTES */
#include<sys/socket.h>

intlisten(int sockfd, int backlog);

描述:將尚未建立連線的socket轉換為被動socket,並監聽發給這個被動socket的connect請求。

引數解析:

sockfd:由socket函式成功返回的值

backlog :核心應該為相應套介面排隊的最大連線個數(不是用來限制socket的最大連線數),一般為以下兩個佇列的大小之和,即未完成三次握手佇列 + 已經完成三次握手佇列。即:TCP模組允許的已完成三次握手過程(TCP模組完成)但還沒來得及被應用程式accept的最大連結數。

核心為任何一個給定的監聽套介面維護兩個佇列:

1、未完成連線佇列(incomplete connection queue),每個這樣的SYN分節對應其中一項:已由某個客戶發出併到達伺服器,而伺服器正在等待完成相應的TCP三次握手過程。這些套介面處於SYN_RCVD狀態。

2、已完成連線佇列(completed connection queue),每個已完成TCP三次握手過程的客戶對應其中一項。這些套介面處於ESTABLISHED狀態。

當來自客戶的SYN到達時,TCP在 未完成連線佇列 中建立一個新項,然後響應以三次握手的第二個分節:伺服器的SYN響應,其中稍帶對客戶SYN的ACK(即SYN+ACK)。這一項一直保留在未完成連線佇列中,直到三路握手的第三個分節(客戶對伺服器SYN的ACK)到達或者該項超時為止(曾經源自Berkeley的實現為這些未完成連線的項設定的超時值為75秒)。如果三路握手正常完成,該項就從未完成連線佇列移到已完成連線佇列的隊尾。當程序呼叫accept時,已完成連線佇列中的隊頭項將返回給程序,或者如果該佇列為空,那麼程序將被投入睡眠,直到TCP在該佇列中放入一項才喚醒它。

返回值:成功返回0,失敗返回-1,置errno:

  • EADDRINUSE:另一個套接字已在同一埠上偵聽。
  • EADDRINUSE:(Internet域套接字)sockfd引用的套接字以前沒有繫結到地址,在嘗試將其繫結到臨時埠時,確定臨時埠範圍中的所有埠號當前都在使用中。
  • EBADF:引數sockfd不是有效的描述符。
  • ENOTSOCK:檔案描述符sockfd沒有引用套接字。
  • EOPNOTSUPP:套接字的型別不支援listen()操作。

主動socket和被動socket

一般來說,使用socket函式建立的socket預設是主動socket,這意味著一個主動的socket可以呼叫connect跟一個被動socket建立一個連線,對主動socket來說,這叫主動開啟。

被動socket是一個通過呼叫listen函式監聽要發起連線的socket,當被動socket接受一個連線通常稱為被動開啟。

在大多數網路程式中,服務端會作為被動socket被動接受連線,而客戶端會作為主動socket主動發起連線。

服務端通過socket函式建立的socket是主動socket,而listen函式就是把這個還未接受連線的主動socket轉換為被動socket,因為服務端只需要被動接受客戶端的連線請求。

Linux系統設定未連線佇列最大數限制

linux系統tcp/ip協議棧有個選項可以設定未連線佇列大小限制tcp_max_syn_backlog

可以通過命令:cat /proc/sys/net/ipv4/tcp_max_syn_backlog檢視

Linux 系統中提供somaxconn這個引數,它定義了系統中每一個埠最大的監聽佇列的長度,這是個全域性的引數,預設值為128

可以通過命令:cat /proc/sys/net/core/somaxconn檢視

connect

c
#include<sys/types.h>          /* See NOTES */
#include<sys/socket.h>

intconnect(int sockfd, const struct sockaddr *addr,
            socklen_t addrlen);

描述:連線一個被動socket

引數解析:

sockfd:主動socket

addr:目的地址

addrlen:地址屬性的長度(addr的大小)

返回值:成功返回0,失敗返回-1,置errno:

  • EAFNOSUPPORT:傳遞的地址在其sau family欄位中沒有正確的地址系列。
  • EAGAIN :路由快取中的條目不足。
  • EALREADY:套接字未阻塞,上一次連線嘗試尚未完成。
    EBADF:檔案描述符不是描述符表中的有效索引。
  • ECONNREFUSED:沒有人監聽遠端地址。
  • EFAULT :套接字結構地址在使用者的地址空間之外。
  • EINPROGRESS:套接字未阻塞,無法立即完成連線。可以通過選擇要寫入的套接字來選擇(2)或輪詢(2)以完成。
  • EINTR :系統呼叫被捕獲的訊號中斷。
  • EISCONN:套接字已連線。
  • ENETUNREACH:無法訪問網路。
  • ENOTSOCK:sockfd不是套接字。
  • EPROTOTYPE:套接字型別不支援請求的通訊協議。例如,在嘗試將UNIX域資料報套接字連線到流套接字時,可能會發生此錯誤。
  • ETIMEDOUT:嘗試連線時超時。伺服器可能太忙,無法接受新連線。請注意,對於IP套接字,當伺服器上啟用Syncookie時,超時可能非常長。

accept

c
#include<sys/types.h>          /* See NOTES */
#include<sys/socket.h>

intaccept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include<sys/socket.h>

intaccept4(int sockfd, struct sockaddr *addr,
            socklen_t *addrlen, int flags);

描述:從核心的ACCEPT佇列中取出對應被動socket的連線,關於連線的有關屬性填入addr中。

引數解析:

sockfd:對應的被動socket

addr:儲存連線方的addr屬性的容器

len:addr屬性的長度

返回值:成功返回可用於連線的新socket,失敗返回-1,置errno:

此外,可能會返回新套接字的網路錯誤以及為協議定義的網路錯誤。各種Linux核心可以返回其他錯誤,例如ENOSR、ESOCKTNOSUPPORT、EPROTONOSUPPORT、ETIMEDOUT。在跟蹤期間可以看到值ERESTARTSYS。

  • EMFILE :已達到開啟的檔案描述符數的每個程序限制

  • ENFILE :已達到系統範圍內開啟檔案總數的限制

  • ENOBUFS, ENOMEM:沒有足夠的可用記憶體。這通常意味著記憶體分配受到套接字緩衝區限制,而不是系統記憶體的限制

  • ENOTSOCK sockfd不是套接字

  • EOPNOTSUPP 引用的套接字不是SOCK_STREAM型別

  • EPROTO :協議錯誤

  • EPERM (Linux) :防火牆規則禁止連線

例程

我們簡單地進行一次TCP對答通訊的實現

server.c

c
/*
#    Copyright By Schips, All Rights Reserved
#    https://gitee.com/schips/
#
#    File Name:  server.c
#    Created  :  Sat 21 Mar 2020 04:43:39 PM CST
*/

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>          /* See NOTES */
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

typedef struct _info {
    char name[10];
    char text[54];
}info;

intmain(int argc, char *argv[]){
    int my_socket; 
    unsigned int len;
    int ret;

    // 建立套接字
    my_socket = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP socket
    if(my_socket == -1) { perror("Socket"); }
    printf("Creat a socket :[%d]\n", my_socket);

    // 用於接收訊息
    info buf ={0};

    // 指定地址
    structsockaddr_inaddr = {0};
    addr.sin_family = AF_INET;  // 地址協議族
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");   //指定 IP地址
        addr.sin_port = htons(12345); //指定埠號

    // 伺服器 繫結
    bind(my_socket, (struct sockaddr *)&addr, sizeof(addr));

    // my_socket 只用於監聽
    ret = listen(my_socket, 10);
    if(-1 == ret) { perror("listen"); }
    printf("Listening\n");

    int new_socket;
    structsockaddr_innew = {0};
    int new_addr_size;
    // accept以後會返回一個新的套接字,用於與客戶端通訊
    new_socket = accept(my_socket, (struct sockaddr*)&new, &new_addr_size);
    printf("New socket is %d\n", new_socket);
    perror("accept");

    // 接收並列印訊息
    //recvfrom(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
    recv(new_socket, &buf, sizeof(buf), 0);
        perror("recvfrom");

    printf("%s: %s\n", buf.name, buf.text);
  
    // 回覆訊息
    sprintf(buf.name, "Server");
    sprintf(buf.text, "Had recvied your message");
    //sendto(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
    send(new_socket, &buf, sizeof(buf), 0);
    perror("sendto");

    // 關閉連線
    //shutdown(my_socket, SHUT_RDWR); perror("shutdown");
    close(new_socket); perror("close");
    return close(my_socket); perror("close");
    printf("%d\n", errno);
    return errno;
}

client.c

c
/*
#    Copyright By Schips, All Rights Reserved
#    https://gitee.com/schips/
#
#    File Name:  client.c
#    Created  :  Sat 21 Mar 2020 04:43:39 PM CST
*/

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>          /* See NOTES */
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

typedef struct _info {
    char name[10];
    char text[54];
}info;

intmain(int argc, char *argv[]){
    int my_socket;
    unsigned int len;
    int ret;

    // 建立套接字
    my_socket = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP socket
    if(my_socket == -1) { perror("Socket"); }
    printf("Creat a socket :[%d]\n", my_socket);

    // 用於接收訊息
    info buf ={0};

    // 指定地址
    structsockaddr_inaddr = {0};
    addr.sin_family = AF_INET;  // 地址協議族
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");   //指定 IP地址
    addr.sin_port = htons(12345); //指定埠號
    
    // 用於連線伺服器
    connect(my_socket, (struct sockaddr *)(&addr), sizeof(struct sockaddr_in));
    if(-1 == ret) { perror("connect"); }
    printf("connected\n");

    // 回覆訊息
    sprintf(buf.name, "Client");
    sprintf(buf.text, "Hello tcp text.");
    //sendto(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
    send(my_socket, &buf, sizeof(buf), 0);
    perror("sendto");

    // 接收並列印訊息
    //recvfrom(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
    recv(my_socket, &buf, sizeof(buf), 0);
    perror("recvfrom");

    printf("%s: %s\n", buf.name, buf.text);

    // 關閉連線
    //shutdown(my_socket, SHUT_RDWR); perror("shutdown");

    return close(my_socket); perror("close");
    printf("%d\n", errno);
    return errno;
}