在编程学习上踩过的深坑和小结

函数/系统调用/STL使用

  1. fopen(file,w)时,若此时kill进程,则file内容被清空;open系统调用则可以保证内容不被清空
  2. Linux上,fwrite函数返回值为成功写入的块数,相对于第三个参数来说的
  3. fopen在POSIX系统中,会忽略mode b
  4. sscanf %*s可以忽略字符串
  5. getline返回值ret表示数据大小,包含回车换行,不包含\0,参数n >= ret+1,参数buf包含\0
  6. getline若读取不到换行符,则返回值是实际数据大小
  7. STL vector中的assign操作会包含resize操作
  8. STL的删除操作需注意很多问题,比如要小心map遍历删除操作
  9. 使用STL list::size()方法时,是通过遍历list来获取大小的。调用size()频繁要注意性能问题。可以的话,自己定义变量记录大小
  10. 手动对string里面添加\0的时候,比如s[1] = ‘\0’。string的长度size()方法是不会变的
  11. memcpy一定要对char*操作,如果对A*操作,结果未知是的
  12. sigaction处理过程中会屏蔽处理的信号,exec会继承这个屏蔽,值得注意。sa.sa_flags = sa.sa_flags | SA_NOMASK
  13. sockaddr_in使用前,必须将sin_zero字段填冲0
  14. glob出来的结果gl_pathv中如果是目录,会有/结尾
  15. 非阻塞socket read的时候,当缓冲区的数据小于指定读的数据时,会返回实际读取的数据,不会阻塞
  16. 阻塞socket read的时候,若缓冲区满了,会返回实际读的数据,并不一定是指定的大小。

jsoncpp的使用

  1. 各种Writer的所有write方法最后序列化出来的字符串都包含\n
  2. 一旦使用了[],就会存在这个节点。若之前存在数据,则取值;若不存在,则创建了一个空节点,类型为nullValue
  3. Reader的parse方法,若传入istream,则只读一行内容解析
  4. 字段默认使用双引号引用,若字符串中存在双引号,则会自动添加转义字符’\’
  5. 对于浮点数,无穷大为1e+9999,非数字为null
  6. 创建空的列表节点,value[“xxx”] = Json::Value(Json::arrayValue);
  7. 解析json字符串时,是按照顺序字符串解析数组的

杂七杂八

  1. SSL缓冲区最大为16K。所以每次最多只能读出16K的数据,描述长度的字段只有2个字节,包含头的长度,所以受限
  2. 浮点数存在inf,nan,-inf,-nan,所以/0的时候不会coredump,isnan和isinf可以用来判断
  3. PIPE — 管道有上限,一般64KB就满了,如果写满了,则write调用会阻塞。
  4. 写管道PIPE ,如果数据在4KB(PIPE_BUF)以下则是原子操作
  5. libevent,若fd已经close,但没有在event_base中移除,则下次添加同样的fd时(数字一样,实际表示对象不同),会在之前无效fd上操作,不会有事件响应

go 语言学习

定时器的使用

timer := time.NewTimer(time.Minute * 1)

//do something

<-timer.C

创建定时器时,内部创建了一个chan C,当时间到时,会将当时的时间写入C,所以在到时之前,读C的时候会阻塞,只有当 消耗时间>=定时时间 ,读C才会成功。

 

chan传递string时,是引用,不需要使用地址传递

 

const定义常量时,若未指明值,则为上个常量的值

const(

A=1

B

C=2

D

)

则B=1,D=2

 

package <package name> // import “xxx/xxx/xxx”

这样可以让引用这个包时,强制import后面的位置。比如强制import到一个网页,这样可以保持代码的更新

code in directory **** expects import “xxx/xxx/xxx”

 

 

sql

Rows的Next方法,结果返回false有两种情况,没有next了和执行出错。

所以需要对返回false的情况做判断

如何判断:

if rows.Err() != nil {

// err handler

}

执行出错时,会自动调用rows.Close()

执行正常时,需要自己调用Close,

多次调用Close方法是安全的

 

所以等Next返回false后,不管什么结果调用下Close最方便

gcc,g++常见编译问题解决方法

  1. cannot call member function ‘XXX’ without object  
    XXX 可能需要定义为 static

  2. error: expected initializer before ‘*’ token  
    将对象指针赋值移出 for的第一个条件

  3. error: overloaded function with no contextual type information  
    变量名错了

  4. error: ‘XXX’ was not declared in this scope  
    函数定义前面缺少类::

  5. error: declaration of ‘XXX’ shadows a parameter  
    重名了

  6. warning: backslash and newline separated by space  
    /后面多一个空格

  7. no match for call to ‘XXX’  
    很可能是定义变量和函数名重名了

  8. error: expected unqualified-id before ‘using’  
    class类定义的结尾少;

  9. warning: ‘xxx’ will be initialized after  
    在构造函数初始化列表中的变量初始化顺序应该和变量声明顺序一致

  10. error: request for member ‘find’ in xxx  
    指针,对象使用错误

  11. linking… multiple definition of xxx  
    引用了cpp文件

  12. undefined reference to xxx  
    在单例模式中,若返回对象指针,则提供给外部使用的内联函数不能在cpp中定义 若返回对象引用,可以使用inline,没有这个错误 函数名前面没有类名 XXX::

  13. crosses initialization of xxx  
    goto 后面不能再有定义

git常用操作

修改最后一次commit注释: git commit –amend

 

撤回到前面的提交:

git reset –hard <tag>

git push -f

C++学习笔记一

dynamic_caststatic_castreinterpret_castconst_cast这四种都是C++定义的关键字。

c++标准由ISO制定的,常见的有C++98,C++11,C++17(正在开发)。其中C++11又称为C++0x。linux系统上GCC默认采用的是gnu++98,是C++98的扩展。
在GCC中,C语言默认标准为:gnu89;C++默认标准为:gnu++98

都是用来转换数据类型的。

表达式为:xxx_cast<T>(v)

dynamic_cast

可以转换指针和类型引用

    // class A : class Base
    A a;
    Base &p = dynamic_cast<Base&>(a); // 引用转换
    Base *p = dynamic_cast<Base*>(&a); // 指针转换

类之间转换的时候可以从子类转为基类,只有当基类中有虚函数virtual时,可以从基类转为子类。

当 v 是由子类转成基类的,则可以从基类转成子类的时候可以转换回去,否则转换结果为空。

如果子类中实现了基类的virtual方法,则转成基类后,基类调用该方法时,调用的是子类的方法。

class Base
{
public:
 virtual void print(){};
};

class A : public Base
{
public:
 virtual void print(){};
};

   A a;
   Base *base = dynamic_cast<Base *>(a);
   base->print(); // 会调用 A 的print方法。
  
   A *a_back = dynamic_cast<A *>(base); // 可以转回来,因为 base 是由 A 转成的
   // A *a_back = dynamic_cast<A *>(new Base); // 返回值为空0x0
   
   // Base base = dynamic_cast<Base&>(a); // 引用同样

static_cast

可以转换指针,引用和类型。

在类之间转换时,和 dynamic_cast 类似。区别在与从基类转为子类时,首先不需要基类有虚函数,其次不需要基类是由子类转化过去的。

对于满足 T t(e) 表达式的都可以使用 static_cast 转换。

reinterpret_cast 和 const_cast

比较少见。

 

 

class noncopyable
{
protected:
    noncopyable() {}
    ~noncopyable() {}

private: // emphasize the following members are private
    noncopyable( const noncopyable& );
    noncopyable& operator=( const noncopyable& );
};

noncopyable() 和 noncopyable( const noncopyable& ) 都为构造函数,operator= 是操作符。

    A a; // noncopyable()
    A b(a); //noncopyable( const noncopyable& )
    A b = a; // noncopyable( const noncopyable& )
    b = a; // noncopyable& operator=( const noncopyable& )

在构造对象时,只有一个参数的构造函数会发生隐式转换,会将“=” 右边的值转为相应的构造函数。例如

A b = a;

根据a的类型自动转为使用noncopyable( const noncopyable& )构造函数。

有时候为了避免隐式转换,需要在函数前面加上explicit关键字。

 

 

在利用C++多态性开发插件模式的情况下,基类的析构函数必须为virtual,否则,delete基类指针的时候会出问题。

class A

{ ...};

class B

{...};

A *p = new B;

delete p; // 这里如果A的析构函数不是virtual的,那么不会调用到B的析构函数

期望执行结果:

A -> B -> ~B -> ~A

没加virtual的实际执行结果:

A -> B -> ~A

TLB CACHE Interface

TLB flushing interfaces:

函数 说明 使用场景
void flush_tlb_all(void) 清空全部TLB 内核页表发生改变
void flush_tlb_mm(struct mm_struct *mm) 清空TLB中mm对应的用户地址空间 fork/exec
void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) 清空TLB中vma->vm_mm对应的[start, end-1]范围 munmap
void flush_tlb_page(struct vm_area_struct *vma, unsigned long addr) 同上类似,刷新范围为一页 page fault
void update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep) 清空TLB中的address对应的页表项ptep page fault
void tlb_migrate_finish(struct mm_struct *mm) ??? 进程迁移

 

Cache flushing interfaces:

函数 说明 使用场景
void flush_cache_mm(struct mm_struct *mm) 清空整个用户地址空间的缓存。 exit/exec
void flush_cache_dup_mm(struct mm_struct *mm) 同上,区别是优化了VIPT缓存 fork
void flush_cache_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) 清空缓存中vma->vm_mm对应的[start, end-1]范围 mumap
void flush_cache_page(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn) 同上类似,范围大小为一页。
pfn为物理页框号。
pfn << PAGE_SHIFT = 物理地址
page fault
void flush_cache_kmaps(void) 清空内核地址范围[PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP)]的缓存 highmem平台上kmaps
void flush_cache_vmap(unsigned long start, unsigned long end) 清空[start, end – 1]范围内的缓存 map_vm_area
void flush_cache_vunmap(unsigned long start, unsigned long end) 同上 unmap_kernel_range

 

这里的cache指的是CPU上的缓存。

Linux内核中一般先清空缓存,然后在清空TLB。一下是几种常见的流程:

1) flush_cache_mm(mm);
change_all_page_tables_of(mm);
flush_tlb_mm(mm);

2) flush_cache_range(vma, start, end);
change_range_of_page_tables(mm, start, end);
flush_tlb_range(vma, start, end);

3) flush_cache_page(vma, addr, pfn);
set_pte(pte_pointer, new_pte_val);
flush_tlb_page(vma, addr);

CENTOS6 配置DNS服务器

配置DNS服务器的文章网上很多,这里记录最简化的配置。

1.安装bind,可以通过 yum 进行安装。#yum install bind

安装完成之后,在 /etc 目录下有 named.conf 配置文件,还有在 /var/named/ 目录下有很多文件。

2.修改 /etc/named.conf 。

在此配置文件中,修改以下两行:

listen-on port 53 { any; };

allow-query     { any; };

使得对外也提供服务。

3.在 /etc/named.conf 中添加一段:

zone "linux.xy" IN {
    type master;
    file "linux.zone";
};

将需要解析的地址添加到配置文件中,file 字段对应的是 /var/named/ 目录下的文件。我这里想要解析的域名是linux.xy。

所以还需要在 /var/named/ 目录下创建域名对应的文件,我这里是 “linux.zone” 。

在 /var/named/linux.zone 文件中写入以下内容:

$TTL 3H
@	IN SOA	linux.xy. rname.invalid. (
					0	; serial
					1D	; refresh
					1H	; retry
					1W	; expire
					3H )	; minimum
@	NS	linux.xy.
@	A	192.117.132.24
www     IN      A       192.117.132.24

前面部分可以参考 /var/named/named.empty 文件。

linux.xy 是要解析的域名,后面一定要有个点”.”

rname.invalid. 这个不用管。

192.117.132.24 是解析出来的地址。

4.打开DNS端口。

默认防火墙是关闭这个端口的,打开端口可参照《centos防火墙设置》

nmap 命令可以扫描端口的开放情况。

5.启动服务,测试。

启动服务: #service named start

添加DNS服务器:在 /etc/resolv.conf 文件中

  1. 对于服务器,添加 nameserver 127.0.0.1 到第一条
  2. 对于客户机,添加 nameserver 192.117.132.24 到第一条

测试:在客户机上,执行 ping linux.xy ,若 ping 通了,则简易的服务器搭建完成了。

MySQL创建外键约束无效

CentOS 6 中,使用的MySQL是5.1的版本。

创建外键约束的时候,发现不起作用。

foreign key (<foreign key>) references <original table>(<original key>) on update cascade

后来发现时因为MySQL数据库中存在两种数据存储引擎 InnoDB 和 MyISAM。

默认创建表的时候使用的是 MyISAM,而 MyISAM 不支持外键约束,InnoDB 支持。

所以要使用外键约束,必须使用 InnoDB 引擎。

查看表的存储引擎: >show create table <table name>;

可以在新建表的时候可以在后面追加引擎类型

create table <table name>(
….
) engine=innodb;

如果需要设置MySQL默认使用 InnoDB 作为默认引擎,可以修改 /etc/my.cnf 文件,在 [mysqld] 这一节中添加:

default-storage-engine=INNODB

然后重起数据库就生效了。

 

查看约束: >SELECT * FROM information_schema.`TABLE_CONSTRAINTS`;

如果外键约束创建成功了,则会有显示。

python学习 —— 三、结合thrift的RPC的应用

Thrift 是apache公司的一个服务框架,提供了跨编程语言之间的PRC服务,可以用于python,C,C++,JAVA,PHP等。

thrift 文件

thrift文件是用户需要自己编写的文件,文件中规定了远程服务和接口。这些接口由服务端实现,提供给客户端调用。thrift文件由自己的描述语言(IDL)编写,规定了数据类型,定义变量,常量,定义结构体,定义异常,定义服务等等。具体内容请参考Thrift interface description language,一个 thrift 文件示例  ThriftTest.thrift

自己编写的一个thrift文件:

// agent.thrift

//封装成模块
namespace py agent

//定义了一个结构体
struct Task {
 1: string uuid,
 2: string source,
 3: string cmd,
 4: string name,
}

//定义了一个服务,提供4中接口,其中oneway表示客户端不需要等待服务端的回复
service AgentService {
 bool execute(1: Task task),
 bool status(),
 oneway void test(),
}

thrift文件编写完成之后,由 thrift 自带的工具生成对应语言的代码文件。

$thrift --gen <language> <thrift file>

执行之后会生成一个为 gen 打头的文件夹 ,然后在编程的时候调用里面的内容就可以了。

在生成 thrift 文件的源码之后,就可以开始编写程序了。

服务端编程

引用Thrift network stack的层次图

  +-------------------------------------------+
  | Server                                    |
  | (single-threaded, event-driven etc)       |
  +-------------------------------------------+
  | Processor                                 |
  | (compiler generated)                      |
  +-------------------------------------------+
  | Protocol                                  |
  | (JSON, compact etc)                       |
  +-------------------------------------------+
  | Transport                                 |
  | (raw TCP, HTTP etc)                       |
  +-------------------------------------------+

服务端编写的时候需要设置 Transport,Protocol, Processor, Server。

from agent import AgentService
from agent.ttypes import *

from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer

# use TCP
transport = TSocket.TServerSocket(port=9090)
tfactory = TTransport.TBufferedTransportFactory()
#use binary encoding
pfactory = TBinaryProtocol.TBinaryProtocolFactory()
# use AgentServiceHandler as the processor
handler = AgentServiceHandler()
processor = AgentService.Processor(handler)
# new a server
server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)

AgentServiceHandler 是自己定义的类,里面包含了之前定义的接口,需要在这里进行实现。例如自己需要实现之前定义的 execute 等函数。

实现编写完,那么一个简单的服务端程序就完成了。

客户端编程

同服务端类似,客户端也要设置 Transport,Protocol, Client。

from agent import AgentService
from agent.ttypes import *

from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer

try:
    # 'localhost' should be the real host address.
    # the '9090' port should be the same with the server's
    transport = TSocket.TSocket('localhost', 9090)
    transport = TTransport.TBufferedTransport(transport)
    #use binary encoding
    protocol = TBinaryProtocol.TBinaryProtocol(transport)
    # new a client
    client = AgentService.Client(protocol)

    transport.open()
    # do your job here with the 'client', like, client.test()
    transport.close()
except Thrift.TException, tx:
    print '%s' % (tx.message)

客户端在 open 打开链接之后就可以调用client的接口函数,最后关闭链接 close。

服务端和客户端程序都写完,就可以进行测试使用服务了。服务端的程序是一直在运行的,客户端运行结束后服务端也不会主动关闭服务。

上面的程序实现后,当多个客户端同时请求一项服务时,服务端因为只有一个进程,所以依次进行处理,客户端会出现等待较长的时间。要解决这类问题,可以在服务端使用多线程或者线程池。使用多线程的时候需要注意临界资源的保护。

# use as a multithreaded server
server = TServer.TThreadedServer(processor, transport, tfactory, pfactory)

 

python学习 —— 二、创建进程subprocess模块

subprocess模块用于创建进程,控制进程的输入输出及错误输出管道。

call 系列函数

创建一个新的子进程可以调用 subprocess 下的 call 系列方法。

callcheck_callcheck_output 都可以创建子进程运行指定命令。运行命令的参数可以使用列表结构表示,第 0 项为命令,后面的依次为传入命令的参数。例如 [‘ls’, ‘-a’, ‘-l’]。

  • call 传递子程序运行的参数,返回子程序运行的代码。
  • check_call 和 call 类似,区别是如果返回值非0,则产生异常 CalledProcessError。
  • check_output 运行子进程,以字符串的形式返回子进程的输出结果,子进程输出不会显示出来。可以通过 print 来输出结果。子进程运行返回值如果非0,产生异常 CalledProcessError,这点和 check_call 一样。

这三个函数都会等待子进程执行完才继续执行。如果子进程没有执行完,则父进程就会阻塞

CalledProcessError异常信息是由子进程返回值非0产生的,信息包含:

  1. returncode:返回值
  2. cmd:运行的命令
  3. output:在check_output中子进程的输出,其余为NONE

示例如下:

import subprocess

try:
    ret_code = subprocess.check_call(['./test'])
except subprocess.CalledProcessError as e:
    print "code", e.returncode
    print "cmd:", e.cmd
    print "output", e.output

test 是自己写的程序,直接返回一个非0值,使其产生异常。

Popen 类

subprocess 模块中的 Popen 类也可以用来创建一个子进程,函数返回一个Popen对象。Popen对象包含进程的信息。

Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0);

Popen对象包含 wait 、communicate、kill、pipe_cloexec、poll、send_signal、terminate等方法。

wait:等待进程结束

Popen.wait():等待子进程的结束,返回子进程的退出状态。

比较常用的还有 os 模块中的 wait 函数。

os.wait():等待一个子进程结束,返回一个元组 (pid, status),pid 就是子进程的 pid,status 是一个16位的数,高8位表示子进程退出状态,低8位表示杀死进程的信号。如果有多个子进程,则返回的是第一个结束的子进程的信息,与子进程创建的顺序无关。

import subprocess
import os

p = subprocess.Popen(["sleep", "5"])
print p.pid
p = subprocess.Popen(["sleep", "2"])
print p.pid

sts = os.wait()
print sts
sts = os.wait()
print sts

运行上面这段代码可以看到 wait 返回的是先结束的子进程。

os.waitpid(pid, options):等待给定 pid 的子进程结束。返回元组 (pid, status)。

给定的 pid 不同,影响范围不同:

  1. 若 pid > 0:等待指定的进程
  2. 若 pid = 0 :等待当前进程组的任意子进程
  3. 若 pid = -1 :等待当前进程的任意子进程
  4. 若 pid < -1 :等待进程组 id 为 (-pid) 的任意进程

 communicate:进程间通信

Popen可以重定向管道。设置 input 或者 output 参数。output 用于读取子进程输出,input 用于给子进程输入信息。两者可以结合起来用,形成一个进程之间的双向通信。

Popen 的 communicate 函数通过输入输出管道可以和子进程进行交互

communicate(self, input=None):input 用于发送数据。读取的时候不用传参数,返回值为 tuple (stdout, stderr)。