structc 开源框架介绍
引言 - 一切才刚刚开始
structc 是 c 结构基础库. 简单可复用.
- https://github.com/wangzhione/structc
之前也描述过几次 structc, 文字多代码风格少. 最近加班不多, 准备详细解说哈其思考初衷.
0.0 整体结构
structc ├── extern ├── license ├── makefile ├── readme.md ├── structc └── structc.sln
structc.sln : winds 项目管理文件 visual studio
structc : 项目整体源码和素材文件目录
readme.md : 项目介绍 markdown
makefile : linux 编译文件 make
license : mit 开源协议
extern : 项目引入的外部库目录
extern ├── jemalloc ├── jemalloc-vc141-release-static.lib ├── libuv.lib ├── pthread.h ├── pthread_lib.lib ├── sched.h ├── semaphore.h ├── strings.h ├── uv └── uv.h
以上就是我们看到 structc 项目整体结构.
0.1 外部库
当前很谨慎的引入两个半外部库. 最大程度会静态库编译链接运行. 荣我慢慢细说.
1. - https://github.com/jemalloc/jemalloc
jemalloc 是 c 构建底层高性能 malloc 库. 也被称为系统编程末期最后免费午餐. 整个 structc
malloc 全权交给 je_malloc 抗压. 其中 winds 编译静态库部分, 项目本身也有细说 -
https://github.com/jemalloc/jemalloc/tree/dev/msvc
how to build jemalloc for windows ================================= 1. install cygwin with at least the following packages: * autoconf * autogen * gawk * grep * sed 2. install visual studio 2015 or 2017 with visual c++ 3. add cygwin\bin to the path environment variable 4. open "x64 native tools command prompt for vs 2017" (note: x86/x64 doesn't matter at this point) 5. generate header files: sh -c "cc=cl ./autogen.sh" 6. now the project can be opened and built in visual studio: msvc\jemalloc_vc2017.sln
( 注: vs 使用最新版本. 网址打不开那就fq. 后面其也一样, 时刻保证最新 2018/10/10 ~ )
对于 linux 编译安装参照下面脚本
# 开发环境安装 sudo apt install gcc gdb autogen autoconf # jemalloc 安装 cd wget https://github.com/jemalloc/jemalloc/releases/download/5.1.0/jemalloc-5.1.0.tar.bz2 tar -jxvf jemalloc-5.1.0.tar.bz2 cd jemalloc-5.1.0 sh autogen.sh make -j4 sudo make install sudo ldconfig cd rm -rf jemalloc-5.1.0 jemalloc-5.1.0.tar.bz2
当 jemalloc 构建好了. 设计 alloc 层引入到 structc 框架中, 用户取代系统 malloc...
- https://github.com/wangzhione/structc/blob/master/structc/system/alloc.h
#ifndef _h_alloc #define _h_alloc #include <stdlib.h> #include <string.h> // :) 高效内存分配, 莫名伤感 ~ // _msc_ver -> winds cl // __gnuc__ -> linux gcc // #ifdef _msc_ver // // cpu 检测 x64 or x86 // isx64 defined 表示 x64 否则 x86 // # if defined(_m_arm64) || defined(_m_x64) # define isx64 # endif // // _m_ppc 为 powerpc 平台定义, 现在已不支持 // so winds 可以认为都是小端平台 // # if defined(_m_ppc) # define isbenian # endif #elif __gnuc__ # if defined(__x86_64__) # define isx64 # endif // // 大小端检测 : isbenian defined 表示大端 // # if defined(__big_endian__) || defined(__big_endian_bitfield) # define isbenian # endif #else # error build ( ̄︶ ̄) s #endif // off_alloc - 关闭全局 free / malloc 配置 #ifndef off_alloc # undef free # define free free_ # undef strdup # define strdup strdup_ # undef malloc # define malloc malloc_ # undef calloc # define calloc calloc_ # undef realloc # define realloc realloc_ #endif//off_alloc // // free_ - free 包装函数 // ptr : 内存首地址 // return : void // extern void free_(void * ptr); // // malloc_ - malloc 包装, 封装一些特殊业务 // size : 分配的内存字节 // return : 返回可使用的内存地址. // extern void * malloc_(size_t size); // // strdup_ - strdup 包装函数 // s : '\0' 结尾 c 字符串 // return : 拷贝后新的 c 字符串 // extern char * strdup_(const char * s); // // calloc_ - calloc 包装, 封装一些特殊业务 // num : 数量 // size : 大小 // return : 返回可用内存地址, 并且置0 // extern void * calloc_(size_t num, size_t size); // // realloc_ - realoc 包装函数, 封装一些特殊业务 // ptr : 内存首地址, null 等同于 malloc // size : 重新分配的内存大小 // return : 返回重新分配好的新地址内容 // extern void * realloc_(void * ptr, size_t size); #endif//_h_stdexit
- https://github.com/wangzhione/structc/blob/master/structc/system/alloc.c
#include <stdio.h> #define off_alloc #include "alloc.h" #define jemalloc_no_demangle #include <jemalloc/jemalloc.h> // // free_ - free 包装函数 // ptr : 内存首地址 // return : void // inline void free_(void * ptr) { je_free(ptr); } // 简单内存不足检测处理 static inline void * mcheck(void * ptr, size_t size) { if (null == ptr) { fprintf(stderr, "out of memory trying to allocate %zu\n", size); fflush(stderr); abort(); } return ptr; } // // malloc_ - malloc 包装, 封装一些特殊业务 // size : 分配的内存字节 // return : 返回可使用的内存地址. // inline void * malloc_(size_t size) { void * ptr = je_malloc(size); return mcheck(ptr, size); } // // strdup_ - strdup 包装函数 // s : '\0' 结尾 c 字符串 // return : 拷贝后新的 c 字符串 // inline char * strdup_(const char * s) { if (s) { size_t n = strlen(s) + 1; char * ptr = malloc_(n); return memcpy(ptr, s, n); } return null; } // // calloc_ - calloc 包装, 封装一些特殊业务 // num : 数量 // size : 大小 // return : 返回可用内存地址, 并且置0 // inline void * calloc_(size_t num, size_t size) { void * ptr = je_calloc(num, size); return mcheck(ptr, size); } // // realloc_ - realoc 包装函数, 封装一些特殊业务 // ptr : 内存首地址, null 等同于 malloc // size : 重新分配的内存大小 // return : 返回重新分配好的新地址内容 // inline void * realloc_(void * ptr, size_t size) { void * ntr = je_realloc(ptr, size); return mcheck(ntr, size); }
包装了一层. 从 alloc.h 中 off_alloc 宏可以看出, 具备支持插拔能力 ~
2. - https://github.com/libuv/libuv
libuv 用 c 写的高性能单线程网络 io 库. 希望通过它来支撑网络层. winds 编译静态库
参照 libuv 项目首页燥起来就行. 其中 gyp 安装了这个版本, 其它随波逐流 ~
- https://github.com/adblockplus/gyp
linux 编译安装脚本
# libuv 安装 cd wget https://github.com/libuv/libuv/archive/v1.23.1.zip unzip v1.23.1.zip cd libuv-1.23.1 sh autogen.sh ./configure make -j4 sudo make install sudo ldconfig cd # # 注意 uv 头文件, 全部导入到系统 include 目录下面 # rm -rf libuv-1.23.1 v1.23.1.zip
注意要将编译后 include 完整拷贝到安装目录 include下. 这样 uv 头文件全, 日后会用到.
libuv 开箱即用, 不太需要什么基础封装.
3. pthread - https://github.com/gerhobbelt/pthread-win32
这是最后那半个, 为 winds 引入 posix thread 模型. 编译起来很简单(前提咱们 vs 玩的熟).
扯点闲篇. linux 和 winds 相辅相成, 对立而统一. 一个是一切从头码, 一个开始就已经注册未来.
描述比较粗, 但大概这意思. (两个都不 eary, 玩很久才敢入门见岳父岳母) . 这里包装了一层
- https://github.com/wangzhione/structc/blob/master/structc/system/thread.h
#ifndef _h_thread #define _h_thread #include "struct.h" #include <pthread.h> #include <semaphore.h> // // pthread_end - 等待启动线程结束 // tid : 线程id // return : void // inline void pthread_end(pthread_t tid) { pthread_join(tid, null); } // // pthread_run - 异步启动线程 // id : &tid 线程id地址 // frun : 运行的主体 // arg : 运行参数 // return : 返回线程构建结果, 0 is success // #define pthread_run(id, frun, arg) \ pthread_run_(&(id), (node_f)(frun), (void *)(intptr_t)(arg)) inline int pthread_run_(pthread_t * id, node_f frun, void * arg) { return pthread_create(id, null, (start_f)frun, arg); } // // pthread_async - 异步启动分离线程 // frun : 运行的主体 // arg : 运行参数 // return : 返回 0 is success // #define pthread_async(frun, arg) \ pthread_async_((node_f)(frun), (void *)(intptr_t)(arg)) inline int pthread_async_(node_f frun, void * arg) { int ret; pthread_t tid; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, pthread_create_detached); ret = pthread_create(&tid, &attr, (start_f)frun, arg); pthread_attr_destroy(&attr); return ret; } #endif//_h_thread
利用现代编译器兼容性构建了 pthread 两种启动宏, 后续写 pthread create 相关代码会得心应手!
到此我们大一统治线程模型就定下来了. 还顺带引出了一个很重要辅助头文件.
- https://github.com/wangzhione/structc/blob/master/structc/struct/struct.h
#ifndef _h_struct #define _h_struct #include <math.h> #include "alloc.h" #include <ctype.h> #include <float.h> #include <stdio.h> #include <errno.h> #include <assert.h> #include <stdarg.h> #include <stdint.h> #include <stddef.h> #include <limits.h> #include <stdbool.h> #include <inttypes.h> // // enum flag int - 函数返回值全局状态码 // >= 0 标识 success 状态, < 0 标识 error 状态 // enum { sbase = +0, // 正确基础类型 ebase = -1, // 错误基础类型 eparam = -2, // 输入参数错误 efd = -3, // 文件打开失败 eclose = -4, // 文件操作关闭 eaccess = -5, // 没有操作权限 ealloc = -6, // 内存操作错误 eparse = -7, // 协议解析错误 esmall = -8, // 过小基础错误 ebig = -9, // 过大基础错误 etimeout = -10, // 操作超时错误 }; // // dcode - debug 模式下的测试宏 // dcode({ // puts("debug start..."); // }); // #ifndef dcode # ifdef _debug # define dcode(code) do code while(0) # else # define dcode(code) # endif // ! _debug #endif // ! dcode // // icmp_f - 比较行为的类型 // : int add_cmp(const void * now, const void * node) // typedef int (* icmp_f)(); // // vnew_f - 根据规则构建对象 // : void * rtree_new(void * node) // typedef void * (* vnew_f)(); // // node_f - 销毁当前对象节点 // : void list_die(void * node); // typedef void (* node_f)(void * node); // // start_f - pthread create func // : int * run(int * arg) // typedef void * (* start_f)(void * arg); // // each_f - each 循环操作, arg 外部参数, node 是内部结点 // : int dict_echo(struct dict * node, void * arg) { return 0; } // typedef int (* each_f)(void * node, void * arg); // // cerr - 打印错误信息 // exit - 打印错误信息, 并 exit // if - 条件判断异常退出的辅助宏 // #define cerr(fmt, ...) \ fprintf(stderr, "[%s:%s:%d][%d:%s]" fmt "\n", \ __file__, __func__, __line__, errno, strerror(errno), ##__va_args__) #define exit(fmt, ...) \ do { \ cerr(fmt, ##__va_args__); \ exit(exit_failure); \ } while(0) #define if(cond) \ if ((cond)) exit(#cond) // // return - 打印错误信息, 并 return 返回指定结果 // val : return的东西, 当需要 return void; 时候填 ',' 就过 or nil // fmt : 双引号包裹的格式化字符串 // ... : fmt中对应的参数 // return : val // #define return(val, fmt, ...) \ do { \ cerr(fmt, ##__va_args__); \ return val; \ } while(0) #define nil #define retnil(fmt, ...) \ return(nil , fmt, ##__va_args__) #define retnul(fmt, ...) \ return(null, fmt, ##__va_args__) #endif//_h_struct
作者尝试写 structc 项目时第一个源文件 : )
0.2 ide 弱议
winds 没得选, 最新最全的 visual studio best version 有才能统治一切. 这里主要说
的是 linux 上面我们的选择. 最开始我是 vi + make + gcc + gdb 开发和编译的.
makefile - https://github.com/wangzhione/structc/blob/master/makefile
# 编译的目录结构 # release : make # debug : make d=-d_debug # clean : make clean
make 是编译发布, make d=-d_debug 是编译 debug, make clean 项目清理. 手工操作.
这样搞对我都还好, 什么都行.
但不妨更精进一步 [vi + make + gcc + gdb] -> [code + f5 + f10 + f11] 是不是更妙.
微软作为桌面软件霸主, code(vscode 简称)不用我多说, 不得不服. 那开搞
1. 安装软件
ubuntu best version
vscode
安装好 vscode 后, 在其内部安装插件 microsoft c/c++ for visual studio code
2. f1 -> edit configurations -> c_cpp_properties.json
设置如下内容和vs配置很相似
{ "configurations": [ { "name": "linux", "includepath": [ "/usr/include/c++/7", "/usr/include/x86_64-linux-gnu/c++/7", "/usr/include/c++/7/backward", "/usr/lib/gcc/x86_64-linux-gnu/7/include", "/usr/local/include", "/usr/lib/gcc/x86_64-linux-gnu/7/include-fixed", "/usr/include/x86_64-linux-gnu", "/usr/include", "${workspaceroot}", "${workspaceroot}/structc/base", "${workspaceroot}/structc/struct", "${workspaceroot}/structc/system" ], "defines": [ "_debug", "__gnuc__" ], "intellisensemode": "clang-x64", "browse": { "path": [ "/usr/include/c++/7", "/usr/include/x86_64-linux-gnu/c++/7", "/usr/include/c++/7/backward", "/usr/lib/gcc/x86_64-linux-gnu/7/include", "/usr/local/include", "/usr/lib/gcc/x86_64-linux-gnu/7/include-fixed", "/usr/include/x86_64-linux-gnu", "/usr/include", "${workspaceroot}" ], "limitsymbolstoincludedheaders": true, "databasefilename": "" }, "compilerpath": "/usr/bin/clang", "cstandard": "c11", "cppstandard": "c++17" } ], "version": 4 }
3. f5 -> launch.json
{ // 使用 intellisense 了解相关属性。 // 悬停以查看现有属性的描述。 // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "(gdb) launch", "type": "cppdbg", "request": "launch", "program": "${workspacefolder}/out/main.exe", "args": [], "stopatentry": false, "cwd": "${workspacefolder}", "environment": [], "externalconsole": true, "prelaunchtask": "debug", "mimode": "gdb", "setupcommands": [ { "description": "enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignorefailures": true } ] } ] }
4. f5 -> tasks.json
{ // see https://go.microsoft.com/fwlink/?linkid=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "type" : "shell", "label" : "debug", "command" : "make d=-d_debug" } ] }
此刻我们就可以 f5 搞起来 ~
兄弟们是不是很亲切, 这么复杂定制化项目都可以可视化调试. 还有谁 ~ 当然 ide 有没有
都好说, 难说的是你是否耐的下心去感悟技术的脉络, 可不能学京东技术, 对开源缺失敬畏
之心, 技术不见得多厉害, 节操提前贷款没了 ~ 最终成为奥义之梗 : )
前言 - 不妨说点设计
进入 structc/structc 看到以下项目结构
wzhi@wzc:~/structc/structc$ tree -l 1 . ├── base ├── conf ├── main ├── readme.md ├── struct ├── structc.vcxproj ├── structc.vcxproj.filters ├── structc.vcxproj.user ├── system └── test
base : 基础接口封装目录
conf : 配置文件目录
main : 主函数目录
struct : 数据结构接口目录
system : 系统库包装目录
test : 单元测试目录
1.0 main 主函数设计
wzhi@wzc:~/structc/structc/main$ tree . ├── main.c ├── main_init.c ├── main_run.c └── main_test.c
重点关注下入口 mian 主函数设计 main.c
#include "head.h" // // main - 程序的总入口, 从扯开始 // argc : 输入参数个数 // argv : 参数集 // return : 返回程序退出的状态码 // int main(int argc, char * argv[]) { // // 初始化 ... ... // ... ... extern_run(main_init); // // make d=-d_debug // main_test 单元测试才会启动 // #ifdef _debug extern_run(main_test); #endif // ... // ... 启动当前项目运行的主函数 // extern_run(main_run, argc, argv); return exit_success; }
其中 extern_run 也很奇巧
// // extern_run - 简单的声明, 并立即使用的宏 // ftest : 需要执行的函数名称 // ... : 可变参数, 保留 // #define extern_run(ftest, ...) \ do { \ extern void ftest(); \ ftest (__va_args__); \ } while(0)
越过声明直接使用的宏声明. structc 中 main 函数一共做了二件半事情.
main_init 初始化函数, main_run 业务运行函数, 还有半个 main_test 运行单元测试.
随后我们好好看看这个单元测试套路.
1.1 test 单元测试套路设计
先看看 main_test.c
#include "head.h" // // test - 用于单元测试函数, 执行并输出运行时间 // ftest : 需要执行的测试函数名称 // ... : 可变参数, 保留 // #define test(ftest, ...) \ do { \ extern void ftest(); \ clock_t $s = clock(); \ ftest (##__va_args__); \ double $e = (double)clock(); \ printf(str(ftest)" run time:%lfs\n", ($e-$s)/clocks_per_sec);\ } while(0) // // main_test - *_test is here run // return : void // void main_test(void) { // // 开始你的表演, 单元测试 // extern_run(uv_tty_test); }
以上只给予了业务测试的能力. 其中 uv_tty_test 函数就是单元测试目录下其中一个的单元测试函数体.
而我们每个业务测试函数, 顺带会创建一个同名的 .c 文件. 例如这里是 uv_tty_test.c
#include <uv.h> #include <stdio.h> #include <stdlib.h> #include <string.h> // // 测试 libuv tty 操作控制台 // 输出一段有颜色的文字 // void uv_tty_test(void) { uv_tty_t tty; uv_buf_t buf[3]; unsigned i, len = sizeof buf / sizeof *buf; uv_loop_t * loop = uv_default_loop(); // 目前只对 tty 控制台处理 if (uv_guess_handle(1) != uv_tty) { fprintf(stderr, "uv_guess_handle(1) != uv_tty!\n"); exit(exit_failure); } uv_tty_init(loop, &tty, 1, 0); uv_tty_set_mode(&tty, uv_tty_mode_normal); // 开始发送消息 buf[0].base = "\033[46;37m"; buf[1].base = u8"(✿◡‿◡) 喵酱 ((●'-'●)) 比 ♥ 里~ \n"; buf[2].base = "\033[0m"; for (i = 0; i < len; ++i) buf[i].len = (int)strlen(buf[i].base); uv_try_write((uv_stream_t *)&tty, buf, len); // 重置终端行为 uv_tty_reset_mode(); uv_run(loop, uv_run_default); }
思路很直白. 这些就是单元测试的真相... . 比较清晰的展示(业务是复杂中减负)
1.2 system 系统库设计
这里面设计东东不少, 只挑一些经典的供人看看. 代码即注释 ~
- https://github.com/wangzhione/structc/blob/master/structc/system/socket.h
#ifndef _h_socket #define _h_socket #include <time.h> #include <fcntl.h> #include "struct.h" #include <signal.h> #include <sys/types.h> #ifdef __gnuc__ #include <netdb.h> #include <unistd.h> #include <sys/un.h> #include <sys/uio.h> #include <sys/time.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/tcp.h> #include <sys/resource.h> // // this is used instead of -1, since the. by winsock // on now linux eagain and ewouldblock may be the same value // connect 链接中, linux 是 einprogress,winds 是 wsaewouldblock // typedef int socket_t; #define invalid_socket (~0) #define socket_error (-1) // socket_init - 初始化 socket 库初始化方法 inline void socket_init(void) { // 管道破裂, 忽略 sigpipe 信号 signal(sigpipe, sig_ign); } inline int socket_close(socket_t s) { return close(s); } // socket_set_block - 设置套接字是阻塞 // socket_set_nonblock - 设置套接字是非阻塞 inline int socket_set_block(socket_t s) { int mode = fcntl(s, f_getfl, 0); return fcntl(s, f_setfl, mode & ~o_nonblock); } inline int socket_set_nonblock(socket_t s) { int mode = fcntl(s, f_getfl, 0); return fcntl(s, f_setfl, mode | o_nonblock); } // socket_recv - 读取数据 // socket_send - 写入数据 inline int socket_recv(socket_t s, void * buf, int sz) { return (int)read(s, buf, sz); } inline int socket_send(socket_t s, const void * buf, int sz) { return (int)write(s, buf, sz); } #endif #ifdef _msc_ver #include <ws2tcpip.h> #undef errno #define errno wsagetlasterror() #undef strerror #define strerror ((char * (*)(int))strerr) #undef eintr #define eintr wsaeintr #undef eagain #define eagain wsaewouldblock #undef einprogress #define einprogress wsaewouldblock /* * winsock 2 extension -- manifest constants for shutdown() */ #define shut_rd sd_receive #define shut_wr sd_send #define shut_rdwr sd_both #define so_reuseport so_reuseaddr typedef socket socket_t; typedef int socklen_t; // // gettimeofday - linux sys/time.h 中得到微秒时间实现 // tv : 返回结果包含秒数和微秒数 // tz : 包含的时区, winds 上这个变量没有作用 // return : 默认返回 0 // extern int gettimeofday(struct timeval * tv, void * tz); // // strerr - linux 上替代 strerror, winds 替代 formatmessage // error : linux 是 errno, winds 可以是 wsagetlasterror() ... // return : system os 拔下来的提示常量字符串 // extern const char * strerr(int err); // socket_init - 初始化 socket 库初始化方法 inline void socket_init(void) { wsadata wsad; wsastartup(winsock_version, &wsad); } // socket_close - 关闭上面创建后的句柄 inline int socket_close(socket_t s) { return closesocket(s); } // socket_set_block - 设置套接字是阻塞 // socket_set_nonblock - 设置套接字是非阻塞 inline int socket_set_block(socket_t s) { u_long mode = 0; return ioctlsocket(s, fionbio, &mode); } inline int socket_set_nonblock(socket_t s) { u_long mode = 1; return ioctlsocket(s, fionbio, &mode); } // socket_recv - 读取数据 // socket_send - 写入数据 inline int socket_recv(socket_t s, void * buf, int sz) { return sz > 0 ? recv(s, buf, sz, 0) : 0; } inline int socket_send(socket_t s, const void * buf, int sz) { return send(s, buf, sz, 0); } #endif // // 通用 sockaddr_in ipv4 地址 // typedef struct sockaddr_in sockaddr_t[1]; // socket_dgram - 创建 udp socket // socket_stream - 创建 tcp socket inline socket_t socket_dgram(void) { return socket(pf_inet, sock_dgram, ipproto_udp); } inline socket_t socket_stream(void) { return socket(pf_inet, sock_stream, ipproto_tcp); } // socket_set_reuse - 开启端口和地址复用 // socket_set_keepalive - 开启心跳包检测, 默认2h 5次 inline int socket_set_enable(socket_t s, int optname) { int ov = 1; return setsockopt(s, sol_socket, optname, (void *)&ov, sizeof ov); } inline int socket_set_reuse(socket_t s) { return socket_set_enable(s, so_reuseport); } inline int socket_set_keepalive(socket_t s) { return socket_set_enable(s, so_keepalive); } // socket_set_rcvtimeo - 设置接收数据毫秒超时时间 // socket_set_sndtimeo - 设置发送数据毫秒超时时间 inline int socket_set_time(socket_t s, int ms, int optname) { struct timeval ov = { 0,0 }; if (ms > 0) { ov.tv_sec = ms / 1000; ov.tv_usec = (ms % 1000) * 1000; } return setsockopt(s, sol_socket, optname, (void *)&ov, sizeof ov); } inline int socket_set_rcvtimeo(socket_t s, int ms) { return socket_set_time(s, ms, so_rcvtimeo); } inline int socket_set_sndtimeo(socket_t s, int ms) { return socket_set_time(s, ms, so_sndtimeo); } // socket_get_error - 得到当前socket error 值, 0 表示正确, 其它都是错误 inline int socket_get_error(socket_t s) { int err; socklen_t len = sizeof(err); int r = getsockopt(s, sol_socket, so_error, (void *)&err, &len); return r < 0 ? errno : err; } // socket_recvfrom - recvfrom 接受函数 // socket_sendto - sendto 发送函数 inline int socket_recvfrom(socket_t s, void * buf, int len, int flags, sockaddr_t in) { socklen_t inlen = sizeof (sockaddr_t); return recvfrom(s, buf, len, flags, (struct sockaddr *)in, &inlen); } inline int socket_sendto(socket_t s, const void * buf, int len, int flags, const sockaddr_t to) { return sendto(s, buf, len, flags, (const struct sockaddr *)to, sizeof(sockaddr_t)); } // // socket_recvn - socket 接受 sz 个字节 // socket_sendn - socket 发送 sz 个字节 // extern int socket_recvn(socket_t s, void * buf, int sz); extern int socket_sendn(socket_t s, const void * buf, int sz); // socket_bind - bind 绑定函数 // socket_listen - listen 监听函数 // socket_accept - accept 等接函数 // socket_connect - connect 链接函数 inline int socket_bind(socket_t s, const sockaddr_t addr) { return bind(s, (const struct sockaddr *)addr, sizeof(sockaddr_t)); } inline int socket_listen(socket_t s) { return listen(s, somaxconn); } inline socket_t socket_accept(socket_t s, sockaddr_t addr) { socklen_t len = sizeof (sockaddr_t); return accept(s, (struct sockaddr *)addr, &len); } inline int socket_connect(socket_t s, const sockaddr_t addr) { return connect(s, (const struct sockaddr *)addr, sizeof(sockaddr_t)); } // // socket_binds - 端口绑定返回绑定好的 socket fd, 返回 invalid_socket or pf_inet pf_inet6 // socket_listens - 端口监听返回监听好的 socket fd. // extern socket_t socket_binds(const char * ip, uint16_t port, uint8_t protocol, int * family); extern socket_t socket_listens(const char * ip, uint16_t port, int backlog); // // socket_addr -socket_recv 通过 ip, port 构造 ipv4 结构 // extern int socket_addr(const char * ip, uint16_t port, sockaddr_t addr); // socket_pton - 返回 ip 串 inline char * socket_pton(sockaddr_t addr, char ip[inet_addrstrlen]) { return (char *)inet_ntop(af_inet, &addr->sin_addr, ip, inet_addrstrlen); } // // socket_host - 通过 ip:port 串得到 socket addr 结构 // host : ip:port 串 // addr : 返回最终生成的地址 // return : >= ebase 表示成功 // extern int socket_host(const char * host, sockaddr_t addr); // // socket_tcp - 创建 tcp 详细套接字 // host : ip:port 串 // return : 返回监听后套接字 // extern socket_t socket_tcp(const char * host); // // socket_udp - 创建 udp 详细套接字 // host : ip:port 串 // return : 返回绑定后套接字 // extern socket_t socket_udp(const char * host); // // socket_connects - 返回链接后的阻塞套接字 // host : ip:port 串 // return : 返回链接后阻塞套接字 // extern socket_t socket_connects(const char * host); // // socket_connectos - 返回链接后的非阻塞套接字 // host : ip:port 串 // ms : 链接过程中毫秒数 // return : 返回链接后非阻塞套接字 // extern socket_t socket_connectos(const char * host, int ms); #endif//_h_socket
- https://github.com/wangzhione/structc/blob/master/structc/system/socket.c
#include "socket.h" #ifdef _msc_ver // // gettimeofday - linux sys/time.h 中得到微秒时间实现 // tv : 返回结果包含秒数和微秒数 // tz : 包含的时区, winds 上这个变量没有作用 // return : 默认返回 0 // int gettimeofday(struct timeval * tv, void * tz) { struct tm m; systemtime se; getlocaltime(&se); m.tm_year = se.wyear - 1900; m.tm_mon = se.wmonth - 1; m.tm_mday = se.wday; m.tm_hour = se.whour; m.tm_min = se.wminute; m.tm_sec = se.wsecond; m.tm_isdst = -1; // 不考虑夏令时 tv->tv_sec = (long)mktime(&m); tv->tv_usec = se.wmilliseconds * 1000; return 0; } #endif // // socket_recvn - socket 接受 sz 个字节 // socket_sendn - socket 发送 sz 个字节 // int socket_recvn(socket_t s, void * buf, int sz) { int r, n = sz; while (n > 0) { r = recv(s, buf, n, 0); if (r == 0) break; if (r == socket_error) { if (errno == eintr) continue; return socket_error; } n -= r; buf = (char *)buf + r; } return sz - n; } int socket_sendn(socket_t s, const void * buf, int sz) { int r, n = sz; while (n > 0) { r = send(s, buf, n, 0); if (r == 0) break; if (r == socket_error) { if (errno == eintr) continue; return socket_error; } n -= r; buf = (char *)buf + r; } return sz - n; } // // socket_addr - 通过 ip, port 构造 ipv4 结构 // int socket_addr(const char * ip, uint16_t port, sockaddr_t addr) { addr->sin_family = af_inet; addr->sin_port = htons(port); addr->sin_addr.s_addr = inet_addr(ip); if (addr->sin_addr.s_addr == inaddr_none) { struct hostent * host = gethostbyname(ip); if (!host || !host->h_addr) return eparam; // 尝试一种, 默认 ipv4 memcpy(&addr->sin_addr, host->h_addr, host->h_length); } memset(addr->sin_zero, 0, sizeof addr->sin_zero); return sbase; } // // socket_binds - 端口绑定返回绑定好的 socket fd, 返回 invalid_socket or pf_inet pf_inet6 // socket_listens - 端口监听返回监听好的 socket fd. // socket_t socket_binds(const char * ip, uint16_t port, uint8_t protocol, int * family) { socket_t fd; char ports[sizeof "65535"]; struct addrinfo * addr = null, hint = { 0 }; if (null == ip || *ip == '\0') ip = "0.0.0.0"; // default inaddr_any sprintf(ports, "%hu", port); hint.ai_family = af_unspec; if (protocol == ipproto_tcp) hint.ai_socktype = sock_stream; else { assert(protocol == ipproto_udp); hint.ai_socktype = sock_dgram; } hint.ai_protocol = protocol; if (getaddrinfo(ip, ports, &hint, &addr)) return invalid_socket; fd = socket(addr->ai_family, addr->ai_socktype, 0); if (fd == invalid_socket) goto err_free; if (socket_set_reuse(fd)) goto err_close; if (bind(fd, addr->ai_addr, (int)addr->ai_addrlen)) goto err_close; // success return ip family if (family) *family = addr->ai_family; freeaddrinfo(addr); return fd; err_close: socket_close(fd); err_free: freeaddrinfo(addr); return invalid_socket; } socket_t socket_listens(const char * ip, uint16_t port, int backlog) { socket_t fd = socket_binds(ip, port, ipproto_tcp, null); if (invalid_socket != fd && listen(fd, backlog)) { socket_close(fd); return invalid_socket; } return fd; } // host_parse - 解析 host 内容 static int host_parse(const char * host, char ip[bufsiz], uint16_t * pprt) { int port = 0; char * st = ip; if (!host || !*host || *host == ':') strcpy(ip, "0.0.0.0"); else { char c; // 简单检查字符串是否合法 size_t n = strlen(host); if (n >= bufsiz) return(eparam, "host err %s", host); // 寻找分号 while ((c = *host++) != ':' && c) *ip++ = c; *ip = '\0'; if (c == ':') { if (n > ip - st + sizeof "65535") return(eparam, "host port err %s", host); port = atoi(host); // 有些常识数字, 不一定是魔法 ... :) if (port <= 1024 || port > 65535) return(eparam, "host port err %s, %d", host, port); } } *pprt = port; return sbase; } // // socket_host - 通过 ip:port 串得到 socket addr 结构 // host : ip:port 串 // addr : 返回最终生成的地址 // return : >= ebase 表示成功 // int socket_host(const char * host, sockaddr_t addr) { uint16_t port; char ip[bufsiz]; if (host_parse(host, ip, &port) < sbase) return eparam; // 开始构造 addr if (null == addr) { sockaddr_t nddr; return socket_addr(ip, port, nddr); } return socket_addr(ip, port, addr); } // // socket_tcp - 创建 tcp 详细套接字 // host : ip:port 串 // return : 返回监听后套接字 // socket_t socket_tcp(const char * host) { uint16_t port; char ip[bufsiz]; if (host_parse(host, ip, &port) < sbase) return eparam; return socket_listens(ip, port, somaxconn); } // // socket_udp - 创建 udp 详细套接字 // host : ip:port 串 // return : 返回绑定后套接字 // socket_t socket_udp(const char * host) { uint16_t port; char ip[bufsiz]; if (host_parse(host, ip, &port) < sbase) return eparam; return socket_binds(ip, port, ipproto_udp, null); } // // socket_connects - 返回链接后的阻塞套接字 // host : ip:port 串 // return : 返回链接后阻塞套接字 // socket_t socket_connects(const char * host) { sockaddr_t addr; socket_t s = socket_stream(); if (invalid_socket == s) { return(s, "socket_stream is error"); } // 解析配置成功后尝试链接 if (socket_host(host, addr) >= sbase) if (socket_connect(s, addr) >= sbase) return s; socket_close(s); return(invalid_socket, "socket_connects %s", host); } // // socket_connecto - connect 超时链接, 返回非阻塞 socket // static int socket_connecto(socket_t s, const sockaddr_t addr, int ms) { int n, r; struct timeval to; fd_set rset, wset, eset; // 还是阻塞的connect if (ms < 0) return socket_connect(s, addr); // 非阻塞登录, 先设置非阻塞模式 r = socket_set_nonblock(s); if (r < sbase) return r; // 尝试连接, connect 返回 -1 并且 errno == einprogress 表示正在建立链接 r = socket_connect(s, addr); // connect 链接中, linux 是 einprogress,winds 是 wsaewouldblock if (r >= sbase || errno != einprogress) { socket_set_block(s); return r; } // 超时 timeout, 直接返回结果 ebase = -1 错误 if (ms == 0) { socket_set_block(s); return ebase; } fd_zero(&rset); fd_set(s, &rset); fd_zero(&wset); fd_set(s, &wset); fd_zero(&eset); fd_set(s, &eset); to.tv_sec = ms / 1000; to.tv_usec = (ms % 1000) * 1000; n = select((int)s + 1, &rset, &wset, &eset, &to); // 超时直接滚 if (n <= 0) { socket_set_block(s); return ebase; } // 当连接成功时候,描述符会变成可写 if (n == 1 && fd_isset(s, &wset)){ socket_set_block(s); return sbase; } // 当连接建立遇到错误时候, 描述符变为即可读又可写 if (fd_isset(s, &eset) || n == 2) { socklen_t len = sizeof n; // 只要最后没有 error 那就 链接成功 if (!getsockopt(s, sol_socket, so_error, (char *)&n, &len) && !n) r = sbase; } socket_set_block(s); return r; } // // socket_connectos - 返回链接后的非阻塞套接字 // host : ip:port 串 // ms : 链接过程中毫秒数 // return : 返回链接后非阻塞套接字 // socket_t socket_connectos(const char * host, int ms) { sockaddr_t addr; socket_t s = socket_stream(); if (invalid_socket == s) { return(s, "socket_stream is error"); } // 解析配置成功后尝试链接 if (socket_host(host, addr) >= sbase) if (socket_connecto(s, addr, ms) >= sbase) return s; socket_close(s); return(invalid_socket, "socket_connectos %s", host); }
哪怕 winds, 设计思路也是仿照 linux socket 套路. 构建 socket 接口, 希望上面代码能说明什么是少林
拳法, 千锤百练. 后面 base struct system 代码量不少, 难一一说. 喜欢的可以后续抄袭一次. (我也是
抄袭别人而走入了编程的世界, 了解这分形的人生吧)
正文 - 风吹草动
通过引言和前言认识了 structc 是什么样项目, 项目构建, 代码风格等. 这里准备说一下设计 structc
项目初衷. 很久前写 c 代码, 发现数据结构确定后, 基本整个脉络就定下了. 所以想用 c 构建一个通用
简单的数据结构库. 所以有了这个项目.
扯一点, 了解 structc 项目后能够为怎样技能加点. 例如学完 struct 目录, 数据结构可以轻松结课.
抄完 system 操作系统可以结课. base 清楚后, 框架中间件设计也算入门了. 了解整体布局后, 实战中的
脚手架设计也不过如此. 但缺点也有, 见效慢. c 太老了, 想通过 c 看清编程源头, 不下时间是不现实的,
幸运的是最终收获 -> 哈哈 -> 怎么脱发又严重了 ....
比如 - https://github.com/wangzhione/structc/blob/master/structc/system/atom.h
#ifndef _h_atom #define _h_atom #include "atomic.h" // // atom_t 自旋锁类型 // [static] atom_t o = 0; // atom_lock(o); // - one man rpg // atom_unlock(o); // typedef volatile long atom_t; // atom_acquire - 维护优化后读写代码不在其前 #define atom_acquire() atomic_fence(atomic_acquire) // atom_release - 维护优化后读写代码不在其后 #define atom_release() atomic_fence(atomic_release) // atom_seq_cst - 维护优化后读写代码前后不动 #define atom_seq_cst() atomic_fence(atomic_seq_cst) #ifdef __gnuc__ #define atom_trylock(o) (!__sync_lock_test_and_set(&(o), 1)) #define atom_lock(o) while(__sync_lock_test_and_set(&(o), 1)) #define atom_unlock(o) __sync_lock_release(&(o)) // 内存屏障, 维持代码顺序 #define atom_sync() __sync_synchronize() // v += a ; return v; #define atom_add(v, a) __sync_add_and_fetch(&(v), (a)) // type tmp = v ; v = a; return tmp; #define atom_set(v, a) __sync_lock_test_and_set(&(v), (a)) // v &= a; return v; #define atom_and(v, a) __sync_and_and_fetch(&(v), (a)) // return ++v; #define atom_inc(v) __sync_add_and_fetch(&(v), 1) // return --v; #define atom_dec(v) __sync_sub_and_fetch(&(v), 1) // bool b = v == c; b ? v=a : ; return b; #define atom_cas(v, c, a) __sync_bool_compare_and_swap(&(v), (c), (a)) #endif #ifdef _msc_ver #include <intrin.h> #include <intrin0.h> /* interlocked intrinsic mapping for _nf/_acq/_rel */ #if defined(_m_arm) || defined(_m_arm64) #define _acquire(x) atomic_concat(x, _acq) #else /* defined(_m_arm) || defined(_m_arm64) */ #define _acquire(x) x #endif /* defined(_m_arm) || defined(_m_arm64) */ #define atom_trylock(o) (!_acquire(_interlockedbittestandset)(&(o), 0)) #define atom_lock(o) while(_acquire(_interlockedbittestandset)(&(o), 0)) inline void store_release(atom_t * x) { /* store _value atomically with release memory order */ #if defined(_m_arm) || defined(_m_arm64) __dmb(0xb /* _arm_barrier_ish or _arm64_barrier_ish*/); __iso_volatile_store32((volatile int *)x, 0); #else _readwritebarrier(); *x = 0; #endif } #define atom_unlock(o) store_release(&(o)) // 保证代码优化后不乱序执行 #define atom_sync() memorybarrier() // v 和 a 都是 long 这样数据 #define atom_add(v, a) interlockedadd((volatile long *)&(v), (long)(a)) #define atom_set(v, a) interlockedexchange((volatile long *)&(v), (long)(a)) #define atom_and(v, a) interlockedand((volatile long *)&(v), (long)(a)) #define atom_inc(v) interlockedincrement((volatile long *)&(v)) #define atom_dec(v) interlockeddecrement((volatile long *)&(v)) // // 对于 interlockedcompareexchange(v, c, a) 等价于下面 // long tmp = v ; v == a ? v = c : ; return tmp; // // 咱们的 atom_cas(v, c, a) 等价于下面 // long tmp = v ; v == c ? v = a : ; return tmp; // #define atom_cas(v, c, a) ((long)(c) == interlockedcompareexchange((volatile long *)&(v), (long)(a), (long)(c))) #endif #endif//_h_atom
代码在改中变的有味道, 有态度. 当然更欢迎同行给予补充, 共同提高进步 ~
毕竟错误是难免的 : )
后记 - 江湖再会
- https://music.163.com/#/song?id=376994