如何实现在Linux下创建服务程序
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何实现在Linux下创建服务程序相关的知识,希望对你有一定的参考价值。
Linux系统能提供强大可靠的网络服务,并有管理程序对服务进行管理。例如我们熟悉的Web、FTP和电子邮件等,它们既可以单独运行,也可以被守护进程inetd调用,而且运行得都非常好。但我们不能仅停留在赞叹中,下面就给出两个服务程序程序和一个客户程序的例子,介绍服务程序和客户程序之间是如何沟通的。另外还要编辑配置一些文件,让服务程序也能接受服务管理程序管理。这两个服务程序功能相同,但一个是独立服务程序,另一个是被inetd调用的服务程序。这是TCP/IP网络服务的两大类,这里将两个程序放在一起是为了比较程序结构和运行方式。两服务程序都在Red Hat Linux 7.1和TurboLinux 7.0上调试通过。
独立服务器
TCP和UDP是两大TCP/IP数据传输方式,套接口是建立服务器客户机连接的机制,首先介绍它们建立通信联系的过程,然后给出一个TCP服务程序例子。
1.TCP套接口通信方式
对于TCP服务器端,服务程序首先调用建立套接口的函数socket(),然后调用绑定服务IP地址和协议端口号函数bind()。绑定成功后调用被动监听函数listen()等待客户连接,还要调用获取连接请求函数accept(),并一直阻塞到客户连接请求的到达,这个函数获取客户机IP地址和协议端口号。
对于TCP客户端,客户程序启动后后调用建立套接口函数socket(),然后调用连接函数connect(),此函数与服务器通过三次握手建立连接。
服务器和客户机建立连接后,就可以使用读函数read()和写函数write()收发数据了。数据交换完成后便各自调用关闭套接口函数close()删除套接口。TCP套接口通信方式见图1所示。
图1 TCP套接口通信方式
2.UDP套接口通信方式
UDP程序与TCP的区别是无需建立连接。服务器首先启动,然后等待用户请求。客户机启动后便直接向服务器请求服务,服务器接到请求后给出应答。
对于UDP服务器端,服务程序首先调用套接口函数socket(),然后调用绑定IP地址和协议端口号函数bind()。之后调用函数recvfrom()接收客户数据,调用sendto()向客户发送数据。
对于UDP客户端,客户机程序启动后调用套接口函数socket(),然后调用sendto()向服务器发送数据,调用recvfrom()接收服务器数据。
双方数据交换成功后,各自调用关闭套接口函数close()关闭套接口。UDP套接口通信方式见图2所示。
图2 UDP套接口通信方式
下面给出独立服务程序的例子。这个程序虽然简单,但是与复杂程序有着相同的结构。
//程序名:server.c
//功能:服务器从客户机读入一个字符,并将排在此字符后面的字符回送客户机
//服务器端口:9000
#include "sys/types.h"
#include "sys/socket.h"
#include "stdio.h"
#include "netinet/in.h"
#include "arpa/inet.h"
#include "unistd.h"
int main()
int pid; //用于存放fork()执行结果
int server_sockfd,client_sockfd; //用于服务器和客户机套接口描述符
int bind_flag,listen_flag; //用于存放bind()和listen()执行结果
int server_address_length,client_address_length; //作为服务器客户机地址长变量
struct sockaddr_in server_address; //作为服务器地址结构变量(含地址和端口)
struct sockaddr_in client_address; //作为客户机地址结构变量(含地址和端口)
if((pid=fork())!=0) //用fork()产生新进程
exit(0) ;
setsid() ; //以子进程开始下面的程序
函数socket(),创建一个套接口,成功则返回套接口描述符。
server_sockfd=socket(AF_INET,SOCK_STREAM,0);
if(server_sockfd<0)
printf(“socket error /n”);
exit(1);
server_address.sin_family=AF_INET;
函数htonl()用于将32位主机字节顺序转换为网络字节顺序,其中参数INADDR_ANY表示任何IP地址。
server_address.sin_addr.s_addr=htonl(INADDR_ANY);
函数htons()用于将16位主机字节顺序转换为网络字节顺序,其中的参数是绑定的端口号,读者可根据环境自行改动,目的是不与其它服务端口冲突。
server_address.sin_port=htons(9000);
server_address_length=sizeof(server_address);
函数bind()用于绑定本地地址和服务端口号,若调用成功返回值为0。
bind_flag=bind(server_sockfd,/
(struct sockaddr *)&server_address,/
server_address_length);
if(bind_flag<0)
printf(“bind error /n”);
exit(1);
函数listen(),指明服务器的队列长度,被动等待客户连接,调用成功返回值为0。
listen_flag=listen(server_sockfd,5);
if(listen_flag<0)
printf(“listen error /n”);
exit(1);
while(1)
char ch;
函数accept()等待和获取用户请求,为每个新连接请求创建一个新的套接口,调用成功返回新套接口描述符。
client_sockfd=accept(server_sockfd,/
(struct sockaddr *)&client_address,/
&client_address_length);
函数read()和write()用于在服务器和客户机之间传送数据,调用成功返回读和写的字节数。
函数close(),用于程序使用完一个套接口后关闭套接口,调用成功返回值0。其中的参数为accept()创建的套接口的描述符client_sockfd。
read(client_sockfd,&ch,1);
printf(“cli_ch=%c”,ch);
ch++;
write(client_sockfd,&ch,1);
close(client_sockfd);
程序完成后就可以使用命令进行编译。在命令行中输入“gcc -o server server.c”,将server.c编译成可执行程序server,这时便可用客户程序进行测试。在命令行执行“./server”启动服务程序,执行“netstat -na”查看有无server的服务端口。如果存在,则执行下面编写的客户程序“./client”。不过这仅是手工启动的方法,下面给出用服务管理程序管理server程序的方法。只要在目录/etc/rc.d/init.d下放入服务程序的脚本就能被服务程序读到。在命令行执行“touch server”创建文件server,并将文件属性改成可执行。在管理程序中并不能看到此服务名,脚本文件必须有一些结构才能被管理程序认为是服务程序脚本。
为了减少工作量,拷贝/etc/rc.d/init.d下脚本httpd,将拷贝脚本名命名为server,然后对其编辑。
(1)执行“cp httpd server”。
(2)用文本编辑器vi(其它编辑器亦可)将server打开进入编辑状态。首先用字符串server替换httpd。然后找到daemon server行,如果编写的程序放在变量PATH目录中,不需要修改此行;如果把服务程序放在其它目录中,就要写服务的全路径。例如程序在/root的目录中,就要写成daemon /root/server,还要删除“rm -f /var/run/server.pid”这一行。
(3)执行“chmod 755 server”,将server属性设定为可执行。
此时就可以用chkconfig、ntsysv等工具,在希望的运行级中增加这个新服务程序,然后测试客户机与服务器能否通信。
被xinetd调用的服务程序
在Linux系统中,有很多服务是被xinetd(较早版本使用的是inetd)超级守护服务器启动的。其实凡是基于TCP和UDP的服务都可使用超级守护进程启动,只是在服务量很大影响效率的情况下不被采用。
1.依赖xinetd启动的服务建立通信过程
为了与独立服务器程序比较,我们看一下依赖xinetd的服务器是如何启动的。
(1)xinetd启动时读取/etc/xinetd目录中的文件(早期版本为/etc/inetd文件),根据其中的内容给所有允许启动的服务创建一个指定类型的套接口,并将套接口放入select()中的描述符集合中。
(2)对每个套接口绑定bind(),所用的端口号和其它参数来自/etc/xinetd目录下每个服务的配置文件。
(3)如果是TCP套接口就调用函数listen(),等待用户连接。如果是UDP套接口,就不需调用此函数。
(4)所有套接口建立后,调用函数select()检查哪些套接口是活动的。
(5)若select()返回TCP套接口,就调用accept()接收这个连接。如果为UDP,就不需调用此函数。
(6)xinetd调用fork()创建子进程,由子进程处理连接请求。
◆ 子进程关闭所有其它描述符,只剩下套接口描述符。这个套接口描述符对于TCP是accept()返回的套接口,对于UDP为最初建立的套接口。然后子进程连续三次dup()函数,将套接口描述符复制到0、1和2,它们分别对应标准输入、标准输出和标准错误输出,并关闭套接口描述符。
◆ 子进程查看/etc/xinetd下文件中的用户,如果不是root用户,就用调用命令setuid和setgid将用户ID和组ID改成文件中指定的用户。
(7)对于TCP套接口,与用户交流结束后父进程需要关闭已连接套接口。父进程重新处于select()状态,等待下一个可读的套接口。
最后调用配置文件中指定的外部服务程序,外部程序启动后就可与用户进行信息传递了。
2.为xinetd编写专门的服务程序
除了独立服务程序能被xinetd启动外,还可以为xinetd编写专门的程序。此处的例子程序与上面server.c功能相同。不过两者的程序区别是很大的,此例的代码仅相当于上面传输数据的部分。我们还将程序名定为server.c,所以不能放在相同目录中,同名仅是为了和上面程序对照。
#include "unistd.h"
int main()
char ch;
read(0,&ch,1);
ch++;
write(1,&ch,1);
将程序编译成可执行文件,并做些设置就可被xinetd启动。注意不要和上面的独立服务程序server一起启动,因为客户程序写得比较简单,访问的是固定端口,服务器都设成了相同的端口号。
(1)编辑/etc/services文件,在行末增加一条记录:
server 9000/tcp
(2)在目录/etc/xinetd.d下编写文件server,内容为:
service server
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /home/test/server (此处设置成自己程序所在的目录)
如果使用的是较早版本,则需在/etc/inetd.conf文件中添加下面的行:
server tcp nowait root /path/to/yourdirectory/server
(3)执行/etc/rc.d/initd.d/xinetd restart重新启动xinetd服务器。早期版本执行/etc/rc.d/initd.d/inetd restart重新启动inetd。
(4)执行netstat -an查看有没有server程序使用的端口号,如果有就可使用下面客户机程序进行测试了。
客户机程序
下面就客户机函数做一简单介绍。
//程序名client.c
/*功能:从客户的控制台输入一个字符,然后将这个字符送到服务器,并将服务器返回的字符显示出来*/
#include "sys/types.h"
#include "sys/socket.h"
#include "stdio.h"
#include "netinet/in.h"
#include "arpa/inet.h"
#include "unistd.h"
int main()
int sockfd;//
int address_len;
int connect_flag;
struct sockaddr_in address;
int connect_result;
char client_ch,server_ch;
函数socket()用于建立一个套接口,创建成功返回套接口描述符。
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
printf(“sockfd error /n”);
address.sin_family=AF_INET;
address.sin_addr.s_addr=inet_addr(“192.168.0.1”);/*读者根据自己环境改成服务器地址*/
address.sin_port=htons(9000);
address_len=sizeof(address);
函数connect()用于与服务器建立一个主动连接,调用成功返回值为0。
connect_flag=connect(sockfd,(struct sockaddr *)&address,address_len);
if(connect_flag==-1)
perror(“client”);
exit(1);
printf(“Input a character :”);
函数scanf()用于从控制台输入一个字符,并将字符存入client_ch的地址。函数write()和read()用于传输数据。函数printf()在客户机屏幕上显示服务器传回的字符。函数close()关闭套接口。
scanf(“%c”,&client_ch);
write(sockfd,&client_ch,1);
read(sockfd,&server_ch,1);
printf(“character from server : %c/n”,server_ch);
close(sockfd);
exit(0);
执行命令“gcc -o client client.c”,将client.c编译成client。执行“./client”,在程序提示下输入一个字符,就能看到服务器传回的字符。
以上介绍的仅是简单的例子。平时见到的服务程序远比它复杂,而且很多是多协议服务程序或是多协议多服务程序。多协议服务程序就是在main()中分别创建供服务的TCP和UDP套接口。为每个服务分别写出相应程序好处是便于控制,但是这样每个服务都启动两个服务器,而它们的算法响应是一样的,就要耗费不必要的资源,并且出了问题排错也较困难。多服务是将不同的服务集成在一起由一个程序完成,可用一个数组表示服务,数组中的每一项表示某协议某服务的一种,这样很容易扩展程序的服务功能。 参考技术A 用B/S或者C/S等,以及WebSerice等模式。 服务还是在Windows上,而使用者在Linux上。没有必要非要把服务和使用者放在同一个平台。具体介绍参考《Linux就该这么学》。
linux下如何实现增量备份?
服务器系统为linux,每月都有版本升级,这就需要对原程序代码进行备份。备份的方式我想实现增量备份,而非全量备份,即将服务器现有代码与版本升级内容做比较,若有相同目录下的相同文件,则将此文件连同目录一起备份。
比如:原程序代码platform/test文件夹下的a文件为这次的升级内容,则我的备份为platform/test/a 望高手指点,急,分数不多了,还望包涵!
需要备份文件的服务器(服务器端):192.168.10.2 (RHEL 5)
接收备份文件的服务器(客户端):192.168.10.3 (RHEL 5)
实现功能:
通过rsync工具对turbomail邮件服务器的accounts目录和conf目录进行增量备份。
accounts目录:用于存储用户相关信息的目录
conf目录:用户配置文件存放目录
前提条件:
先将turbomail邮件系统安装好(服务器端与客户端操作一样)
安装过程如下:
1、挂载光驱:mount /dev/cdrom
2、将turbomail的安装包拷贝到跟目录下/
#cd /dev/cdrom
#cp turbomail_linux_x86_xxx.tgz
3、解压turbomail安装包
tar –zxvf turbomail_linux_x86_xxx.tgz
4、启动turbomail
#cd /turbomail
#./starttm.sh
#cd /turbomail/web/bin/
#./startup.sh 5、修改启动脚本/etc/rc.d/rc.local
使邮件服务器开机自动启动加入以下几行:#/turbomail/starttm.sh &
#/turbomail/web/bin/startup.sh & #/turbomail/safestart.sh &
rsync的配置如下:
1.服务器端rsync的配置:使用系统自带的rsync工具,插入系统启动光盘,通过rpm安装rsync工具。
若采用采用源码包安装,上传安装包到服务器1)tar –zxvf rsyncrsync-2.6.9.tar.gz #解压2)cd rsyncrsync-2.6.9 3)./configure #默认配置,生成编译环境
4)make #编译
5)make install #安装完成,任何步骤有疑问输入echo $?查返回值,0表示成功,其他都为失败rsync 版本
2.4.6(可以从http://rsync.samba.org/rsync/获得最新版本)
1)编辑/etc/rsyncd.conf文件,rsync的主要配置文件:
#[globale]
strict modes= yes
#check passwd file
port= 873 #rsync工具默认使用的端口
#default port
logfile= /var/log/rsyncd.log #日志文件存放路径pidfile= /var/run/rsyncd.pid #记录rsync运行时的进程ID
max connections= 4 #同上最大的链接数
#[modules] #同步模块的配置
[rsync]
uid= root #该选项指定当该模块传输文件时守护进程应该具有的uid
gid= root #该选项指定当该模块传输文件时守护进程应该具有的gid
ignore errors #忽略一些无关的错误信息
#要备份的目录
path= /turbomail/accounts #需要同步的目录
read only= no #是否设置以只读的方式运行
host allow= 192.168.10.3 #同步数据的客户端ip如有多个客户端,以“,”隔开即可
auth users= zhouhw #同步用的用户名
secrets file= /etc/rsyncd.scrt #同步的密码认证文件
[test]
uid= root #该选项指定当该模块传输文件时守护进程应该具有的uid
gid= root #该选项指定当该模块传输文件时守护进程应该具有的gid
ignore errors #忽略一些无关的错误信息
#要备份的目录
path= /turbomail/conf #需要同步的目录
read only= no #是否设置以只读的方式运行
host allow= 192.168.10.3 #同步数据的客户端ip如有多个客户端,以“,”隔开即可
auth users= zhouhw #同步用的用户名
secrets file= /etc/rsyncd.scrt #同步的密码认证文件2)编辑/etc/rsyncd.scrt文件,密码认证文件
zhouhw:123456
该更权限为600:chmod 600 /etc/rsyncd.scrt 3) 运行rsync --daemon,并在/etc/rc.d/rc.local加入此语句,开机自动启动。
备注:rsync默认端口873,若安装了iptables,如需改动启动时启动rsync –port 873,请在iptables 中开放该端口,语句如下:
iptables -I INPUT -p tcp --dport 873 -j ACCEPT
4)编辑/etc/xinetd.d/rsync
rsync服务的监听由xinet来统一分配:
service rsync
disable = no #将此项改为no
socket_typ
e = stream
wait = no
user = root
server = /usr/bin/rsync
server_args = --daemon
log_on_failure += USERID
重启xinetd以启动rsync服务
service xinetd restart
5)查看rsync事故正常运行
输入命令:netstat -ant|grep :873若有显示以下一条语句,则说明rsync服务已经正常启动了。
tcp 0 0 0.0.0.0:873 0.0.0.0:* LISTEN 2.客户端的配置:使用系统自带的rsync工具,插入系统启动光盘,通过rpm安装rsync工具。
1)编辑/etc/rsyncd.scrt文件,密码认证文件
zhouhw:123456
该更权限为600:chmod 600 /etc/rsyncd.scrt 2)编辑脚本vi /root/scrpit/rsync.sh并加入开机自动启动。
#!/bin/sh /usr/bin/rsync -vazu --progress --delete
zhouhw@192.168.10.2::rsync /turbomail --password-file=/etc/rsyncd.scrt
zhouhw@192.168.10.2::test /turbomail
--password-file=/etc/rsyncd.scrt修改权限:chmod u+x
/root/scrpit/rsync.sh执行./rsync.sh 同步数据。
3)编辑/etc/crontab文件,定义为每小时30分钟执行一次数据同步。
vi /etc/crontab
# run-parts
01 * * * * root run-parts /etc/cron.hourly 02 4 * * * root
run-parts /etc/cron.daily 22 4 * * 0 root run-parts /etc/cron.weekly 42 4
1 * * root run-parts /etc/cron.monthly 30 * * * * root
/root/scrpit/rsync.sh客户端的配置到此就可以了,服务器与客户端的数据也会在每小时的第30分钟执行一次。
客户端同步命令详解:
v, --verbose 详细模式输出-q, --quiet 精简输出模式-c, --checksum
打开校验开关,强制对文件传输进行校验-a, --archive 归档模式,表示以递归方式传输文件,并保持所有文件属性,等于-rlptgoD
-r, --recursive 对子目录以递归模式处理-R, --relative 使用相对路径信息-b, --backup
创建备份,也就是对于目的已经存在有同样的文件名时,将老的文件重新命名为~filename。可以使用--suffix选项来指定不同的备份文件前缀。
--backup-dir 将备份文件(如~filename)存放在在目录下。
-suffix=SUFFIX定义备份文件前缀
-u, --update 仅仅进行更新,也就是跳过所有已经存在于DST,并且文件时间晚于要备份的文件。(不覆盖更新的文件) -l, --links 保留软链结-L, --copy-links 想对待常规文件一样处理软链结
--copy-unsafe-links仅仅拷贝指向SRC路径目录树以外的链结
--safe-links忽略指向SRC路径目录树以外的链结
-H, --hard-links 保留硬链结-p, --perms 保持文件权限-o, --owner 保持文件属主信息-g,
--group 保持文件属组信息-D, --devices 保持设备文件信息-t, --times 保持文件时间信息-S, --sparse
对稀疏文件进行特殊处理以节省DST的空间-n, --dry-run现实哪些文件将被传输-W, --whole-file
拷贝文件,不进行增量检测-x, --one-file-system 不要跨越文件系统边界-B, --block-size=SIZE
检验算法使用的块尺寸,默认是700字节-e, --rsh=COMMAND 指定替代rsh的shell程序
--rsync-path=PATH指定远程服务器上的rsync命令所在路径信息
-C, --cvs-exclude 使用和CVS一样的方法自动忽略文件,用来排除那些不希望传输的文件--existing 仅仅更新那些已经存在于DST的文件,而不备份那些新创建的文件
--delete删除那些DST中SRC没有的文件
--delete-excluded同样删除接收端那些被该选项指定排除的文件
--delete-after传输结束以后再删除
--ignore-errors及时出现IO错误也进行删除
--max-delete=NUM最多删除NUM个文件
--partial 保留那些因故没有完全传输的文件,以是加快随后的再次传输--force 强制删除目录,即使不为空
--numeric-ids不将数字的用户和组ID匹配为用户名和组名
--timeout=TIME IP超时时间,单位为秒-I, --ignore-times
不跳过那些有同样的时间和长度的文件--size-only
当决定是否要备份文件时,仅仅察看文件大小而不考虑文件时间--modify-window=NUM 决定文件是否时间相同时使用的时间戳窗口,默认为0
-T --temp-dir=DIR在DIR中创建临时文件
--compare-dest=DIR同样比较DIR中的文件来决定是否需要备份
-P等同于--partial
--progress显示备份过程
-z, --compress 对备份的文件在传输时进行压缩处理
--exclude=PATTERN指定排除不需要传输的文件模式
--include=PATTERN指定不排除而需要传输的文件模式
--exclude-from=FILE排除F
ILE中指定模式的文件
--include-from=FILE不排除FILE指定模式匹配的文件
--version打印版本信息
--address绑定到特定的地址
--config=FILE 指定其他的配置文件,不使用默认的rsyncd.conf文件
--port=PORT指定其他的rsync服务端口
--blocking-io对远程shell使用阻塞IO
-stats给出某些文件的传输状态
--progress在传输时现实传输过程
--log-format=formAT指定日志文件格式
--password-file=FILE从FILE中得到密码
--bwlimit=KBPS 限制I/O带宽,KBytes per second -h, --help 显示帮助信息 参考技术A 源代码的备份?
为什么不上 cvs 、svn 、 git 这种源代码管理系统来管理?
他们是基于版本控制的。
可以方便的导出代码变动,某特定版本的代码,最新代码等等,很多功能的。
至于增量备份,tar 就可以,不过他是打包备份。
你也可以选择检测每个文件的修改日期,在某个日期之后的文件复制出来就可以了。
find 可以借助。cp 也可以用日期条件的。
看看这个吧:古老的系统,古老的文章,不过应该还管用。
http://www.chinaunix.net/jh/4/310686.html
服务器的管理,在 chinaunix 上面的资料还是很多很多的。 参考技术B rsync -avz
以上是关于如何实现在Linux下创建服务程序的主要内容,如果未能解决你的问题,请参考以下文章