C高級 伺服器內核分析和構建 (一)

来源:http://www.cnblogs.com/life2refuel/archive/2016/04/20/5412934.html
-Advertisement-
Play Games

引言 最經看cloud wind 的 skynet伺服器設計. 覺得特別精妙. 想來個專題先剖析其通信層伺服器內核 的設計原理. 最後再優化.本文是這個小專題的第一部分, 重點會講解對於不同平臺通信基礎的介面封裝. linux是epoll, unix是 kqueue. 沒有封裝window上的ioc ...


引言

   最經看cloud wind 的 skynet伺服器設計. 覺得特別精妙. 想來個專題先剖析其通信層伺服器內核

的設計原理. 最後再優化.本文是這個小專題的第一部分, 重點會講解對於不同平臺通信基礎的介面封裝.

linux是epoll, unix是 kqueue. 沒有封裝window上的iocp模型(瞭解過,沒實際用過).

可能需要以下關於 linux epoll 基礎. 請按個參照.

  1. Epoll在LT和ET模式下的讀寫方式 http://www.ccvita.com/515.html

上面文字寫的很好, 讀的很受用. 代碼外表很漂亮. 但是不對. 主要是 buf越界沒考慮, errno == EINTR要繼續讀寫等沒處理.

可以適合初學觀摩.

  2. epoll 詳解  http://blog.csdn.net/xiajun07061225/article/details/9250579

總結的很詳細, 適合面試. 可以看看. 這個是csdn上的. 扯一點

最經在csdn上給一個大牛留言讓其來博客園, 結果被csdn禁言發評論了. 感覺無辜. 內心很受傷, csdn太武斷了.

  3. epoll 中 EWOULDBLOCK = EAGAIN http://www.cnblogs.com/lovevivi/archive/2013/06/29/3162141.html

這個兩個信號意義和區別.讓其明白epoll的一些註意點.

  4. epoll LT模式的例子 http://bbs.chinaunix.net/thread-1795307-1-1.html

網上都是ET模式, 其實LT不一定就比ET效率低,看使用方式和數量級.上面是個不錯的LT例子.

 

到這裡基本epoll就會使用了. epoll 還是挺容易的. 複雜在於 每個平臺都有一套基礎核心通信介面封裝.統一封裝還是麻煩的. 

現在到重頭戲了.  ※skynet※ 主要看下麵文件

再具體點可以看 一個cloud wind分離的 githup 項目

/socket-server  https://github.com/cloudwu/socket-server

引言基本都講完了.

 

  這裡再扯一點, 對於伺服器編程,個人認識. 開發基本斷層了. NB的框架很成熟不需要再瘋狂造輪子. 最主要的是 難,見效慢, 風險大, 待遇低.

 

前言

  我們先看cloud wind的代碼. 先分析一下其中一部分.

 

   紅線標註的是本文要分析優化的文件. 那開始吧.

Makefile

socket-server : socket_server.c test.c
    gcc -g -Wall -o $@ $^ -lpthread

clean:
    rm socket-server

很基礎很實在生成編譯. 沒的說.

socket_poll.h

#ifndef socket_poll_h
#define socket_poll_h

#include <stdbool.h>

typedef int poll_fd;

struct event {
    void * s;
    bool read;
    bool write;
};

static bool sp_invalid(poll_fd fd);
static poll_fd sp_create();
static void sp_release(poll_fd fd);
static int sp_add(poll_fd fd, int sock, void *ud);
static void sp_del(poll_fd fd, int sock);
static void sp_write(poll_fd, int sock, void *ud, bool enable);
static int sp_wait(poll_fd, struct event *e, int max);
static void sp_nonblocking(int sock);

#ifdef __linux__
#include "socket_epoll.h"
#endif

#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__)
#include "socket_kqueue.h"
#endif

#endif

一眼看到這個頭文件, 深深的為這個設計感到佩服. 這個跨平臺設計的思路真巧妙. 設計統一的訪問介面. 對於不同平臺

採用不同設計. 非常的出彩. 這裡說一下. 可能在 雲風眼裡, 跨平臺就是linux 和 ios 能跑就可以了. window 是什麼. 是M$嗎.

這是玩笑話, 其實 window iocp是內核讀取好了通知上層. epoll和kqueue是通知上層可以讀了. 機制還是很大不一樣.

老虎和禿鷲很難配對.window 網路編程自己很不好,目前封裝不出來. 等有機會真的需要再window上設計再來個. (伺服器linux和unix最強).

那我們開始吐槽雲風的代碼吧.

1). 代碼太隨意,約束不強

static void sp_del(poll_fd fd, int sock);
static void sp_write(poll_fd, int sock, void *ud, bool enable);

上面明顯 第二個函數 少了 參數 ,應該也是 poll_fd fd.

2). 過於追求個人美感, 忽略了編譯速度

#ifdef __linux__
#include "socket_epoll.h"
#endif

#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__)
#include "socket_kqueue.h"
#endif

這個二者是 if else 的關係. 雙if不會出錯就是編譯的時候多做一次if判斷. c系列的語言本身編譯就慢. 要註意

設計沒的說. 好,真好. 多一份難受,少一份不完整.

socket_epoll.h

#ifndef poll_socket_epoll_h
#define poll_socket_epoll_h

#include <netdb.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>

static bool 
sp_invalid(int efd) {
    return efd == -1;
}

static int
sp_create() {
    return epoll_create(1024);
}

static void
sp_release(int efd) {
    close(efd);
}

static int 
sp_add(int efd, int sock, void *ud) {
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.ptr = ud;
    if (epoll_ctl(efd, EPOLL_CTL_ADD, sock, &ev) == -1) {
        return 1;
    }
    return 0;
}

static void 
sp_del(int efd, int sock) {
    epoll_ctl(efd, EPOLL_CTL_DEL, sock , NULL);
}

static void 
sp_write(int efd, int sock, void *ud, bool enable) {
    struct epoll_event ev;
    ev.events = EPOLLIN | (enable ? EPOLLOUT : 0);
    ev.data.ptr = ud;
    epoll_ctl(efd, EPOLL_CTL_MOD, sock, &ev);
}

static int 
sp_wait(int efd, struct event *e, int max) {
    struct epoll_event ev[max];
    int n = epoll_wait(efd , ev, max, -1);
    int i;
    for (i=0;i<n;i++) {
        e[i].s = ev[i].data.ptr;
        unsigned flag = ev[i].events;
        e[i].write = (flag & EPOLLOUT) != 0;
        e[i].read = (flag & EPOLLIN) != 0;
    }

    return n;
}

static void
sp_nonblocking(int fd) {
    int flag = fcntl(fd, F_GETFL, 0);
    if ( -1 == flag ) {
        return;
    }

    fcntl(fd, F_SETFL, flag | O_NONBLOCK);
}

#endif

這個代碼沒有什麼問題, 除非雞蛋里挑骨頭. 就是前面介面層 socket_poll.h 中已經定義了變數名,就不要再換了.

fd -> efd. 例如最後一個將 sock 換成fd 不好.

static void
sp_nonblocking(int fd) {

可能都是大神手寫的. 心隨意動, ~~無所謂~~.

我後面會在正文部分開始全面優化. 保證有些變化. 畢竟他的代碼都是臨摹兩遍之後才敢說話的.

socket_kqueue.h

#ifndef poll_socket_kqueue_h
#define poll_socket_kqueue_h

#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/event.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

static bool 
sp_invalid(int kfd) {
    return kfd == -1;
}

static int
sp_create() {
    return kqueue();
}

static void
sp_release(int kfd) {
    close(kfd);
}

static void 
sp_del(int kfd, int sock) {
    struct kevent ke;
    EV_SET(&ke, sock, EVFILT_READ, EV_DELETE, 0, 0, NULL);
    kevent(kfd, &ke, 1, NULL, 0, NULL);
    EV_SET(&ke, sock, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
    kevent(kfd, &ke, 1, NULL, 0, NULL);
}

static int 
sp_add(int kfd, int sock, void *ud) {
    struct kevent ke;
    EV_SET(&ke, sock, EVFILT_READ, EV_ADD, 0, 0, ud);
    if (kevent(kfd, &ke, 1, NULL, 0, NULL) == -1) {
        return 1;
    }
    EV_SET(&ke, sock, EVFILT_WRITE, EV_ADD, 0, 0, ud);
    if (kevent(kfd, &ke, 1, NULL, 0, NULL) == -1) {
        EV_SET(&ke, sock, EVFILT_READ, EV_DELETE, 0, 0, NULL);
        kevent(kfd, &ke, 1, NULL, 0, NULL);
        return 1;
    }
    EV_SET(&ke, sock, EVFILT_WRITE, EV_DISABLE, 0, 0, ud);
    if (kevent(kfd, &ke, 1, NULL, 0, NULL) == -1) {
        sp_del(kfd, sock);
        return 1;
    }
    return 0;
}

static void 
sp_write(int kfd, int sock, void *ud, bool enable) {
    struct kevent ke;
    EV_SET(&ke, sock, EVFILT_WRITE, enable ? EV_ENABLE : EV_DISABLE, 0, 0, ud);
    if (kevent(kfd, &ke, 1, NULL, 0, NULL) == -1) {
        // todo: check error
    }
}

static int 
sp_wait(int kfd, struct event *e, int max) {
    struct kevent ev[max];
    int n = kevent(kfd, NULL, 0, ev, max, NULL);

    int i;
    for (i=0;i<n;i++) {
        e[i].s = ev[i].udata;
        unsigned filter = ev[i].filter;
        e[i].write = (filter == EVFILT_WRITE);
        e[i].read = (filter == EVFILT_READ);
    }

    return n;
}

static void
sp_nonblocking(int fd) {
    int flag = fcntl(fd, F_GETFL, 0);
    if ( -1 == flag ) {
        return;
    }

    fcntl(fd, F_SETFL, flag | O_NONBLOCK);
}

#endif

unix 一套機制. 個人覺得比 epoll好,不需要設置開啟大小值. 真心話linux epoll 夠用了. 估計伺服器開發用它也就到頭了.

上面代碼還是很好懂得單獨註冊讀寫. 後面再單獨刪除.用法很相似.

前言總結. 對於大神的代碼, 臨摹的效果確實很好, 解決了很多開發中的難啃的問題. 而自己只需要臨摹抄一抄就豁然開朗了.

他的還有一個, 設計上細節值得商榷, 條條大路通羅馬. 對於 函數返回值

......
    if (kevent(kfd, &ke, 1, NULL, 0, NULL) == -1) {
        sp_del(kfd, sock);
        return 1;
    }
    return 0;

一般約定 返回0表示成功, 返回 -1表示失敗公認的. 還有一個潛規則是返回 <0的表示錯誤, -1, -2, -3 各種錯誤狀態.

返回 1, 2, 3 也表示成功, 並且有各種狀態.

基於上面考慮,覺得它返回 1不好, 推薦返回-1.

還有

static int
sp_create() {
    return epoll_create(1024);
}

上面的代碼, 菜鳥寫也就算了. 對於大神只能理解為大巧若拙吧. 推薦用巨集表示, 說不定哪天改了. 重新編譯.

這裡吐槽完了, 總的而言 雲風的代碼真的 很有感覺, 有一種細細而來的美感. 

 

正文

  到這裡我們開始優化上面的代碼.目前優化後結構是這樣的.

說一下, sckpoll.h 是對外提供的介面文件. 後面 sckpoll-epoll.h 和 sckpoll-kqueue.h 是sckpoll 對應不同平臺設計的介面補充.

中間的 '-' 標誌表示這個文件是私有的不完整(部分)的. 不推薦不熟悉的實現細節的人使用.  

這也是個潛規則. 好 先看 sckpoll.h

#ifndef _H_SCKPOLL
#define _H_SCKPOLL

#include <stdbool.h>

// 統一使用的句柄類型
typedef int poll_t;

// 轉存的內核通知的結構體
struct event {
    void* s;        // 通知的句柄
    bool read;        // true表示可讀
    bool write;        // true表示可寫
};

/*
 * 統一的錯誤檢測介面.
 * fd        : 檢測的文件描述符(句柄)
 *             : 返回 true表示有錯誤
 */
static inline bool sp_invalid(poll_t fd);

/*
 * 句柄創建函數.可以通過sp_invalid 檢測是否創建失敗!
 *            : 返回創建好的句柄
 */
static inline poll_t sp_create(void);

/*
 * 句柄釋放函數
 * fd        : 句柄
 */
static inline void sp_release(poll_t fd);

/*
 * 在輪序句柄fd中添加 sock文件描述符.來檢測它
 * fd        : sp_create() 返回的句柄
 * sock        : 待處理的文件描述符, 一般為socket()返回結果
 * ud        : 自己使用的指針地址特殊處理
 *            : 返回0表示成功, -1表示失敗
 */
static int sp_add(poll_t fd, int sock, void* ud);

/*
 * 在輪詢句柄fd中刪除註冊過的sock描述符
 * fd        : sp_create()創建的句柄
 * sock        : socket()創建的句柄
 */
static inline void sp_del(poll_t fd, int sock);

/*
 * 在輪序句柄fd中修改sock註冊類型
 * fd        : 輪詢句柄
 * sock        : 待處理的句柄
 * ud        : 用戶自定義數據地址
 * enable    : true表示開啟寫, false表示還是監聽讀
 */
static inline void sp_write(poll_t fd, int sock, void* ud, bool enable);

/*
 * 輪詢句柄,等待有結果的時候構造當前用戶層結構struct event 結構描述中
 * fd        : sp_create 創建的句柄
 * es        : 一段struct event記憶體的首地址
 * max        : es數組能夠使用的最大值
 *            : 返回等待到的變動數, 相對於 es
 */
static int sp_wait(poll_t fd, struct event es[], int max);

/*
 * 為套接字描述符設置為非阻塞的
 * sock        : 文件描述符
 */
static inline void sp_nonblocking(int sock);

// 當前支持linux的epoll和unix的kqueue, window會error. iocp機制和epoll機制好不一樣呀
#if defined(__linux__)
#    include "sckpoll-epoll.h"
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD) || defined(__NetBSD__)
#    include "sckpoll-kqueue.h"
#else
#    error Currently only supports the Linux and Unix
#endif

#endif // !_H_SCKPOLL

參照原先總設計沒有變化, 改變在於加了註釋和統一了參數名,還有編譯的判斷流程.

繼續看 epoll 優化後封裝的代碼 sckpoll-epoll.h 

#ifndef _H_SCKPOLL_EPOLL
#define _H_SCKPOLL_EPOLL

#include <unistd.h>
#include <netdb.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>

// epoll 創建的時候創建的監測文件描述符最大數
#define _INT_MAXEPOLL (1024)

/*
 * 統一的錯誤檢測介面.
 * fd        : 檢測的文件描述符(句柄)
 *             : 返回 true表示有錯誤
 */
static inline bool 
sp_invalid(poll_t fd) {
    return fd < 0;
}

/*
 * 句柄創建函數.可以通過sp_invalid 檢測是否創建失敗!
 *            : 返回創建好的句柄
 */
static inline poll_t 
sp_create(void) {
    return epoll_create(_INT_MAXEPOLL);
}

/*
 * 句柄釋放函數
 * fd        : 句柄
 */
static inline 
void sp_release(poll_t fd) {
    close(fd);
}

/*
 * 在輪序句柄fd中添加 sock文件描述符.來檢測它
 * fd        : sp_create() 返回的句柄
 * sock        : 待處理的文件描述符, 一般為socket()返回結果
 * ud        : 自己使用的指針地址特殊處理
 *            : 返回0表示成功, -1表示失敗
 */
static int 
sp_add(poll_t fd, int sock, void* ud) {
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.ptr = ud;
    return epoll_ctl(fd, EPOLL_CTL_ADD, sock, &ev);
}

/*
 * 在輪詢句柄fd中刪除註冊過的sock描述符
 * fd        : sp_create()創建的句柄
 * sock        : socket()創建的句柄
 */
static inline void 
sp_del(poll_t fd, int sock) {
    epoll_ctl(fd, sock, EPOLL_CTL_DEL, 0);
}

/*
 * 在輪序句柄fd中修改sock註冊類型
 * fd        : 輪詢句柄
 * sock        : 待處理的句柄
 * ud        : 用戶自定義數據地址
 * enable    : true表示開啟寫, false表示還是監聽讀
 */
static inline void 
sp_write(poll_t fd, int sock, void* ud, bool enable) {
    struct epoll_event ev;
    ev.events = EPOLLIN | (enable? EPOLLOUT : 0);
    ev.data.ptr = ud;
    epoll_ctl(fd, EPOLL_CTL_MOD, sock, &ev);
}

/*
 * 輪詢句柄,等待有結果的時候構造當前用戶層結構struct event 結構描述中
 * fd        : sp_create 創建的句柄
 * es        : 一段struct event記憶體的首地址
 * max        : es數組能夠使用的最大值
 *            : 返回等待到的變動數, 相對於 es
 */
static int 
sp_wait(poll_t fd, struct event es[], int max) {
    struct epoll_event ev[max], *st = ev, *ed;
    int n = epoll_wait(fd, ev, max, -1);
    // 用指針遍歷速度快一些, 最後返回得到的變化量n
    for(ed = st + n; st < ed; ++st) {
        unsigned flag = st->events;
        es->s = st->data.ptr;
        es->read = flag & EPOLLIN;
        es->write = flag & EPOLLOUT;
        ++es;
    }
    
    return n;
}

/*
 * 為套接字描述符設置為非阻塞的
 * sock        : 文件描述符
 */
static inline void 
sp_nonblocking(int sock) {
    int flag = fcntl(sock, F_GETFL, 0);
    if(flag < 0) return;
    fcntl(sock, F_SETFL, flag | O_NONBLOCK);
}

#endif // !_H_SCKPOLL_EPOLL

還是有些變化的. 看人喜好了. 思路都是一樣的. 這裡用了C99 部分特性. 可變數組, 數組在棧上聲明的 struct event ev[max]; 這樣.

還有特殊語法糖 for(int i=0; i<.......) 等. 確實挺好用的. 要是目前編譯器都支持C11(2011 年C指定標準)就更好了.

sckpoll-kqueue.h

#ifndef poll_socket_kqueue_h
#define poll_socket_kqueue_h

#include <unistd.h>
#include <netdb.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/event.h>

/*
 * 統一的錯誤檢測介面.
 * fd        : 檢測的文件描述符(句柄)
 *             : 返回 true表示有錯誤
 */
static inline bool 
sp_invalid(poll_t fd) {
    return fd < 0;
}

/*
 * 句柄創建函數.可以通過sp_invalid 檢測是否創建失敗!
 *            : 返回創建好的句柄
 */
static inline poll_t 
sp_create(void) {
    return kqueue();
}

/*
 * 句柄釋放函數
 * fd        : 句柄
 */
static inline 
void sp_release(poll_t fd) {
    close(fd);
}

/*
 * 在輪序句柄fd中添加 sock文件描述符.來檢測它
 * fd        : sp_create() 返回的句柄
 * sock        : 待處理的文件描述符, 一般為socket()返回結果
 * ud        : 自己使用的指針地址特殊處理
 *            : 返回0表示成功, -1表示失敗
 */
static int 
sp_add(poll_t fd, int sock, void* ud) {
    struct kevent ke;
    EV_SET(&ke, sock, EVFILT_READ, EV_ADD, 0, 0, ud);
    if (kevent(fd, &ke, 1, NULL, 0, NULL) == -1) {
        return -1;
    }
    EV_SET(&ke, sock, EVFILT_WRITE, EV_ADD, 0, 0, ud);
    if (kevent(fd, &ke, 1, NULL, 0, NULL) == -1) {
        EV_SET(&ke, sock, EVFILT_READ, EV_DELETE, 0, 0, NULL);
        kevent(fd, &ke, 1, NULL, 0, NULL);
        return -1;
    }
    EV_SET(&ke, sock, EVFILT_WRITE, EV_DISABLE, 0, 0, ud);
    if (kevent(fd, &ke, 1, NULL, 0, NULL) == -1) {
        sp_del(fd, sock);
        return -1;
    }
    return 0;
}

/*
 * 在輪詢句柄fd中刪除註冊過的sock描述符
 * fd        : sp_create()創建的句柄
 * sock        : socket()創建的句柄
 */
static inline void 
sp_del(poll_t fd, int sock) {
    struct kevent ke;
    EV_SET(&ke, sock, EVFILT_READ, EV_DELETE, 0, 0, NULL);
    kevent(fd, &ke, 1, NULL, 0, NULL);
    EV_SET(&ke, sock, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
    kevent(fd, &ke, 1, NULL, 0, NULL);
}

/*
 * 在輪序句柄fd中修改sock註冊類型
 * fd        : 輪詢句柄
 * sock        : 待處理的句柄
 * ud        : 用戶自定義數據地址
 * enable    : true表示開啟寫, false表示還是監聽讀
 */
static inline void 
sp_write(poll_t fd, int sock, void* ud, bool enable) {
    struct kevent ke;
    EV_SET(&ke, sock, EVFILT_WRITE, enable ? EV_ENABLE : EV_DISABLE, 0, 0, ud);
    kevent(fd, &ke, 1, NULL, 0, NULL);
}

/*
 * 輪詢句柄,等待有結果的時候構造當前用戶層結構struct event 結構描述中
 * fd        : sp_create 創建的句柄
 * es        : 一段struct event記憶體的首地址
 * max        : es數組能夠使用的最大值
 *            : 返回等待到的變動數, 相對於 es
 */
static int 
sp_wait(poll_t fd, struct event es[], int max) {
    struct kevent ev[max], *st = ev, *ed;
    int n = kevent(fd, NULL, 0, ev, max, NULL);

    for(ed = st + n; st < ed; ++st) {
        unsigned filter = st->filter;
        es->s = st->udata;
        es->write = EVFILT_WRITE == filter;
        es->read = EVFILT_READ == filter;
        ++es;
    }

    return n;
}

/*
 * 為套接字描述符設置為非阻塞的
 * sock        : 文件描述符
 */
static inline void 
sp_nonblocking(int sock) {
    int flag = fcntl(sock, F_GETFL, 0);
    if(flag < 0) return;
    fcntl(sock, F_SETFL, flag | O_NONBLOCK);
}

#endif
View Code

這個沒有使用, 感興趣可以到unix上測試.

到這裡 那我們開始 寫測試文件了 首先是編譯的文件Makefile

test.out : test.c
    gcc -g -Wall -o $@ $^

clean:
    rm *.out ; ls

測試的 demo test.c. 強烈推薦值得參考

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "sckpoll.h"

// 目標埠和伺服器監聽的套接字個數
#define _INT_PORT    (7088)
#define _INT_LIS    (18)
// 一次處理事件個數
#define _INT_EVS    (64)

//4.0 控制台列印錯誤信息, fmt必須是雙引號括起來的巨集
#define CERR(fmt, ...) \
    fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\
         __FILE__, __func__, __LINE__, errno, strerror(errno),##__VA_ARGS__)

//4.1 控制台列印錯誤信息並退出, t同樣fmt必須是 ""括起來的字元串常量
#define CERR_EXIT(fmt,...) \
    CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE)

//4.3 if 的 代碼檢測
#define IF_CHECK(code)    \
    if((code) < 0) \
        CERR_EXIT(#code)
    
/*
 * 創建本地使用的伺服器socket.
 * ip        : 待連接的ip地址, 預設使用NULL
 * port        : 使用的埠號
 *             : 返回創建好的伺服器套接字
 */    
static int _socket(const char* ip, unsigned short port) {
    int sock, opt = SO_REUSEADDR;
    struct sockaddr_in saddr = { AF_INET };
    
    // 開啟socket 監聽
    IF_CHECK(sock = socket(PF_INET, SOCK_STREAM, 0));
    //設置埠復用, opt 可以簡寫為1,只要不為0
    IF_CHECK(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt));
    // 設置bind綁定埠
    saddr.sin_addr.s_addr = !ip || !*ip ? INADDR_ANY : inet_addr(ip);
    saddr.sin_port = htons(port);
    IF_CHECK(bind(sock, (struct sockaddr*)&saddr, sizeof saddr));
    //開始監聽
    IF_CHECK(listen(sock, _INT_LIS));
    
    // 這時候服務就啟動起來並且監聽了
    return sock;
}


/*
 * 主邏輯, 測試sckpoll.h封裝的簡單讀取發送 伺服器
 * 需要 C99或以上
 */
int main(int argc, char* argv[]) {
    int i, n, csock, nr;
    char buf[BUFSIZ];
    struct sockaddr_in addr;
    socklen_t clen = sizeof addr;
    struct event es[_INT_EVS];
    // 開始創建伺服器套接字和my poll監聽文件描述符
    int sock = _socket(NULL, _INT_PORT);
    poll_t fd = sp_create();
    if(sp_invalid(fd)) {
        close(sock);
        CERR_EXIT("sp_create is error");
    }
    
    // 開始設置非阻塞調節字後面註冊監聽
    sp_nonblocking(sock);
    // sock 值需要客戶端下來, 這裡會有警告沒關係
    if(sp_add(fd, sock, (void*)sock) < 0) {
        CERR("sp_add fd,sock:%d, %d.", fd, sock);
        goto __exit;
    }
    
    //開始監聽
    for(;;) {
        n = sp_wait(fd, es, _INT_EVS);
        if(n < 0) {
            if(errno == EINTR)
                continue;
            CERR("sp_wait is error");
            break;
        }
        
        //這裡處理 各種狀態
        for(i=0; i<n; ++i) {
            struct event* e = es + i;
            int nd = (int)e->s;
            
            // 有新的鏈接過來,開始註冊鏈接
            if(nd == sock) {
                for(;;){
                    csock = accept(sock, (struct sockaddr*)&addr, &clen);
                    if(csock < 0 ) {
                        if(errno == EINTR)
                            continue;
                        CERR("accept errno = %d.", errno);
                    }
                    break;
                }
                // 開始設置非阻塞調節字後面註冊監聽
                sp_nonblocking(csock);
                // sock 值需要客戶端下來, 這裡會有警告沒關係
                if(sp_add(fd, csock, (void*)csock) < 0) {
                    close(csock);
                    CERR("sp_add fd,sock:%d, %d.", fd, csock);
                }
                continue;
            }
            
            // 事件讀取操作
            if(e->read) {
                for(;;){
                    nr = read(nd, buf, BUFSIZ-1);
                    if(nr < 0 && errno != EINTR && errno != EAGAIN) {
                        CERR("read buf error errno:%d.", errno);
                        break;
                    }
                    buf[nr] = '\0';
                    printf("%s", buf);
                    if(nr < BUFSIZ-1) //讀取完畢也直接返回
                        break;
                }
                //添加寫事件, 方便給客戶端回覆信息
   

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

-Advertisement-
Play Games
更多相關文章
  • 一:準備工作 APPcmd.exe 位於 C:\Windows\System32\inetsrv 目錄 使用 Cd c:\Windows\System32\inetsrv 切換到該目錄 二:命令操作簡介 IIS 命令行管理工具基本格式: APPCMD (命令) (對象類型) <標識符> </參數1: ...
  • 本文內容 引入 ASP.NET 頁生命周期概述 常規頁生命周期階段 生命周期事件 其他頁生命周期註意事項 數據綁定控制項的數據綁定事件 引入 工作之初,我用 VS 開發 Web 應用程式,最常用的頁面事件是 Page_Load 和 Page_Init,它們處於不同的頁面生命期。但漸漸地,這兩個事件已經 ...
  • 很久以前,我們就有Snoop這樣的工具實時修改、查看正在運行的WPF程式,那時候調個樣式,修改個模板,相當滋潤。隨著歷史的車輪陷進WP的泥潭中,無論WP7的Silverlight還是WP8.1的runtime,偶們都不能方便快捷的查看APP的可視化樹(Visual Tree)了,嗚呼哉,是可忍孰不可 ...
  • 在上面一章我們以實例演示的方式介紹了幾種讀取配置的幾種方式,其中涉及到三個重要的對象,它們分別是承載結構化配置信息的Configuration,提供原始配置源數據的ConfigurationProvider,以及作為“中間人”的ConfigurationBuilder。接下來我們將會對由這三個核心對... ...
  • WebService訪問oracle資料庫本地調試 一步一個坑 上篇文章提到我們額資料庫掛了,重裝了資料庫,然後呢我需要在本地調試WebService,看看那些數據結構缺失,遷移到新資料庫中去。踩坑之路正式開始,當然這不是WebService這個項目埋下的坑,應該是每個使用oracle開發WebSe ...
  • 今天看了下C#創建自定義控制項的東西,就創建了一個自定義的控制項,但在控制項庫編譯完後來測試控制項時發現在ToolBox面找不到自定義的這個控制項,把控制項dll導入後還是找不到,最好網上查資料發現是因為VS配置的一個選項沒開, Tools-》Options-》Windows Forms Designer-》G ...
  • 1.攝像機的跟隨運動,邏輯就是保持攝像機跟主角的距離不變(Undate()函數)。 offset=trandform.position-player.position. Undate() { transform.position=player.position+offset; } 2.控制物體的移動 ...
  • windows上安裝mongodb的php擴展 下載地址https://s3.amazonaws.com/drivers.mongodb.org/php/index.html 找到對應的php版本的dll文件,下載php_mongo.dll,放到php安裝目錄下的ext目錄中,修改php.ini,添 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...