主頁 > 後端開發 > Linux網路編程:socket實作client/server通信

Linux網路編程:socket實作client/server通信

2023-05-12 07:23:18 後端開發

一、問題引入

閱讀UNIX網路編程 卷1:套接字聯網API 第3版的前4個章節,覺得有必要對書籍上的原始碼案例進行復現,并推敲TCP的C/S通信程序,

二、解決程序

2-1 server

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>

#define PORT 8887
#define QUEUE 20
#define BUFFER_SIZE 1024

int main()
{
    // 定義sockfd, AF_INET:IPv4協議,SOCK_STREAM:位元組流套接字
    int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

    // 定義sockaddr_in
    struct sockaddr_in server_sockaddr;
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    // bind,成功回傳0,出錯回傳-1
    if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1)
    {
        perror("bind failure");
        exit(1);
    }

    // listen,成功回傳0,出錯回傳-1
    if (listen(server_sockfd, QUEUE) == -1)
    {
        perror("listen failure");
        exit(1);
    }

    // 客戶端套接字
    char buffer[BUFFER_SIZE];
    struct sockaddr_in client_addr;
    socklen_t length = sizeof(client_addr);

    // 成功回傳非負描述字,出錯回傳-1
    // accept()被阻塞,等待客戶端連接,一旦客戶端和服務器完成TCP三次握手,accept()回傳一個已連接描述值
    int conn = accept(server_sockfd, (struct sockaddr *)&client_addr, &length);
    if (conn < 0)
    {
        perror("connect failure");
        exit(1);
    }

    printf("**************************\n");
    printf("accept successful !!!\n");
    char ipbuf[64] = {0};
    printf("client || ip: %s, port: %d\n",
           inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
           ntohs(client_addr.sin_port));
    printf("**************************\n");
    while (1)
    {
        memset(buffer, 0, sizeof(buffer));
        // 接收
        int len = recv(conn, buffer, sizeof(buffer), 0);
        if (strcmp(buffer, "exit") == 0 || strcmp(buffer, "q") == 0)
            break;
        if(len > 0)
        {
            printf("Recv:%s (%d Byte)\n", buffer, strlen(buffer));
        }
    }
    close(conn);
    close(server_sockfd);
    return 0;
}

2-2 client

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>

#define PORT 8887
#define BUFFER_SIZE 1024

int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    /// 連接服務器,成功回傳0,錯誤回傳-1
    if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
    {
        perror("connect");
        exit(1);
    }
    printf("**************************\n");
    printf("connect successful !!!\n");
    char ipbuf[64] = {0};
    printf("server || ip: %s, port: %d\n",
           inet_ntop(AF_INET, &servaddr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
           ntohs(servaddr.sin_port));
    printf("now you can send message to server\n");
    printf("**************************\n");
    char sendbuf[BUFFER_SIZE];
    while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
    {
        if(sendbuf[strlen(sendbuf)-1] == '\n')
            sendbuf[strlen(sendbuf)-1] = '\0';
        // 發送
        send(sockfd, sendbuf, strlen(sendbuf), 0);
        if (strcmp(sendbuf, "exit") == 0 || strcmp(sendbuf, "q") == 0)
            break;
        memset(sendbuf, 0, sizeof(sendbuf));
    }
    close(sockfd);
    return 0;
}

2-3 編譯和運行測驗

?? 測驗環境:CentOS7.6 x64

編譯server.c 和 client.c

gcc server.c -g -std=gnu99 -o servergcc client.c -g -std=gnu99 -o client

運行測驗:

?? server.c僅僅實作對單個客戶端的連接請求,接受客戶端的請求訊息,但不作應答

client:

server:

2-4 socket 流程圖

所有客戶和服務器都從呼叫socket() 開始,它回傳一個套接字描述符,客戶隨后呼叫connect() ,服務器則呼叫bind() 、listen() 和 accept() ,套接字通常使用標準的close()函式關閉,

2-5 socket API 函式

  • IPv4套接字地址結構
// struct sockaddr原型宣告在如下頭檔案中
#include <sys/socket.h>
// uint8_t 原型在如下頭檔案中
#include <sys/types.h>

struct sockaddr
{
  uint8_t     sa_len;
  sa_family_t sa_family;   /* address family: AF_xxx value */
  char        sa_data[14]; /* protocol specific address */
};
// sockaddr_in原型宣告在如下頭檔案中
#include <netinet/in.h>
// uint8_t 原型在如下頭檔案中
#include <sys/types.h>
// in_addr_t、in_port_t的原型宣告在如下頭檔案中
#include <netinet/in.h>
// sa_family_t、socklen_t的原型宣告在如下頭檔案中
#include <sys/socket.h>

struct in_addr
{
  in_addr_t s_addr; /* 32-bit IPv4 address */
                    /* network byte ordered */
};

struct sockaddr_in
{
  uint8_t        sin_len;     /* length of structure (16) */
  sa_family_t    sin_family;  /* AF_INET */
  in_port_t      sin_port;    /* 16-bit TCP or UDP port number */
   
  struct in_addr sin_addr;    /* 32-bit IPv4 address */
                              /* network byte ordered */
  char           sin_zero[8]; /* unused */
};
  • socket函式
// socket()原型宣告在如下頭檔案中
#include <sys/socket.h>

/*
 * socket函式的功能是:申請套接字資源
 * @param family 指協議族,可選項有如下:
 * AF_INET:IPv4協議, AF_INET6:IPv6, AF_LOCAL:Unix域協議, AF_ROUTE:路由套接字, AF_KEY:密鑰套接字
 * @param type 指套接字型別,可選項有如下:
 * SOCK_STREAM:位元組流套接字, SOCK_DGRAM:資料報套接字, SOCK_SEQPACKET:有序分組套接字, SOCK_RAW:原始套接字
 * @param protocol 指某個協議型別常值,一般默認設定為0,可選項有如下:
 * IPPROTO_TCP:TCP傳輸協議, IPPROTO_UDP:UDP傳輸協議, IPPROTO_SCTP:SCTP傳輸協議
 * @return 若成功回傳非負值,否者回傳-1,回傳值稱之為套接字描述符(監聽套接字)
 */
int socket(int family, int type, int protocol);
  • connect函式

?? 客戶端呼叫connect函式將激發TCP的三次握手

// connect()原型宣告在如下頭檔案中
#include <sys/socket.h>

/*
 * connect函式的功能是:與TCP服務器建立連接
 * @param sockfd 使用socket函式獲取的套接字描述符
 * @param *servaddr 一個指向套接字地址結構的指標
 * @param addrlen 套接字結構的大小
 * @return 若成功則回傳0,出錯回傳-1
 */
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
  • bind函式

?? 服務器在啟動時,利用bind函式捆綁一個固定眾所周知的埠號,但對于客戶端不需要,可以讓內核去分配一個臨時埠號

// bind()原型宣告在如下頭檔案中
#include <sys/socket.h>

/*
 *
 * @param sockfd 使用socket函式獲取的套接字描述符
 * @param *myaddr 一個指向特定協議的地址結構的指標
 * @param addrlen 特定協議的結構的大小
 * @return 若成功則回傳0,出錯回傳-1
 */
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
  • listen函式

?? listen函式只能由TCP服務器呼叫

// listen()原型宣告在如下頭檔案中
#include <sys/socket.h>

/*
 * listen函式把一個未連接的套接字轉換成一個被動套接字
 * @param sockfd 使用socket函式獲取的套接字描述符
 * @param backlog 套接字排隊的最大連接個數
 * @return 若成功則回傳0,出錯回傳-1
 */
int listen(int sockfd, int backlog);
  • accept函式

?? accept函式只能由TCP服務器呼叫,回傳一個已連接套接字,若有客戶端發起連接請求,并完成三次握手,accept函式被觸發

// accept()原型宣告在如下頭檔案中
#include <sys/socket.h>

/*
*
* @param sockfd 使用socket函式獲取的套接字描述符
* @param *cliaddr 一個指向特定協議的地址結構的指標
* @param *addrlen 特定協議的結構的大小
* @return 若成功則回傳非負描述符,出錯則回傳-1,回傳值稱之為:已連接套接字
*/
int accpet(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

三、反思總結

本隨筆案例僅實作了TCP服務器針對單個客戶端的應答,但實際中TCP服務器要為成千上萬個客戶端服務的,

文中的重點介紹Linux Socket的API,但關于TCP/IP才是Socket的基石,

四、參考參考

UNIX網路編程 卷1:套接字聯網API 第3版

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/552178.html

標籤:C

上一篇:高效c語言2物件、函式和型別

下一篇:返回列表

標籤雲
其他(158833) Python(38125) JavaScript(25413) Java(18025) C(15226) 區塊鏈(8264) C#(7972) AI(7469) 爪哇(7425) MySQL(7177) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5871) 数组(5741) R(5409) Linux(5338) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4570) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2432) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1972) 功能(1967) Web開發(1951) HtmlCss(1935) python-3.x(1918) C++(1915) 弹簧靴(1913) xml(1889) PostgreSQL(1875) .NETCore(1860) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Linux網路編程:socket實作client/server通信

    一、問題引入 閱讀UNIX網路編程 卷1:套接字聯網API 第3版的前4個章節,覺得有必要對書籍上的原始碼案例進行復現,并推敲TCP的C/S通信程序。 二、解決程序 2-1 server #include <sys/types.h> #include <sys/socket.h> #include < ......

    uj5u.com 2023-05-12 07:23:18 more
  • 高效c語言2物件、函式和型別

    本章中,你將學習物件、函式和型別。我們將研究如何宣告變數(有識別符號的物件)和函式,獲取物件的地址,并對這些物件指標的解參考。你已經看到了C語言程式員可用的一些型別, C語言中的型別不是物件就是函式。 物件、函式、型別和指標 物件是你可以表示數值的存盤。準確地說,C標準(ISO/IEC 9899:20 ......

    uj5u.com 2023-05-12 07:23:01 more
  • C++ 入門

    001 c++ 如何作業 任何以 # 開頭的陳述句,都是預處理陳述句,所謂的預處理陳述句,在編譯之前,就已經被處理了 關鍵字 include:找到 <> 檔案(通常稱為“頭檔案”),然后將 <> 中的所有內容拷貝到現在的檔案里 main()比較特殊,雖然它的回傳值型別是 int,但它不一定需要回傳值,如果 ......

    uj5u.com 2023-05-12 07:21:48 more
  • 在 IDEA 中創建 Spring Boot 專案的方式(詳細步驟教程)

    開發環境 以下是我的開發環境 JDK 1.8 Maven 3.6.3 IDEA 2019(2019 無所畏懼,即使現在已經 2023 年了哈哈哈) 使用 Maven 的方式創建 Spring Boot 專案 下面的內容可能會因 IDEA 版本不同,而有些選項不同,但是大同小異。 1. 打開 IDEA ......

    uj5u.com 2023-05-11 07:32:53 more
  • Java for回圈標簽跳轉到指定位置

    大家是否見過這種for回圈,在for回圈前加了個標記的: outerLoop: for (; ; ) { for (; ; ) { break outerLoop; } } 我之前有一次在公司業務代碼中見過有這種寫法的,沒在意,今天在看JDK執行緒池的代碼時,又看到ThreadPoolExecutor ......

    uj5u.com 2023-05-11 07:32:35 more
  • 訊息推送平臺的實時數倉?!flink消費kafka訊息入到hive

    大家好,3y啊。好些天沒更新了,并沒有偷懶,只不過一直在安裝環境,差點都想放棄了。 上一次比較大的更新是做了austin的預覽地址,把企業微信的應用和機器人訊息各種的訊息型別和功能給完善了。上一篇文章也提到了,austin常規的功能已經更新得差不多了,剩下的就是各種細節的完善。 不知道大家還記不記得 ......

    uj5u.com 2023-05-11 07:32:30 more
  • Hibernate 基本操作、懶加載以及快取

    上一篇咱們介紹了 Hibernate 以及寫了一個 Hibernate 的工具類,快速入門體驗了一波 Hibernate 的使用,我們只需通過 Session 物件就能實作資料庫的操作了。

    現在,這篇介紹使用 Hibernate 進行基本的 CRUD、懶加載以及快取的知識。 ......

    uj5u.com 2023-05-11 07:32:24 more
  • 【Visual Leak Detector】核心原始碼剖析(VLD 2.5.1)

    使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。本篇對 VLD 2.5.1 原始碼做記憶體泄漏檢測的思路進行剖析。 ......

    uj5u.com 2023-05-11 07:32:18 more
  • 34基于Java的學生選課系統或學生課程管理系統

    基于java的學生課程管理系統,基于java的學生選課系統,javaWeb的學生選課系統,學生成績管理系統,課表管理系統,學院管理系統,大學生選課系統設計與實作,網上選課系統,課程成績打分。 ......

    uj5u.com 2023-05-11 07:32:13 more
  • 最佳實踐:路徑路由匹配規則的設計與實作

    本文設計并實作了一種專用于路徑路由匹配的規則,以一種簡單而通用的方式描述一組路徑的特征,來簡化這種場景路由描述難度,讓小白可以快速學習并上手。 ......

    uj5u.com 2023-05-11 07:25:44 more