分类: LINUX
2009-08-22 13:08:28
1. 收到信号的进程对各种信号有不同的处理方法。
处理方法可以分为三类:
l 第一种是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。
l 第二种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。
l 第三种方法是,对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。
2. 信号名值默认处理动作发出信号的原因符合标准
SIGHUP 终止程序终端挂起
POSIXSIGINT 终止程序中断键(如break)被按下
ANSISIGQUIT 核心映像转储coredump键盘的退出键被按下
POSIXSIGILL core dump遇到非法指令
ANSISIGTRAP core dump跟踪捕捉
POSIXSIGIOT core dumpI/O捕捉指令,硬件故障4.2
BSDSIGABRT core dump核心标准子例程abort发出
ANSISIGEMT 7,-,7core dump仿真捕捉指令4.2
BSDSIGFPE core dump浮点异常
ANSISIGKILL 终止程序终止程序
POSIXSIGBUS 10,7,10core dump总线错误4.2
BSDSIGSEGV 11core dump段违例
ANSISIGSYS 12,-,12core dump调用不存在的系统调用
SVIDSIGPIPE 13终止程序写一个没有读端口的管道
3. 系统调用alarm的功能是设置一个定时器,当定时器计时到达时,将发出一个信号给进程。
该调用的声明格式如下:
unsigned int alarm(unsigned int seconds);
在使用该调用的进程中加入以下头文件:
#include
系统调用alarm安排核心为调用进程在指定的seconds秒后发出一个SIGALRM的信号。
如果指定的参数seconds为0,则不再发送SIGALRM信号。
后一次设定将取消前一次的设定。
该调用返回值为上次定时调用到发送之间剩余的时间,或者因为没有前一次定时调用而返回0。
注意,在使用时,alarm只设定为发送一次信号,如果要多次发送,就要多次使用alarm调用。对于alarm,这里不再举例。
现在的系统中很多程序不再使用alarm调用,而是使用setitimer调用来设置定时器,用getitimer来得到定时器的状态,
这两个调用的声明格式如下:
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
〓第二节 信号程序
〓第一节 网络基本介绍
〓第二节 网络编程
1. 调用socket用来建立一个通信的端点。
该调用的声明格式如下:
int socket(int domain, int type, int protocol);
在使用该调用的程序中加入头文件:
#include
#include
socket调用建立一个通信端点并返回一个socket描述符。
在调用中参数domain用来指定发生通信的域,是用来选择要使用的协议族的参数。
这些协议族在头文件
2. socket调用中,SOCK_STREAM类型是全双工的字节流。
一个流类型的socket必须连接以后才能传递数据。与另一个socket的连接由connect调用产生的。连接后就可以使用read和write调用或send和recv调用来传输数据。
当一次会话关闭后,带外数据仍将被传送。
实现SOCK_STREAM的通信协议可以保证传输的数据不丢失和不重复。
如果一个节点缓冲中的一块数据在一段时间内不能成功发送,那么就认为该连接已经断开,并且给传输数据的调用返回-1,设置错误代码ETIMEOUT。
3. 如果一个进程在一个断开连接的流上发送或接收数据,则产生一个SIGPIPE信号。
4. SOCK_SEQPACKET类型与SOCK_STREAM基本相同,唯一的区别是read调用只有在所需数据数量满足时才返回,其余到达的报文(packet)将被丢弃。
5. 该调用成功时返回socket描述符,否则返回-1
1. 该调用将关闭一个全双工连接的一端。
该调用声明的格式如下:int shutdown(int s, int how);
在使用这个系统调用的程序文件中加入以下头文件:
#include
shutdown调用将导致socket描述符s所指的全双工socket连接被部分或全部关闭。
如果参数how指定为0,则被该调用关闭的socket将禁止接收数据。
如果参数how为1,则禁止继续发送数据。
如果how为2,则禁止在该socket上接收或发送数据。
2. 目前的系统基本上对socket编程都提供原始方式的接口。在创建socket时选择类型
SOCK_RAW就能创建一个原始类型的socket,在该socket上,程序员可以自己写icmp头、tcp头等来发送原始报文
源端口号 (16位) |
目的端口号 (16位) |
序号 (32位) |
确认号 (32位) |
数据偏移 (4位) |
保留域 (6位) |
标志 (6位) |
窗口 (16位) |
校验和 (16位) |
紧急指针 (16位) |
选项 (可变长) |
填充位 |
其中:数据偏移用于标识数据段的开始;
保留段6位必须为0;
标志包括紧急标志(URG)、确认标志(ACK)、入栈标志(PUSH)、重置标志(RST)、同步标志(SYN)和结束标志(FIN);
窗口指定接收方愿意接受的字节数;
校验和计算方式为将头与数据的所有16位字的补码求和,再求该补码和的补码;
选项长度是可变的,填充区域随选项长度变化,用于确保长度为整字节的倍数。
3. 在程序中可以由gethostbyname函数得到一个指定的主机名的地址等信息。在该调用中,库函数首先调用一个解析器的库过程,如果该名字不是以“.”结束,该过程首先在本地的/etc/hosts中查找是否由该指定的名字,如果有该名字,则返回该名字在/etc/hosts中指定的IP地址。如果没有该名字或以“.”结束,则从/etc/resolv.conf(注:resolv.conf 是DNS中客户端)得到名字服务器的位置与默认的主机所位于的域,向名字服务器查询,如果服务器中有该映射则返回地址,否则名字服务器向它上级名字服务器查询。这里要注意一个主机名是否以“.”结束之间的区别。例如:在gethostbyname里使用参数abc,这时查询/etc/hosts中是否有关于abc主机名的说明,没有的话,假如这台主机处于ml.org域中,则要查询是否有abc.ml.org,如果没有则得到host unknown。但如果一个主机名以“.”结束的话,例如abc.edu.cn.则表示该主机处于edu.cn域,不会是abc.edu.cn.ml.org。这有点象绝对路径和相对路径的关系。这里介绍该过程是为了使读者有一个概念,一般编程中可直接使用gethostbyname来得到该映射关系。
4. 根据RFC 1700,常用端口号是从0到1023,已注册端口号是从1024到49151,动态或私有的端口号从49152到65535。
5. getservent函数
我们已经看到一个/etc/services文件,该文件纪录了服务的名称与它的公认端口号等参数。例如,我们常见的服务协议telnet(23)、ftp(21)、SMTP(25)、http(80)等都有描述。我们可以使用getserventbyname等调用获得名称与端口号之间映射的关系。
6. 一般在编程时,要注意存放地址、端口号的变量。
这些变量在使用时,一般是以网络字节顺序使用的,而一般的整型变量的存放顺序是按主机的字节顺序存放的,可以htonl、htons、ntohl、ntohs等函数进行转换。有的系统主机字节顺序与网络字节顺序一样,在这样的系统中,这些函数调用是一些空的宏定义。
! EXAMPLE:
struct hostent* host;
struct in_addr inadd;
unsigned long lna;
host = gethostbyname("172.26.5.198");
//此处的地址改为其他真实的主机名或IP地址
printf("%s %d %d\n",host->h_name,host->h_addrtype,host->h_length);
bcopy(host->h_addr,&inadd.s_addr, 4);
printf("%s \n", inet_ntoa(inadd));
lna = htonl(inet_lnaof(inadd));
7. TCP协议是一种保证传输可靠,保证顺序的传输层协议。在连接服务器之前,先要申请一个SOCK_STREAM类型的套接字,该套接字的通信域应指定为PF_INET类型,
可以使用下面的例子来获得该套接字:
int sockfd;
struct protoent *proth;
……..
proth = getprotobyname(“tcp”);
sockfd = socket(PF_INET, SOCK_STREAM, proth->p_proto);
………
这时,我们获得了一个指定通信域为internet、使用TCP协议的套接字。下面应该将该套接字与服务端连接,在连接前要知道服务端的地址(或主机名)和端口号(或服务名)。
struct sockaddr_in sadd;
struct hostent *hostin;
int portnum; //用来存放端口号,可以在程序中使用常量代替。
char hostname[80]; //程序得到主机名的方法由程序员来处理,这里只为叙述方便定义。
上面变量定义中的portnum只是为了下面叙述方便定义的,程序员可以指定为常量,也可通过命令行参数得到。字符串hostname的定义也是为了叙述方便,怎样得到该字符串由程序员来自己处理,该变量中存放的为主机名或IP地址。在前面例子的getprotobyname调用前加入以下代码:
bzero(&sadd, sizeof(sadd));
sadd.sin_family = AF_INET;
if (hostin = gethostbyname(hostname)) //这里是gethostbyname调用成功的情况。
bcopy(hostin->h_addr, (char *) &sadd.sin_addr, hostin->h_length);
else { //这里是host unknown的情况。}
sadd.sin_port = htons(portnum);然后在socket调用后加入connect连接调用:
if (connect(sockfd, (struct sockaddr *) &sadd, sizeof(sadd)) < 0) {
printf(“cannot connect\n”);
exit(1);
}
8. 在BSD中,可以使用下面的程序段来使得守护进程与控制终端脱离。
if (fork() != 0)
exit(0);
setgrp(0, getpid());
if ((fd = open(“dev/tty” O_RDWR)) >= 0) {
ioctl(fd, TIOCNTTY, (char *) 0);
close(fd);
}
如果守护进程要对文件进行访问,比如读写临时文件或配置文件,则要注意工作目录,最好可以使别人安装你的程序到其他位置,利于他人安装。当守护进程要创建文件时,要注意掩码,可以使用umask系统调用来防止守护进程继承父进程的umask,而使得在创建文件时,得到错误的访问权限。一般守护进程运行在后台(否则,如果守护进程由/etc/rc启动,而该进程不在后台运行,会导致/etc/rc的脚本无法执行下去,一直要等待该进程退出为止)。
9. 面向连接的服务器编程要相对简单一些,这主要是因为,传输层使用TCP协议保证了传输的可靠性、顺序性。使得服务端程序不需要再考虑这些。服务端进程可以接受一个连接请求,并通过该连接进行通信。当建立连接后,TCP协议保证通信的可靠性,其中包括丢失数据和报文校验错误时的重新传递机制,以及报文到达乱序时的重新排序。TCP协议还可以告知客户端连接断开。这些都是使用面向连接的TCP协议带来的好处。
使用面向连接的服务端也有它的不足,服务端每接受一个连接请求,将会产生一个新的套接字,而在无连接的UDP中,一个套接字可以与多个主机通信。这样就带来了问题,因为每个套接字都要占用系统资源,这就又可能耗尽系统资源。比如,当客户端程序请求并建立连接后因为错误或其他原因停止运行,而TCP又不向空闲的连接发送数据,这时这些资源就被浪费了而不能回收(这些情况在现在的系统中有所改善),当系统长时间运行,而客户端经常发生这样的问题,就又可能耗尽系统的资源。由于TCP协议要求连接,并且是一个点点通信的模式,所以不能实现组播和广播的应用。
使用UDP作为传输层协议来实现比较复杂的应用时(比如有多种状态,在设计应用层协议时,使用了状态机的情况),用UDP来实现就过于复杂。因为,UDP协议不保证传输的可靠性和顺序。这样在服务端程序设计中,就要考虑报文的顺序、错误校验等问题。一般情况下,报文丢失由客户端程序负责重新传输。这样,客户端又要考虑多长时间认为是报文丢失。这给应用又带来了新的问题,因为在局域网中传输时间短,而广域网中传输时间长,为判断报文丢失带来了一定的复杂度。
不管使用TCP还是UDP协议,在程序结构上都可以使用两种基本模式:
循环模式和并发模式。
所谓循环模式,就是服务端进程在总体结构上是一个循环,一次处理一个请求。
10. 阻塞与非阻塞
flags = fcntl(sock, F_GETFL);
fcntl(sock, F_SETFL, ~O_NDELAY & flags); /**设为阻塞方式**/
flags = fcntl(sock[i], F_GETFL);
fcntl(sock[i], F_SETFL, flags | O_NDELAY); /* 设置非阻塞方式 */