文件监控
在 linux 内核中,Inotify 是一种用于通知用户空间程序文件系统变化的机制, 从 2.6.13 开始引入。它监控文件系统的变化,如文件新建、修改、删除等,并可以将相应的事件通知给应用程序。Inotify 既可以监控文件,也可以监控目录。当监控目录时,它可以同时监控目录及目录中的各子目录及文件。Golang 的标准库 syscall 实现了该机制。为了进一步扩展和抽象,fsnotify 包实现了一个基于 channel 的、跨平台的实时监听接口。
max_queued_evnets 表示调用inotify_init时分配给inotify instance中可排队的event最大值,超出该值的event将被丢弃并触发IN_Q_OVERFLOW事件
max_user_instances 表示每一个real user ID可创建的inotify instatnces的数量上限
max_user_watches 表示每个inotify instance可监控的最大文件数量
cat /proc/sys/fs/inotify/max_user_watches
fs.inotify.max_user_watches=99999999
sysctl -p
业务相关:
inode是UNIX操作系统中的一种数据结构,其本质是结构体。包含了与文件系统中各个文件相关的一些重要信息。inode存储的是文件的元信息,比如:文件字节数、文件属主UID、文件属组GID、读写执行权限、时间戳等。inode是linux系统识别文件的唯一标识
查看文件inode命令
ls -i 文件名
stat 文件名
Linux 系统中,每个用户的 ID 细分为 2 种,分别是用户 ID(User ID,简称 UID)和组 ID(Group ID,简称 GID),这与文件有拥有者和拥有群组两种属性相对应
锁定:usermod -L xxx
解锁:usermod -U xxx
锁定:passwd -l xxx
解锁:passwd -u xxx
文件监控仓库:
https://github.com/fsnotify/fsnotify
示例代码
package main
import (
"log"
"github.com/fsnotify/fsnotify"
)
func main() {
// Create new watcher.
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
// Start listening for events.
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
log.Println("event:", event)
if event.Has(fsnotify.Write) {
log.Println("modified file:", event.Name)
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()
// Add a path.
err = watcher.Add("/tmp")
if err != nil {
log.Fatal(err)
}
// Block main goroutine forever.
<-make(chan struct{})
}
type Event struct {
Name string // Relative path to the file or directory.
Op Op // File operation that triggered the event.
}
// Op describes a set of file operations.
type Op uint32
// These are the generalized file operations that can trigger a notification.
const (
Create Op = 1 << iota
Write
Remove
Rename
Chmod
)
// fsnotify.go
func (op Op) String() string {
// Use a buffer for efficient string concatenation
var buffer bytes.Buffer
if op&Create == Create {
buffer.WriteString("|CREATE")
}
if op&Remove == Remove {
buffer.WriteString("|REMOVE")
}
if op&Write == Write {
buffer.WriteString("|WRITE")
}
if op&Rename == Rename {
buffer.WriteString("|RENAME")
}
if op&Chmod == Chmod {
buffer.WriteString("|CHMOD")
}
if buffer.Len() == 0 {
return ""
}
return buffer.String()[1:] // Strip leading pipe
}
touch命令新建文件,实际会修改文件的时间属性,所以会有一个CREATE事件,一个CHMOD事件。
重命名时会产生两个事件,一个是原文件的RENAME事件,一个是新文件的CREATE事件。
fsnotify 不足的是目前它无法递归的监听子目录变更事件,需要我们自已去实现。 create 事件+目录
does not provide network level support for file notifications, and neither do the /proc and /sys virtual filesystems.
#查看cpu冲高
top -p pid
#查看内存占用
cat /proc/pid/status
#查看Linux内核版本命令
方法一:cat /proc/version
方法二:uname-a
挂载(mounting)是指由操作系统使一个存储设备(诸如硬盘、CD-ROM或共享资源)上的计算机文件和目录可供用户通过计算机的文件系统访问的一个过程。
检查linux是否支持netlink
go语言实现进程监控
https://github.com/njcx/gonlconnector
内核源代码:
1.arch目录包括了所有和体系结构相关的核心代码。它下面的每一个子目录都代表一种Linux支持的体系结构,例如i386就是Intel CPU及与之相兼容体系结构的子目录。PC机一般都基于此目录。
2.include目录包括编译核心所需要的大部分头文件,例如与平台无关的头文件在include/linux子目录下。
3.init目录包含核心的初始化代码(不是系统的引导代码),有main.c和Version.c两个文件。这是研究核心如何工作的好起点。
4.mm目录包含了所有的内存管理代码。与具体硬件体系结构相关的内存管理代码位于arch//mm目录下。
5.drivers目录中是系统中所有的设备驱动程序。它又进一步划分成几类设备驱动,每一种有对应的子目录,如声卡的驱动对应于drivers/sound。
6.ipc目录包含了核心进程间的通信代码。
7.modules目录存放了已建好的、可动态加载的模块。
8.fs目录存放Linux支持的文件系统代码。不同的文件系统有不同的子目录对应,如ext3文件系统对应的就是ext3子目录。
Kernel内核管理的核心代码放在这里。同时与处理器结构相关代码都放在arch//kernel目录下。
9.net目录里是核心的网络部分代码,其每个子目录对应于网络的一个方面。
10.lib目录包含了核心的库代码,不过与处理器结构相关的库代码被放在arch/*/lib/目录下。
11.scripts目录包含用于配置核心的脚本文件。
12.documentation目录下是一些文档,是对每个目录作用的具体说明。
ipc 通信
接收消息
reader := bufio.NewReader(conn)
go func() {
for {
if ipc.isAlive {
//读取客户端内容
message, err := reader.ReadString('\n')
if err != nil {
return
}
var xxx XXX
error := json.Unmarshal([]byte(message), &xxx)
if err != nil {
util.HandleErr(error)
}
}
}
}()
Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。
Http连接:http连接就是所谓的短连接,及客户端向服务器发送一次请求,服务器端相应后连接即会断掉。 TCP/IP是传输层协议,主要解决数据如何在网络中传输;而Http是应用层协议,主要解决如何包装数据。
socket连接:socket连接及时所谓的长连接,理论上客户端和服务端一旦建立连接,则不会主动断掉;他包含网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远程主机的IP地址,远地进程的协议端口。
建立socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket,另一个运行与服务器,称为ServerSocket。
套接字之间的连接分为三个步骤:服务器监听、客户端请求、连接确认。
1.服务器监听:服务端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
2.客户端请求:至客户端的套接字提出连接请求,要连接的目标是服务器端的套接字,为此客户端的套接字必须首先描述他要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后向服务器端套接字提出连接请求。
3.连接确认:当服务器端套接字监听到或者说接收到客户端的套接字连接请求是,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式链接链接,而服务器端 套接字继续处于监听状态,继续接受其他客户端套接字的连接请求。
服务端
阻塞IO是指客户端请求服务器端,服务器端处理完成后,再将返回值给客户端。此时客户端一直处在阻塞状态。当客户端太多、同时并发请求的时候,服务器端处理不过来,服务端就会一直卡在那里等待,影响用户体验。因此提出了非阻塞IO。
非阻塞IO是指客户端请求服务器端,服务器端有一些监听器负责接受请求,接收到客户端发来的请求之后立即返回,即告诉客户端已经接受好了,等处理完成后再把数据返回客户端,在服务端处理数据期间客户端不会一直阻塞等待。
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
package main
import (
"bufio"
"fmt"
"io"
"net"
)
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
var buf [1024]byte
for {
n, err := reader.Read(buf[:])
if err == io.EOF {
break
}
if err != nil {
fmt.Println("read from client failed, err:", err)
break
}
recvStr := string(buf[:n])
fmt.Println("收到client发来的数据:", recvStr)
}
}
func main() {
listen, err := net.Listen("tcp", "127.0.0.1:port")
if err != nil {
fmt.Println("listen failed, err:", err)
return
}
defer listen.Close()
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("accept failed, err:", err)
continue
}
go process(conn)
}
}
客户端
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:port")
if err != nil {
fmt.Println("dial failed, err:", err)
return
}
defer conn.Close()
msg := `Hello, How are you?`
data, err := Encode(msg)
if err != nil {
fmt.Println("encode msg failed, err:", err)
return
}
conn.Write(data)
}
编码 解码|组包 拆包
import(
"encoding/binary"
)
// Encode 将消息编码
func Encode(message string) ([]byte, error) {
// 读取消息的长度,转换成int32类型(占4个字节)
var length = int32(len(message))
var pkg = new(bytes.Buffer)
// 写入消息头
err := binary.Write(pkg, binary.LittleEndian, length)
if err != nil {
return nil, err
}
// 写入消息实体
err = binary.Write(pkg, binary.LittleEndian, []byte(message))
if err != nil {
return nil, err
}
return pkg.Bytes(), nil
}
// Decode 解码消息
func Decode(reader *bufio.Reader) (string, error) {
// 读取消息的长度
lengthByte, _ := reader.Peek(4) // 读取前4个字节的数据
lengthBuff := bytes.NewBuffer(lengthByte)
var length int32
err := binary.Read(lengthBuff, binary.LittleEndian, &length)
if err != nil {
return "", err
}
// Buffered返回缓冲中现有的可读取的字节数。
if int32(reader.Buffered()) < length+4 {
return "", err
}
// 读取真正的消息数据
pack := make([]byte, int(4+length))
_, err = reader.Read(pack)
if err != nil {
return "", err
}
return string(pack[4:]), nil
}
su 命令 报错 su: Permission denied,不管是su普通用户还是root,都会报这个错误,可以确定的是密码是正确的,因为ssh可以正常登录,root用户 su 其他用户正常。
查看su的PAM认证配置
cd /etc/pam.d
cat su
su 的PAM配置文件中有auth required pam_wheel.so use_uid根据上一句的说明可以知道要使用su命令则该用户必须在wheel用户组中,而我的普通用户没有在wheel用户组中。有两种方法可以解决这个问题,一是注释该行,二是将普通用户加入wheel组。
“!” E212: Can’t open file for writing
sudo vim su