md5基础知识与Qt中的应用
md5简介md5(Message-Digest Algorithm 5(信息-摘要算法5))MD5算法具有以下特点:任意长度的数据,算出的MD5值长度都是固定的。把一个任意长度的字节串变换成一定长的十六进制数字串压缩性: ? 容易计算:从原数据计算出MD5值很容易。抗修改性对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。md5过程是不可逆的md5的应用文件校验软件下载站、论坛数据库、系统文件安全等方面数字证书互联网通讯中标志通讯各方身份信息的一串数字提供了一种在Internet上验证通信实体身份的方式登录验证操作系统的登陆认证,如Unix、各类BSD系统登录密码MD5算法描述MD5就是将输入的信息以512位分组,且每一分组被划分为16个32位子分组经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值QCryptographicHash应用计算方法 // MD5
方法一:
QByteArray data = "hello, world";
QCryptographicHash hash(QCryptographicHash::Md5);
hash.addData(data);
hash.addData("你好, 世界");
// 计算
QByteArray arry = hash.result();
qDebug() << arry;
arry = arry.toHex();
qDebug() << arry;
方法二:
QByteArray arry = QCryptographicHash::hash("hello, world", QCryptographicHash::Md5);
qDebug() << arry.toHex();
md5转换工具下载链接:https://pan.baidu.com/s/1rYQxOwSdlWTcDMoFdMlmHg提取码:f53f
Linux 命令 su 和 sudo 的区别?
之前一直对 su 和 sudo 这两个命令犯迷糊,最近专门搜了这方面的资料,总算是把两者的关系以及用法搞清楚了,这篇文章来系统总结一下。准备工作因为本篇博客中涉及到用户切换,所以我需要提前准备好几个测试用户,方便后续切换。Linux 中新建用户的命令是 useradd ,一般系统中这个命令对应的路径都在 PATH 环境变量里,如果直接输入 useradd 不管用的话,就用绝对路径名的方式:/usr/sbin/useradd 。useradd 新建用户命令只有 root 用户才能执行,我们先从普通用户 ubuntu 切换到 root 用户(如何切换后文会介绍):ubuntu@VM-0-14-ubuntu:~$ su -Password: # 输入 root 用户登录密码root@VM-0-14-ubuntu:~# useradd -m test_user # 带上 -m 参数root@VM-0-14-ubuntu:~# ls /hometest_user ubuntu # 可以看到 /home 目录下面有两个用户了因为还没有给新建的用户 test_user 设置登录密码,这就导致我们无法从普通用户 ubuntu 切换到 test_user,所以接下来,我们需要用 root 来设置 test_user 的登录密码。需要用到 passwd 命令:root@VM-0-14-ubuntu:~# passwd test_userEnter new UNIX password: # 输出 test_user 的密码Retype new UNIX password:passwd: password updated successfullyroot@VM-0-14-ubuntu:~#接着我们输入 exit 退出 root 用户到 普通用户 ubuntu:root@VM-0-14-ubuntu:~# exitlogoutubuntu@VM-0-14-ubuntu:~$可以看到,命令提示符前面已经由 root 变成 ubuntu,说明我们现在的身份是 ubuntu 用户。su命令介绍及主要用法首先需要解释下 su 代表什么意思。之前一直以为 su 是 super user,查阅资料之后才知道原来表示 「switch user」。知道 su 是由什么缩写来的之后,那么它提供的功能就显而易见了,就是「切换用户」。2.1 - 参数su 的一般使用方法是:su <user_name>或者su - <user_name>两种方法只差了一个字符 -,会有比较大的差异:如果加入了 - 参数,那么是一种 login-shell 的方式,意思是说切换到另一个用户 <user_name> 之后,当前的 shell 会加载 <user_name> 对应的环境变量和各种设置;如果没有加入 - 参数,那么是一种 non-login-shell 的方式,意思是说我现在切换到了 <user_name>,但是当前的 shell 还是加载切换之前的那个用户的环境变量以及各种设置。光解释会比较抽象,我们看一个例子就比较容易理解了。我们首先从 ubuntu 用户以 non-login-shell 的方式切换到 root 用户,比较两种用户状态下环境变量中 PWD 的值(su 命令不跟任何 <user_name> ,默认切换到 root 用户):ubuntu@VM-0-14-ubuntu:~$ env | grep ubuntuUSER=ubuntuPWD=/home/ubuntu # 是 /home/ubuntuHOME=/home/ubuntu省略…ubuntu@VM-0-14-ubuntu:~$ su # non-login-shell 方式Password: # 输入 root 用户登录密码root@VM-0-14-ubuntu:/home/ubuntu# env | grep ubuntuPWD=/home/ubuntu # 可以发现还是 /home/ubunturoot@VM-0-14-ubuntu:/home/ubuntu#我们的确是切换到 root 用户了,但是 shell 环境中的变量并没有改变,还是用之前 ubuntu 用户的环境变量。接着我们从 ubuntu 用户以 login-shell 的方式切换到 root 用户,同样比较两种用户转台下环境变量中 PWD 的值:ubuntu@VM-0-14-ubuntu:~$ env | grep ubuntuUSER=ubuntuPWD=/home/ubuntu # 是 /home/ubuntuHOME=/home/ubuntu省略…ubuntu@VM-0-14-ubuntu:~$ su - # 是 login-shell 方式Password:root@VM-0-14-ubuntu:~# env | grep rootUSER=rootPWD=/root # 已经变成 /root 了HOME=/rootMAIL=/var/mail/rootLOGNAME=rootroot@VM-0-14-ubuntu:~#可以看到用 login-shell 的方式切换用户的话,shell 中的环境变量也跟着改变了。「总结」:具体使用哪种方式切换用户看个人需求:如果不想因为切换到另一个用户导致自己在当前用户下的设置不可用,那么用 non-login-shell 的方式;如果切换用户后,需要用到该用户的各种环境变量(不同用户的环境变量设置一般是不同的),那么使用 login-shell 的方式。切换到指定用户前面已经介绍了,如果 su 命令后面不跟任何 <user_name>,那么默认是切换到 root 用户:ubuntu@VM-0-14-ubuntu:~$ su -Password: # root 用户的密码root@VM-0-14-ubuntu:/home/ubuntu#因为我们在 1. 准备工作 部分已经新建了一个 test_user 用户,并且我们也知道 test_user 用户的登录密码(root 用户设置的),我们就能从 ubuntu 用户切换到 test_user 用户:ubuntu@VM-0-14-ubuntu:~$ su -Password: # root 用户的密码root@VM-0-14-ubuntu:/home/ubuntu#2.3 -c 参数前面的方法中,我们都是先切换到另一个用户(root 或者 test_user),在哪个用户的状态下执行命令,最后输入 exit 返回当前 ubuntu 用户。还有一种方式是:不需要先切换用户再执行命令,可以直接在当前用户下,以另一个用户的方式执行命令,执行结束后就返回当前用户。这就得用到 -c 参数。具体使用方法是:su - -c “指令串” # 以 root 的方式执行 “指令串”我么看个例子:ubuntu@VM-0-14-ubuntu:~$ cat /etc/shadowcat: /etc/shadow: Permission denied # ubuntu 用户不能直接查看 /etc/shadow 文件内容ubuntu@VM-0-14-ubuntu:~$ su - -c “tail -n 4 /etc/shadow”Password: # 输入 root 用户密码ubuntu:1 11fZKcWEDI$uwZ64uFvVbwpHTbCSgim0/:18352:0:99999:7:::ntp:*:17752:0:99999:7:::mysql:!:18376:0:99999:7:::test_user:6 66.ZY1lj4mi i 0 x 9 C G 8 h . J H l h 6 z K b f B X R u o l J m I D B H A d 5 e q h v W 7 l b U Q X T R S / / 89 j c u T z R i l K q R k P 8 Y b Y W 4 V P x m T V H W R L Y N G S / : 18406 : 0 : 99999 : 7 : : : u b u n t u @ V M ? 0 ? 14 ? u b u n t u : ? ii0x9CG8h.JHlh6zKbfBXRuolJmIDBHAd5eqhvW7lbUQXTRS//89jcuTzRilKqRkP8YbYW4VPxmTVHWRLYNGS/:18406:0:99999:7::: ubuntu@VM-0-14-ubuntu:~ii0x9CG8h.JHlh6zKbfBXRuolJmIDBHAd5eqhvW7lbUQXTRS//89jcuTzRilKqRkP8YbYW4VPxmTVHWRLYNGS/:18406:0:99999:7:::ubuntu@VM?0?14?ubuntu: ?# 执行完马上返回 ubuntu 用户而不是 root 用户这种执行方式和后面要介绍的 sudo 很像,都是临时申请一下 root 用户的权限。但还是有差异,我们接着往后看。sudo命令介绍及主要用法首先还是解释下 sudo 命令是什么意思。sudo 的英文全称是 super user do,即以超级用户(root 用户)的方式执行命令。这里的 sudo 和之前 su 表示的 switch user 是不同的,这点需要注意,很容易搞混。我们先介绍 sudo 命令能做什么事情,然后说明为何能做到这些,以及如何做到这些。我们开始。3.1 主要用法我们在 Linux 中经常会碰到 Permission denied 这种情况,比如以 ubuntu 用户的身份查看 /etc/shadow 的内容。因为这个文件的内容是只有 root 用户能查看的。那如果我们想要查看怎么办呢?这时候就可以使用 sudo :ubuntu@VM-0-14-ubuntu:~$ tail -n 3 /etc/shadowtail: cannot open ‘/etc/shadow’ for reading: Permission denied # 没有权限ubuntu@VM-0-14-ubuntu:~$ sudo !! # 跟两个惊叹号sudo tail -n 3 /etc/shadowntp:*:17752:0:99999:7:::mysql:!:18376:0:99999:7:::test_user:6 66.ZY1lj4mi i 0 x 9 C G 8 h . J H l h 6 z K b f B X R u o l J m I D B H A d 5 e q h v W 7 l b U Q X T R S / / 89 j c u T z R i l K q R k P 8 Y b Y W 4 V P x m T V H W R L Y N G S / : 18406 : 0 : 99999 : 7 : : : u b u n t u @ V M ? 0 ? 14 ? u b u n t u : ? ii0x9CG8h.JHlh6zKbfBXRuolJmIDBHAd5eqhvW7lbUQXTRS//89jcuTzRilKqRkP8YbYW4VPxmTVHWRLYNGS/:18406:0:99999:7::: ubuntu@VM-0-14-ubuntu:~ii0x9CG8h.JHlh6zKbfBXRuolJmIDBHAd5eqhvW7lbUQXTRS//89jcuTzRilKqRkP8YbYW4VPxmTVHWRLYNGS/:18406:0:99999:7:::ubuntu@VM?0?14?ubuntu: 实例中,我们使用了 sudo !! 这个小技巧,表示重复上面输入的命令,只不过在命令最前面加上 sudo 。因为我已经设置了 sudo 命令不需要输入密码,所以这里 sudo !! 就能直接输出内容。如果没有设置的话,需要输入当前这个用户的密码,例如本例中,我就应该输入 ubuntu 用户的登录密码。两次相邻的 sudo 操作,如果间隔在 5min 之内,第二次输入 sudo 不需要重新输入密码;如果超过 5min,那么再输入 sudo 时,又需要输入密码。所以一个比较省事的方法是设置 sudo 操作不需要密码。后面介绍如何设置。sudo 除了以 root 用户的权限执行命令外,还有其它几个用法,这里做简单介绍。切换到 root 用户:sudo su -这种方式也能以 login-shell 的方式切换到 root 用户,但是它和 su - 方法是由区别的:前者输入 sudo su - 后,需要提供当前用户的登录密码,也就是 ubuntu 用户的密码;后者输入 su - 后,需要提供 root 用户的登录密码。还有一个命令:sudo -i这个命令和 sudo su - 效果一致,也是切换到 root 用户,也是需要提供当前用户(ubuntu 用户)的登录密码。我们现在切换到 test_user 用户,尝试显示 /etc/shadow 文件的内容:ubuntu@VM-0-14-ubuntu:~$ su - test_userPassword: # test_user 的密码$ sudo cat /etc/shadow[sudo] password for test_user: # test_user 的密码test_user is not in the sudoers file. This incident will be reported.$我们会看到倒数第二行中的错误提示信息,我们无法查看 /etc/shadow 的内容,这是为什么?为什么 ubuntu 可以使用 sudo 但是 test_user 不行呢?这就涉及到 sudo 的工作原理了。3.2 sudo 工作原理一个用户能否使用 sudo 命令,取决于 /etc/sudoers 文件的设置。从 3.1 节中我们已经看到,ubuntu 用户可以正常使用 sudo ,但是 test_user 用户却无法使用,这是因为 /etc/sudoers 文件里没有配置 test_user。/etc/sudoers 也是一个文本文件,但是因其有特定的语法,我们不要直接用 vim 或者 vi 来编辑它,需要用 visudo 这个命令。输入这个命令之后就能直接编辑 /etc/sudoers 这个文件了。需要说明的是,只有 root 用户有权限使用 visudo 命令。我们先来看下输入 visudo 命令后显示的内容。输入(root 用户):root@VM-0-14-ubuntu:~# visudo输出:User privilege specificationroot ALL=(ALL:ALL) ALLMembers of the admin group may gain root privileges%admin ALL=(ALL) ALLAllow members of group sudo to execute any command%sudo ALL=(ALL:ALL) ALLSee sudoers(5) for more information on “#include” directives:#includedir /etc/sudoers.dubuntu ALL=(ALL:ALL) NOPASSWD: ALL解释下每一行的格式:第一个表示用户名,如 root 、ubuntu 等;接下来等号左边的 ALL 表示允许从任何主机登录当前的用户账户;等号右边的 ALL 表示:这一行行首对一个的用户可以切换到系统中任何一个其它用户;行尾的 ALL 表示:当前行首的用户,能以 root 用户的身份下达什么命令,ALL 表示可以下达任何命令。我们还注意到 ubuntu 对应的那一行有个 NOPASSWD 关键字,这就是表明 ubuntu 这个用户在请求 sudo 时不需要输入密码,到这里就解释了前面的问题。同时我们注意到,这个文件里并没有 test_user 对应的行,这也就解释了为什么 test_user 无法使用 sudo 命令。接下来,我们尝试将 test_user 添加到 /etc/sudoers 文件中,使 test_user 也能使用 sudo 命令。我们在最后一行添加:test_user ALL=(ALL:ALL) ALL # test_user 使用 sudo 需要提供 test_user 的密码接下来我们再在 test_user 账户下执行 sudo :ubuntu@VM-0-14-ubuntu:~$ su - test_userPassword:$ tail -n 3 /etc/shadowtail: cannot open ‘/etc/shadow’ for reading: Permission denied$ sudo tail -n 3 /etc/shadow # 加上 sudontp:*:17752:0:99999:7:::mysql:!:18376:0:99999:7:::test_user:6 66.ZY1lj4m$ii0x9CG8h.JHlh6zKbfBXRuolJmIDBHAd5eqhvW7lbUQXTRS//89jcuTzRilKqRkP8YbYW4VPxmTVHWRLYNGS/:18406:0:99999:7:::$可以看到,现在已经可以使用 sudo 了。3.3 思考我们已经看到了,如果一个用户在 /etc/sudoers 文件中,那么它就具有 sudo 权限,就能通过 sudo su - 或者 sudo -i 等命令切换到 root 用户了,那这时这个用户就变成 root 用户了,那这不对系统造成很大的威胁吗?实际上的确是这样的。所以如果在编辑 /etc/sudoers 文件赋予某种用户 sudo 权限时,必须要确定该用户是「可信任」的,不会对系统造成恶意破坏,否则将所有 root 权限都赋予该用户将会有非常大的危险。当然,root 用户也可以编辑 /etc/sudoers 使用户只具备一部分权限,即只能执行一小部分命令。有兴趣的读者可以参考 Reference 部分第二条,这篇文章不再赘述。二者的差异对比我们已经看到:使用 su - ,提供 root 账户的密码,可以切换到 root 用户;使用 sudo su - ,提供当前用户的密码,也可以切换到 root 用户两种方式的差异也显而易见:如果我们的 Linux 系统有很多用户需要使用的话,前者要求所有用户都知道 root 用户的密码,这显然是非常危险的;后者是不需要暴露 root 账户密码的,用户只需要输入自己的账户密码就可以,而且哪些用户可以切换到 root,这完全是受 root 控制的(root 通过设置 /etc/sudoers 实现的),这样系统就安全很多了。
FastCGI与spawn-fcg简介
FastCGI1 什么是FastCGI快速通用网关接口(Fast Common Gateway Interface/FastCGI)是通用网关接口(CGI)的改进,描述了客户端和服务器程序之间传输数据的一种标准。FastCGI致力于减少Web服务器与CGI程式之间互动的开销,从而使服务器可以同时处理更多的Web请求。与为每个请求创建一个新的进程不同,FastCGI使用持续的进程来处理一连串的请求。这些进程由FastCGI进程管理器管理,而不是web服务器。2 FastCGI处理流程1.Web 服务器启动时载入初始化FastCGI执行环境。 例如IIS、ISAPI、apache mod_fastcgi、nginx ngx_http_fastcgi_module、lighttpd mod_fastcgi。2.FastCGI进程管理器自身初始化,启动多个CGI解释器进程并等待来自Web服务器的连接。启动FastCGI进程时,可以配置以ip和UNIX 域socket两种方式启动。3.当客户端请求到达Web 服务器时, Web 服务器将请求采用socket方式转发FastCGI主进程,FastCGI主进程选择并连接到一个CGI解释器。Web 服务器将CGI环境变量和标准输入发送到FastCGI子进程。4.FastCGI子进程完成处理后将标准输出和错误信息从同一socket连接返回Web 服务器。当FastCGI子进程关闭连接时,请求便处理完成。5.FastCGI子进程接着等待并处理来自Web 服务器的下一个连接。由于FastCGI程序并不需要不断的产生新进程,可以大大降低服务器的压力并且产生较高的应用效率。它的速度效率最少要比CGI 技术提高 5 倍以上。它还支持分布式的部署,即FastCGI 程序可以在web 服务器以外的主机上执行。CGI 是所谓的短生存期应用程序,FastCGI 是所谓的长生存期应用程序。FastCGI像是一个常驻(long-live)型的CGI,它可以一直执行着,不会每次都要花费时间去fork一次(这是CGI最为人诟病的fork-and-execute 模式)。3 进程管理器管理:spawn-fcgi7.2.3.1 什么是spawn-fcgiNginx不能像Apache那样直接执行外部可执行程序,但Nginx可以作为代理服务器,将请求转发给后端服务器,这也是Nginx的主要作用之一。其中Nginx就支持FastCGI代理,接收客户端的请求,然后将请求转发给后端FastCGI进程。由于FastCGI进程由FastCGI进程管理器管理,而不是Nginx。这样就需要一个FastCGI进程管理器,管理我们编写FastCGI程序。spawn-fcgi是一个通用的FastCGI进程管理器,简单小巧,原先是属于lighttpd的一部分,后来由于使用比较广泛,所以就迁移出来作为独立项目。spawn-fcgi使用pre-fork 模型,功能主要是打开监听端口,绑定地址,然后fork-and-exec创建我们编写的FastCGI应用程序进程,退出完成工作。FastCGI应用程序初始化,然后进入死循环侦听socket的连接请求。3.1 什么是spawn-fcgiNginx不能像Apache那样直接执行外部可执行程序,但Nginx可以作为代理服务器,将请求转发给后端服务器,这也是Nginx的主要作用之一。其中Nginx就支持FastCGI代理,接收客户端的请求,然后将请求转发给后端FastCGI进程。由于FastCGI进程由FastCGI进程管理器管理,而不是Nginx。这样就需要一个FastCGI进程管理器,管理我们编写FastCGI程序。spawn-fcgi是一个通用的FastCGI进程管理器,简单小巧,原先是属于lighttpd的一部分,后来由于使用比较广泛,所以就迁移出来作为独立项目。spawn-fcgi使用pre-fork 模型,功能主要是打开监听端口,绑定地址,然后fork-and-exec创建我们编写的FastCGI应用程序进程,退出完成工作。FastCGI应用程序初始化,然后进入死循环侦听socket的连接请求。
FastCGI—— CGI简介
CGI1 什么是CGI通用网关接口(Common Gateway Interface、CGI)描述了客户端和服务器程序之间传输数据的一种标准,可以让一个客户端,从网页浏览器向执行在网络服务器上的程序请求数据。CGI独立于任何语言的,CGI 程序可以用任何脚本语言或者是完全独立编程语言实现,只要这个语言可以在这个系统上运行。Unix shell script、Python、 Ruby、PHP、 perl、Tcl、 C/C++和 Visual Basic 都可以用来编写 CGI 程序。最初,CGI 是在 1993 年由美国国家超级电脑应用中心(NCSA)为 NCSA HTTPd Web 服务器开发的。这个 Web 服务器使用了 UNIX shell 环境变量来保存从 Web 服务器传递出去的参数,然后生成一个运行 CGI 的独立的进程。2 CGI处理流程web服务器收到客户端(浏览器)的请求Http Request,启动CGI程序,并通过环境变量、标准输入传递数据CGI进程启动解析器、加载配置(如业务相关配置)、连接其它服务器(如数据库服务器)、逻辑处理等CGI进程将处理结果通过标准输出、标准错误,传递给web服务器web服务器收到CGI返回的结果,构建Http Response返回给客户端,并杀死CGI进程web服务器与CGI通过环境变量、标准输入、标准输出、标准错误互相传递数据。在遇到用户连接请求:先要创建CGI子进程,然后CGI子进程处理请求,处理完事退出这个子进程:fork-and-executeCGI方式是客户端有多少个请求,就开辟多少个子进程,每个子进程都需要启动自己的解释器、加载配置,连接其他服务器等初始化工作,这是CGI进程性能低下的主要原因。当用户请求非常多的时候,会占用大量的内存、cpu等资源,造成性能低下。CGI使外部程序与Web服务器之间交互成为可能。CGI程序运行在独立的进程中,并对每个Web请求建立一个进程,这种方法非常容易实现,但效率很差,难以扩展。面对大量请求,进程的大量建立和消亡使操作系统性能大大下降。此外,由于地址空间无法共享,也限制了资源重用。3 环境变量GET请求,它将数据打包放置在环境变量QUERY_STRING中,CGI从环境变量QUERY_STRING中获取数据。常见的环境变量如下表所示:4 标准输入环境变量的大小是有一定的限制的,当需要传送的数据量大时,储存环境变量的空间可能会不足,造成数据接收不完全,甚至无法执行CGI程序。因此后来又发展出另外一种方法:POST,也就是利用I/O重新导向的技巧,让CGI程序可以由stdin和stdout直接跟浏览器沟通。当我们指定用这种方法传递请求的数据时,web服务器收到数据后会先放在一块输入缓冲区中,并且将数据的大小记录在CONTENT_LENGTH这个环境变量,然后调用CGI程序并将CGI程序的stdin指向这块缓冲区,于是我们就可以很顺利的通过stdin和环境变数CONTENT_LENGTH得到所有的信息,再没有信息大小的限制了。
安装nginx:src/os/unix/ngx_user.c:26:7: error: ‘struct crypt_data’ has no member named ‘curren
错误一:安装nginx报错“src/os/unix/ngx_user.c:26:7: error: ‘struct crypt_data’ has no member named ‘current_salt’”“src/os/unix/ngx_user.c:26:7: error: ‘struct crypt_data’ has no member named ‘current_salt’”
如果没有改下面错误二的“-Werror”,先把按照错误二改好试运行make或者sudo make install如果依然报错,就进去报错目录下,打开指定的报错文件,将报错行注释错误二:src/core/ngx_murmurhash.c:37:11: error: this statement may fall through [-Werror=implicit-fallthrough=]
h ^= data[2] << 16;
~~^~~~~~~~~~~~~~~~
src/core/ngx_murmurhash.c:38:5: note: here
case 2:
^~~~
src/core/ngx_murmurhash.c:39:11: error: this statement may fall through [-Werror=implicit-fallthrough=]
h ^= data[1] << 8;
~~^~~~~~~~~~~~~~~
src/core/ngx_murmurhash.c:40:5: note: here
原因:将警告当成错误处理解决错误1:进入到nginx-1.10.3目录下(即解压的目录)找到当前目录下找到objs文件夹,并进入,打开文件Makefile,找到有一下内容的这行:CFLAGS = -pipe -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g
把这行内容中的 “-Werror”去掉-Werror: gcc将所有的警告当成错误进行处理
Nginx简介说明
1 Nginx简介1 什么是NginxNginx是一款轻量级的Web 服务器、反向代理服务器、电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。由俄罗斯的程序设计师Igor Sysoev所开发,供俄国大型的入口网站及搜索引擎Rambler(俄文:Рамблер)使用。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。2 Netcraft 数据统计Netcraft公司于1994年底在英国成立,多年来一直致力于互联网市场以及在线安全方面的咨询服务,其中在国际上最具影响力的当属其针对网站服务器,域名解析/主机提供商,以及SSL市场所做的客观严谨的分析研究。公司官网每月公布的调研数据(Web Server Survey)已成为当今人们了解全球网站数量以及服务器市场分额情况的主要参考依据,时常被诸如华尔街杂志,英国BBC,Slashdot等媒体报道或引用。3 Nginx优势1)更快正常情况下单次请求得到更快的响应,高峰期(数以万计的并发时)Nginx可以比其它web服务器更快的响应请求。2)高扩展性低耦合设计的模块组成,丰富的第三方模块支持。3)高可靠性经过大批网站检验,每个worker进程相对独立,master进程在一个worker 进程出错时,可以快速开启新的worker进程提供服务。4)低内存消耗一般情况下,10000个非活跃的HTTP Keep-Alive连接在Nginx中仅消耗 2.5M内存,这是Nginx支持高并发的基础。5)单机支持10万以上的并发连接取决于内存,10万远未封顶。6)热部署master和worker的分离设计,可实现7x24小时不间断服务的前提下,升级Nginx可执行文件,当然也支持更新配置项和日志文件。7)最自由的BSD许可协议BSD许可协议允许用户免费使用Nginx、修改Nginx源码,然后再发布。这吸引了无数的开发者继续为 Nginx贡献智慧。4 Nginx相关资源Nginx维护包的官方网站:http://nginx.orgNginx官方文档:http://nginx.org/en/docs/淘宝团队翻译文档:http://tengine.taobao.org/documentation_cn.htmlNginx开发从入门到精通: http://tengine.taobao.org/book/index.html
Redis相关介绍
缓存数据库:redis1 为什么需要缓存?5400转的笔记本硬盘:50-90MB/s7200转的台式机硬盘:90-190MB/s固态硬盘读写速度可以达到500MB/s内存DDRIII1333的速度读写速度大概在8G/s其他频率的条子速度根据大小会有很大的浮动2 什么是redis2.1 redis简介redis是一个开源的key-value存储系统。与memcached类似,redis将大部分数据存储在内存中。redis支持的数据类型包括:字符串、 哈希表、链表、集合、有序集合以及基于这些数据类型的相关操作。简单而言,redis基于内存操作,读写速度很快,100000读写/秒,可以作为内存型缓存服务器,提供持久化存储方案,可以作为结构不复杂的数据库使用。redis使用C语言开发,在大多数像Linux、BSD和Solaris等 POSIX系统上无需任何外部依赖就可以使用。redis支持的客户端语言也非常丰富,常用的计算机语言如C、C#、C++、Object-C、PHP、Python、Java、Perl、Lua、Erlang等均有可用的客户端来访问redis服务器。当前redis的应用已经非常广泛,国内像新浪、淘宝,国外像Flickr、Github等均在使用redis的缓存服务。2.2 redis优点redis有三个主要使其有别于其它很多竞争对手的特点:1)redis是完全在内存中保存数据的数据库,使用磁盘只是为了持久性目的2)redis相比许多键值数据存储系统有相对丰富的数据类型3)redis可以将数据复制到任意数量的从服务器中redis 优点:异常快速: redis是非常快的,每秒可以执行大约110000设置操作,81000个/每秒的读取操作。支持丰富的数据类型: redis支持最大多数开发人员已经知道如列表、集合、可排序集合、哈希等数据类型。操作都是原子的:所有 redis 的操作都是原子(所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它是最小的执行单位,不可能有比它更小的执行单位),从而确保当两个客户同时访问 redis 服务器得到的是更新后的值(最新值) 。MultiUtility工具:redis是一个多功能实用工具,可以在很多如:缓存,消息传递队列中使用( redis原生支持发布/订阅) ,在应用程序中,如:Web应用程序会话、网站页面点击数等任何短暂的数据。2.3 redis使用场景1)取最新N个数据的操作比如典型的取你网站的最新文章,我们可以将最新的5000条评论的ID放在redis的List集合中,并将超出集合部分从数据库获取。2)排行榜应用,取TOP N操作这个需求与上面需求的不同之处在于,前面操作以时间为权重,这个是以某个条件为权重,比如按顶的次数排序,这时候就需要我们的sorted set出马了,将你要排序的值设置成sorted set的score,将具体的数据设置成相应的value,每次只需要执行一条ZADD命令即可。3)需要精准设定过期时间的应用比如你可以把上面说到的sorted set的score值设置成过期时间的时间戳,那么就可以简单地通过过期时间排序,定时清除过期数据了,不仅是清除redis中的过期数据,你完全可以把redis里这个过期时间当成是对数据库中数据的索引,用redis来找出哪些数据需要过期删除,然后再精准地从数据库中删除相应的记录。4)计数器应用redis的命令都是原子性的,你可以轻松地利用INCR,DECR命令来构建计数器系统。5)uniq操作,获取某段时间所有数据排重值这个使用redis的set数据结构最合适了,只需要不断地将数据往set中扔就行了,set意为集合,所以会自动排重。6)Pub/Sub构建实时消息系统redis的Pub/Sub系统可以构建实时的消息系统,比如很多用Pub/Sub构建的实时聊天系统的例子。7)构建队列系统使用list可以构建队列系统,使用sorted set甚至可以构建有优先级的队列系统。8)缓存最常用,性能优于memcached(被libevent拖慢),数据结构更多样化。
分布式存储FastDFS介绍
分布式存储FastDFS1 什么是分布式存储分布式存储简单的来说,就是将数据分散存储到多个存储设备(服务器)上。传统的网络存储系统采用集中的存储服务器存放所有数据,存储服务器成为系统性能的瓶颈,也是可靠性和安全性的焦点,不能满足大规模存储应用的需要。分布式网络存储系统采用可扩展的系统结构,利用多台存储服务器分担存储负荷,利用位置服务器定位存储信息,它不但提高了系统的可靠性、可用性和存取效率,还易于扩展。经典的分布式文件系统介绍:http://os.51cto.com/art/201209/357433.htm2 FastDFS2.1 什么是FastDFSFastDFS是一款开源的、分布式文件系统(Distributed File System), 由淘宝开发平台部资深架构师余庆开发。它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载、文件删除)等,解决了大容量存储和负载均衡的问题。FastDFS是通过纯C实现,支持Linux, FreeBSD等Unix系统类Google FS, 不是通用的文件系统,只能够通过专有API访问,目前提供了C,Java和PHP API为互联网应用量身定做,解决大容量文件存储问题,追求高性能和高扩展性 FastDFS可以看做是基于文件的key-value存储系统,称为分布式文件存储服务更为合适。FastDFS相关资源:开源项目主页:https://code.google.com/archive/p/fastdfs/源码下载:fastdfs.sourceforge.netChinaUnix论坛版块:http://bbs.chinaunix.net/forum-240-1.html2.2 架构简析我们可以通过 FastDFS 对文件的上传过程,来初步了解 FastDFS 的基本架构:① 首先客户端client 发起对 FastDFS 的文件传输动作,是通过连接到某一台 Tracker Server 的指定端口来实现的;② Tracker Server 根据目前已掌握的信息,来决定选择哪一台 Storage Server ,然后将这个Storage Server 的地址等信息返回给 client;③ 然后 client 再通过这些信息连接到这台Storage Server,将要上传的文件传送到给 Storage Server上。FastDFS服务端有两个重要角色:跟踪器(tracker)和存储节点(storage):跟踪器主要做调度工作,在访问上起负载均衡的作用存储节点存储文件,完成文件管理的所有功能Tracker Server 与 Storage Server 之间不直接通信,其基本的信息由配置文件在系统启动加载时获知。多台 Tracker Server 之间保证了 Tracker 的分布式,Tracker Server 之间是对等的,防止了单点故障。Storage Server 是分成多个 Group(组),每个 Group 中的Storage 都是互相备份的,也就是说,如果 Group1 有 Storage1、Storage2、Storage3,其容量分别是100GB、200GB、300GB,那么 Group1 的存储能力是 100GB,而不是 300GB,这就是互相备份的意思。进一步说,整个 Group 的存储能力由该组中该储能力最小的 Storage 决定。多个 Group 之间的存储方式,可以采用 round robin( 轮训) 、load balanced( 负载均衡) 或指定 Group 的方式。另一点相对于MS( Master-Slave) 模式的优势,不仅 master 有上面可能提到的单点故障问题,而且 client 与 master 之间可能会出现瓶颈。但 FastDFS 架构中,Tracker Server 不会称为系统瓶颈,数据最终是与一个available 的 Storage Server 进行传输的。简单总结一下,FastDFS的特点包括:高可靠性:无单点故障高吞吐量:只要 Group 足够多,数据流量是足够分散的3 FastDFS集群 - (了解内容)简图Tracker集群Tracker server之间是相互平等关系同时提供服务○ Tracker server不存在单点故障。客户端请求Tracker server采用轮询方式,如果请求的tracker无法提供服务则换另一个tracker。Storage集群○ Storage集群采用了分组存储方式, 由一个或多个组构成○ 集群存储总容量为集群中所有组的存储容量之和一个组由一台或多台存储服务器组成,组内的Storage server之间是平等关系○不同组的Storage server之间不会相互通信,同组内的Storage server之间会相互连接进行文件同步,从而保证同组内每个storage上的文件完全一致的。○ 一个组的存储容量为该组内存储服务器容量最小的那个FastDFS的扩容分纵向扩容与横向扩容○ 横向 扩容增加容量添加group组○ 纵向扩容数据备份存储节点中容量最小的那个当前组的最大容量所有存储节点组名必须一样
Socket的基本操作函数socket()、bind()、listen()、connect()、accept()、recv()、send()、select()、close()
Socket的基本操作函数1、socket()函数int socket(int domain, int type, int protocol);socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPv4)、AF_INET6(IPv6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。type:指定socket类型。常用的socket类型有,SOCK_STREAM(流式套接字)、SOCK_DGRAM(数据报式套接字)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等protocol:就是指定协议。常用的协议有,IPPROTO_TCP、PPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。返回值:若无错误发生,socket()返回引用新套接口的描述字。否则的话,返回INVALID_SOCKET错误,应用程序可通过WSAGetLastError()获取相应错误代码。**注意:**并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。1.1、 命名socketSOCK_STREAM式套接字的通信双方均需要具有地址,其中服务器端的地址需要明确指定,ipv4的指定方法是使用 struct sockaddr_in类型的变量。struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址设置成INADDR_ANY,让系统自动获取本机的IP地址。
servaddr.sin_port = htons(DEFAULT_PORT);//设置的端口
INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。也就是表示本机的所有IP,因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。客户端connect时,不能使用INADDR_ANY选项。必须指明要连接哪个服务器IP。htons将主机的无符号短整形数转换成网络字节顺序htonl将主机的无符号长整形数转换成网络字节顺序网络字节序与主机字节序:主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下: ?a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。 ?b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给socket。当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。2、bind()函数bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函数的三个参数分别为:sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。struct sockaddr{
sa_family_t sin_family; //地址族(Address Family),也就是地址类型
char sa_data[14]; //IP地址和端口号
};
sockaddr 是一种通用的结构体,可以用来保存多种类型的IP地址和端口号。要想给 sa_data 赋值,必须同时指明IP地址和端口号,例如”127.0.0.1:80“,但没有相关函数将这个字符串转换成需要的形式,也就很难给 sockaddr 类型的变量赋值。正是由于通用结构体 sockaddr 使用不便,才针对不同的地址类型定义了不同的结构体。 如ipv6对应的是:
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* port number */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */
};
struct in6_addr {
unsigned char s6_addr[16]; /* IPv6 address */
};
Unix域对应的是:
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
addrlen:对应的是地址的长度。通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。返回值:如无错误发生,则bind()返回0。否则的话,将返回-1,应用程序可通过WSAGetLastError()获取相应错误代码。3、listen()、connect()函数如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。返回值:如无错误发生,listen()返回0。否则的话,返回-1,应用程序可通过WSAGetLastError()获取相应错误代码。connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。返回值:若无错误发生,则connect()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。对非阻塞套接口而言,若返回值为SOCKET_ERROR则应用程序调用WSAGetLastError()。如果它指出错误代码为WSAEWOULDBLOCK,则您的应用程序可以:用select(),通过检查套接口是否可写,来确定连接请求是否完成。如果您的应用程序使用基于消息的WSAAsyncSelect()来表示对连接事件的兴趣,则当连接操作完成后,您会收到一个FD_CONNECT消息。4、accept()函数TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为客户端协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。返回值:如果没有错误产生,则accept()返回一个描述所接受包的SOCKET类型的值。否则的话,返回INVALID_SOCKET错误,应用程序可通过调用WSAGetLastError()来获得特定的错误代码。注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。两个套接字不一样。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。5、recv()、send()等函数至此服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!网络I/O操作有下面几组:? read()/write()? recv()/send()? readv()/writev()? recvmsg()/sendmsg()? recvfrom()/sendto()它们的声明如下:#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
read函数是负责从fd中读取内容。当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。write函数将buf中的nbytes字节内容写入文件描述符fd。成功时返回写的字节数。失败时返回-1,并设置errno变量。 在网络程序中,当我们向套接字文件描述符写时有两种可能。1)write的返回值大于0,表示写了部分或者是全部的数据。 2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。recv函数和send函数提供了read和write函数一样的功能,不同的是他们提供了四个参数。前面的三个参数和read、write函数是一样的。参数第一个参数指定发送端套接字描述符;第二个参数指明一个存放应用程序要发送数据的缓冲区;第三个参数指明实际要发送的数据的字节数;第四个参数一般置0。或者是以下组合:MSG_DONTROUTE:不查找表,是send函数使用的标志,这个标志告诉IP,目的主机在本地网络上,没有必要查找表,这个标志一般用在网络诊断和路由程序里面。MSG_OOB:表示可以接收和发送带外数据。MSG_PEEK:查看数据,并不从系统缓冲区移走数据。是recv函数使用的标志,表示只是从系统缓冲区中读取内容,而不清楚系统缓冲区的内容。这样在下次读取的时候,依然是一样的内容,一般在有个进程读写数据的时候使用这个标志。MSG_WAITALL:等待所有数据,是recv函数的使用标志,表示等到所有的信息到达时才返回,使用这个标志的时候,recv返回一直阻塞,直到指定的条件满足时,或者是发生了错误。同步Socket的send函数的执行流程当调用该函数时,(1)send先比较待发送数据的长度len和套接字s的发送缓冲的长度, 如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;(2)如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议s的发送缓冲中的数据是否正在发送,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len(3)如果len大于剩余空间大小,send就一直等待协议把s的发送缓冲中的数据发送完(4)如果len小于剩余 空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。注意:send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个socket函数就会返回SOCKET_ERROR。(每一个除send外的socket函数在执 行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回 SOCKET_ERROR)在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。通过测试发现,异步socket的send函数在网络刚刚断开时还能发送返回相应的字节数,同时使用select检测也是可写的,但是过几秒钟之后,再send就会出错了,返回-1。select也不能检测出可写了。同步Socket的recv函数的执行流程当应用程序调用recv函数时,(1)recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,(2)如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。注意:在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。6、select()函数connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout);
struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄,fd_set集合可以通过一些宏由人为来操作。FD_ZERO(fd_set *set); //Clear all entries from the set.
FD_SET(int fd, fd_set *set); //Add fd to the set.
FD_CLR(int fd, fd_set *set); //Remove fd from the set.
FD_ISSET(int fd, fd_set *set); //Return true if fd is in the set.
struct timeval代表时间值。struct timeval {
int tv_sec; //seconds
int tv_usec; //microseconds,注意这里是微秒不是毫秒
};
参数:int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1。fd_set * readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。fd_set * writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。fd_set * errorfds同上面两个参数的意图,用来监视文件错误异常。struct timeval * timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。返回值:返回状态发生变化的描述符总数。 负值:select错误 ;正值:某些文件可读写或出错 ;0:等待超时,没有可读写或错误的文件理解select模型:理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。(1)执行fd_set set;FD_ZERO(&set);则set用位表示是0000,0000。(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)(3)若再加入fd=2,fd=1,则set变为0001,0011(4)执行select(6,&set,0,NULL,NULL)阻塞等待(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。基于上面的讨论,可以轻松得出select模型的特点:select模型的特点:(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。(2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。(3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。使用select和non-blocking实现server处理多client实例SELECT7、close()/shutdown()函数int close(int sockfd);close 一个套接字的默认行为是把套接字标记为已关闭,然后立即返回到调用进程,该套接字描述符不能再由调用进程使用,也就是说它不能再作为read或write的第一个参数,然而TCP将尝试发送已排队等待发送到对端,发送完毕后发生的是正常的TCP连接终止序列。注:多进程中close操作解释在多进程并发服务器中,父子进程共享着套接字,套接字描述符引用计数记录着共享着的进程个数,当父进程或某一子进程close掉套接字时,描述符引用计数会相应的减一,当引用计数仍大于零时,这个close调用就不会引发TCP的四路握手断连过程。int shutdown(int sockfd,int howto);
该函数的行为依赖于howto的值SHUT_RD:值为0,关闭连接的读这一半。SHUT_WR:值为1,关闭连接的写这一半。SHUT_RDWR:值为2,连接的读和写都关闭。终止网络连接的通用方法是调用close函数。但使用shutdown能更好的控制断连过程(使用第二个参数)。close与shutdown的区别主要表现在:close函数会关闭套接字ID,如果有其他的进程共享着这个套接字,那么它仍然是打开的,这个连接仍然可以用来读和写,并且有时候这是非常重要的 ,特别是对于多进程并发服务器来说。而shutdown会切断进程共享的套接字的所有连接,不管这个套接字的引用计数是否为零,那些试图读得进程将会接收到EOF标识,那些试图写的进程将会检测到SIGPIPE信号,同时可利用shutdown的第二个参数选择断连的方式。
Linux处理机管理——线程
1.线程线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进 程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。一个进程可以有很多线程,每条线程并行执行不同的任务。在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。1. 线程的特点在多线程OS中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。1)轻型实体线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。TCB包括以下信息:(1)线程状态。(2)当线程不运行时,被保存的现场资源。(3)一组执行堆栈。(4)存放每个线程的局部变量主存区。(5)访问同一个进程中的主存和其它资源。用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。2)独立调度和分派的基本单位。在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。3)可并发执行。在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。4)共享进程资源。在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。2.如何创建线程pthread_create(); pthread_create();
pthread_create是类Unix操作系统(Unix、Linux、Mac OS X等)的创建线程的函数。它的功能是创建线程(实际上就是确定调用该线程函数的入口点),在线程创建以后,就开始运行相关的线程函数。pthread_create的返回值:若成功,返回0;若出错,返回出错编号。头文件 #include<pthread.h>函数声明 int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,void *(*start_rtn)(void*),void *arg);编译链接参数-lpthread返回值若线程创建成功,则返回0。若线程创建失败,则返回出错编号,并且thread中的内容是未定义的。返回成功时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于指定各种不同的线程属性。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个万能指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。linux下用C语言开发多线程程序,Linux系统下的多线程遵循POSIX线程接口,称为pthread。参数第一个参数为指向线程标识符的指针。第二个参数用来设置线程属性。第三个参数是线程运行函数的起始地址。最后一个参数是运行函数的参数。注意事项因为pthread并非Linux系统的默认库,而是POSIX线程库。在Linux中将其作为一个库来使用,因此加上 -lpthread(或-pthread)以显式链接该库。函数在执行错误时的错误信息将作为返回值返回,并不修改系统全局变量errno,当然也无法使用perror()打印错误信息。示例在终端上 ./a.out 后面需加 -lpthread#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void printids(const char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int) pid, (unsigned int) tid, (unsigned int) tid);
}
void *thr_fn(void *arg)
{
printids("new thread: ");
return NULL;
}
int main(void)
{
int err;
pthread_t ntid;
err = pthread_create(&ntid, NULL, thr_fn, NULL);
if (err != 0)
printf("can't create thread: %s\n", strerror(err));
printids("main thread:"); pthread_join(ntid,NULL);
return EXIT_SUCCESS;
}