python的logging模块提供了一个handler,可以解决定期保存日志到文件中。
import logging
from logging.handlers import TimedRotatingFileHandler
import time
# 获取logger,可指定logger名字
logger = logging.getLogger()
# 设置日志级别
logger.setLevel(logging.INFO)
# 生成handler对象,可指定日志生成周期
handler = TimedRotatingFileHandler("a.log", when='D', interval=1, encoding="utf-8")
handler.setLevel(logging.DEBUG)
# 设置日志信息格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(filename)s - %(lineno)d - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
i = 0
while True:
logger.info("msg {}".format(i))
i += 1
time.sleep(2)
还可以根据日志文件大小来保存:
from logging.handlers import RotatingFileHandler
用法类似TimedRotatingFileHandler
本章中演示的是一个基于TCP的client/server程序示例。本文记录了在编程中一些需要注意的细节问题。
当c/s都启动后,3个进程(server父子进程,client进程)都处于阻塞(睡眠)状态:
server父进程阻塞在accept()
server子进程阻塞在read()
client进程阻塞在fgets()
在client连接server过程中,注意三次握手:三次握手是由client的connet()发出第一个分节,server收到后,会回复第二个分节,这时connect()函数才返回,而server直到收到第三个分节才返回。
可以用
netstat -a
ps -t
命令来查看c/s进程的执行状态。
client进程结束,导致client TCP发送一个FIN给server,server则以ACK响应,这就是四次挥手的前本部分。此时,server套接字处于CLOSE_WAIT状态,client套接字处于FIN_WAIT_2状态。
当server收到FIN时,导致子进程终止,那么子进程打开的所有文件描述符也随之关闭,在这里会引发TCP FIN的发送,client则响应ACK,四次挥手的后半部分也结束了,至此server/client都进入TIME_WAIT状态。
这里需要注意的是,server子进程终止时,会给server父进程发送一个SIGCHLD信号,该信号的默认行为是被忽略,如果父进程未处理,那子进程就会进入僵死状态,我们必须处理僵死进程,因为它会占用系统资源。
信号通常是异步发生的,可以由一个进程发给另一个进程,也可以由内核发给某个进程,我们可以设置信号处理函数来捕捉某些信号,但有两个信号不能被捕捉,即SIGKILL和SIGSTOP。
信号处理函数的原型是:
void handler(int signo);
可以通过signal()来设置,比如:
singal(SIGCHLD, handler);
两个函数都可以用来处理已终止的进程,但用法有区别:
wait()可以用来等待单个子进程结束,当由多个子进程时,它将阻塞直到第一个子进程终止为止。
void sigChild(int signo)
{
pid_t pid;
int stat;
pid = wait(&stat);
return;
}
waitpid()通过指定附加选项来对子进程进行更多的控制,最常用的参数是WNOHANG,它告诉内核在没有已终止子进程时不要阻塞。
void sigChild(int signo)
{
pid_t pid;
int stat;
while ((pid=waitpid(-1, &stat, WNOHANG)) > 0) ;
return;
}
如果我们忽略SIGCHLD信号,那僵死的子进程在父进程结束后会被交给init进程去处理,但在服务器中,程序通常是一直运行的,需要手动处理僵死进程。那就需要捕获SIGCHLD信号了:
singal(SIGCHLD, sigChild);
慢系统调用是指那些可能会永远阻塞的系统调用,多数网络支持函数都属于此,慢系统调用基本规则是:当阻塞于某个慢系统调用的进程捕获某个信号且相应信号处理函数返回时,该系统调用可能返回一个EINTR错误。
因此,当server父进程阻塞于accept()时,在sigChild()返回时,内核就会使accept()可能返回一个EINTR错误,如果server父进程不处理EINTR错误,就会中止。可以这么处理:
if ((connFd=accept()) < 0)
{
if (errno == EINTR)
continue;
else
err_sys("accept error")
}
当三次握手完成,连接建立后,client发送了一个RST,在server看来,该连接已经由TCP排队,正在等待server进程调用accept()的时候RST到达了,稍后,server进程调用accept()。此种情况在不同系统中有不同的处理,大多数系统会返回一个错误给server进程,POSIX下的errno为ECONNABORTED。
如何模拟这种情况呢?
server调用listen()后,sleep一小段时间,此时启动client,一旦connect()返回,就设置SO_LINGER选项,就可以产生RST。
先模拟这种情况: c/s正常启动连接后,killserver子进程,作为进程终止处理的部分工作,子进程中所有打开的文件描述符都被关闭,这导致向client发送一个FIN,client响应ACK,四次挥手前半部分正常进行。注意这里,client收到FIN只是表示server进程关闭了连接的服务器端,不再往其中发送任何数据而已,因此client还不知道server已经终止了。此时client继续向连接套接字中写数据,server接收到数据时,发现该进程已终止,于是响应一个RST,如果client阻塞在read()函数时,会正确处理RST反馈,client会有错误信息“server terminated prematurely”;但是当client没有阻塞在read()函数时,client对此毫不知情。鉴于此,select和poll函数的作用就在此,当前进程立即检测到所持有的套接字状态。
当一个进程向某个已收到RST的套接字上进行写操作时,内核向该进程发送SIGPIPE信号,该信号默认行为是终止进程,因此进程必须捕获这个信号,以免意外地被终止。
正确的做法时捕捉后,对该信号进行忽略。
signal(SIGPIPE, SIG_IGN)
c/s正常启动连接后,主机崩溃,已有的网络连接上不发出任何东西,client会阻塞于write(),会启动重传机制,当尝试重传失败后放弃时,client进程会返回一个错误,如果主机崩溃,对client连接无响应,错误是ETIMEOUT;如果中间路由判定主机不可达,则错误是EHOSTUNREACH。
如果想让client自己发现连接断开了,则可以用SO_KEEPLIVE选项
c/s正常启动连接后,主机崩溃重启,此时client发送数据给server,由于server重启丢失了所有连接信息,因此回复client RST。
关机操作时,OS会给仍在运行的进程发送SIGKILL信号,这样就给进程一小段时间来清除和终止,当服务器进程终止时,所有打开的文件描述符也被关闭,之后情况和7一样。
1.笛卡尔坐标系
2.世界坐标系
3.物体坐标系
4.惯性坐标系:是世界坐标系和物体坐标系的半途,从物体坐标系到惯性坐标系只需旋转,从惯性坐标系到世界坐标系只需平移
5.相机坐标系
向量:只有大小和方向的有向线段(几何意义);数字列表或数组(数学意义)
标量
向量与点:向量描述位移[x,y],点描述位置(x,y)
3*3矩阵的几何意义,为什么向量乘以矩阵可以实现坐标空间变换? 向量运算
零向量是加性单位元
负向量是加性逆元
| 向量的大小(长度或模):用向量两边加竖线表示 | v |
标量与向量的乘法:结果还是一个向量
标准化向量(单位向量或法线)
向量的加法减法
向量点乘(内积):结果是标量,等于向量大小与向量夹角的cos值
向量投影,在某个轴上的分量
向量叉乘(叉积):结果是向量,得到的向量垂直于原来2个向量,结果向量的长度等于向量的大小与向量夹角的sin值的积
矩阵:描述2个坐标系之间的关系,描述转换关系
方阵
单位矩阵
转置
矩阵乘法 矩阵与矩阵相乘M1(rx) 和 M2(xc)相乘
如果把矩阵的行当作坐标系的基向量,那么乘以该矩阵就相当于执行了一次坐标转换
矩阵的每一行都能解释为转换后的基向量
线性变换,包括旋转/投影/缩放/切变/镜像
包含平移的变换叫仿射变换,仿射变换是指线性变换后接着平移
具有形式v’ = vM + b都是仿射变换
满足F(a+b) = F(a) + F(b)和F(ka) = kF(a),则称映射F(a)是线性的
所有基本变换除了投影都是可逆的,求逆变换等价于求矩阵的逆,如果矩阵是奇异的,则变换不可逆
如果变换前后两向量的夹角大小和方向都不改变,则该变换是等角的(平移/旋转/均匀缩放)
正交变换是轴保持互相垂直,而且不进行缩放变换
刚体变换是只改变物体的位置和方向,所有长度,角度,面积,体积都不变,平移和旋转是仅有的刚体变换
行列式
矩阵的逆
MM-1 = I
如果一个矩阵有逆矩阵,那它是可逆的或非奇异的;奇异矩阵的行列式为零
伴随矩阵,M的代数余子式矩阵的转置矩阵
矩阵M的逆M-1 = 伴随矩阵 / M的行列式
正交矩阵:M与它的转置矩阵乘积等于I;如果M是正交的就可以完全避免计算逆矩阵了,因为逆矩阵计算量大
如果一组向量互相垂直,他们称为正交基
4*4齐次矩阵(x,y,z,w),齐次坐标,齐次空间
引入齐次矩阵是因为3*3变换矩阵是线性变换,不包括平移,齐次矩阵包含了旋转和平移
方向:比如向量有方向,但没有方位,因为向量没有长宽厚度之说
方位:物体的朝向,是通过相对已知方位的旋转来描述的,旋转的量称作角位移
用矩阵和四元数来表示角位移;欧拉角来表示方位
矩阵
欧拉角将方位分解为绕三个互相垂直轴的旋转(heading(y)-pitch(x)-bank(z)/roll(z)-pitch(x)-yaw(y))
万向锁,做了限制:如果pitch为正负90,则bank为零
简单插值,限制:bank范围是正负180,pitch范围是正负90
四元数的共轭,向量部分变负
四元数的逆等于它的共轭除以它的模
四元数乘积的模等于模的乘积
本文的目标是创建一个自己的Python库,并安装到Python系统目录下,就像调用通常的第三方包一样。
包结构如下:

其中mihooke_package下面是要打包的库文件,包括3个文件__init__.py,client.py,server.py;setup.py位于mihooke_package同级目录,是安装库的安装文件,此文件必不可少。
from setuptools import setup
setup(
name = "MihookePackage",
description = "A package for test",
author = "Mihooke",
packages = ["mihooke_package"]
)
setup函数有很多参数,不过对于安装一个简单的包,上述已经足够了。 这里值得注意的是,setup函数用的是setuptools包里面的,而非distutils.core里的,我们不用distutils.core,是因为在Python3中setuptools是更高级的更好用,功能更多。
setup.py一定要和module同级目录,并在包的最外层
3.在此目录下运行命令
此命令会build你的代码,在此目录下生成build文件夹
4.继续运行命令
此命令生成安装文件wheel文件,若提示bdist_wheel命令不识别,则是电脑里没有装wheel库,需要执行pip install wheel安装。 命令执行后,会在此目录下生成dist和egg-info文件夹,在dist下面会有wheel文件存在
5.继续运行命令
注意,此命令需要管理员权限执行,会提示安装成功与否的信息。可到Python安装目录Python36\Lib\site-packages下查看是否存在刚才安装的包
atomic_flag使用参考: https://en.cppreference.com/w/cpp/atomic/atomic_flag
mutex使用参考: https://en.cppreference.com/w/cpp/thread/mutex
atomic_flag是一个原子bool类型,它保证是lock free(与锁无关),并且它不提供加载或存储操作。利用atomic_flag可以实现自旋互斥(spin mutex),结合thread中的yield,亦可实现真正的互斥锁。既然C++11已经提供了方便的mutex以及RAII用的lock_guard,那么为什么还要用atomic_flag来手动实现一个互斥锁呢?答案是更高效。
看下面关于atomic_flag 和 mutex的测试代码: 编译环境:vs2015 CPU:i5
#include <iostream>
#include <atomic>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>
using namespace std;
using namespace chrono;
atomic_flag afLock = ATOMIC_FLAG_INIT;
atomic<bool> isStart(false);
mutex mLock;
void atomicFunc(int id)
{
// spin with while loop
while (afLock.test_and_set(memory_order_acquire)) ;
int sum = 0;
for (int i = 0; i < 1000000; ++i)
{
sum += i;
}
cout << "thread " << id << endl;
afLock.clear(memory_order_release);
}
void atomicFunc2(int id)
{
// yield when not satisfy
while (!afLock.test_and_set(memory_order_acquire))
{
this_thread::yield();
}
int sum = 0;
for (int i = 0; i < 1000000; ++i)
{
sum += i;
}
cout << "thread " << id << endl;
afLock.clear(memory_order_release);
}
void mutexFunc(int id)
{
lock_guard<mutex> l(mLock);
int sum = 0;
for (int i = 0; i < 1000000; ++i)
{
sum += i;
}
cout << "thread " << id << endl;
}
int main(int argc, char *argv[])
{
{
auto start = chrono::system_clock::now();
vector<thread> vt;
for (int i = 0; i < 10; ++i)
{
vt.emplace_back(atomicFunc, i);
}
for (auto &t : vt)
{
t.join();
}
auto end = chrono::system_clock::now();
auto duration = chrono::duration_cast<microseconds>(end-start);
cout << "atomicFunc:" << double(duration.count()) * microseconds::period::num / microseconds::period::den << endl;
}
{
auto start = chrono::system_clock::now();
vector<thread> vt;
for (int i = 0; i < 10; ++i)
{
vt.emplace_back(atomicFunc2, i);
}
for (auto &t : vt)
{
t.join();
}
auto end = chrono::system_clock::now();
auto duration = chrono::duration_cast<microseconds>(end-start);
cout << "atomicFunc2:" << double(duration.count()) * microseconds::period::num / microseconds::period::den << endl;
}
{
auto start = chrono::system_clock::now();
vector<thread> vt;
for (int i = 0; i < 10; ++i)
{
vt.emplace_back(mutexFunc, i);
}
for (auto &t : vt)
{
t.join();
}
auto end = chrono::system_clock::now();
auto duration = chrono::duration_cast<microseconds>(end-start);
cout << "mutexFunc:" << double(duration.count()) * microseconds::period::num / microseconds::period::den << endl;
}
return 0;
}
benchmark:
atomicFunc:0.048341
atomicFunc2:0.024079
mutexFunc:0.036097
某一次运行结果如上,多次运行,结果类似。
atomic_flag完全可以实现一个功能相同的mutex,如果使用自旋模式,那么效率并没有原生mutex高,是因为while loop,部分时间花在了CPU等待上;利用yield可实现一个高效的mutex
在Windows下写了一个Python脚本用来启动一系列程序,在启动之后,桌面会显示这些打开的窗口,显得很乱,给人的感觉很不好,是否可以把这些窗口自动最小化呢?答案是可以的。需要用到win32 API,先找到对应的窗口,再设置窗口的显示属性
pip install pypiwin32
安装完pypiwin32,就可以调用win32 API了,
import win32gui, win32api, win32con
notepad_win = win32gui.FindWindow("notepad++", None)
其中FindWindow函数原型(C++)为:
HWND FindWindowA(
LPCSTR lpClassName,//类名,可利用Spy++查看,Spy++可在VS的工具栏打开
LPCSTR lpWindowName//即窗口title,可为空
);
Python版本函数和C++一样
win32gui.ShowWindow(notepad_win , win32con.SW_MINIMIZE)
其中ShowWindow函数原型(C++)为:
BOOL ShowWindow(
HWND hWnd,//FindWindow的返回值
int nCmdShow//窗口属性,属性比较多,具体可参考msdn
);