socket 開發 - 那些年用過的基礎 API

来源:http://www.cnblogs.com/life2refuel/archive/2017/11/11/7818119.html
-Advertisement-
Play Games

前言 - 思考還是 socket 寫過一點點, 總感覺很彆扭. 例如 read, recv, recvfrom 這些為啥這麼奇葩. 這是 linux 的設計嗎. 這種強糅合的 read 代碼, '帶壞'了多少人. 想起很久以前看過的 <<UNIX痛恨者手冊>>, 外加上常寫點跨平臺 庫. 不得不思考 ...


--------------------------------------------------------------------------------------------------------------------------------------------------

前言 - 思考還是

--------------------------------------------------------------------------------------------------------------------------------------------------

  socket 寫過一點點,  總感覺很彆扭. 例如 read, recv, recvfrom 這些為啥這麼奇葩. 這是 linux 的設計嗎.

這種強糅合的 read 代碼, '帶壞'了多少人. 想起很久以前看過的 <<UNIX痛恨者手冊>>, 外加上常寫點跨平臺

庫. 不得不思考設計, 發現 

  1) winds 對於 socket 設計比 linux POSIX 設計理解更加友好一丟丟

  2) linux 性能比 winds 好. (開源哲學 對沖 精英文化)

  3) 應用層是個不完備的域, 不要一條衚衕走不到頭

(備註 : 有一段日子特別討厭 winds, 及其喜歡羡慕 unix, 但是隨著成長認識有了很大變化, 痛恨沒錢沒時間)

--------------------------------------------------------------------------------------------------------------------------------------------------

正文 - 來點證明

--------------------------------------------------------------------------------------------------------------------------------------------------

1. 如果可以不妨多寫點跨平臺, 線程安全的代碼

  不妨舉個爛大街的例子, 我們經常在處理時間的時候直接用  gettimeofday

#include <sys/time.h>

int gettimeofday(struct timeval * tv, struct timezone * tz);

The  functions  gettimeofday() can get and set the time as well as a timezone. 
The tv argument is a struct timeval (as specified in <sys/time.h>):

    struct timeval {
        time_t      tv_sec;     /* seconds */
        suseconds_t tv_usec;    /* microseconds */
    };

and gives the number of seconds and microseconds since the Epoch (see time(2)).  
The tz argument is a struct timezone:

    struct timezone {
        int tz_minuteswest;     /* minutes west of Greenwich */
        int tz_dsttime;         /* type of DST correction */
    };

If either tv or tz is NULL, the corresponding structure is not set or returned.  
(However, compilation warnings will result if tv is NULL.)

The use of the timezone structure is obsolete; 
the tz argument should normally be specified  as  NULL.

只是簡單的得到當前時間秒數和微秒, 附贈一個時區消息. 這個函數一眼看過去, 設計的不優美.

如果希望你的代碼能夠在 winds 上面也奔跑, 可能需要一個移植版本 

#ifdef _MSC_VER

#include <winsock2.h>
// // gettimeofday - Linux sys/time.h 中得到微秒的一種實現 // tv : 返回結果包含秒數和微秒數 // tz : 包含的時區,在winds上這個變數沒有用不返回 // return : 預設返回0 // inline int gettimeofday(struct timeval * tv, void * tz) { struct tm st; SYSTEMTIME wtm; GetLocalTime(&wtm); st.tm_year = wtm.wYear - 1900; st.tm_mon = wtm.wMonth - 1; // winds的計數更好些 st.tm_mday = wtm.wDay; st.tm_hour = wtm.wHour; st.tm_min = wtm.wMinute; st.tm_sec = wtm.wSecond; st.tm_isdst = -1; // 不考慮夏令時 tv->tv_sec = (long)mktime(&st); // 32位使用數據強轉 tv->tv_usec = wtm.wMilliseconds * 1000; // 毫秒轉成微秒 return 0; } #endif

同樣你的工作量已經起來了. 不管高不高效. 總是個下策. 這裡有個更好的主意, 利用  timespec_get 

#include <time.h>


/* Set TS to calendar time based in time base BASE.  */
int
timespec_get (struct timespec *ts, int base)
{
  switch (base)
    {
    case TIME_UTC:
      if (__clock_gettime (CLOCK_REALTIME, ts) < 0)
        return 0;
      break;

    default:
      return 0;
    }

  return base;
}

C11 標準提供的獲取秒和納秒的時間函數, CL 和 GCC clang 都提供了支持. 上面是glibc中一個實現, 是不是很 low.

扯一點

  1.1 寫代碼應該有很強的目的, 非特殊領域應該弱化針對性

  1.2 上層應用, 應該首要向著標準靠攏, 其次是操作系統, 再到編譯器

對於CL 實現了 timespec_get, 應該最主要目的是為了 C++11基礎特性支持, 還有 clang 的實現.

--------------------------------------------------------------------------------------------------------------------------------------------------

2. 你是否和我一樣曾經因為 WSAStartup 大罵微軟SB

  寫 socket winds 一定會有下麵三部曲, 或者兩部曲. 

// 1. CL 編譯器 設置
引入庫 ws2_32.lib 
引入巨集 _WINSOCK_DEPRECATED_NO_WARNINGS

// 2. 載入 socket dll
    WSADATA wsad;
    WSAStartup(WINSOCK_VERSION, &wsad);

// 3. 卸載 
    WSACleanup

當時想, linux 為啥木有上面這麼無意義的操作.  其實其中有個故事, 當初微軟不得了時期, 無法和unix socket互連.

後面來回扯, 其它無數巨擎給其 Winsock 升級, dll 版本變化厲害. 所以有了上面拋給用戶層載入綁定dll版本的操作.

那麼再linux 上面真的不需要嗎. 其實也需要, 只是在運行 _start 時候幫助我們做了. 所以這點上面完全可以這麼

封裝 

//
// socket_init - 單例啟動socket庫的初始化方法
//  
inline void socket_init(void) {
#ifdef _MSC_VER
    WSADATA wsad;
    WSAStartup(WINSOCK_VERSION, &wsad);
#elif __GUNC__
    signal(SIGPIPE, SIG_IGN)  
#endif
}

--------------------------------------------------------------------------------------------------------------------------------------------------

3. 還記得 read, recv, recvfrom 嗎 ?

  還處在一切皆文件支配的恐懼中嗎. 實現這種思路無外乎註冊和switch工廠分支. 那就意味著 read 是個雜糅

體. 在我們只是使用 socket fd 讀取的時候 最終 read -> recv 這個函數調用, 即 recv(fd, buf, sz, 0). 對於後者 

ssize_t
__libc_recv (int fd, void *buf, size_t len, int flags)
{
#ifdef __ASSUME_RECV_SYSCALL
  return SYSCALL_CANCEL (recv, fd, buf, len, flags);
#elif defined __ASSUME_RECVFROM_SYSCALL
  return SYSCALL_CANCEL (recvfrom, fd, buf, len, flags, NULL, NULL);
#else
  return SOCKETCALL_CANCEL (recv, fd, buf, len, flags);
#endif
}

可以表明 recv 和  recvfrom 實現層面有過糾纏. 但是和 read 上層沒有耦合. 所以對於單純 TCP socket 最好的

做法還是 recv 走起. 

       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t recv(int sockfd, void *buf, size_t len, int flags);

       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

其中對於 recv flags 有下麵幾個多平臺都支持的巨集 

#define MSG_OOB         0x1             /* process out-of-band data */
#define MSG_PEEK        0x2             /* peek at incoming message */
#define MSG_DONTROUTE   0x4             /* send without using routing tables */

#if(_WIN32_WINNT >= 0x0502)
#define MSG_WAITALL     0x8             /* do not complete until packet is completely filled */
#endif //(_WIN32_WINNT >= 0x0502)

其實開發中, MSG_OOB 帶外數據, 除非學習. 否則無意義. MSG_PEEK 在以前的 \r\n 切分流協議的時候還用.

現在基本都沒有場景. MSG_WAITALL 可以嘗試一下替代很久以前的 for read. 可以有輕微提升性能. 

recv(fd, buf, len, 0) or recv(fd, buf, len, MSG_WAITALL) 用在你的常說的'高性能'伺服器中而不是大雜燴 read.

--------------------------------------------------------------------------------------------------------------------------------------------------

4. 是否為 listen, accept 好奇過 !

  首先從 listen 和 accept 一對好cp說起. 其實大體過程無外乎 listen -> connect -> accept .  這裡只是從用法

而言首先看 listen 部分 

/*
 *    Perform a listen. Basically, we allow the protocol to do anything
 *    necessary for a listen, and if that works, we mark the socket as
 *    ready for listening.
 */

SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
    struct socket *sock;
    int err, fput_needed;
    int somaxconn;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (sock) {
        somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
        if ((unsigned int)backlog > somaxconn)
            backlog = somaxconn;

        err = security_socket_listen(sock, backlog);
        if (!err)
            err = sock->ops->listen(sock, backlog);

        fput_light(sock->file, fput_needed);
    }
    return err;
}

這段 listen 代碼寫得真好看. 我從中看出來, 內核的思路還是註冊.  對於 backlog 存在一個最大值.

所以對於高性能伺服器 listen 正確的寫法推薦 

listen(fd, SOMAXCONN)

把 listen創建的監聽和鏈接成功隊列大小交給操作系統的內核配置. 

對於 accept 原本想講一講 accept4 + SOCK_NONBLOCK 降低 socket 開發流程. 但是一想起 unix or winds

應該不支持算了. 還是老實 accept + O_NONBLOCK. 

SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
        int __user *, upeer_addrlen)
{
    return sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
}

突然意識到優化就是生命枯竭, 打擊痛點才是王道.

--------------------------------------------------------------------------------------------------------------------------------------------------

5. 你為 select 苦惱過嗎, 去它的 poll 

  其實想想 select 這種函數設計的真的很奇葩. select -> poll -> epoll 從床上到床下經歷過多少夜晚. 

主要是 winds 和 linux 對於 select 完全是兩個函數, 恰巧名字一樣. 通過下麵一個不好的材料瞭解

一個真正的客戶端非阻塞的 connect

select 開發中的用法. 為什麼講 select, 因為方便 winds 移植調試 !! iocp很弔但是真的很難把它和epoll

揉在一起. 因為二者都很意外. epoll 是 61 + 10 分 一個iocp是 90 - 20 分. 如果強揉就要對 socket 行為

讀寫鏈接都需要抽出一層. 但是用 select 只需要抽出 poll 監聽觸發抽出來就可以了. 後期有時間我們

詳細分析 iocp. 當前帶大家感受下 epoll 那些操作.

#include <sys/epoll.h>

int epoll_create(int size);
int epoll_create1(int flags);

epoll_create()  creates a new epoll(7) instance.  Since Linux 2.6.8, the size argument is 
ignored, but must be greater than zero; see NOTES below. epoll_create() returns a file descriptor referring to the
new epoll instance. This file
descriptor is used for all the subse‐quent calls to the epoll interface. When no longer
required, the file descriptor returned by epoll_create() should be closed by
using close(2).
When all file descriptors referring to an epoll instance have been closed, the kernel
destroys the instance and releases the associated resources
for reuse. epoll_create1() If flags is 0, then, other than the fact that the obsolete size argument is dropped,
epoll_create1() is the same as epoll_create(). The following value can be included in
flags to obtain different behavior: EPOLL_CLOEXEC Set the close
-on-exec (FD_CLOEXEC) flag on the new file descriptor. See the description of
the O_CLOEXEC flag in open(2) for reasons why this may be useful.

更加具體是

SYSCALL_DEFINE1(epoll_create, int, size)
{
    if (size <= 0)
        return -EINVAL;

    return sys_epoll_create1(0);
}

從上面可以看出來目前推薦的 epoll_create 用法是 

epoll_create1(EPOLL_CLOEXEC)

不再需要 size這個歷史包袱, 並且 exec 重新開進程的時候能夠 close 返回的 efd 防止句柄泄漏. 

還有一個就是關於 epoll 的 EPOLLIN 預設LT水平觸髮狀態, 另外一個是 EPOLLET 邊緣觸發. 

/* Flags for epoll_create1.  */

#define EPOLL_CLOEXEC O_CLOEXEC


/* Valid opcodes to issue to sys_epoll_ctl() */

#define EPOLL_CTL_ADD 1
#define EPOLL_CTL_DEL 2
#define EPOLL_CTL_MOD 3


/* Epoll event masks */

#define EPOLLIN     0x00000001
#define EPOLLPRI    0x00000002
#define EPOLLOUT    0x00000004
#define EPOLLERR    0x00000008
#define EPOLLHUP    0x00000010

/* Set the Edge Triggered behaviour for the target file descriptor */

#define EPOLLET (1U << 31)

對於普通伺服器例如游戲伺服器, 大型Web系統伺服器 LT 這種高級 select 操作就足夠了.  剛好把驗證

代碼拋給上層. ET 模式的話就需要在框架的網路層處理包異常. 但是安全的高速度的通道通信可以嘗試

一套ET流程交互. epoll 功能特別好理解, 註冊, 監聽, 返回結果. 最噁心就是返回結果的操作. 

不妨展示個局部代碼 

//
// sp_wait - poll 的 wait函數, 等待別人自投羅網
// sp       : poll 模型
// e        : 返回的操作事件集
// max      : e 的最大長度
// return   : 返回待操作事件長度, <= 0 表示失敗
//
int 
sp_wait(poll_t sp, struct event e[], int max) {
    struct epoll_event ev[max];
    int i, n = epoll_wait(sp, ev, max, -1);

    for (i = 0; i < n; ++i) {
        uint32_t flag = ev[i].events;
        e[i].s = ev[i].data.ptr;
        e[i].write = flag & EPOLLOUT;
        e[i].read = flag & (EPOLLIN | EPOLLHUP);
        e[i].error = flag & EPOLLERR;
    }

    return n;
}

一個最簡單的展示結果, 這裡就處理了 EPOLLOUT 和 EPOLLHUP 還有 EPOLLERR 枚舉.

EPOLLHUP 解決 listen -> connect -> accept 占用資源不釋放, 空轉問題. 其實想想最簡單的TCP網路也不好搞.

要求很多 (網路細節, 是個大工程)

--------------------------------------------------------------------------------------------------------------------------------------------------

6. 講的有點泛泛, 文末不妨展示個 不忘初心 

#include <stdio.h>
#include <limits.h>
#include <stdint.h>

//
// 強迫症 × 根治
// file : len.c
// make : gcc -g -Wall -O2 -o love.out love.c
// test : objdump -S love.out
//
int main(int argc, char * argv[]) {
    const char heoo[] = "Hello World";

    for (size_t i = sizeof heoo - 1; i < SIZE_MAX; --i)
        printf(" %c", heoo[i]);
    putchar('\n');

    return 0;
}

--------------------------------------------------------------------------------------------------------------------------------------------------

後記 - 力求走過

--------------------------------------------------------------------------------------------------------------------------------------------------

  錯誤是難免的歡迎指正. 

       昨日重現 : http://music.163.com/m/song?id=3986241&userid=16529894

       The Carpenters - Yesterday Once[SD,854x480].mp4 : https://pan.baidu.com/s/1slA0yU5


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 下麵是轉發博客內容,挺有用的 大家好哈,現在呢靜覓博客已經兩年多啦,可能大家過來更多看到的是爬蟲方面的博文,首先非常感謝大家的支持,希望我的博文對大家有幫助! 之前我寫了一些Python爬蟲方面的文章,Python爬蟲學習系列教程,涉及到了基礎和進階的一些內容,當時更多用到的是Urllib還有正則, ...
  • package com.swift; public class Maopao { //冒泡法 public static void main(String[] args) { int[] arr= {28,2,38,1,390,17,10,9,323}; for(int i=0;iarr[j+1])... ...
  • 2.3 複合類型 2.3.1 引用 引用就是為對象起了個別名,引用類型引用另外一種類型。通過將聲明符寫成&d的形式來定義引用類型,其中d是聲明的變數名。 int ival =1024; int &refVal= ival; // refVal 指向ival (是ival 的另一個名字) 2.3.2 ...
  • 1.創建project django-admin.py startproject myblog 2.創建app python manage.py startapp blog 3. 創建資料庫表 或 更改資料庫表或欄位 Python manage.py makemigrations blog Pyth ...
  • 本文來是從 java web輕量級開發麵試教程從摘錄的。 為什麼要從諸多的Java書籍里選擇這本?為什麼在當前網路信息量如此大的情況下還要買這本書,而不是自己通過查閱網路資料學習?我已經會開發Java Web程式了,有沒有必要買這本書? 筆者有12年的Java經驗,目前是某大型公司的架構師,知道軟體 ...
  • DTOJ 2704:數字互換 解題報告 2017.11.11 第一版 ——由翱翔的逗比w原創 題目信息: 題目描述 輸入兩個數作為交換數,輸出已交換順序後的兩個值。 輸入兩個數作為交換數,輸出已交換順序後的兩個值。 輸入 兩個整數,空格隔開 兩個整數,空格隔開 輸出 交換後的兩個整數,空格隔開 交換 ...
  • #include #define uint unsigned int #define uchar unsigned char sbit wei=P2^7; sbit duan=P2^6; sbit key1=P3^4; sbit key2=P3^5; sbit key3=P3^6; uchar co... ...
  • 本文主要針對Mac的jdk的安裝、環境變數配置、jdk卸載方面進行方法總結。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...