linux消息分发机制,linux下使用hiredis异步API实现sub/pub消息订阅和发布的功能

2023-11-14

最近使用redis的c接口——hiredis,使客户端与redis服务器通信,实现消息订阅和发布(PUB/SUB)的功能,我把遇到的一些问题和解决方法列出来供大家学习。

废话不多说,先贴代码。

redis_publisher.h

/*************************************************************************

> File Name: redis_publisher.h

> Author: chenzengba

> Mail: chenzengba@gmail.com

> Created Time: Sat 23 Apr 2016 10:15:09 PM CST

> Description: 封装hiredis,实现消息发布给redis功能

************************************************************************/

#ifndef REDIS_PUBLISHER_H

#define REDIS_PUBLISHER_H

#include

#include

#include

#include

#include

#include

#include

#include

#include

class CRedisPublisher

{

public:

CRedisPublisher();

~CRedisPublisher();

bool init();

bool uninit();

bool connect();

bool disconnect();

bool publish(const std::string &channel_name,

const std::string &message);

private:

// 下面三个回调函数供redis服务调用

// 连接回调

static void connect_callback(const redisAsyncContext *redis_context,

int status);

// 断开连接的回调

static void disconnect_callback(const redisAsyncContext *redis_context,

int status);

// 执行命令回调

static void command_callback(redisAsyncContext *redis_context,

void *reply, void *privdata);

// 事件分发线程函数

static void *event_thread(void *data);

void *event_proc();

private:

// libevent事件对象

event_base *_event_base;

// 事件线程ID

pthread_t _event_thread;

// 事件线程的信号量

sem_t _event_sem;

// hiredis异步对象

redisAsyncContext *_redis_context;

};

#endif

redis_publisher.cpp

/*************************************************************************

> File Name: redis_publisher.cpp

> Author: chenzengba

> Mail: chenzengba@gmail.com

> Created Time: Sat 23 Apr 2016 10:15:09 PM CST

> Description:

************************************************************************/

#include

#include

#include

#include "redis_publisher.h"

CRedisPublisher::CRedisPublisher():_event_base(0), _event_thread(0),

_redis_context(0)

{

}

CRedisPublisher::~CRedisPublisher()

{

}

bool CRedisPublisher::init()

{

// initialize the event

_event_base = event_base_new();// 创建libevent对象

if (NULL == _event_base)

{

printf(": Create redis event failed.\n");

return false;

}

memset(&_event_sem, 0,sizeof(_event_sem));

int ret = sem_init(&_event_sem, 0, 0);

if (ret != 0)

{

printf(": Init sem failed.\n");

return false;

}

return true;

}

bool CRedisPublisher::uninit()

{

_event_base = NULL;

sem_destroy(&_event_sem);

return true;

}

bool CRedisPublisher::connect()

{

// connect redis

_redis_context = redisAsyncConnect("127.0.0.1", 6379);    // 异步连接到redis服务器上,使用默认端口

if (NULL == _redis_context)

{

printf(": Connect redis failed.\n");

return false;

}

if (_redis_context->err)

{

printf(": Connect redis error: %d, %s\n",

_redis_context->err, _redis_context->errstr);// 输出错误信息

return false;

}

// attach the event

redisLibeventAttach(_redis_context, _event_base);// 将事件绑定到redis context上,使设置给redis的回调跟事件关联

// 创建事件处理线程

int ret = pthread_create(&_event_thread, 0, &CRedisPublisher::event_thread, this);

if (ret != 0)

{

printf(": create event thread failed.\n");

disconnect();

return false;

}

// 设置连接回调,当异步调用连接后,服务器处理连接请求结束后调用,通知调用者连接的状态

redisAsyncSetConnectCallback(_redis_context,

&CRedisPublisher::connect_callback);

// 设置断开连接回调,当服务器断开连接后,通知调用者连接断开,调用者可以利用这个函数实现重连

redisAsyncSetDisconnectCallback(_redis_context,

&CRedisPublisher::disconnect_callback);

// 启动事件线程

sem_post(&_event_sem);

return true;

}

bool CRedisPublisher::disconnect()

{

if (_redis_context)

{

redisAsyncDisconnect(_redis_context);

redisAsyncFree(_redis_context);

_redis_context = NULL;

}

return true;

}

bool CRedisPublisher::publish(const std::string &channel_name,

const std::string &message)

{

int ret = redisAsyncCommand(_redis_context,

&CRedisPublisher::command_callback,this, "PUBLISH %s %s",

channel_name.c_str(), message.c_str());

if (REDIS_ERR == ret)

{

printf("Publish command failed: %d\n", ret);

return false;

}

return true;

}

void CRedisPublisher::connect_callback(const redisAsyncContext *redis_context,

int status)

{

if (status != REDIS_OK)

{

printf(": Error: %s\n", redis_context->errstr);

}

else

{

printf(": Redis connected!\n");

}

}

void CRedisPublisher::disconnect_callback(

const redisAsyncContext *redis_context, int status)

{

if (status != REDIS_OK)

{

// 这里异常退出,可以尝试重连

printf(": Error: %s\n", redis_context->errstr);

}

}

// 消息接收回调函数

void CRedisPublisher::command_callback(redisAsyncContext *redis_context,

void *reply, void *privdata)

{

printf("command callback.\n");

// 这里不执行任何操作

}

void *CRedisPublisher::event_thread(void *data)

{

if (NULL == data)

{

printf(": Error!\n");

assert(false);

return NULL;

}

CRedisPublisher *self_this =reinterpret_cast(data);

return self_this->event_proc();

}

void *CRedisPublisher::event_proc()

{

sem_wait(&_event_sem);

// 开启事件分发,event_base_dispatch会阻塞

event_base_dispatch(_event_base);

return NULL;

}

redis_subscriber.h

/*************************************************************************

> File Name: redis_subscriber.h

> Author: chenzengba

> Mail: chenzengba@gmail.com

> Created Time: Sat 23 Apr 2016 10:15:09 PM CST

> Description: 封装hiredis,实现消息订阅redis功能

************************************************************************/

#ifndef REDIS_SUBSCRIBER_H

#define REDIS_SUBSCRIBER_H

#include

#include

#include

#include

#include

#include

#include

#include

#include

class CRedisSubscriber

{

public:

typedef std::tr1::function \

NotifyMessageFn;// 回调函数对象类型,当接收到消息后调用回调把消息发送出去

CRedisSubscriber();

~CRedisSubscriber();

bool init(const NotifyMessageFn &fn);   // 传入回调对象

bool uninit();

bool connect();

bool disconnect();

// 可以多次调用,订阅多个频道

bool subscribe(const std::string &channel_name);

private:

// 下面三个回调函数供redis服务调用

// 连接回调

static void connect_callback(const redisAsyncContext *redis_context,

int status);

// 断开连接的回调

static void disconnect_callback(const redisAsyncContext *redis_context,

int status);

// 执行命令回调

static void command_callback(redisAsyncContext *redis_context,

void *reply, void *privdata);

// 事件分发线程函数

static void *event_thread(void *data);

void *event_proc();

private:

// libevent事件对象

event_base *_event_base;

// 事件线程ID

pthread_t _event_thread;

// 事件线程的信号量

sem_t _event_sem;

// hiredis异步对象

redisAsyncContext *_redis_context;

// 通知外层的回调函数对象

NotifyMessageFn _notify_message_fn;

};

#endif

redis_subscriber.cpp:

/*************************************************************************

> File Name: redis_subscriber.cpp

> Author: chenzengba

> Mail: chenzengba@gmail.com

> Created Time: Sat 23 Apr 2016 10:15:09 PM CST

> Description:

************************************************************************/

#include

#include

#include

#include "redis_subscriber.h"

CRedisSubscriber::CRedisSubscriber():_event_base(0), _event_thread(0),

_redis_context(0)

{

}

CRedisSubscriber::~CRedisSubscriber()

{

}

bool CRedisSubscriber::init(const NotifyMessageFn &fn)

{

// initialize the event

_notify_message_fn = fn;

_event_base = event_base_new();// 创建libevent对象

if (NULL == _event_base)

{

printf(": Create redis event failed.\n");

return false;

}

memset(&_event_sem, 0,sizeof(_event_sem));

int ret = sem_init(&_event_sem, 0, 0);

if (ret != 0)

{

printf(": Init sem failed.\n");

return false;

}

return true;

}

bool CRedisSubscriber::uninit()

{

_event_base = NULL;

sem_destroy(&_event_sem);

return true;

}

bool CRedisSubscriber::connect()

{

// connect redis

_redis_context = redisAsyncConnect("127.0.0.1", 6379);    // 异步连接到redis服务器上,使用默认端口

if (NULL == _redis_context)

{

printf(": Connect redis failed.\n");

return false;

}

if (_redis_context->err)

{

printf(": Connect redis error: %d, %s\n",

_redis_context->err, _redis_context->errstr);// 输出错误信息

return false;

}

// attach the event

redisLibeventAttach(_redis_context, _event_base);// 将事件绑定到redis context上,使设置给redis的回调跟事件关联

// 创建事件处理线程

int ret = pthread_create(&_event_thread, 0, &CRedisSubscriber::event_thread, this);

if (ret != 0)

{

printf(": create event thread failed.\n");

disconnect();

return false;

}

// 设置连接回调,当异步调用连接后,服务器处理连接请求结束后调用,通知调用者连接的状态

redisAsyncSetConnectCallback(_redis_context,

&CRedisSubscriber::connect_callback);

// 设置断开连接回调,当服务器断开连接后,通知调用者连接断开,调用者可以利用这个函数实现重连

redisAsyncSetDisconnectCallback(_redis_context,

&CRedisSubscriber::disconnect_callback);

// 启动事件线程

sem_post(&_event_sem);

return true;

}

bool CRedisSubscriber::disconnect()

{

if (_redis_context)

{

redisAsyncDisconnect(_redis_context);

redisAsyncFree(_redis_context);

_redis_context = NULL;

}

return true;

}

bool CRedisSubscriber::subscribe(const std::string &channel_name)

{

int ret = redisAsyncCommand(_redis_context,

&CRedisSubscriber::command_callback,this, "SUBSCRIBE %s",

channel_name.c_str());

if (REDIS_ERR == ret)

{

printf("Subscribe command failed: %d\n", ret);

return false;

}

printf(": Subscribe success: %s\n", channel_name.c_str());

return true;

}

void CRedisSubscriber::connect_callback(const redisAsyncContext *redis_context,

int status)

{

if (status != REDIS_OK)

{

printf(": Error: %s\n", redis_context->errstr);

}

else

{

printf(": Redis connected!");

}

}

void CRedisSubscriber::disconnect_callback(

const redisAsyncContext *redis_context, int status)

{

if (status != REDIS_OK)

{

// 这里异常退出,可以尝试重连

printf(": Error: %s\n", redis_context->errstr);

}

}

// 消息接收回调函数

void CRedisSubscriber::command_callback(redisAsyncContext *redis_context,

void *reply, void *privdata)

{

if (NULL == reply || NULL == privdata) {

return ;

}

// 静态函数中,要使用类的成员变量,把当前的this指针传进来,用this指针间接访问

CRedisSubscriber *self_this =reinterpret_cast(privdata);

redisReply *redis_reply =reinterpret_cast(reply);

// 订阅接收到的消息是一个带三元素的数组

if (redis_reply->type == REDIS_REPLY_ARRAY &&

redis_reply->elements == 3)

{

printf(": Recieve message:%s:%d:%s:%d:%s:%d\n",

redis_reply->element[0]->str, redis_reply->element[0]->len,

redis_reply->element[1]->str, redis_reply->element[1]->len,

redis_reply->element[2]->str, redis_reply->element[2]->len);

// 调用函数对象把消息通知给外层

self_this->_notify_message_fn(redis_reply->element[1]->str,

redis_reply->element[2]->str, redis_reply->element[2]->len);

}

}

void *CRedisSubscriber::event_thread(void *data)

{

if (NULL == data)

{

printf(": Error!\n");

assert(false);

return NULL;

}

CRedisSubscriber *self_this =reinterpret_cast(data);

return self_this->event_proc();

}

void *CRedisSubscriber::event_proc()

{

sem_wait(&_event_sem);

// 开启事件分发,event_base_dispatch会阻塞

event_base_dispatch(_event_base);

return NULL;

}

hiredis提供了几个异步通信的API,一开始根据API名字的理解,我们实现了跟redis服务器建立连接、订阅和发布的功能,可在实际使用的时候,程序并没有像我们预想的那样,除了能够建立连接外,任何事情都没发生。

网上查了很多资料,原来hiredis的异步实现是通过事件来分发redis发送过来的消息的,hiredis可以使用libae、libev、libuv和libevent中的任何一个实现事件的分发,网上的资料提示使用libae、libev和libuv可能发生其他问题,这里为了方便就选用libevent。hireds官网并没有对libevent做任何介绍,也没用说明使用异步机制需要引入事件的接口,所以一开始走了很多弯路。

关于libevent的使用这里就不再赘述,详情可以见libevent官网。

libevent官网:http://libevent.org/

libevent api文档:https://www.monkey.org/~provos/libevent/doxygen-2.0.1/include_2event2_2event_8h.html#6e9827de8c3014417b11b48f2fe688ae

CRedisPublisher和CRedisSubscriber的初始化过程:

初始化事件处理,并获得事件处理的实例:

_event_base = event_base_new();

在获得redisAsyncContext *之后,调用

redisLibeventAttach(_redis_context, _event_base);

这样就将事件处理和redis关联起来,最后在另一个线程调用

event_base_dispatch(_event_base);

启动事件的分发,这是一个阻塞函数,因此,创建了一个新的线程处理事件分发,值得注意的是,这里用信号灯_event_sem控制线程的启动,意在程序调用

redisAsyncSetConnectCallback(_redis_context,

&CRedisSubscriber::connect_callback);

redisAsyncSetDisconnectCallback(_redis_context,

&CRedisSubscriber::disconnect_callback);

之后,能够完全捕捉到这两个回调。

有些人会觉得这两个类设计有点冗余,我们发现CRedisPublisher和CRedisSubscriber很多逻辑是一样的,为什么不把他们整合到一起成一个类,既能够发布消息也能够订阅消息。其实一开始我就是这么干的,在使用的时候发现,用同个redisAsynContex *对象进行消息订阅和发布,与redis服务连接会自动断开,disconnect_callback回调会被调用,并且返回奇怪的错误:ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context,因此,不能使用同个redisAsyncContext *对象实现发布和订阅。这里为了减少设计的复杂性,就将两个类的逻辑分开了。

当然,你也可以将相同的逻辑抽象到一个基类里,并实现publish和subscribe接口。

编译之前,需要安装hiredis、libevent和boost库,我是用的是Ubuntu x64系统。

hiredis官网:https://github.com/redis/hiredis

下载源码解压,进入解压目录,执行make && make install命令。

libevent官网:http://libevent.org/下载最新的稳定版

解压后进入解压目录,执行命令

./configure -prefix=/usr

sudo make && make install

boost库:直接执行安装:sudo apt-get install libboost-dev

如果你不是用std::tr1::function的函数对象来给外层通知消息,就不需要boost库。你可以用接口的形式实现回调,把接口传给CRedisSubscribe类,让它在接收到消息后调用接口回调,通知外层。

最后贴出例子代码。

publisher.cpp,实现发布消息:

/*************************************************************************

> File Name: publisher.cpp

> Author: chenzengba

> Mail: chenzengba@gmail.com

> Created Time: Sat 23 Apr 2016 12:13:24 PM CST

************************************************************************/

#include "redis_publisher.h"

int main(int argc, char *argv[])

{

CRedisPublisher publisher;

bool ret = publisher.init();

if (!ret)

{

printf("Init failed.\n");

return 0;

}

ret = publisher.connect();

if (!ret)

{

printf("connect failed.");

return 0;

}

while (true)

{

publisher.publish("test-channel", "Test message");

sleep(1);

}

publisher.disconnect();

publisher.uninit();

return 0;

}

subscriber.cpp实现订阅消息:

/*************************************************************************

> File Name: subscriber.cpp

> Author: chenzengba

> Mail: chenzengba@gmail.com

> Created Time: Sat 23 Apr 2016 12:26:42 PM CST

************************************************************************/

#include "redis_subscriber.h"

void recieve_message(const char *channel_name,

const char *message, int len)

{

printf("Recieve message:\n    channel name: %s\n    message: %s\n",

channel_name, message);

}

int main(int argc, char *argv[])

{

CRedisSubscriber subscriber;

CRedisSubscriber::NotifyMessageFn fn =

bind(recieve_message, std::tr1::placeholders::_1,

std::tr1::placeholders::_2, std::tr1::placeholders::_3);

bool ret = subscriber.init(fn);

if (!ret)

{

printf("Init failed.\n");

return 0;

}

ret = subscriber.connect();

if (!ret)

{

printf("Connect failed.\n");

return 0;

}

subscriber.subscribe("test-channel");

while (true)

{

sleep(1);

}

subscriber.disconnect();

subscriber.uninit();

return 0;

}

关于编译的问题:在g++中编译,注意要加上-lhiredis -levent参数,下面是一个简单的Makefile:

EXE=server_main client_main

CC=g++

FLAG=-lhiredis -levent

OBJ=redis_publisher.o publisher.o redis_subscriber.o subscriber.o

all:$(EXE)

$(EXE):$(OBJ)

$(CC) -o publisher redis_publisher.o publisher.o $(FLAG)

$(CC) -o subscriber redis_subscriber.o subscriber.o $(FLAG)

redis_publisher.o:redis_publisher.h

redis_subscriber.o:redis_subscriber.h

publisher.o:publisher.cpp

$(CC) -c publisher.cpp

subscriber.o:subscriber.cpp

$(CC) -c subscriber.cpp

clean:

rm publisher subscriber *.o

致谢:

redis异步API使用libevent:http://www.tuicool.com/articles/N73uuu

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux消息分发机制,linux下使用hiredis异步API实现sub/pub消息订阅和发布的功能 的相关文章

  • @RequestParam、@PathVariable、@RequestBody、@ResponseBody注解辨析

    RequestParam RequestParam 将请求参数绑定到你控制器的方法参数上 是springmvc中接收普通参数的注解 例如 defaultValue为给name设定默认值 RequestParam中的value值必须跟http
  • UML笔记

    UML笔记 枫叶云笔记
  • GLSL-TBN矩阵

    TBN矩阵 一 简述 1 1 TBN矩阵作用 我们研究一个矩阵的时候通常需要了解一个矩阵是从哪一个空间或者说矩阵而来的 如果搜索一下TBN矩阵运算公式可以发现其决定于物体坐标系下的顶点和纹理坐标系下的纹理坐标 想到这里我们需要明确TBN运算
  • 数据库服务版本升级

    数据库版本升级方法 第一种方法 本地升级 数据库服务5 6 5 7 8 0 停库 第二种方法 迁移升级 数据库服务数据迁移到另一台新的数据库服务中 旧版数据库服务地址 10 0 0 51 网络停止 新版数据库服务地址 10 0 0 51 8
  • AR回归模型详解

    转 http geodesy blog sohu com 273714573 html 1 自回归模型的定义 自回归模型 Autoregressive Model 是用自身做回归变量的过程 即利用前期若干时刻的随机变量的线性组合来描述以后某
  • JavaJDK实现无钥签名根证书与沙箱安全机制

    1 起因 接到项目经理的需求 项目有涉及文件的上传 需要把上传的文件进行数字签名 简称无钥签名 然后对签名后的文件进行无钥验证 对于从来没有听过无钥签名的我感觉很懵 后面就去上网查数字签名是java的哪一块 得到以下结果 Java里其实有两
  • 9.1 Linux配置网络服务

    9 1 1 配置网络参数 9 1 2 创建网络会话 9 1 3 绑定两块网卡 第1步 第2步 第3步 第4步 9 1 1 配置网络参数 在 Linux 系统上配置服务 在此之前 必须先保证主机之间能够顺畅地通信 如果网络不通 即便服务部署得
  • 朱嘉明:区块链成为经济转型、形成产业新业态的技术手段

    文章来自巴比特https www 8btc com live 14 在港珠澳大桥开通 以及粤港澳大湾区规划发展的效应下 珠海和澳门的城市发展进入到一个里程碑式的协同新阶段 尤其是拥有中央战略定位加持的国家级新区 横琴 早已吹响创新发展的号角
  • 第二章:恶意软件动态分析基础

    文章目录 前言 动态分析的局限 前言 静态分析侧重的是恶意软件在文件形式中的表现 动态分析则在一个安全 受控的环境中运行恶意软件以查看其行为方式 通过动态分析 我们可以绕过常见的静态分析障碍 例如加壳 混淆 以更直观地了解给定恶意软件样本的
  • Java 中数据结构LinkedList的用法

    LinkList 链表 Linked list 是一种常见的基础数据结构 是一种线性表 但是并不会按线性的顺序存储数据 而是在每一个节点里存到下一个节点的地址 链表可分为单向链表和双向链表 一个单向链表包含两个值 当前节点的值和一个指向下一
  • IDEA创建父项目和子项目

    一 创建父项目 1 首先在IDEA中使用Spring Initializr的方式创建一个Springboot的工程 点击File gt New gt Project gt Spring Initializr gt Next 2 Projec
  • 首期 OSCHINA 季度软件评选活动正式开启,快来投票吧!

    gt https www oschina net project 2020 q1 project 上周我们发出了 OSCHINA 开源软件趋势榜 即将上线的通知 并收到不少软件推荐 首先要感谢大家的热情参与 若有对此还不了解的朋友 OSCH
  • CSS 滑动门

    先来体会下现实中的滑动门 或者你可以叫做推拉门 滑动门出现的背景 制作网页时 为了美观 常常需要为网页元素设置特殊形状的背景 比如微信导航栏 有凸起和凹下去的感觉 最大的问题是里面的字数不一样多 咋办 为了使各种特殊形状的背景能够自适应元素
  • TypeScript 基础 — Null 和 Undefined

    null 和 undefined 都有各自的类型名称 这些类型本身没有用处 因为我们只能将 null 和 undefined 赋值给定义为 null 或 undefined 类型的变量 let u undefined undefined u
  • Mac os系统下使用python3与Django进行网站搭建-2

    后台管理 站点分为内容发布和公共访问两部分 内容发布的部分是由网站的管理员负责查看 添加 修改 删除数据 开发这些重复的功能是一件繁琐的工作 所以Django能够根据定义的模型类自动地生成管理模块 使用Django的管理模块 需要按照如下步
  • 智能随访系统:提升患者综合服务能力和就医体验,提高医院品牌价值与服务质量

    随着互联网技术的不断发展以及 全民健康 全生命周期管理 概念的深化落实 随访作为医疗过程中的闭环环节 医院传统的人工电话随访方式已不能适应需求 将逐渐被智能化随访系统替代 智能化随访是指结合互联网等主流技术 以专业的随访知识库为基础 提供以
  • uni-app微信小程序开发自定义select下拉多选内容篇

    欢迎点击领取 前端面试题进阶指南 前端登顶之巅 最全面的前端知识点梳理总结 分享一个使用比较久的 技术框架公司的选型 uni app uni ui vue3 vite4 ts 需求分析 微信小程序 uni ui内容 1 创建一个自定义的下拉
  • 基于个人开发的C++MySQL插件使用UE4蓝图连接MySQL数据库

    关于UE4连接数据库 其实很简单 本质上就是使用c 来建立DB操作 再通过封装成蓝图可调用的函数即可 当然一般网络游戏是不需要在蓝图中连接数据库的 因为db操作放在客户端来做是不安全 也是不合理的 试想一下 我如果把你的游戏客户端破解了 是
  • 【推荐算法】FM模型:Factorization Machines

    1 线性回归 在介绍FM之前 我们先简单回顾以下线性回归 回归分析是一种预测性的建模技术 它研究的是因变量 目标 和自变量 预测器 之间的关系 这种技术通常用于预测分析 时间序列模型以及发现变量之间的因果关系 通常使用曲线 直线来拟合数据点

随机推荐

  • Jmeter之json提取器

    目标 步骤 添加 线程组 HTTP 请求 后置处理器 JSON 提取器 配置 引用名称 匹配后的数据要存储的变量名 JSON path json 路径 weatherinfo city 引用 直接引用变量名即可
  • 代码思维怎么训练

    做一个基础页面 表格 表单 导航条 模态框 轮播图 做一个主页 顶部是导航条 导航条的下面是轮播图 右上角是一个注册按钮 点击以后 弹出一个注册的模态框 1 记录思路 2 思路转成注释 越详细越好 3 看着注释写代码 4 如果写不下去 继续
  • 数据库常用SQL语句(二):多表连接查询

    前面主要介绍了单表操作时的相关查询语句 接下来介绍一下多表之间的关系 这里主要是多表数据记录的查询 也就是如何在一个查询语句中显示多张表的数据 这也叫多表数据记录的连接查询 在实现连接查询时 首先是将两个或两个以上的表按照某种关系连接起来
  • nfc(近距离无线通讯技术)

    这个技术由非接触式射频识别 RFID 演变而来 由 飞利浦半导体 现恩智浦半导体 诺基亚和 索尼共同研制开发 其基础是RFID及互连技术 近场通信 Near Field Communication NFC 是一种短距高频的无线电技术 在13
  • 零基础学区块链专栏文章目录

    前往老猿Python博文目录 零基础学区块链专栏 为免费专栏 基于老猿自己零基础学习区块链的知识总结 因此文章一定是循序渐进的介绍区块链相关知识 供类似老猿这种有一定计算机基础但区块链知识为零的同好们参考 但老猿介绍的内容都是概念性的基础知
  • 让你的应用支持新iPad的Retina显示屏

    一 应用图片 标准iOS控件里的图片资源 苹果已经做了相应的升级 我们需要操心的是应用自己的图片资源 就像当初为了支持iPhone 4而制作的 2x高分辨率版本 译者 以下简称高分 图片一样 我们要为iPad应用中的图片制作对应的高分版本
  • java 身边距离怎么查询_附近的人位置距离计算方法

    附近的人的位置用经纬度表示 然后通过两点的经纬度计算距离 根据网上的推荐 最终采用geohash geohash的实现java版 1 importjava util BitSet 2 importjava util HashMap 3 im
  • Pandas删除缺失数据函数--dropna

    在pandas中 dropna函数分别存在于DataFrame Series和Index中 下面我们以DataFrame dropna函数为例进行介绍 Series和Index中的参数意义同DataFrame中大致相同 pandas Dat
  • C# 网络编程之webBrowser乱码问题及解决知识

    在使用PHP MySQL编写网页时 曾近就因为显示中文乱码 口口口 困扰我很长时间 没想到在C 制作浏览器或获取XML页面时也经常会遇到显示中文乱码的问题 可想而知怎样解决编码问题或统一编码问题是非常严重的问题 下面就讲讲我的一些理解及解决
  • 《曾国藩家书》读书手记(修身篇一)

    曾国藩被章太炎评价为 誉之则圣相 谳之则元凶 为什么有这样的评价呢 我们可以看出曾国藩这个人褒贬不一 不过毛和蒋对于曾国藩都是推崇备至 毛说过 吾近于人 独服于曾国藩 看来曾国藩还是有可取之处的 尤其是他的家书 很多人评价甚高 一 修身篇
  • mysql存储引擎层核心服务层_MySQL(逻辑分层,存储引擎,sql优化,索引优化以及底层实现(B+Tree))...

    一 逻辑分层 连接层 连接与线程处理 这一层并不是MySQL独有 一般的基于C S架构的都有类似组件 比如连接处理 授权认证 安全等 服务层 包括缓存查询 解析器 优化器 这一部分是MySQL核心功能 包括解析 优化SQL语句 查询缓存目录
  • 无痕渗透“INSERT INTO”型SQL注入

    原文链接 http www mathyvanhoef com 2011 10 exploiting insert into sql injections html 在某个寂静的深夜 你徘徊在一个网站中 其中包含一个可提交form 需要你输入
  • 通过C#学习redis(集合)

    static void Main string args RedisClient cli new RedisClient 127 0 0 1 6379 password defaultDatabase 0 region 集合操作 Redis
  • Latex 作者上角标,通讯作者的小信封标记

    一 作者上角标 论文中作者的上角标一般用于标记一作二作的单位 添加方式如下 author Lily textsuperscript 1 and Alexw textsuperscript 2 结果如图所示 二 通讯作者的小信封标识 用来表示
  • Java时间日期格式转换

    Java时间格式转换大全 import java text import java util Calendar public class VeDate 获取现在时间 return 返回时间类型 yyyy MM dd HH mm ss pub
  • Dockerfile——ENTRYPOINT详解

    文章目录 前言 一 ENTRYPOINT 命令格式介绍 二 示例 总结 前言 Entrypoint的作用是 把整个container变成了一个可执行的文件 这样不能够通过替换CMD的方法来改变创建container的方式 但是可以通过参数传
  • XYZZY 【POJ - 1932】【SPFA】

    题目链接 有N个点 然后输入1 N个点 输入从它到其他点的血量变化 然后有几个点能到达 最后是这几个点 我们起点为1 终点为N 然后求的是我们是不是有可能或者达到终点 gt 0 直接SPFA跑最长路 感觉是在造样例 6 0 1 2 1000
  • 文件通配符

    一 文件通配符 通配符主要用通过设定一定的条件来查找匹配到的字符 匹配任意个字符包括0个 匹配任意单个字符 username 匹配username的家目录 cp root file1 tom 把file1文件复制到tom用户的家目录中 匹配
  • C语言算法复杂度大O表示法

    算法 程序运行的次数 O 1 常数复杂度 printf hello world O log n 对数复杂度 for int i 1 i lt n i i 2 printf hello world n O n 线性时间复杂度 for int
  • linux消息分发机制,linux下使用hiredis异步API实现sub/pub消息订阅和发布的功能

    最近使用redis的c接口 hiredis 使客户端与redis服务器通信 实现消息订阅和发布 PUB SUB 的功能 我把遇到的一些问题和解决方法列出来供大家学习 废话不多说 先贴代码 redis publisher h gt File