1.linux系统下邮件服务器sendmail的收发收配置
2.linux内核通信核心技术:Netlink源码分析和实例分析
3.深入理解Linux的epoll机制
4.开源即时通讯GGTalk源码剖析之:客户端全局缓存及本地存储
5.go-iptables功能与源码详解
linux系统下邮件服务器sendmail的配置
安装所需软件sendmail.8...tar.gz ment and edit the following line if your mail needs to be sent out
dnl through an external mail server:
dnl define(`SMART_HOST',`smtp.your.provider')
define(`confDEF_USER_ID',``8:'')dnl
undefine(`UUCP_RELAY')dnl
undefine(`BITNET_RELAY')dnl
dnl define(`confAUTO_REBUILD')dnl
define(`confTO_CONNECT', `1m')dnl
define(`confTRY_NULL_MX_LIST',true)dnl
define(`confDONT_PROBE_INTERFACES',true)dnl
define(`PROCMAIL_MAILER_PATH',`/usr/bin/procmail')dnl
define(`ALIAS_FILE', `/etc/mail/aliases')dnl
define(`STATUS_FILE', `/etc/mail/statistics')dnl
define(`UUCP_MAILER_MAX', `')dnl
define(`confUSERDB_SPEC', `/etc/mail/userdb.db')dnl
define(`confPRIVACY_FLAGS', `authwarnings,novrfy,noexpn,restrictqrun')dnl
define(`confAUTH_OPTIONS', `A')dnl
TRUST_AUTH_MECH(`EXTERNAL DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl
define(`confAUTH_MECHANISMS', `EXTERNAL GSSAPI DIGEST-MD5 CRAM-MD5
LOGIN PLAIN')dnl
dnl define(`confCACERT_PATH',`/usr/share/ssl/certs')
dnl define(`confCACERT',`/usr/share/ssl/certs/ca-bundle.crt')
dnl define(`confSERVER_CERT',`/usr/share/ssl/certs/sendmail.pem')
dnl define(`confSERVER_KEY',`/usr/share/ssl/certs/sendmail.pem')
dnl define(`confTO_QUEUEWARN', `4h')dnl
dnl define(`confTO_QUEUERETURN', `5d')dnl
dnl define(`confQUEUE_LA', `')dnl
dnl define(`confREFUSE_LA', `')dnl
define(`confTO_IDENT', `0')dnl
dnl FEATURE(delay_checks)dnl
FEATURE(`no_default_msa',`dnl')dnl
FEATURE(`smrsh',`/usr/sbin/smrsh')dnl
FEATURE(`mailertable',`hash -o /etc/mail/mailertable.db')dnl
FEATURE(`virtusertable',`hash -o /etc/mail/virtusertable.db')dnl
FEATURE(redirect)dnl
FEATURE(always_add_domain)dnl
FEATURE(use_cw_file)dnl
FEATURE(use_ct_file)dnl
dnl The '-t' option will retry delivery if e.g. the user runs over his quota.
FEATURE(local_procmail,`',`procmail -t -Y -a $h -d $u')dnl
FEATURE(`access_db',`hash -T; -o /etc/mail/access.db')dnl
FEATURE(`blacklist_recipients')dnl
EXPOSED_USER(`root')dnl
dnl This changes sendmailto only listen on the loopback device .0.0.1
dnl and not on any other network devices. Comment this out if you want
dnl to accept email over the network.
DAEMON_OPTIONS(`Port=smtp,Addr=0.0.0.0, Name=MTA')
dnl NOTE: binding both IPv4 and IPv6 daemon to the same port requires
dnl a kernel patch
dnl DAEMON_OPTIONS(`port=smtp,Addr=::1, Name=MTA-v6, Family=inet6')
dnl We strongly recommend to comment this one out if you want to protect
dnl yourself from spam. However, the laptop and users on computers that do
dnl not have x7 DNS do need this.
FEATURE(`accept_unresolvable_domains')dnl
dnl FEATURE(`relay_based_on_MX')dnl
MAILER(smtp)dnl
MAILER(procmail)dnl
记得要使以下两个条语句有效,他的系统系统意思是假如access.db访问控制没有配置,则启用以下
验证方式进行smtp验证。源码源码有什用
TRUST_AUTH_MECH(`EXTERNAL DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl
define(`confAUTH_MECHANISMS',收发收 `EXTERNAL GSSAPI DIGEST-MD5 CRAM-MD5
LOGIN PLAIN')dnl
sendmail宏定义说明
divert(n) 为m4定义一个缓冲动作,当n=-1时缓冲被删除,系统系统n=0时开始一个新缓冲
OSTYPE定义宏所使用的源码源码有什用米店平台源码操作系统,该宏允许m4程式增加同相关操作系统相关的收发收
文档
Domain定义MTA将使用哪些域来传输邮件
Feature定义配置文档中使用的一个特定的功能集
Define定义配置文档中的一个特定的选项值
MASQUERADE_AS 定义sendmail来应答邮件的其他主机名
MAILER定义sendmail使用的邮件传输方法
dnl注释
完成sendmail.mc文档的编写后就能够用m4程式生成正式的sendmail.cf配置文档。语法如
下:
# m4 sendmail.mc ; sendmail.cf
也能够用源码目录树下的系统系统cf/cf/Build命令生成。前提是源码源码有什用您要在该目录下有一个sendmail.mc
文档。语法如下:
# ./Build sendmail.cf
ok,收发收接着就能够把sendmail.cf and submit.cf文档安装到/etc/mail目录中的系统系统,语法如下:
# ./Build install-cf
其实也能够自已用cp命令拷贝的源码源码有什用啦。记得把sendmail.mc文档也拷贝一份到/etc/mail目录,收发收
以便以后修改配置时可重新生成sendmail.cf文档。系统系统
ok,源码源码有什用接着到/etc/mail目录下配置一些文档。
# cd /etc/mail
# echo 'examply.com' ;; local-host-names 接收邮件的主机名
# echo 'localhost RELAY' ;; access 用来拒绝或允许来自某个域的邮件,本例允许
本地转发。
# makemap hash access 生成access.db数据库
# touch domaintable 用来把旧域名映射互新域名
# makemap hash domaintable 来覆盖向指定域的路由
# makemap hash mailertable 用来把用户和域名映射到其他地址
# makemap hash virtusertable 别名数据库,文本形式。可参照源码目录树下
sendmail/aliases文档。
# newaliases 从文本文档中创建一个新的别名数据库文档。
# sendmail-v -bi 调试启动。
/etc/mail/aliases: aliases, longest bytes, bytes total
假如出现以上提示信息,则启动成功。可用以下命令正式启动:
# sendmail-bd -qm
该命令以后台进程方式(-bd)运行,并使其每隔分钟(-qm)轮询一次未发送邮件队
列,检查是否有新邮件。
服务器启动后,能够用telnet连接服务器。
# telnet localhost
Trying .0.0.1...
Connected to localhost.
Escape character is '^]'.
test.tigerhead ESMTP Sendmail8../8..; Tue, Mar :: +
ehlo test 您输入的命令,按回车结束。
-test.tigerhead Hello LOCALHOST.localdomain [.0.0.1], pleased to meet you
-ENHANCEDSTATUSCODES
-PIPELINING
-8BITMIME
-SIZE
-DSN
-ETRN
-AUTH DIGEST-MD5 CRAM-MD5 LOGIN PLAIN
-DELIVERBY
HELP
以-开头的为服务器的响应信息。注意倒数第三行,这就是成功配置smtp验证的显示。
输入quit离开。
安装完成后为确保系统安全,还需调整一些目录的权限。可参考sendmail源码目录下
sendmail/SECURITY文档。
# chmod /etc/mail/aliases /etc/mail/aliases.{ db,pag,dir}
# chmod /etc/mail/*.{ db,pag,dir}
# chmod /etc/mail/statistics /var/log/sendmail.st
# chmod /var/run/sendmail.pid /etc/mail/sendmail.pid
-r-xr-sr-x root smmsp ... /PATH/TO/sendmail
drwxrwx--- smmsp smmsp ... /var/spool/clientmqueue
drwx------ root wheel ... /var/spool/mqueue
-r--r--r-- root wheel ... /etc/mail/sendmail.cf
-r--r--r-- root wheel ... /etc/mail/submit.cf
6、sendmail功能介绍
aliases 别名数据库配置
test: test1,test2,test3 配置test群组别名,test不是个实际的用户,只是个别名。发给
test的信,test1,2,3都可收到。
test: test,testbak 在testbak邮箱中备份test的邮件。
test: test,test@.com 远程邮件备份,原理同上。
test: :include: /etc/mail/userlist 使用用户列表配置群组。
userlist格式如下:
test1, /
test2, /
test3, /
test4
关于系统预设aliases,由于sendmail预设用mailer-daemon and postmaster作为资料传送者,
或邮件退回的帐号,但系统实际没有这两个帐号,所以要如下配置别名。
mailer-daemon: postmaster
postmaster: root
配置完成后不要忘了用newaliases命令生成数据库。
~/.forward文档配置
其实该文档的作用和aliases数据库的作差不多啦,都是配置别名,做邮件转发的。因为alises
只能由管理员控制,个人用户不能修改,所以就能够在个人的目录下建立一个转寄文档。以
配置个人的邮件转寄列表。文档格式如下:
test
test1
test2
test3
and os on
但由于个人用户安全意识差,假如配置不当会有安全漏洞,不建议使用。
access访问控制列表配置
. RELAY
test.NET OK
test.COM REJECT
test.COM SORRY,WE DON'T ALLOW SPAMMERS HERE
test.ORG DISCARD
Ok--远程主机能够向您的邮件服务器发送邮件;
RELAY--允许中转;
REJECT--不能向您的邮件服务器发邮件和不能中转;
DISCARD--发来的邮件将被丢弃,同时并不向发送者返回错误信息。
nnn text-- 发来的邮件将被丢弃,但sendmail将会向发送者返回nnn确定的smtp代码和text
变量确定的文本描述。
配置完成后要用makemap hash access.db 命令生成数据库。
mailq邮件队列查询命令。
Q-ID邮件id号。
Size邮件容量。
Q-Time邮件进入队列(也就是/var/spool/mqueue目录)的时间和不能邮寄的原因。
Sender/Recipient 发信和收信人的邮箱地址。
mailstats邮寄状态查询命令,可查询sendmail运行作至今邮件收发总计资料。
M :
msgsfr:发送的邮件数量。
bytes_from:邮件容量
megsto:收到邮件的数量。
bytes_to:同上
msgsrej:邮件deny的次数。
msgsdis:邮件discard的2048androidstodio源码次数。
Mailer :esmtp对外邮件 ,local本地邮件 。
mail邮件命令
mail 查看/var/spool/mail/目录下自已邮箱内容。以q退出把看过的邮件保存在
~/mbox中。
mail test@example.com 直接发邮件给人。
mail -s 'title text' test@example.com 把文档中内容邮寄出去。
mail -f ~/mbox查看home目录下邮箱内容。
用mail发附件也是可行,要用到uuencode and uudecode命令进行编码。
编码:uuencode [file] name example:uuencode hello hello;hello.uue default input
is stdin;default output is stdout.
解码:uudecode [-o outfile] name example:uudecode hello.uue 能够用-o选项输出另外一个
文档名。
# uuencode ~/.bashrc bashrc | mail -s 'test uuencode'
test@example.com
五、其他配置。
要想更好的使用sendmail,常用到的一些配置:
1、限制最大邮件。
vi /etc/sendmail.cf
# maximum message size
O MaxMessageSize= (注:5M)
2、最大的群发数目。
vi /etc/sendmail.cf
# maximum number of recipients per SMTP envelope
O MaxRecipientsPerMessage= (注:个)
3、域名文档----local-host-name
能够用他来实现虚拟域名或多域名支持。
/etc/mail/local-host-name
v1mail.com
v2mail.com
.......
4、mail别名文档--aliases。
vi /etc/aliases
系统内部别名:peng:zpeng,hero,mmmn,yt peng是我的用户名,其他的是别名,用逗号隔开。
转发到其他的邮箱:peng:zpeng@sina.com,yt.sohu.com
#newaliases --写到库中
5、邮件控制文档--access
relay、ok、reject和discard。relay能够实现转发。ok是用来允许用户的任意访问,他会覆盖任何其他已建立的检查(实际配置中,最好别设这项,除非您对该用户是绝对信任的);reject能够实现对来访地址的拒绝,他根本就不容许该地址和您的邮件服务器进行连接通信;discard的作用是在接收到传输的邮件消息后,偷偷地把他丢弃掉(在发送者看来,他的邮件的确是接收了,但他并不知道,发送的目的地址根本不可能接收到他的邮件,服务器巧妙地欺骗了他。
vi /etc/mail/access.
localhost.localdomain RELAY ---允许
localhostRELAY
.0.0.1 RELAY
peng@sina.com ok
@sexgirl.net reject
... discard
#makemap hash access.db 写入库中
6、虚拟用户文档---virtusertable.
这个文档能够mail重名问题。
例如:您有两个域名,virt1.com and virt2.com,但是都有peng这个用户:
peng@virt1.com and peng@virt2.com,这是两个用户。但对于系统用户来说,他只认系统用户peng,只能通过一下改:
vi /etc/mail/virtusertable
peng@virt2.com r
这样就能够了。在新开一个用户r,但是peng@virt2.com在客户端的pop3 and smtp server的用户名和口令要用r的。
#makemap hask virtusertable.db
# /usr/sbin/sendmail -d0.1 -bv root |grep SASL
NAMED_BIND NETINET NETINET6 NETUNIX NEWDB NIS QUEUE SASL SCANF
我的问题已找到了是/usr/lib/sasl/Sendmail.conf的文档名中的S没有大写,问题已解决
linux内核通信核心技术:Netlink源码分析和实例分析
Linux内核通信核心技术:Netlink源码分析和实例分析
什么是netlink?Linux内核中一个用于解决内核态和用户态交互问题的机制。相比其他方法,netlink提供了更安全高效的交互方式。它广泛应用于多种场景,例如路由、用户态socket协议、防火墙、netfilter子系统等。
Netlink内核代码走读:内核代码位于net/netlink/目录下,包括头文件和实现文件。头文件在include目录,提供了辅助函数、宏定义和数据结构,对理解消息结构非常有帮助。关键文件如af_netlink.c,其中netlink_proto_init函数注册了netlink协议族,使内核支持netlink。
在客户端创建netlink socket时,使用PF_NETLINK表示协议族,SOCK_RAW表示原始协议包,NETLINK_USER表示自定义协议字段。sock_register函数注册协议到内核中,以便在创建socket时使用。
Netlink用户态和内核交互过程:主要通过socket通信实现,包括server端和client端。netlink操作基于sockaddr_nl协议套接字,nl_family制定协议族,nl_pid表示进程pid,nl_groups用于多播。消息体由nlmsghdr和msghdr组成,易语言 采集 源码用于发送和接收消息。内核创建socket并监听,用户态创建连接并收发信息。
Netlink关键数据结构和函数:sockaddr_nl用于表示地址,nlmsghdr作为消息头部,msghdr用于用户态发送消息。内核函数如netlink_kernel_create用于创建内核socket,netlink_unicast和netlink_broadcast用于单播和多播。
Netlink用户态建立连接和收发信息:提供测试例子代码,代码在github仓库中,可自行测试。核心代码包括接收函数打印接收到的消息。
总结:Netlink是一个强大的内核和用户空间交互方式,适用于主动交互场景,如内核数据审计、安全触发等。早期iptables使用netlink下发配置指令,但在iptables后期代码中,使用了iptc库,核心思路是使用setsockops和copy_from_user。对于配置下发场景,netlink非常实用。
链接:内核通信之Netlink源码分析和实例分析
深入理解Linux的epoll机制
在Linux系统之中有一个核心武器:epoll池,在高并发的,高吞吐的IO系统中常常见到epoll的身影。IO多路复用在Go里最核心的是Goroutine,也就是所谓的协程,协程最妙的一个实现就是异步的代码长的跟同步代码一样。比如在Go中,网络IO的read,write看似都是同步代码,其实底下都是异步调用,一般流程是:
write(/*IO参数*/)请求入队等待完成后台loop程序发送网络请求唤醒业务方Go配合协程在网络IO上实现了异步流程的同步代码化。核心就是用epoll池来管理网络fd。
实现形式上,后台的程序只需要1个就可以负责管理多个fd句柄,负责应对所有的业务方的IO请求。这种一对多的IO模式我们就叫做IO多路复用。
多路是指?多个业务方(句柄)并发下来的IO。
复用是指?复用这一个后台处理程序。
站在IO系统设计人员的角度,业务方咱们没办法提要求,因为业务是上帝,只有你服从的份,他们要创建多个fd,那么你就需要负责这些fd的处理,并且最好还要并发起来。
业务方没法提要求,那么只能要求后台loop程序了!
要求什么呢?快!快!快!这就是最核心的要求,处理一定要快,要给每一个fd通道最快的感受,要让每一个fd觉得,你只在给他一个人跑腿。
那有人又问了,那我一个IO请求(比如write)对应一个线程来处理,这样所有的IO不都并发了吗?是可以,但是有瓶颈,线程数一旦多了,性能是反倒会差的。
这里不再对比多线程和IO多路复用实现高并发之间的区别,详细的可以去了解下nginx和redis高并发的秘密。
最朴实的实现方式?我不用任何其他系统调用,能否实现IO多路复用?
可以的。那么写个for循环,每次都尝试IO一下,读/写到了就处理,读/写不到就sleep下。这样我们不就实现了1对多的IO多路复用嘛。
whileTrue:foreach句柄数组{ read/write(fd,/*参数*/)}sleep(1s)慢着,有个问题,上面的程序可能会被卡死在第三行,使得整个系统不得运行,为什么?
默认情况下,小米盒子的源码我们没有加任何参数create出的句柄是阻塞类型的。我们读数据的时候,如果数据还没准备好,是会需要等待的,当我们写数据的时候,如果还没准备好,默认也会卡住等待。所以,在上面伪代码第三行是可能被直接卡死,而导致整个线程都得到不到运行。
举个例子,现在有,,这3个句柄,现在读写都没有准备好,只要read/write(,/*参数*/)就会被卡住,但,这两个句柄都准备好了,那遍历句柄数组,,的时候就会卡死在前面,后面,则得不到运行。这不符合我们的预期,因为我们IO多路复用的loop线程是公共服务,不能因为一个fd就直接瘫痪。
那这个问题怎么解决?
只需要把fd都设置成非阻塞模式。这样read/write的时候,如果数据没准备好,返回EAGIN的错误即可,不会卡住线程,从而整个系统就运转起来了。比如上面句柄还未就绪,那么read/write(,/*参数*/)不会阻塞,只会报个EAGIN的错误,这种错误需要特殊处理,然后loop线程可以继续执行,的读写。
以上就是最朴实的IO多路复用的实现了。但是好像在生产环境没见过这种IO多路复用的实现?为什么?
因为还不够高级。for循环每次要定期sleep1s,这个会导致吞吐能力极差,因为很可能在刚好要sleep的时候,所有的fd都准备好IO数据,而这个时候却要硬生生的等待1s,可想而知。。。
那有同学又要质疑了,那for循环里面就不sleep嘛,这样不就能及时处理了吗?
及时是及时了,但是CPU估计要跑飞了。不加sleep,那在没有fd需要处理的时候,估计CPU都要跑到%了。这个也是无法接受的。
纠结了,那sleep吞吐不行,不sleep浪费cpu,怎么办?
这种情况用户态很难有所作为,只能求助内核来提供机制协助来。因为内核才能及时的管理这些通知和调度。
我们再梳理下IO多路复用的需求和原理。IO多路复用就是1个线程处理多个fd的模式。我们的要求是:这个“1”就要尽可能的快,避免一切无效工作,要把所有的时间都用在处理句柄的IO上,不能有任何空转,sleep的时间浪费。
有没有一种工具,我们把一箩筐的fd放到里面,只要有一个fd能够读写数据,后台loop线程就要立马唤醒,全部马力跑起来。其他时间要把cpu让出去。
能做到吗?能,这种需求只能内核提供机制满足你。分时资金博弈源码
这事Linux内核必须要给个说法?是的,想要不用sleep这种辣眼睛的实现,Linux内核必须出手了,毕竟IO的处理都是内核之中,数据好没好内核最清楚。
内核一口气提供了3种工具select,poll,epoll。
为什么有3种?
历史不断改进,矬->较矬->卧槽、高效的演变而已。
Linux还有其他方式可以实现IO多路复用吗?
好像没有了!
这3种到底是做啥的?
这3种都能够管理fd的可读可写事件,在所有fd不可读不可写无所事事的时候,可以阻塞线程,切走cpu。fd有情况的时候,都要线程能够要能被唤醒。
而这三种方式以epoll池的效率最高。为什么效率最高?
其实很简单,这里不详说,其实无非就是epoll做的无用功最少,select和poll或多或少都要多余的拷贝,盲猜(遍历才知道)fd,所以效率自然就低了。
举个例子,以select和epoll来对比举例,池子里管理了个句柄,loop线程被唤醒的时候,select都是蒙的,都不知道这个fd里谁IO准备好了。这种情况怎么办?只能遍历这个fd,一个个测试。假如只有一个句柄准备好了,那相当于做了1千多倍的无效功。
epoll则不同,从epoll_wait醒来的时候就能精确的拿到就绪的fd数组,不需要任何测试,拿到的就是要处理的。
epoll池原理下面我们看一下epoll池的使用和原理。
epoll涉及的系统调用epoll的使用非常简单,只有下面3个系统调用。
epoll_createepollctlepollwait就这?是的,就这么简单。
epollcreate负责创建一个池子,一个监控和管理句柄fd的池子;
epollctl负责管理这个池子里的fd增、删、改;
epollwait就是负责打盹的,让出CPU调度,但是只要有“事”,立马会从这里唤醒;
epoll高效的原理Linux下,epoll一直被吹爆,作为高并发IO实现的秘密武器。其中原理其实非常朴实:epoll的实现几乎没有做任何无效功。我们从使用的角度切入来一步步分析下。
首先,epoll的第一步是创建一个池子。这个使用epoll_create来做:
原型:
intepoll_create(intsize);示例:
epollfd=epoll_create();if(epollfd==-1){ perror("epoll_create");exit(EXIT_FAILURE);}这个池子对我们来说是黑盒,这个黑盒是用来装fd的,我们暂不纠结其中细节。我们拿到了一个epollfd,这个epollfd就能唯一代表这个epoll池。
然后,我们就要往这个epoll池里放fd了,这就要用到epoll_ctl了
原型:
intepoll_ctl(intepfd,intop,intfd,structepoll_event*event);示例:
if(epoll_ctl(epollfd,EPOLL_CTL_ADD,,&ev)==-1){ perror("epoll_ctl:listen_sock");exit(EXIT_FAILURE);}上面,我们就把句柄放到这个池子里了,op(EPOLL_CTL_ADD)表明操作是增加、修改、删除,event结构体可以指定监听事件类型,可读、可写。
第一个跟高效相关的问题来了,添加fd进池子也就算了,如果是修改、删除呢?怎么做到时间快?
这里就涉及到你怎么管理fd的数据结构了。
最常见的思路:用list,可以吗?功能上可以,但是性能上拉垮。list的结构来管理元素,时间复杂度都太高O(n),每次要一次次遍历链表才能找到位置。池子越大,性能会越慢。
那有简单高效的数据结构吗?
有,红黑树。Linux内核对于epoll池的内部实现就是用红黑树的结构体来管理这些注册进程来的句柄fd。红黑树是一种平衡二叉树,时间复杂度为O(logn),就算这个池子就算不断的增删改,也能保持非常稳定的查找性能。
现在思考第二个高效的秘密:怎么才能保证数据准备好之后,立马感知呢?
epoll_ctl这里会涉及到一点。秘密就是:回调的设置。在epoll_ctl的内部实现中,除了把句柄结构用红黑树管理,另一个核心步骤就是设置poll回调。
思考来了:poll回调是什么?怎么设置?
先说说file_operations->poll是什么?
在fd篇说过,Linux设计成一切皆是文件的架构,这个不是说说而已,而是随处可见。实现一个文件系统的时候,就要实现这个文件调用,这个结构体用structfile_operations来表示。这个结构体有非常多的函数,我精简了一些,如下:
structfile_operations{ ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);__poll_t(*poll)(structfile*,structpoll_table_struct*);int(*open)(structinode*,structfile*);int(*fsync)(structfile*,loff_t,loff_t,intdatasync);//....};你看到了read,write,open,fsync,poll等等,这些都是对文件的定制处理操作,对于文件的操作其实都是在这个框架内实现逻辑而已,比如ext2如果有对read/write做定制化,那么就会是ext2_read,ext2_write,ext4就会是ext4_read,ext4_write。在open具体“文件”的时候会赋值对应文件系统的file_operations给到file结构体。
那我们很容易知道read是文件系统定制fd读的行为调用,write是文件系统定制fd写的行为调用,file_operations->poll呢?
这个是定制监听事件的机制实现。通过poll机制让上层能直接告诉底层,我这个fd一旦读写就绪了,请底层硬件(比如网卡)回调的时候自动把这个fd相关的结构体放到指定队列中,并且唤醒操作系统。
举个例子:网卡收发包其实走的异步流程,操作系统把数据丢到一个指定地点,网卡不断的从这个指定地点掏数据处理。请求响应通过中断回调来处理,中断一般拆分成两部分:硬中断和软中断。poll函数就是把这个软中断回来的路上再加点料,只要读写事件触发的时候,就会立马通知到上层,采用这种事件通知的形式就能把浪费的时间窗就完全消失了。
划重点:这个poll事件回调机制则是epoll池高效最核心原理。
划重点:epoll池管理的句柄只能是支持了file_operations->poll的文件fd。换句话说,如果一个“文件”所在的文件系统没有实现poll接口,那么就用不了epoll机制。
第二个问题:poll怎么设置?
在epoll_ctl下来的实现中,有一步是调用vfs_poll这个里面就会有个判断,如果fd所在的文件系统的file_operations实现了poll,那么就会直接调用,如果没有,那么就会报告响应的错误码。
staticinline__poll_tvfs_poll(structfile*file,structpoll_table_struct*pt){ if(unlikely(!file->f_op->poll))returnDEFAULT_POLLMASK;returnfile->f_op->poll(file,pt);}你肯定好奇poll调用里面究竟是实现了什么?
总结概括来说:挂了个钩子,设置了唤醒的回调路径。epoll跟底层对接的回调函数是:ep_poll_callback,这个函数其实很简单,做两件事情:
把事件就绪的fd对应的结构体放到一个特定的队列(就绪队列,readylist);
唤醒epoll,活来啦!
当fd满足可读可写的时候就会经过层层回调,最终调用到这个回调函数,把对应fd的结构体放入就绪队列中,从而把epoll从epoll_wait出唤醒。
这个对应结构体是什么?
结构体叫做epitem,每个注册到epoll池的fd都会对应一个。
就绪队列很高级吗?
就绪队列就简单了,因为没有查找的需求了呀,只要是在就绪队列中的epitem,都是事件就绪的,必须处理的。所以就绪队列就是一个最简单的双指针链表。
小结下:epoll之所以做到了高效,最关键的两点:
内部管理fd使用了高效的红黑树结构管理,做到了增删改之后性能的优化和平衡;
epoll池添加fd的时候,调用file_operations->poll,把这个fd就绪之后的回调路径安排好。通过事件通知的形式,做到最高效的运行;
epoll池核心的两个数据结构:红黑树和就绪列表。红黑树是为了应对用户的增删改需求,就绪列表是fd事件就绪之后放置的特殊地点,epoll池只需要遍历这个就绪链表,就能给用户返回所有已经就绪的fd数组;
哪些fd可以用epoll来管理?再来思考另外一个问题:由于并不是所有的fd对应的文件系统都实现了poll接口,所以自然并不是所有的fd都可以放进epoll池,那么有哪些文件系统的file_operations实现了poll接口?
首先说,类似ext2,ext4,xfs这种常规的文件系统是没有实现的,换句话说,这些你最常见的、真的是文件的文件系统反倒是用不了epoll机制的。
那谁支持呢?
最常见的就是网络套接字:socket。网络也是epoll池最常见的应用地点。Linux下万物皆文件,socket实现了一套socket_file_operations的逻辑(net/socket.c):
staticconststructfile_operationssocket_file_ops={ .read_iter=sock_read_iter,.write_iter=sock_write_iter,.poll=sock_poll,//...};我们看到socket实现了poll调用,所以socketfd是天然可以放到epoll池管理的。
还有吗?
有的,其实Linux下还有两个很典型的fd,常常也会放到epoll池里。
eventfd:eventfd实现非常简单,故名思义就是专门用来做事件通知用的。使用系统调用eventfd创建,这种文件fd无法传输数据,只用来传输事件,常常用于生产消费者模式的事件实现;
timerfd:这是一种定时器fd,使用timerfd_create创建,到时间点触发可读事件;
小结一下:
ext2,ext4,xfs等这种真正的文件系统的fd,无法使用epoll管理;
socketfd,eventfd,timerfd这些实现了poll调用的可以放到epoll池进行管理;
其实,在Linux的模块划分中,eventfd,timerfd,epoll池都是文件系统的一种模块实现。
思考前面我们已经思考了很多知识点,有一些简单有趣的知识点,提示给读者朋友,这里只抛砖引玉。
问题:单核CPU能实现并行吗?
不行。
问题:单线程能实现高并发吗?
可以。
问题:那并发和并行的区别是?
一个看的是时间段内的执行情况,一个看的是时间时刻的执行情况。
问题:单线程如何做到高并发?
IO多路复用呗,今天讲的epoll池就是了。
问题:单线程实现并发的有开源的例子吗?
redis,nginx都是非常好的学习例子。当然还有我们Golang的runtime实现也尽显高并发的设计思想。
总结IO多路复用的原始实现很简单,就是一个1对多的服务模式,一个loop对应处理多个fd;
IO多路复用想要做到真正的高效,必须要内核机制提供。因为IO的处理和完成是在内核,如果内核不帮忙,用户态的程序根本无法精确的抓到处理时机;
fd记得要设置成非阻塞的哦,切记;
epoll池通过高效的内部管理结构,并且结合操作系统提供的poll事件注册机制,实现了高效的fd事件管理,为高并发的IO处理提供了前提条件;
epoll全名eventpoll,在Linux内核下以一个文件系统模块的形式实现,所以有人常说epoll其实本身就是文件系统也是对的;
socketfd,eventfd,timerfd这三种”文件“fd实现了poll接口,所以网络fd,事件fd,定时器fd都可以使用epoll_ctl注册到池子里。我们最常见的就是网络fd的多路复用;
ext2,ext4,xfs这种真正意义的文件系统反倒没有提供poll接口实现,所以不能用epoll池来管理其句柄。那文件就无法使用epoll机制了吗?不是的,有一个库叫做libaio,通过这个库我们可以间接的让文件使用epoll通知事件,以后详说,此处不表;
后记epoll池使用很简洁,但实现不简单。还是那句话,Linux内核帮你包圆了。
今天并没有罗列源码实现,以很小的思考点为题展开,简单讲了一些epoll的思考,以后有机会可以分享下异步IO(aio)和epoll能产生什么火花?Golang是怎样使用epoll池的?敬请期待哦。
原创不易,更多干货,关注:奇伢云存储
开源即时通讯GGTalk源码剖析之:客户端全局缓存及本地存储
继上篇详细介绍了 GGTalk 内置的虚拟数据库,本文将深入探讨 GGTalk 客户端的全局缓存及本地存储机制。对于还没有获取GGTalk源码的朋友,文章底部附有下载链接。
一. GGTalk 客户端缓存设计
核心在于ClientGlobalCache类,它在内存中保存用户和群组数据。此类接受泛型参数TUser和TGroup,且限定TUser和TGroup需实现特定接口,还继承自BaseGlobalCache类。三个私有字段分别用于存储用户、群组和缓存信息。
构造函数接收五个参数,用于初始化私有字段,并调用父类BaseGlobalCache的Initialize方法,实现缓存初始化逻辑。
二. GGTalk 客户端本地持久化存储
BaseGlobalCache类中,originUserLocalPersistence字段负责本地文件存储。它包含四个属性,代表好友列表、群组列表、快捷回复列表和最近联系人/群列表。
Load和Save方法用于读写本地文件,将数据存入或从文件加载。在了解本地缓存的核心概念后,回到Initialize方法,读取本地文件数据,缓存到内存中。
三. 更新本地缓存
在用户登录或断线重连时,系统会比较本地缓存与服务器数据,更新缺失或过时的信息。当缓存中只有用户自己时,会从服务器加载所有联系人;当存在其他数据时,会更新本地缓存以反映服务器最新状态。
四. 总结
GGTalk客户端缓存流程包括读取本地缓存、从服务器加载更新数据,以及在窗口关闭时将当前用户数据缓存。下篇将解析消息收发及处理机制。
敬请期待:《GGTalk 开源即时通讯系统源码剖析之:消息收发及处理》。底部链接提供下载GGTalk源码。
go-iptables功能与源码详解
介绍iptables之前我们先搬出他的父亲netfilter,netfilter是基于 Linux 2.4.x或更新的内核,提供了一系列报文处理的能力(过滤+改包+连接跟踪),具体来讲可以包含以下几个功能:
其实说白了,netfilter就是操作系统实现了网络防火墙的能力(连接跟踪+过滤+改包),而iptables就是用户态操作内核中防火墙能力的命令行工具,位于用户空间。快问快答,为啥计算机系统需要内核态和用户态(狗头)。
既然netfilter是对报文进行处理,那么我们就应该先了解一下内核是如何进行收发包的,发生报文大致流程如下:
netfilter框架就是作用于网络层中,在一些关键的报文收发处理路径上,加一些hook点,可以认为是一个个检查点,有的在主机外报文进入的位置(PREROUTING ),有的在经过路由发觉要进入本机用户态处理之前(INPUT ),有的在用户态处理完成后发出的地方(OUTPUT ),有的在报文经过路由并且发觉不是本机决定转发走的位置(FOWARD ),有的在路由转发之后出口的位置(POSTROUTING ),每个检查点有不同的规则集合,这些规则会有一定的优先级顺序,如果报文达到匹配条件(五元组之类的)且优先级最高的规则(序号越小优先级越高),内核会执行规则对应的动作,比如说拒绝,放行,记录日志,丢弃。
最后总结如下图所示,里面包含了netfilter框架中,报文在网络层先后经过的一些hook点:
报文转发视角:
iptables命令行工具管理视角:
规则种类:
流入本机路径:
经过本机路径:
流出本机路径:
由上一章节我们已经知道了iptables是用户态的命令行工具,目的就是为了方便我们在各个检查点增删改查不同种类的规则,命令的格式大致如下,简单理解就是针对具体的哪些流(五元组+某些特定协议还会有更细分的匹配条件,比如说只针对tcp syn报文)进行怎样的动作(端口ip转换或者阻拦放行):
2.1 最基本的增删改查
增删改查的命令,我们以最常用的filter规则为例,就是最基本的防火墙过滤功能,实验环境我先准备了一个centos7的docker跑起来(docker好啊,实验完了直接删掉,不伤害本机),并通过iptables配置一些命令,然后通过主机向该docker发生ping包,测试增删改查的filter规则是否生效。
1.查询
如果有规则会把他的序号显示出来,后面插入或者删除可以用 iptables -nvL -t filter --line
可以看出filter规则可以挂载在INPUT,FORWARD,OUTPUT检查点上,并且兜底的规则都是ACCEPT,也就是没有匹配到其他规则就全部放行,这个兜底规则是可以修改的。 我们通过ifconfig查看出docker的ip,然后主机去ping一波:
然后再去查一下,会发现 packets, bytes ---> 对应规则匹配到的报文的个数/字节数:
2. 新增+删除 新增一条拒绝的报文,我们直接把docker0网关ip给禁了,这样就无法通过主机ping通docker容器了(如果有疑问,下面有解答,会涉及docker的一些小姿势): iptables -I INPUT -s ..0.1 -j DROP (-I不指定序号的话就是头插) iptables -t filter -D INPUT 1
可见已经生效了,拦截了ping包,随后我删除了这条规则,又能够ping通了
3. 修改 通过-R可以进行规则修改,但能修改的部分比较少,只能改action,所以我的建议是先通过编号删除规则,再在原编号位置添加一条规则。
4. 持久化 当我们对规则进行了修改以后,如果想要修改永久生效,必须使用service iptables save保存规则,当然,如果你误操作了规则,但是并没有保存,那么使用service iptables restart命令重启iptables以后,规则会再次回到上次保存/etc/sysconfig/iptables文件时的模样。
再使用service iptables save命令保存iptables规则
5. 自定义链 我们可以创建自己的规则集,这样统一管理会非常方便,比如说,我现在要创建一系列的web服务相关的规则集,但我查询一波INPUT链一看,妈哎,条规则,这条规则有针对mail服务的,有针对sshd服务的,有针对私网IP的,有针对公网IP的,我这看一遍下来头都大了,所以就产生了一个非常合理的需求,就是我能不能创建自己的规则集,然后让这些检查点引用,答案是可以的: iptables -t filter -N MY_WEB
iptables -t filter -I INPUT -p tcp --dport -j MY_WEB
这就相当于tcp目的端口的报文会被送入到MY_WEB规则集中进行匹配了,后面有陆续新规则进行增删时,完全可以只针对MY_WEB进行维护。 还有不少命令,详见这位大佬的总结:
回过头来,讲一个关于docker的小知识点,就是容器和如何通过主机通讯的?
这就是veth-pair技术,一端连接彼此,一端连接协议栈,evth—pair 充当一个桥梁,连接各种虚拟网络设备的。
我们在容器内和主机敲一下ifconfig:
看到了吧,容器内的eth0和主机的vetha9就是成对出现的,然后各个主机的虚拟网卡通过docker0互联,也实现了容器间的通信,大致如下:
我们抓个包看一哈:
可以看出都是通过docker0网关转发的:
最后引用一波 朱老板总结的常用套路,作为本章结尾:
1、规则的顺序非常重要。
如果报文已经被前面的规则匹配到,IPTABLES则会对报文执行对应的动作,通常是ACCEPT或者REJECT,报文被放行或拒绝以后,即使后面的规则也能匹配到刚才放行或拒绝的报文,也没有机会再对报文执行相应的动作了(前面规则的动作为LOG时除外),所以,针对相同服务的规则,更严格的规则应该放在前面。
2、当规则中有多个匹配条件时,条件之间默认存在“与”的关系。
如果一条规则中包含了多个匹配条件,那么报文必须同时满足这个规则中的所有匹配条件,报文才能被这条规则匹配到。
3、在不考虑1的情况下,应该将更容易被匹配到的规则放置在前面。
4、当IPTABLES所在主机作为网络防火墙时,在配置规则时,应着重考虑方向性,双向都要考虑,从外到内,从内到外。
5、在配置IPTABLES白名单时,往往会将链的默认策略设置为ACCEPT,通过在链的最后设置REJECT规则实现白名单机制,而不是将链的默认策略设置为DROP,如果将链的默认策略设置为DROP,当链中的规则被清空时,管理员的请求也将会被DROP掉。
3. go-iptables安装
go-iptables是组件库,直接一波import " github.com/coreos/go-ip...",然后go mod tidy一番,就准备兴致冲冲的跑一波自带的测试用例集,没想到上来就是4个error:
这还了得,我直接去go-iptables的仓库issue上瞅瞅有没有同道中人,果然发现一个类似问题:
虽然都是test failures,但是错的原因是不一样的,但是看他的版本是1.8的,所以我怀疑是我的iptables的版本太老了,一个iptables -v看一眼:
直接用yum update好像不能升级,yum search也没看到最新版本,看来只能下载iptables源码自己编译了,一套连招先打出来:
不出意外的话,那就得出点意外了:
那就继续下载源码安装吧,然后发现libmnl 又依赖libnftnl ,所以直接一波大招,netfilter全家桶全安装:
Finally,再跑一次测试用例就成功了,下面就可以愉快的阅读源码了:
4. 如何使用go-iptables
5. go-iptables源码分析
关键结构体IPTables
初始化函数func New(opts ...option) (*IPTables, error) ,流程如下:
几个重要函数的实现:
其他好像也米有什么,这里面就主要介绍一下,他的命令行执行是怎么实现的:
6. Reference