Icharle | Don't forget your first thoughts https://icharle.com/ zh-CN C++|技术|分享|技术宅|勿忘初心,方得始终|敢于正视淋漓的error|互联网开发|程序员|不忘初心|Python|Java Tue, 20 Oct 2020 00:29:00 +0800 Tue, 20 Oct 2020 00:29:00 +0800 博客回归 https://icharle.com/blogback.html https://icharle.com/blogback.html Fri, 27 Jan 2017 00:31:00 +0800 Icharle 起源

自2014年开博客至今,算起来也有俩三年啦。以前还在读书没有时间管理,很多东西不了解,如今上到大学才能看懂一些网页的代码,现在趁着寒假对博客重新整理,以前写的文章全部删除,从现在开始重新编写文章。

博客优化

  • 全站开启HTTPS
  • 主题优化
  • WordPress伪静态
  • 域名变更

我的博客我做主

回归初心,记录生活点滴

]]>
2 https://icharle.com/blogback.html#comments https://icharle.com/feed/blogback.html
基于Linux EPOLL多路复用的C++轻量级WebServer https://icharle.com/webserverepolllinux.html https://icharle.com/webserverepolllinux.html Sat, 30 May 2020 22:16:00 +0800 Icharle 功能
  • 可作为静态webserver(常规的html、图片、视频等)
  • 支持GET、POST(交由cgi处理)请求
  • 多站点支持
  • 支持fastcgi(目前已支持php-fpm)
  • 支持反向代理
  • 支持session、cookie

todo:

  • 负载均衡(开发中,完善支持随机模式、hash模式、权重模式三种负载均衡策略)
  • 日志完善
  • 实现类似nginx中rewrite模块

项目地址 欢迎PR、Issue、Star。

技术实现

  • 使用EPOLL边缘触发(ET)的IO多路复用技术,非阻塞IO+ET方式
  • 多线程(one loop per thread思想)充分利用CPU资源
  • 采用基于事件驱动的Reactor模式,主Reactor负责accept,子Reactor负责处理I/O,通过事件循环
  • FastCGI协议实现支持,可处理动态脚本语言
  • cJSON方式读入配置文件
  • 反向代理中域名自动解析IP地址
  • 状态机化HTTP处理过程

演示

  • 作为静态webserver

    编译好的一个vue项目做为测试

  • 大文件测试

    一个7m+大小的图片做为测试

  • 多host支持

    由于服务器国内没有备案,使用curl模拟http请求在头部添加host测试

    curl -H "Host: 1.icharle.com" http://47.115.26.47/    # 预期输出结果为:这里是1.icharle.com站点
    curl -H "Host: 2.icharle.com" http://47.115.26.47/    # 预期输出结果为:这里是2.icharle.com站点
    
  • 反向代理

    采用新启动一个8085端口webserver反向代理80端口,配置文件示例如下

    {
          "server": {
              "PORT": 8085,
              "ThreadNum": 4,
              "BackLog": 1024
          },
          "host": [{
              "default": {
                  "root": "/home/www/default/",
                  "access_log": "/www/log/default.log",
                  "warn_log": "/www/log/default.warn.log",
                  "proxy_pass": "http://127.0.0.1:80" # 反向代理80端口
              }
          }]
     }
    
  • CGI支持连接php-fpm

    简易的支持CURD的PHP项目做为测试(涉及GET、POST、AJAX、cookie、数据库交互)

食用

要求:

  • Linux系统并已安装gcc/g++编译器
  • GCC >= 4.9
git clone https://github.com/icharle/WebServer.git
cd WebServer
make

# 修改配置文件(见配置文件说明)

# fastcgi使用(目前支持php-fpm)
编译安装PHP
修改php-fpm.conf中listen改用监听9000端口(如下为php-fpm.conf文件)

[global]
pid = xxxxx
error_log = xxxxx
log_level = notice

[www]
listen = 127.0.0.1:9000 # 需要修改的地方
listen.backlog = 8192 
listen.allowed_clients = 127.0.0.1
listen.owner = www  # php-fpm运行的用户
listen.group = www  # php-fpm运行的用户组
listen.mode = 0666
user = www
group = www
pm = dynamic
pm.status_path = /phpfpm_70_status
pm.max_children = 80
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 20
request_terminate_timeout = 100
request_slowlog_timeout = 30
slowlog = var/log/slow.log

# 运行启动
nohup ./bin/webserver >> web.log 2>&1 &

注意:
由于php-fpm默认运行在www用户及www用户组中,将root目录改为www用户,否则运行php脚本文件出现权限错误的问题。

# 创建不可登录的组帐户www
groupadd www
useradd www -s /sbin/nologin -g www  

# 修改root文件目录为www用户及组
chown -R www:www /home/www/(修改为自己root目录)

# 下图作者root文件目录示例

root文件目录示例

配置文件说明

{
  "server": {
    "PORT": 80,         // 运行端口
    "ThreadNum": 4,     // 启动线程数
    "BackLog": 1024     // listen最大值
  },
  "host": [
    {
      "default": {                                      // 默认站点(当host不匹配时候默认走default站点)
        "root": "/home/www/default/",                   // 该站点存放目录处
        "access_log": "/www/log/default.log",           // 暂时无日志输出
        "warn_log": "/www/log/default.warn.log",        // 暂时无日志输出
        "proxy_pass": "http://dududu.soarteam.cn"       // 反向代理配置(仅支持HTTP代理) 不写端口时候默认反代80端口
      },
      "1.icharle.com": {                                // host(即域名)
        "root": "/home/www/1.icharle.com/",
        "access_log": "/www/log/1.icharle.com.log",
        "warn_log": "/www/log/1.icharle.com.warn.log",
        "proxy_pass": "http://127.0.0.1:8043"           // 反向代理(IP+端口、域名+端口)
      },
      "2.icharle.com": {
        "root": "/home/www/2.icharle.com/",
        "access_log": "/www/log/2.icharle.com.log",
        "warn_log": "/www/log/2.icharle.com.warn.log"
      }
    }
  ]
}

杂言

阅读完《Linux多线程服务端编程》一书,尝试进行socket编程,不会点这东西天天被打击,五月份沉静一个月撸出来的项目,月底交作业啦。

]]>
0 https://icharle.com/webserverepolllinux.html#comments https://icharle.com/feed/webserverepolllinux.html
此内容被密码保护 https://icharle.com/tr069wireguard.html https://icharle.com/tr069wireguard.html Tue, 20 Oct 2020 00:29:00 +0800 Icharle

请输入密码访问

]]>
0 https://icharle.com/tr069wireguard.html#comments https://icharle.com/feed/tr069wireguard.html
基于余弦相似度算法计算文本相似度 https://icharle.com/cosinesimilarity.html https://icharle.com/cosinesimilarity.html Wed, 02 Sep 2020 00:13:00 +0800 Icharle 原理

在数学几何运算中,余弦定理用于计算两条边的夹角,余弦值越大,夹角越小。当夹角为0°时,两条边(x,y)完全重合。计算公式如下:
$$ cosθ = \frac{ x_{1}x_{2} + y_{1}y_{2} }{\sqrt{ x_{1}^{2} + y_{1}^{2} }\sqrt{ x_{2}^{2} + y_{2}^{2} }} $$

而对于多维图像(x,y,z...)三(甚至n)条边,计算公式如下:
$$ cos(θ) = \frac{a*b}{|a||b|} = \frac{ \sum \limits_{i=1}^{n}(x_{i} * y_{i})}{ \sqrt{ \sum \limits_{i=1}^{n}(x_{i})^{2}} \sqrt{ \sum \limits_{i=1}^{n}(y_{i})^{2} } } $$

同样对于计算两段文本,计算两段文本的相似度。将文本切割成若干个词向量,切词则交由NLP进行。而将每一个词向量看成数学几何运行中的边,最后则演变成计算多维余弦夹角。

  • 文本切词

    • 文本1:明天\不\下雨
    • 文本2:明天\不\可能\下雨
  • 文本词频统计

    • 文本1词频:(明天=>1, 不=>1, 下雨=>1, 可能=>0)
    • 文本2词频:(明天=>1, 不=>1, 下雨=>1, 可能=>1)
  • 文本词向量

    • 文本1词向量:(1,1,1,0)
    • 文本2词向量:(1,1,1,1)
  • 余弦计算

$$ cos(θ) = \frac{a*b}{|a||b|} = \frac{ \sum \limits_{i=1}^{n}(x_{i} * y_{i})}{ \sqrt{ \sum \limits_{i=1}^{n}(x_{i})^{2}} \sqrt{ \sum \limits_{i=1}^{n}(y_{i})^{2} } } = \frac{ 1×1+1×1+1×1+0×1 }{ \sqrt{1^{2}+1^{2}+1^{2}+0^{2}} \sqrt{1^{2}+1^{2}+1^{2}+1^{2}}} =0.87 $$

配置LAC切词

百度NLP:分词,词性标注,命名实体识别

  • CMakeLists.txt: 修改line:6中"paddle path"
  • main.cpp: 修改line:9中"you model path"

编译运行

git clone https://github.com/icharle/cosine-similarity.git

cd cosine-similarity

cmake .

make && make install

# 运行(output/bin目录下即为二进制运行文件)
./output/bin/main

运行效果

cosine-similarity

项目地址:https://github.com/icharle/cosine-similarity

]]>
0 https://icharle.com/cosinesimilarity.html#comments https://icharle.com/feed/cosinesimilarity.html
让树莓派不再吃灰-VS Code https://icharle.com/raspberryvscode.html https://icharle.com/raspberryvscode.html Sat, 15 Aug 2020 14:47:00 +0800 Icharle 网页版VS Code,通过浏览器远程编辑代码。以后买iPad Pro不再是买后"爱奇艺"。

博主体验一下,用来写脚本代码或者刷题挺好的。当然写大型项目docker的话建议用host网络,避免各种端口映射。

部署

博主采用docker部署,由于官方提示说未使用HTTPS某些功能会不支持,所以加上一层nginx反向代理端口。

## docker-compose.yml
version: "3"

services:
  code-server:
    image: codercom/code-server:latest
    restart: always
    container_name: code-server
    ports:
      - "8083:8080"
    environment:
      - PASSWORD=xxxxxxxxx    # 登录使用的密码
    user: 0:0                 # 0:0表示使用root账户。默认情况是coder用户。当然这个值通过宿主机 id root命令查看
    volumes:
      - ./config:/root/.config       # vs code插件存放目录
      - ./code:/home/coder/project   # 代码存放目录

## nginx配置

# 官方只有只一块配置会导致静态文件404
location / {
    proxy_pass http://ip:8083/;
    proxy_set_header Host $host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection upgrade;
    proxy_set_header Accept-Encoding gzip;
}

# 解决静态文件404文件    
location ~ /static/ {
    proxy_pass http://ip:8083;
}
location ~ /webview/ {
    proxy_pass http://ip:8083;
}

该docker镜像基于debian系统,所以可以当成linux,换国内apt-get镜像源妥妥的安装各种环境,比如Java8、gcc、Python等环境。~~(装上插件上班摸鱼神器)~~

博主经常用到的VScode C++扩展即将支持ARM64平台Add ARM64 support (linux)

]]>
0 https://icharle.com/raspberryvscode.html#comments https://icharle.com/feed/raspberryvscode.html
手撸基于C++11线程池 https://icharle.com/c11threadpool.html https://icharle.com/c11threadpool.html Wed, 05 Aug 2020 00:44:00 +0800 Icharle 技术点
  • 采用queue作为一个队列,生产者消费者模式
  • std::condition_variable条件变量代替Linux Pthread库中的pthread_cond_*()函数提供条件变量相关的功能
  • C++11风格的std::function,后续可以改造成templete模板方法

代码实现

// ThreadPool.h
#ifndef THREADPOOL_H
#define THREADPOOL_H

#include <functional>
#include <vector>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>

namespace icharle {
    class ThreadPool {
    public:
        using Task = std::function<void()>;

        ThreadPool(int threadNum);

        bool addTask(const Task &);

        ~ThreadPool();

    private:
        void Run(ThreadPool *);

    private:
        bool _stop;
        std::vector<std::thread> _threads;
        std::mutex _mutex;
        std::condition_variable _cond;
        std::queue<Task> _tasks;
    };
}
#endif //THREADPOOL_H


// ThreadPool.cpp

#include "ThreadPool.h"

namespace icharle {
    ThreadPool::ThreadPool(int threadNum) :
            stop(false),
            _threads(threadNum) {
        for (int i = 0; i < threadNum; ++i) {
            _threads[i] = thread(Run, this);
        }
    }


    void ThreadPool::Run(ThreadPool *pool) {
        while (true) {
            std::unique_lock<std::mutex> lock(pool->_mutex);
            while (!pool->_stop && pool->_tasks.empty()) {
                pool->_cond.wait(lock);
            }
            if (pool->_stop && pool->_tasks.empty()) {
                return;
            }
            Task nextTask = pool->_tasks.front();
            pool->_tasks.pop();
            lock.unlock();
            nextTask();
        }
    }

    bool ThreadPool::addTask(const Task &) {
        std::unique_lock<std::mutex> lock(_mutex);
        if (!stop) {
            _tasks.emplace(Task);
            lock.unlock();
            _cond.notify_one();
            return true;
        }
        return false;
    }

    ThreadPool::~ThreadPool() {
        std::unique_lock<std::mutex> lock(_mutex);
        _stop = true;
        lock.unlock();
        _cond.notify_all();
        for (size_t i = 0; i < _threads.size(); ++i) {
            _threads[i].join();
        }
    }
}
]]>
0 https://icharle.com/c11threadpool.html#comments https://icharle.com/feed/c11threadpool.html
undefined reference to `vtable for原因解决 https://icharle.com/undefinedreferencevtable.html https://icharle.com/undefinedreferencevtable.html Mon, 27 Jul 2020 09:24:00 +0800 Icharle 前言

最近在开发编译C++新模块项目,犯了一个低级错误,基类中虚析构函数忘记给它添加实现体。看到这个错误联想到是虚函数中出来问题,先是在子类一直没有找到,后面细细发现问题出现在基类中。

问题定位以及解决

首先看存在问题的代码

// Test.h
class Test {
    public:
        Test(std::string name);

        virtual ~Test();

    private:
        std::string username;
};


// Test.cpp
Test::Test(std::string name) :
    username(name) {

}

Test类的虚析构函数中没有函数体,导致编译可以通过,但是在链接的时候就会出现undefined reference to vtable不能链接虚表。

解决方法很简单,在Test析构函数添加{},但更重要在排查问题的过程,写个文章记录一下。

]]>
0 https://icharle.com/undefinedreferencevtable.html#comments https://icharle.com/feed/undefinedreferencevtable.html
Redis5之stream数据结构 https://icharle.com/redis5stream.html https://icharle.com/redis5stream.html Thu, 23 Jul 2020 00:44:00 +0800 Icharle redis中stream数据结构用于实现消息队列,当然之前版本也存在有pubsub(发布订阅方式的消息队列),而这个stream的设计引进类似kafka中的消费组

引用来自官方:Consumer groups were initially introduced by the popular messaging system called Kafka (TM). Redis reimplements a similar idea in completely different terms, but the goal is the same: to allow a group of clients to cooperate consuming a different portion of the same stream of messages.


Stream是仅追加数据结构,xadd将每条信息加入到消息链表中串起来。每条消息都有对应唯一的ID即称为key。
每个Stream可以有多个消费组,每个消费组有多个消费者,每个消费组会有游标last_delivered_id,用于记录当前消费到哪条消息。
消费者内部会有一个状态变量pending_ids,它记录了当前已经被客户端读取但还未ack的消息。

基本使用

  • xadd:追加消息

    127.0.0.1:6379> xadd people * name icharle age 18
    "1595668369649-0"
    127.0.0.1:6379> xadd people * name pad age 13
    "1595668593661-0"
    
    # people为key
    # name icharle age 18为消息内容,是键值对,类似于hash结构
    # *为自动生成 消息ID。生成的格式为:时间戳-x 大致表示为某个时间点第x条消息
    
  • xdel:删除消息

    127.0.0.1:6379> xlen people
    (integer) 2
    127.0.0.1:6379> xdel people 1595668593661-0
    (integer) 1
    127.0.0.1:6379> xlen people
    (integer) 1
    
  • xlen:消息链长度

  • xrange:消息链范围列表

    127.0.0.1:6379> xrange people - + # -表示最小消息id +表示最大消息id
    1) 1) "1595668369649-0"
       2) 1) "name"
          2) "icharle"
          3) "age"
          4) "18"
    2) 1) "1595668593661-0"
       2) 1) "name"
          2) "pad"
          3) "age"
          4) "13"
    

消费者&消费组

单独消费者模式

127.0.0.1:6379> xadd people * name icharle age 18
"1595668369649-0"
127.0.0.1:6379> xadd people * name pad age 13
"1595668593661-0"
127.0.0.1:6379> xadd people * name soarteam age 14
"1595668593661-0"
127.0.0.1:6379>  xread count 1 streams people 0-0  # 从消息链头部消费一条
1) 1) "people"
   2) 1) 1) "1595668369649-0"
         2) 1) "name"
            2) "icharle"
            3) "age"
            4) "18"
127.0.0.1:6379>  xread count 1 streams people $  # 从消息链尾消费最新一条,之前已经在链表中数据不计入其中,因此返回为空
(nil)
127.0.0.1:6379> xread block 0 count 1 streams people $ # 采用永久阻塞方式block 0等待新数据到来,最后一行显示等待时间。 其中0可以换成具体时间,比如1000ms
1) 1) "people"
   2) 1) 1) "1595669721851-0"
         2) 1) "name"
            2) "test"
            3) "age"
            4) "14"
(77.87s)

消费组模式

127.0.0.1:6379> xgroup create people cg1 0-0  # 创建从头开始消费的消费组。将0-0替换为$表示从最新开始消费
OK
127.0.0.1:6379> xreadgroup GROUP cg1 c1 count 1 streams people >  # 创建一个消费者。> 号表示从当前消费组的last_delivered_id后面开始读
1) 1) "people"
   2) 1) 1) "1595669445372-0"
         2) 1) "name"
            2) "icharle"
            3) "age"
            4) "18"
127.0.0.1:6379>
127.0.0.1:6379> xinfo groups people   # 查看消费者
1) 1) "name"
   2) "cg1"
   3) "consumers"       # 总共有一个消费者
   4) (integer) 1
   5) "pending"             
   6) (integer) 1       # 有一条消息未被ack确认
   7) "last-delivered-id"
   8) "1595669445372-0"
   
127.0.0.1:6379> xack people cg1 1595669445372-0  # ack该条消息
(integer) 1
127.0.0.1:6379>
127.0.0.1:6379> xinfo groups people
1) 1) "name"
   2) "cg1"
   3) "consumers"
   4) (integer) 1
   5) "pending"
   6) (integer) 0        # pending状态变为0
   7) "last-delivered-id"
   8) "1595669445372-0"

持久化方面:同样支持AOF、RDB方式(Stream被异步复制到副本中并持久存储到AOF和RDB文件),但消息可能存在丢失,因此适合一些日志业务可以容忍少量消息丢失。Redis全内存型相对比kafka速度上有所提升。

强烈安利官方文档Introduction to Redis Streams

]]>
0 https://icharle.com/redis5stream.html#comments https://icharle.com/feed/redis5stream.html
POSIX线程 https://icharle.com/posixthread.html https://icharle.com/posixthread.html Mon, 29 Jun 2020 06:59:00 +0800 Icharle 基础函数
  • pthread_create用于创建一个线程。
    • pthread_t *tid:线程id的类型为pthread_t,通常为无符号整型,当调用pthread_create成功时,通过*tid指针返回。
    • const pthread_attr_t *attr:指定创建线程的属性,如线程优先级、初始栈大小、是否为守护进程。通过使用NULL作为默认值。
    • void *(*func)(void *):函数指针func,指定当新的线程创建之后,将执行的函数。
    • void *arg:线程将执行的函数的参数。
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg);
  • pthread_join用于等待某个线程退出
    • pthread_t tid:指定要等待的线程ID
    • void **status:如果不为NULL,那么线程的返回值存储在status指向的空间
int pthread_join (pthread_t tid, void ** status);
  • pthread_self用于返回当前线程的ID
pthread_t pthread_self (void);
  • pthread_detach用于指定线程变为分离状态。变为分离状态的线程,如果线程退出,它的所有资源将全部释放。
int pthread_detach (pthread_t tid);
  • pthread_exit用于终止线程,可以指定返回值,以便其他线程通过pthread_join函数获取线程的返回值。
    • void *status:指针线程终止的返回值
void pthread_exit (void *status);

互斥同步

  • pthread_mutex_lock用于临界资源加锁&pthread_mutex_unlock用于临界资源解锁
    • pthread_mutex_t *mptr:pthread_mutex_t类型的变量
int pthread_mutex_lock(pthread_mutex_t * mptr); 
int pthread_mutex_unlock(pthread_mutex_t * mptr); 
  • pthread_cond_wait用于阻塞当前线程(pthread_cond_timedwait限定阻塞时间)
  • pthread_cond_signal/pthread_cond_broadcast用于唤醒一个线程或者全部线程,需要配合pthread_mutex_lock锁的使用。
int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr); 
int pthread_cond_timedwait (pthread_cond_t * cptr, pthread_mutex_t *mptr, const struct timespec *abstime);
int pthread_cond_signal(pthread_cond_t *cptr); 
int pthread_cond_broadcast (pthread_cond_t * cptr);

例子(使用互斥同步)

代码含义:当x<=y时自动挂起thread1,thread2执行修改x、y值使得x>y自动唤醒thread2

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>

typedef void* (*fun)(void*);

int x=1, y = 2;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void* thread1(void*);
void* thread2(void*);

int main(int argc, char** argv)
{
    printf("enter main\n");
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, thread1, NULL);
    pthread_create(&tid2, NULL, thread2, NULL);
    //等待所有线程结束
    pthread_join(tid2, NULL);
    printf("leave main\n");
    exit(0);
}


void* thread1(void* arg)
{
    printf("enter thread1\n");
    pthread_mutex_lock(&mutex);
    
    if (x <= y) {
        printf("x比y小,等待条件符合\n");
        pthread_cond_wait(&cond, &mutex);
    }
    
    pthread_mutex_unlock(&mutex);
    printf("leave thread1\n");
    pthread_exit(0);
}

void* thread2(void* arg)
{
    printf("enter thread2\n");
    pthread_mutex_lock(&mutex);
    // 更改x值使得 x>y 并唤醒thread1
    x = 3;
    if (x > y) {
        printf("x比y大,唤醒thread1\n");
        pthread_cond_signal(&cond);
    }
    pthread_mutex_unlock(&mutex);
    printf("leave thread2\n");
    pthread_exit(0);
}

执行后的结果:

enter main
enter thread1
enter thread2
x比y小,等待条件符合
x比y大,唤醒thread1
leave thread2
leave thread1
leave main
]]>
0 https://icharle.com/posixthread.html#comments https://icharle.com/feed/posixthread.html
C++ socket编程常用函数 https://icharle.com/csocketfunc.html https://icharle.com/csocketfunc.html Thu, 11 Jun 2020 10:21:00 +0800 Icharle 前言

将最近经常使用到的常见socket编程函数做一个笔记记录。

socket

创建进程的套接字描述符。

int socket(int domain, int type, int protocol);

domain:协议族,常用的AF_INET(对应ipv4地址)、AF_INET6(对应ipv6地址)。
type:socket类型,常用的面向连接的流式Socket(SOCK_STREAM)即TCP连接,面向数据报的Socket(SOCK_DGRAM)即UDP连接。
protocol:指定协议,常用的协议IPPROTO_TCP(TCP传输)IPPROTO_UDP(UDP传输)。该protocol与type需要对应,即不能socket类型为UDP而协议为TCP。一般情况下该参数填为0自动使用socket类型的协议。

bind

绑定(ipv4/ipv6)地址并赋值给socket。

int bind(int socketfd, const struct sockaddr *addr, socklen_t addrlen)

socketfd:进程套接字描述符。
addr:sockaddr结构体指针,包含IP地址以及端口。
addrlen:sockaddr结构体长度。

listen

监听新的连接请求。

int listen(int socketfd, int backlog)

socketfd:监听的套接字描述符。
backlog:最大连接数。

accept

接收客户端连接。客户端socket、connect后发送请求,此时服务端通过accept接受请求并建立链接。正常建立连接后返回新的通信套接字描述符(这里称为connect_fd需要与前面的socketfd区分)之前博主也栽倒这坑里

int accept(int socketfd, struct sockaddr *addr, socklen_t *addrlen)

socketfd:监听的套接字描述符。
addr:sockaddr结构体指针,与socket时候的区别是,这里保存的是客户端的IP地址以及端口
addrlen:addr结构体所占有的字节数。

write/read、send/recv

服务端与客户端进行I/O通信。

ssize_t write(int connect_fd, const void *buf, size_t len)
ssize_t read(int connect_fd, void *buf, size_t len)
ssize_t send(int connect_fd, const void *buf, size_t len, int flags)
ssize_t recv(int connect_fd, void *buf, size_t len, int flags)

connect_fd:已连接套接口的描述字。
buf:数据缓冲区。
len:缓冲区长度。
flags:参数如果为0则与write、read相同意义。或者有如下参数可选。

@see https://www.cnblogs.com/ben-ben/articles/2812494.html
MSG_DONTROUTE:不查找表。是send函数使用的标志,这个标志告诉IP,目的主机在本地网络上,没有必要查找表,这个标志一般用在网络诊断和路由程序里面。
MSG_OOB:接受或者发送带外数据
MSG_PEEK:查看数据,并不从系统缓冲区移走数据。是recv函数使用的标志,表示只是从系统缓冲区中读取内容,而不清楚系统缓冲区的内容。这样在下次读取的时候,依然是一样的内容,一般在有过个进程读写数据的时候使用这个标志。
MSG_WAITALL:等待所有数据。是recv函数的使用标志,表示等到所有的信息到达时才返回,使用这个标志的时候,recv返回一直阻塞,直到指定的条件满足时,或者是发生了错误。

读取/写入成功返回字节数大小,失败的时候返回-1。可通过errno捕捉异常。

inet_addr

功能:将点分十进制IP地址转换为网络字节序

in_addr_t inet_addr(const char *cp);

示例

char ip[] = "127.0.0.1";
struct sockaddr_in addr;
# 转换为网络字节序
addr.sin_addr = inet_addr(ip);

inet_aton

功能:将点分十进制IP地址转换为网络字节序

int inet_aton(const char *string, struct in_addr *addr);

示例

char ip[] = "127.0.0.1";
struct sockaddr_in addr;
# 转换为网络字节序
inet_aton(ip, &addr.sin_addr);

inet_ntoa

功能:将网络字节序转换为点分十进制IP地址

char *inet_ntoa(struct in_addr);

示例

char ip[] = "127.0.0.1";
struct sockaddr_in addr;
# 转换为网络字节序
inet_aton(ip, &addr.sin_addr);
# 转换为点分十进制IP地址
char *strAddr = inet_ntoa(addr);

上手博主socket编程的一个简单例子

]]>
0 https://icharle.com/csocketfunc.html#comments https://icharle.com/feed/csocketfunc.html
负载均衡之加权轮询调度算法 https://icharle.com/balancewrr.html https://icharle.com/balancewrr.html Tue, 09 Jun 2020 03:11:00 +0800 Icharle 前言

WRR(Weighted Round Robin)基于权重轮询调度算法。
过去只是在nginx上直接使用,由于最近在自行开发一个webserver,涉及到一个负载均衡功能,因此自行实现基于权重轮询调度算法。

分析

假设有三台服务器(IP 端口 权重)分别为如下:

  • 服务器A:10.1.1.92:8081 weight:5
  • 服务器B:10.1.1.93:8081 weight: 1
  • 服务器C:10.1.1.94:8081 weight: 1

当实现加权轮询调度,可能第一想到的方法是先请求5次服务器A,后分别各请求1次服务器B及服务器C。这种方法存在一个弊端,服务器A在某一段时间内被集中调度
像nginx负载均衡实现基于权重轮询调度,会比较均匀的选择服务器,而不是简单的权重分配。Nginx基于权重的轮询算法的实现:Upstream: smooth weighted round-robin balancing,对比它还实现平滑轮询算法。

同样是上面三台服务器,负载均衡选择顺序会变成如下:

A A B A C A A

相当于简单的基于权重的轮询算法,三台服务器之间负载更加均衡。

实现

大致过程如下:

  • 累加计算权重总值
  • 计算当前节点权重,公式:当前节点权重 = 当前节点权重 + 初始权重
  • 选择权重最大的节点作为本次负载节点,并且权重最大的节点减去总权重
A  B  C
0  0  0  (初始化权重)

5  1  1  (服务器A被选中)
-2  1  1 (由于服务器A权重在此次最大,所以减去总权重7)

3  2  2  (服务器A被选中)
-4  2  2 (由于服务器A权重在此次最大,所以减去总权重7)

1  3  3  (服务器B被选中)
1 -4  3  

6 -3  4  (服务器A被选中)
-1 -3  4

4 -2  5  (服务器C被选中)
4 -2 -2

9 -1 -1  (服务器A被选中)
2 -1 -1

7  0  0  (服务器A被选中)
0  0  0  

代码实现

# 创建一个Server类,当然使用struct也可以。
class Server {
private:
    // 负载均衡IP
    std::string ip;
    // 负载均衡端口
    unsigned int port;
    // 权重
    int weight;
    // 当前权重
    int curWeight;

public:
    Server(std::string ip_, unsigned int port_, int weight_)
            : ip(std::move(ip_)),
              port(port_),
              weight(weight_),
              curWeight(0) {

    }

    ~Server() {
        ip = "";
        port = 0;
        weight = 0;
        curWeight = 0;
    }

    std::string getIp() {
        return ip;
    }

    int getPort() const {
        return port;
    }

    void setWeight(int weight_) {
        weight = weight_;
    }

    int getWeight() const {
        return weight;
    }

    void setCurWeight(int weight_) {
        curWeight = weight_;
    }

    int getCurWeight() const {
        return curWeight;
    }
};

# 负载均衡服务器数组(也即对应上述样例子中的A、B、C)
std::vector<Server> serverLists;

# 负载均衡选择方法
int totalWeight = 0; // 总权重
int maxWeight = 0;  // 当前最大权重
int index = -1;     // 最大权重的index
for (int i = 0; i < serverLists.size(); ++i) {
    // 计算总权重
    totalWeight += serverLists[i].getWeight();
    
    // 计算当前权重 公式:当前节点权重 = 当前节点权重 + 初始权重
    serverLists[i].setCurWeight(serverLists[i].getWeight() + serverLists[i].getCurWeight());

    // 选取最大权重的节点
    if (serverLists[i].getCurWeight() > maxWeight) {
        maxWeight = serverLists[i].getCurWeight();
        index = i;
    }
}
// 将被选择的节点权重(权重最大节点) 减 总权重, 用于下一轮选择
serverLists[index].setCurWeight(serverLists[index].getCurWeight() - totalWeight);
// 返回被选择的节点
return serverLists[index];

参考文章:
平滑的基于权重的轮询算法

]]>
0 https://icharle.com/balancewrr.html#comments https://icharle.com/feed/balancewrr.html
linux进程通信eventfd https://icharle.com/linuxeventfd.html https://icharle.com/linuxeventfd.html Mon, 08 Jun 2020 08:47:00 +0800 Icharle 前言

在muduo网络应用框架中EventLoop类中学习到关于eventfd,所以系统性查找资料学习有关eventfd。

知识点

eventfd通过一个进程间共享的64位计数器来实现进程间的通信,基于事件驱动实现的轻量级进程间通信的系统调用。

int eventfd(unsigned int initval, int flags);
# 返回一个新的文件描述符,该描述符可以用于引用eventfd对象。
  • initval:初始化计数器的值。
  • flags:有三个取值。(在2.6.26版本以下的Linux,flags未使用情况下必须为0)
    • EFD_CLOEXEC:在执行fork子进程的时候一般情况会把父进程的文件描述符复制,而使用该参数可以避免这个问题。
    • EFD_NONBLOCK:设置非阻塞。如果没有该参数read操作会一直阻塞到计数器中有值,相反直接返回-1。
    • EFD_SEMAPHORE:在Linux2.6.30内核后支持,每次read计数器会减一。

read:读取成功返回8字节int型整数,如果缓冲区大小小于8字节则返回EINVAL错误。
write:将缓冲区写入8字节int型整数累加到计数器中,可以存储在计数器中的最大值是最大的无符号64位值减去1(即0xfffffffffffffffe)

结合EPOLL实现通信

#include <iostream>
#include <sys/eventfd.h>
#include <sys/epoll.h>
#include <unistd.h>

#define MAXEVENTS 1024
#define EPOLLWAIT_TIME 10000

int createEventfd() {
    int evtfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    if (evtfd < 0) {
        abort();
    }
    return evtfd;
}

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

    int evtfd = createEventfd();
    int epollfd = epoll_create(MAXEVENTS);

    struct epoll_event event;
    event.data.fd = evtfd;
    // 监听可读事件&ET边缘触发
    event.events = EPOLLIN | EPOLLET;

    int ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, evtfd, &event);

    if (ret < 0) {
        std::cout << "create epoll add event fail" << std::endl;
        abort();
    }

    // fork 子进程
    int pid = fork();

    // 父进程
    if (pid > 0) {
        struct epoll_event events[MAXEVENTS];

        // 监听事件发生
        int nfds = epoll_wait(epollfd, events, MAXEVENTS, EPOLLWAIT_TIME);
        for (int i = 0; i < nfds; ++i) {
            if ((events[i].data.fd == evtfd) && (events[0].events & EPOLLIN)) {
                eventfd_t res;
                read(events[i].data.fd, &res, sizeof(eventfd_t));
                std::cout << "parent read is: " << res << std::endl;
            }
        }
        close(evtfd);
        close(epollfd);
    }
        // 子进程
    else if (pid == 0) {
        std::cout << "child sleep 3s" << std::endl;
        sleep(3);

        //向eventfd计数器写入
        eventfd_t num = 2;
        if (write(evtfd, &num, sizeof(eventfd_t)) == sizeof(eventfd_t)) {
            std::cout << "child write succ" << std::endl;
        }
        close(evtfd);
    }

    return 0;
}
]]>
0 https://icharle.com/linuxeventfd.html#comments https://icharle.com/feed/linuxeventfd.html