Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1753820
  • 博文数量: 600
  • 博客积分: 10581
  • 博客等级: 上将
  • 技术积分: 6205
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-06 10:13
文章分类
文章存档

2016年(2)

2015年(9)

2014年(8)

2013年(5)

2012年(8)

2011年(36)

2010年(34)

2009年(451)

2008年(47)

分类: 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的信号。

如果指定的参数seconds0,则不再发送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);

 

 

第二节  信号程序

 

 

 

第五单元    IPC应用

 

第六单元    网络编程

第一节 网络基本介绍

 

第二节 网络编程

 

1.    调用socket用来建立一个通信的端点。

该调用的声明格式如下:

int socket(int domain, int type, int protocol);

 

在使用该调用的程序中加入头文件:

#include

#include

socket调用建立一个通信端点并返回一个socket描述符。

在调用中参数domain用来指定发生通信的域,是用来选择要使用的协议族的参数。

这些协议族在头文件中定义。

 

2.    socket调用中,SOCK_STREAM类型是全双工的字节流。

一个流类型的socket必须连接以后才能传递数据。与另一个socket的连接由connect调用产生的。连接后就可以使用readwrite调用或sendrecv调用来传输数据。

当一次会话关闭后,带外数据仍将被传送。

实现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将禁止接收数据。

如果参数how1,则禁止继续发送数据。

如果how2,则禁止在该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,常用端口号是从01023,已注册端口号是从102449151,动态或私有的端口号从4915265535

 

5.        getservent函数

我们已经看到一个/etc/services文件,该文件纪录了服务的名称与它的公认端口号等参数。例如,我们常见的服务协议telnet(23)ftp(21)SMTP(25)http(80)等都有描述。我们可以使用getserventbyname等调用获得名称与端口号之间映射的关系。

 

6.        一般在编程时,要注意存放地址、端口号的变量。

这些变量在使用时,一般是以网络字节顺序使用的,而一般的整型变量的存放顺序是按主机的字节顺序存放的,可以htonlhtonsntohlntohs等函数进行转换。有的系统主机字节顺序与网络字节顺序一样,在这样的系统中,这些函数调用是一些空的宏定义。

!       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); /* 设置非阻塞方式 */

 

 

阅读(558) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~