东风草堂博客

公众号:开发者来风

  1. 左值引用和右值引用的区别?右值引用的意义?
    左值引用是对左值的引用,右值引用是对右值的引用。
    功能差异:左值引用是为了避免对象拷贝,如函数传参、函数返回值。右值引用是为了实现移动语义和完美转发。
    怎么区分左值和右值?
    左值可以在等号的左边,可以取地址,具名,比如:变量名、返回左值引用的函数调用、前置自增自减、赋值运算或符号赋值运算、解引用。
    右值只能在等号的右边,不能取地址,不具名,右值有纯右值,比如字面值、返回非引用类型的函数调用、后置自增自减、算术逻辑比较表达式,右值还有将亡值,主要为C++11新引入的与右值引用(移动语义)相关的值类型,可以通过move把左值强制转换成将亡值,将亡值将用来触发移动构造或移动赋值构造,并进行资源转移,最后调用析构函数。
    const的左值引用可以指向右值,但不能修改这个值。右值引用可以通过std::move可以执行左值。声明出来的左值引用和右值引用都是左值。
    移动语义是为了对象赋值时,避免资源的重新分配,比如移动构造和移动拷贝构造,stl的unique_ptr也有用到。
    完美转发指的是,函数模板可以将自己的参数完美的转发给内部调用的其他函数,完美指的是不仅能转发参数的值,还能保证转发时参数的左右值属性保持不变。
    借用万能引用,通过引用的方式来接收左右参数的值。万能引用折叠的原则:参数为左值或者左值引用,T &&将转化为int &(假设传进来的值为int类型),参数为右值或者右值引用,T &&将转化为int &&,std::forward(v),T为左值引用,v将转化为T类型的左值,T为右值引用,v将转化为T类型的右值。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    #include <stdio.h>
    #include <string>
    #include <algorithm>

    using namespace std;

    class ResourceOwner
    {
    public:
    ResourceOwner(const char res[])
    {
    theResource = new string(res);
    }

    ResourceOwner(const ResourceOwner &other)
    {
    printf("copy %s\n", other.theResource->c_str());
    theResource = new string(other.theResource->c_str());
    }

    ResourceOwner(ResourceOwner &&other)
    {
    printf("copy2 %s\n", other.theResource->c_str());
    theResource = other.theResource;
    other.theResource = nullptr;
    }

    ResourceOwner &operator=(const ResourceOwner &other)
    {
    ResourceOwner tmp(other);
    swap(theResource, tmp.theResource);
    printf("assign %s\n", other.theResource->c_str());
    }

    ResourceOwner &operator=(ResourceOwner &&other)
    {
    theResource = other.theResource;
    other.theResource = nullptr;
    printf("assign2 %s\n", theResource->c_str());
    }

    ~ResourceOwner()
    {
    if (theResource)
    {
    printf("destructor %s\n", theResource->c_str());
    delete theResource;
    }
    }

    private:
    string *theResource;
    };

    void testCopy()
    {
    // case 1
    printf("=====start testCopy()=====\n");
    ResourceOwner res1("res1");
    ResourceOwner res2 = res1;
    // copy res1
    printf("=====destructors for stack vars, ignore=====\n");
    }

    void testAssign()
    {
    // case 2
    printf("=====start testAssign()=====\n");
    ResourceOwner res1("res1");
    ResourceOwner res2("res2");
    res2 = res1; // 先调拷贝再调赋值,竟然还要调用一次拷贝!
    // copy res1, assign res1, destrctor res2
    printf("=====destructors for stack vars, ignore=====\n");
    }

    void testRValue()
    {
    // case 3
    printf("=====start testRValue()=====\n");
    ResourceOwner res2("res2");
    res2 = ResourceOwner("res1"); // 先调拷贝再调赋值,加了移动复制运算后,直接移动就行了!
    // copy res1, assign res1, destructor res2, destructor res1
    printf("=====destructors for stack vars, ignore=====\n");
    }

    ResourceOwner getRO()
    {
    ResourceOwner res1("tmp");
    return res1;
    }

    void testFuncReturn()
    {
    // case 4
    printf("=====start testFuncReturn()=====\n");
    ResourceOwner res2("res2");
    res2 = getRO();
    // copy res1, assign res1, destructor res2, destructor res1
    printf("=====destructors for stack vars, ignore=====\n");
    }

    int main()
    {
    testCopy();
    testAssign();
    testRValue();
    testFuncReturn();
    }
    没加移动语义前:

    加了移动语义后:

    返回值不需要std::move,编译器会进行返回值优化。
  2. C++11智能指针以及使用场景?
    指针管理的困境:资源释放了,指针没有置空,比如野指针(资源释放了,但没有置为nullptr,还在继续使用)、指针悬挂(多个指针指向同一个资源,其中一个指针释放了资源也置空了,但是其他的指针并不知道)、踩内存(拿着野指针去修改新分配的资源);没有释放资源,产生内存泄漏;重复释放资源,引发coredump;
    通过RAII的方式来解决,有shared_ptr、weak_ptr和unique_ptr。
    unique_ptr 类似于一个独占所有权的指针,只能有一个指针管理一个对象,当 unique_ptr 被销毁时,它所管理的对象也会被随之销毁。它的定义和用法如下:
1
2
std::unique_ptr<int> ptr(new int(10));
std::cout << *ptr << std::endl;

shared_ptr 是一种共享所有权的指针,多个指针可以同时管理一个对象,当最后一个 shared_ptr 被销毁时,它所管理的对象才会被随之销毁。它的定义和用法如下:

1
2
3
std::shared_ptr<int> ptr1(new int(10));
std::shared_ptr<int> ptr2 = ptr1;
std::cout << *ptr1 << " " << *ptr2 << std::endl;

weak_ptr 是 shared_ptr 的一种扩展,它可以不增加所管理对象的引用计数而与 shared_ptr 交互,但它不能直接访问所管理的对象,需要通过 lock() 函数获得一个指向所管理的对象的 shared_ptr。通常用于解决 shared_ptr 循环引用的问题。它的定义和用法如下:

阅读全文 »

1
https://dev.mysql.com/doc/dev/mysql-server/latest/

初识mysql


按照数据结构来组织、存储和管理数据的仓库。

  • OLTP(on-line transaction processing)联机事务处理,主要数据库增删改查。
  • OLAP(on-line analytical processing)联机分析处理,主要对数据库进行统计分析,为决策提供支持。
    SQL命令:
  • DQL,数据查询语言,select
  • DML(data manipulate language),数据操作语言,插入删除更新记录
  • DDL(data define language),数据定义语言,创建修改删除表
  • DCL(data control language),数据控制语言,授予用户权限、收回用户权限
  • TCL(transaction control language),事务控制语言,事务提交,事务回滚
    连接池模型:
1
2
3
4
5
6
7
8
select(listenfd+1, readfds, NULL, NULL, 0); // 一直阻塞。使用select,fd少,可以跨平台
int clientfd = accept(listenfd, &addr, &len);
mysql_thread_create(key_thread_one_connection, &id, &connection_attrib, handle_connection, (void*)channel_info);

while (1) { // 连接作为单独的session
int n = read(clientfd); // 阻塞的
do_command();
}
阅读全文 »

如何生成core文件

系统ulimit -c大于0,一般设为unlimited,进程接收到异常信号后,默认是生成core文件,如果/proc/sys/kernel/core_pattern为core,则文件生成在进程的getcwd工作目录,如果/proc/sys/kernel/core_uses_pid为1,则core文件以pid结尾。

对于正在运行的进程,如果不想让进程退出,可以用gcore pid生成该进程的core文件。

对于代码中有对异常信号进行处理的情况,则接收到异常信号时,进入自定义的信号处理函数中,不再生成core文件,此时如果还想生成core文件,需要在处理函数末尾做如下处理:

1
2
signal(signo, SIG_DFL); // 默认生成core文件
kill(getpid(), signo); // signo为当前正在处理的信号类型
阅读全文 »

ipv6相关的资料UPnP-gw-WANIPv6FirewallControl-v1-Service.pdf

miniupnpd

1
2
3
# 开启debug模式,了解更加详细
$ ./configure --ipv6 --debug
$ make

配置文件如下,最后./miniupnpd -f miniupnpd.conf -d启动,-d可以看到输出的信息,程序中使用了syslog输出日志,相关日志可以在/var/log/messages查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
$ vim miniupnpd.conf
# 具体内容如下
ext_ifname=ens33
listening_ip=ens33
http_port=0
ipv6_disable=yes
secure_mode=no
system_uptime=yes
notify_interval=60
clean_ruleset_interval=600
uuid=00000000-0000-0000-0000-000000000000
allow 1024-65535 192.168.0.0/24 1024-65535
# disallow requests whose description string matches the given regex
# deny 1024-65535 192.168.1.0/24 1024-65535 "My evil app ver [[:digit:]]*"
allow 1024-65535 192.168.1.0/24 1024-65535
allow 1024-65535 192.168.0.0/23 22
allow 12345 192.168.7.113/32 54321
deny 0-65535 0.0.0.0/0 0-65535

$ ./miniupnpd -f miniupnpd.conf -d
# 如果是在路由器上,可以不用启动miniupnpd服务端,因为它本身有网关程序

# ipv6模式下报错:可能需要修改源代码,不支持在本设备跑sever再跑client,miniupnpd 需要部署在网关中
miniupnpd[7241]: Received UDP Packet (IPv6)
miniupnpd[7241]: get_lan_for_peer() looking for LAN interface index=2
miniupnpd[7241]: ifname=eth0 index=2 str=192.168.1.2 addr=c0a80102 mask=ffffff00
miniupnpd[7241]: ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1 (ver=1)
miniupnpd[7241]: SSDP M-SEARCH from [fe80::d160:df3c:91b8:1bcf%eth0]:63218 ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1
miniupnpd[7241]: Single search found
miniupnpd[7241]: SendSSDPResponse(): 0 bytes to [fe80::d160:df3c:91b8:1bcf%eth0]:63218 ST: HTTP/1.1 200 OK
CACHE-CONTROL: max-age=120
ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1
USN: uuid:00000000-0000-0000-0000-000000000000::urn:schemas-upnp-org:device:InternetGatewayDevice:1
EXT:
SERVER: Linux/5.4.170-1.el7.elrepo.x86_64 UPnP/1.1 MiniUPnPd/2.3.1
LOCATION: http://[2408:822a:2901:8780:243c:64ef:3b02:4982]:31877/rootDesc.xml
OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01
01-NLS: 1675676683
BOOTID.UPNP.ORG: 1675676683
CONFIGID.UPNP.ORG: 1337

miniupnpd[7241]: get_src_for_route_to ([2408:822a:2901:8780:243c:64ef:3b02:4982]:7784)
miniupnpd[7241]: get_lan_for_peer() looking for LAN interface index=1
miniupnpd[7241]: ifname=eth0 index=2 str=192.168.1.2 addr=c0a80102 mask=ffffff00
miniupnpd[7241]: HTTP peer [2408:822a:2901:8780:243c:64ef:3b02:4982]:7784 is not from a LAN, closing the connection
阅读全文 »

背景

musl静态链接后,通过_Unwind_Backtrace某些架构无法获取到堆栈信息了,如arm32的SIGSEGV,libbacktrace是对其进行封装,所以也是如此。

使用http://musl.cc/下载的静态编译工具+libunwind同样无法获取到堆栈信息。

需要加入libunwind库的方法解决,具体如下。

参考:
https://cloud.tencent.com/developer/article/1173442
https://github.com/boostorg/stacktrace
https://www.boost.org/doc/libs/develop/doc/html/stacktrace.html
https://www.jianshu.com/p/58d32fbd8dfa
https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html
https://gitlab.alpinelinux.org/alpine/aports/-/blob/master/main/libunwind/musl-mips-fix.patch

阅读全文 »

参考资料

https://datatracker.ietf.org/doc/html/rfc7540
https://www.nghttp2.org/documentation/

h2的优势:

  1. 性能更强:HTTP/2的多路复用技术,不仅可以减少连接次数,同时使用非阻塞I/O技术可以在一次握手过程中提高传输性能。
  2. 更强大的头部压缩:HTTP/2使用HPACK技术对头部信息进行压缩,可以有效减少传输的头部信息大小,从而节省带宽消耗。
  3. 服务端推送:服务器可提前将客户端可能需要的资源进行推送,从而可以减少客户端的重复请求,提升页面访问速度。
  4. 请求优先级:HTTP/2支持浏览器控制请求和响应的优先级,从而可以减少资源的加载时间。
  5. 更安全:HTTP/2默认使用HTTPS,可以有效防止中间人攻击,保障用户信息传输的安全性。

nghttp2 demo使用方法

阅读全文 »

连接池使用

需要先构造连接池对象,并完成初始化,连接池可以连接多个rpc服务器,从连接池取连接时会随机取不同服务器的连接,达到负载均衡的效果。

下图中为连接池里面使用的装饰者模式uml类图,从连接池中拿出的ITransportWrapper连接被PoolingTransport装饰后,close功能是自动放回连接池中,而不是真正的close掉连接,同样的,FakeTransport装饰后,IsOpen总是返回true,因为它是一个假冒的连接

连接池连接状态变更图如下,首先打开一个连接,打开成功进入BUSY状态,失败则进入DEAD状态,如果BUSY状态的连接使用过程中出现异常,也会进入DEAD状态。

阅读全文 »

网络IO主流程

TNonblockingServer使用的网络io模型为非阻塞同步io模型,accept由单个io线程处理,recv由多个io线程处理,相应的rpc事务处理由另外的线程池完成,处理完成后通知对应的io线程将处理结果进行send。

accept连接

所有网络连接由ioThreads_[0]线程监听端口,在listenHandler中处理,accept后生成一个clientConnection,如果存在多个io线程,ioThreads_[0]线程通过robin算法从所有ioThreads_中选择一个io线程处理事务,即ioThreads_[0]通过pipe管道将clientConnection信息通知被选择的io线程,被选择的io线程此时会收到通知并处理notifyHandler,被选择的io线程在notifyHandler中将clientConnection信息读取出来,再进行recv数据并解析处理。

recv接收

阅读全文 »

golang使用起来总体感觉比较方便,里面很多思想都是相同的,学习过c/c++的朋友就很容易理解,封装继承多态都差不多,对异常处理有一些不一样,和java比较类似。

阅读全文 »

两种nlogn时间复杂度的排序算法,一个归并排序,一个快速排序。归并排序和快速排序都采用了分治的思想来解决排序的问题,将大问题转化为小问题进行处理,归并排序是由下到上,先划分子问题到不能再划分的地步,再进行合并操作,而快速排序是先分区排序,将分区点放在最终排好序的位置,然后对分区点两边的子数组再进行分区排序处理,所以快排是由上到下进行处理的。归并排序和原始数组的有序程度无关,时间复杂度都是nlogn,但是空间复杂度为O(n),而快速排序大部分情况下的时间复杂度可以做到nlogn,但也有极端情况退化为n^2,另外归并排序是稳定的排序算法,但快排不是,只是快排可以原地排序,空间复杂度为O(1),所以实际使用过程中,还是快排用的比较广泛。

阅读全文 »
0%