structc 开源框架介绍
引言 - 一切才刚刚开始
structc 是 C 结构基础库. 简单可复用.
structc - 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. jemalloc - 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
================================= . Install Cygwin with at least the following packages:
* autoconf
* autogen
* gawk
* grep
* sed . Install Visual Studio or with Visual C++ . Add Cygwin\bin to the PATH environment variable . Open "x64 Native Tools Command Prompt for VS 2017"
(note: x86/x64 doesn't matter at this point) . Generate header files:
sh -c "CC=cl ./autogen.sh" . Now the project can be opened and built in Visual Studio:
msvc\jemalloc_vc2017.sln
( 注: vs 使用最新版本. 网址打不开那就翻墙. 后面其也一样, 时刻保证最新 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..tar.bz2
cd jemalloc-5.1. sh autogen.sh
make -j4
sudo make install
sudo ldconfig cd
rm -rf jemalloc-5.1. jemalloc-5.1..tar.bz2
当 jemalloc 构建好了. 设计 alloc 层引入到 structc 框架中, 用户取代系统 malloc...
alloc.h - 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
alloc.c - 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) + ;
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. libuv - https://github.com/libuv/libuv
libuv 用 c 写的高性能单线程网络 io 库. 希望通过它来支撑网络层. winds 编译静态库
参照 libuv 项目首页燥起来就行. 其中 gyp 安装了这个版本, 其它随波逐流 ~
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. sh autogen.sh
./configure
make -j4
sudo make install
sudo ldconfig cd
#
# 注意 uv 头文件, 全部导入到系统 include 目录下面
#
rm -rf libuv-1.23. v1.23.1.zip
注意要将编译后 include 完整拷贝到安装目录 include下. 这样 uv 头文件全, 日后会用到.
libuv 开箱即用, 不太需要什么基础封装.
3. pthread - https://github.com/GerHobbelt/pthread-win32
这是最后那半个, 为 winds 引入 POSIX thread 模型. 编译起来很简单(前提咱们 VS 玩的熟).
扯点闲篇. linux 和 winds 相辅相成, 对立而统一. 一个是一切从头码, 一个开始就已经注册未来.
描述比较粗, 但大概这意思. (两个都不 eary, 玩很久才敢入门见岳父岳母) . 这里包装了一层
thread.h - 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 相关代码会得心应手!
到此我们大一统治线程模型就定下来了. 还顺带引出了一个很重要辅助头文件.
struct.h - 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 = +, // 正确基础类型 EBase = -, // 错误基础类型
EParam = -, // 输入参数错误
EFd = -, // 文件打开失败
EClose = -, // 文件操作关闭
EAccess = -, // 没有操作权限
EAlloc = -, // 内存操作错误
EParse = -, // 协议解析错误
ESmall = -, // 过小基础错误
EBig = -, // 过大基础错误
ETimeout = -, // 操作超时错误
}; //
// DCODE - DEBUG 模式下的测试宏
// DCODE({
// puts("debug start...");
// });
//
#ifndef DCODE
# ifdef _DEBUG
# define DCODE(code) do code while()
# 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() #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() #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":
}
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
.
├── 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()
越过声明直接使用的宏声明. 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() //
// 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[];
unsigned i, len = sizeof buf / sizeof *buf;
uv_loop_t * loop = uv_default_loop(); // 目前只对 tty 控制台处理
if (uv_guess_handle() != UV_TTY) {
fprintf(stderr, "uv_guess_handle(1) != UV_TTY!\n");
exit(EXIT_FAILURE);
} uv_tty_init(loop, &tty, , );
uv_tty_set_mode(&tty, UV_TTY_MODE_NORMAL); // 开始发送消息
buf[].base = "\033[46;37m";
buf[].base = u8"(✿◡‿◡) 喵酱 ((●'-'●)) 比 ♥ 里~ \n";
buf[].base = "\033[0m";
for (i = ; 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 系统库设计
这里面设计东东不少, 只挑一些经典的供人看看. 代码即注释 ~
socket.h - 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, );
return fcntl(s, F_SETFL, mode & ~O_NONBLOCK);
} inline int socket_set_nonblock(socket_t s) {
int mode = fcntl(s, F_GETFL, );
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 = ;
return ioctlsocket(s, FIONBIO, &mode);
} inline int socket_set_nonblock(socket_t s) {
u_long mode = ;
return ioctlsocket(s, FIONBIO, &mode);
} // socket_recv - 读取数据
// socket_send - 写入数据
inline int socket_recv(socket_t s, void * buf, int sz) {
return sz > ? recv(s, buf, sz, ) : ;
} inline int socket_send(socket_t s, const void * buf, int sz) {
return send(s, buf, sz, );
} #endif //
// 通用 sockaddr_in ipv4 地址
//
typedef struct sockaddr_in sockaddr_t[]; // 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 = ;
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 = { , };
if (ms > ) {
ov.tv_sec = ms / ;
ov.tv_usec = (ms % ) * ;
}
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 < ? 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
socket.c - 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 - ;
m.tm_mon = se.wMonth - ;
m.tm_mday = se.wDay;
m.tm_hour = se.wHour;
m.tm_min = se.wMinute;
m.tm_sec = se.wSecond;
m.tm_isdst = -; // 不考虑夏令时 tv->tv_sec = (long)mktime(&m);
tv->tv_usec = se.wMilliseconds * ; return ;
} #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 > ) {
r = recv(s, buf, n, );
if (r == ) 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 > ) {
r = send(s, buf, n, );
if (r == ) 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, , 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 ""];
struct addrinfo * addr = NULL, hint = { };
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, );
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 = ;
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 "")
RETURN(EParam, "host port err %s", host);
port = atoi(host);
// 有些常识数字, 不一定是魔法 ... :)
if (port <= || port > )
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 < ) 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 == ) {
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 / ;
to.tv_usec = (ms % ) * ;
n = select((int)s + , &rset, &wset, &eset, &to);
// 超时直接滚
if (n <= ) {
socket_set_block(s);
return EBase;
} // 当连接成功时候,描述符会变成可写
if (n == && FD_ISSET(s, &wset)){
socket_set_block(s);
return SBase;
} // 当连接建立遇到错误时候, 描述符变为即可读又可写
if (FD_ISSET(s, &eset) || n == ) {
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 看清编程源头, 不下时间是不现实的,
幸运的是最终收获 -> 哈哈 -> 怎么脱发又严重了 ....
比如 atom.h - 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, );
#else
_ReadWriteBarrier();
*x = ;
#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
structc 开源框架介绍的更多相关文章
- IOS-常用第三方开源框架介绍
iOS开发-常用第三方开源框架介绍(你了解的ios只是冰山一角) 时间:2015-05-06 16:43:34 阅读:533 评论:0 收藏:0 [点我收藏+] ...
- iOS开发-常用第三方开源框架介绍
iOS开发-常用第三方开源框架介绍 图像: 1.图片浏览控件MWPhotoBrowser 实现了一个照片浏览器类似 iOS 自带的相册应用,可显示来自手机的图片或者是网络图片,可自动从网 ...
- iOS开发-常用第三方开源框架介绍(你了解的ios只是冰山一角)--(转)
图像: 1.图片浏览控件MWPhotoBrowser 实现了一个照片浏览器类似 iOS 自带的相册应用,可显示来自手机的图片或者是网络图片,可自动从网络下载图片并进行缓存.可对图片进行缩放等操作. 下 ...
- structc 开源框架简介
了解 structc-https://github.com/wangzhione/structc structc 是 C 构建基础项目框架. 不是太惊艳, 但绝对是 C 简单项目中一股清流. 它的前身 ...
- C++的ORM 开源框架
C++的ORM 开源框架 介绍一个C++的ORM工具ODB SOCI.LiteSQL.POCO数据库访问类库对比
- 介绍一个非常好用的跨平台C++开源框架:openFrameworks
介绍一个非常好用的跨平台C++开源框架:openFrameworks 简介 首先需要说明的一点是: openFrameworks 设计的初衷不是为计算机专业人士准备的, 而是为艺术专业人士准备的, 就 ...
- 分布式服务框架介绍:最成熟的开源NIO框架Netty
尽管JDK提供了丰富的NIO类库,网上也有很多NIO学习例程,但是直接使用Java NIO类库想要开发出稳定可靠的通信框架却并非易事,原因如下: 1)NIO的类库和API繁杂,使用麻烦,你需要熟练掌握 ...
- 手写开源ORM框架介绍
手写开源ORM框架介绍 简介 前段时间利用空闲时间,参照mybatis的基本思路手写了一个ORM框架.一直没有时间去补充相应的文档,现在正好抽时间去整理下.通过思路历程和代码注释,一方面重温下知识,另 ...
- ERP开源框架 + 二次开发平台 介绍
经历了多年软件开发,深受网络大侠们的资源共享才得以有所成绩, 本人主要是做企业ERP软件,一直有个感受,开发具体某个功能不难,但随着需求的增加,管理庞大的代码却成了最大的问题 而为企业管理所做的开发, ...
随机推荐
- 【BZOJ3675】【Apio2014】序列分割
Description 传送门 Solution 之前我也遇到过一次这种"两段之和乘积作为贡献"的问题:考虑把这一种\((\sum) *(\sum)\)的形式拆括号,就可以发现 ...
- 解题:NOI 1999 生日蛋糕
题面 裸的搜索题,就说剪枝(注:nw->noww->当前,res->rest->剩余): 1.想达到$Nπ$的体积,那么半径一开始最多也就$sqrt(n)$了,再大就超了... ...
- 解题:USACO13NOV Empty Stalls
题面 当然可以用并查集做,不过你需要按秩合并+路径压缩(才可能过),因为数据范围十分不友好...... USACO的官方做法更为优秀.首先题目告诉我们牛们加入的前后顺序不影响结果(自己证明也很容易,显 ...
- bzoj3839【Pa2013】Działka
题目描述 平面上有n个不重复的点.每次询问一个边平行坐标轴的矩形内(包含边界)的点组成的凸包的面积.. 输入格式 第一行两个整数k,n(1<=k<=1000000,3<=n<= ...
- Redis学习基础三
回顾: 上一基础上浅尝了redis的存储数据类型,这一节将分别介绍数据类型的基础使用 一.启动本地Redis服务 1.打开cmd 窗口 使用 cd 命令切换至redis 安装根目录 运行: redis ...
- 预读(读取文件前几行)文件(txt,dat,csv等)程序
需求: txt.dat.csv文件很大,需要花很长的时间打开, 但实际上我们只需要查看文件的前几行,查看数据的内容和格式 exe & code : https://github.com/co ...
- 【python】python安装lxml报错【2】
cl : Command line warning D9025 : overriding '/W3' with '/w' lxml.etree.c c:\docume~\admini~.chi\loc ...
- eos源码分析和应用(一)调试环境搭建
转载自 http://www.limerence2017.com/2018/09/02/eos1/#more eos基于区块链技术实现的开源引擎,开发人员可以基于该引擎开发DAPP(分布式应用).下面 ...
- Linux应用程序设计之网络基础编程
1.TCP/IP协议概述 1.1.OSI参考模型及TCP/IP参考模型 OSI协议参考模型是基于国际标准化组织(ISO)的建议发展起来的,从上到下工分为7层:应用层,表示层,会话层,传输层,网络层,数 ...
- matlab和C语言实现最小二乘法
参考:https://blog.csdn.net/zengxiantao1994/article/details/70210662 Matlab代码: N = ; x = [ ]; y = [ ]; ...