MSG_DONTWAIT为什么打印出现未定义书签啊 我如何设置我的REcv 非阻塞!为什么百度一下这么多牛头不对马嘴的回答?

UNIX网络编程读书笔记:基本SCTP套接口编程
参数的大小。
sctp_peeloff函数
int sctp_peeloff(int sockfd, sctp_assoc_t id); 返回:新的套接口描述字——成功,-1——出错
其语义很像带有一个额外参数的accept函数。调用者把一到多式套接口的sockfd和待抽取的关联标识id传递给函数调用。调用结束时将返回一个新的套接口描述字,它是一个与所请求关联对应的一到一式套接口描述字。
SCTP为应用程序提供了多种可用的通知。SCTP用户可以经由这些通知追踪相关关联的状态。通知传递的是传输级的事件,包括网络状态变动、关联启动、远地运作错误以及消息不可递送。不论是一到一式接口还是一到多式接口,缺省情况下除sctp_data_io_event以外的所有事件都是被禁止的。
使用SCTP_EVENTS套接口选项可以预订8个事件。其中7个事件产生称为通知(notification)的额外数据,通知本身可经由普通的套接口描述字获取。当产生它们的事件发生时,这些通知内嵌在数据中加入到套接口描述字。在预订相应通知的前提下读取某个套接口时,用户数据和通知将在套接口缓冲区中交错出现。为了区分来自对端的数据和由事件产生的通知,用户应该使用recvmsg函数或sctp_recvmsg函数。如果返回的数据是一个事件通知,那么这两个函数返回的msg_flags参数将含有MSG_NOTIFICATION标志。这个标志告诉应用进程刚刚读入的消息不是来自对端的数据,而是来自本地SCTP栈的一个通知。
09:56&&&[]
基本TCP套接字编程 主要介绍一个完整的TCP客户/服务器程序需要的基本套接字函数。 1.概述 在整个TCP客户/服务程序中,用到的函数就那么几个,其整体框图如下:
2.socket函数 为了执行网络I/O,一个进程必须要做的事情就是调用socket函数。其函数声明如下: #include &sys/socket.h& int socket(int family ,int type, int protocol); 其中: family:指定协议族
type:指定套接字类型
protocol:指定某个协议,设为0,以选择所给定family和type组合的系统默认值。 这些参数有一些特定的常值定义如下:
faimly 说明
AF_INET IPv4协议
AF_INET6 IPv6协议
AF_LOCAL Unix域协议
AF_ROUTE 路由套接字
AF_KEY 密钥套接字
表1 socket函数的family常值
SOCK_STREAM 字节流套接字
SOCK_DGRAM 数据报套接字
SOCK_SEQPACKET 有序分组套接字
SOCK_RAW 原始套接字
表2 socket函数的type常值
protocol 说明
IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议
表3 socket函数AF_INET或AF_INET6的protocol常值
socket函数调用成功的时候将返回一个小的非负整数值,成为套接字描述符,简称sockfd。为了得到这个描述符,我们制定了协议族和套接字类型,并未指定本地与远程协议地址。 另外,书中还提到一个AF_XXX(表示地址族)和PF_XXX(表示协议族)的区别,一般情况下都使用AF,知道这个就可以了。 3.connect函数 函数声明如下: #include &sys/socket.h& int connect(int sockfd, const struct sockaddr *servaddr,socklen_t addrlen); sockfd:由socket返回的套接字描述符。
servaddr:套接字地址结构,包含了服务器IP和端口号。
addrlen:套接字地址结构大小,防止读越界。 客户端调用connect时,将向服务器主动发起三路握手连接,直到连接建立和连接出错时才会返回,这里出错返回的可能有一下几种情况: 1)TCP客户没有收到SYN分节的响应。(内核发一个SYN若无响应则等待6s再发一个,若仍无响应则等待24s后再发送一个。总共等待75s仍未收到则返回错误ETIMEDOUT)
2)若对客户的SYN的响应是RST,表明服务器主机在我们指定的端口上没有进程在等待与之连接,客户端收到RST就会返回ECONNREFUSED错误。
产生RST的三个条件是:目的地SYN到达却没有监听的服务器;TCP想取消一个已有连接;TCP接收到一个根本不存在连接上的分节。
3)若客户发出的SYN在中间的某个路由器上引发了一个“destination unreachable”(目的地不可达)ICMP错误,则认为是一种软错误,在某个规定时间(比如上述75s)没有收到回应,内核则会把保存的信息作为EHOSTUNREACH或ENETUNREACH错误返回给进程。 若connect失败则该套接字不再可用,必须关闭,我们不能对这样的套接字再次调用connect函数,当循环调用函数connect为给定主机尝试各个ip地址直到有一个成功时,在每次connect失败后,都必须close当前的套接字描述符并从新调用socket。 4.bind函数 bind函数把一个本地协议地址赋予了一个套接字。协议地址时32位IPv4地址或128位的IPv6地址与16位的TCP/UDP端口号的组合。 在调用bind函数可以制定一个特定的端口号,或者制定一个IP地址,或者两个都指定,后者两者都不指定。
函数声明如下: #include &sys/socket.h& int bind(int sockfd, const struct sockaddr * myaddr,socklen_t addrlen); 参数说明: sockfd:套接字描述符
myaddr:套接字地址结构的指针
addrlen:上述结构的长度,防止内核越界 服务器在启动时捆绑它们的众所周知的端口,例如时间获取服务的端口13。如果不调用bind函数,当调用connect或listen的时候,TCP会创建一个临时的端口,这对于客户端来说很常见(毕竟我们从来没见过客户端程序调用过bind函数),而对于TCP服务器来说就比较少见了,因为TCP服务器就是通过其众所周知的端口被大家认识。 进程可以把一个特定的IP地址绑定到它的套接字上:对于客户端来说,这没有必要,因为内核将根据所外出网络接口来选择源IP地址。对于服务器来说,这将限定服务器只接收目的地为该IP地址的客户连接。 对于IPv4来说,通配地址由常值INADDR_ANY来指定,其值一般为0,它告知内核去选择IP地址,因此我们经常看到如下语句: struct sockaddr_ servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 同理,端口号指定为0时,内核就在bind被调用的时候选择一个临时端口,不过bind函数不返回内核选择的值,第二个参数有const限定。如果想要直到内核所选择的临时端口值,必须调用getsockname来返回协议地址。 最后需要注意的是:bind绑定保留端口号时需要超级用户权限。这就是为什么我们在linux下执行服务器程序的时候要加sudo,如果没有超级用户权限,绑定将会失败。 5.listen函数 listen函数由TCP服务器调用,其函数声明如下: #include &sys/socket.h& int listen (int sockdfd , int backlog); listen函数主要有两个作用: 1.对于参数sockfd来说:当socket函数创建一个套接字时,它被假设为一个主动套接字。listen函数把该套接字转换成一个被动套接字,指示内核应接受指向该套接字的连接请求。
2.对于参数backlog:规定了内核应该为相应套接字排队的最大连接数=未完成连接队列+已完成连接队列
未完成连接队列:表示服务器接收到客户端的SYN但还未建立三路握手连接的套接字(SYN_RCVD状态)
已完成连接队列:表示已完成三路握手连接过程的套接字(ESTABLISHED状态) 结合三路握手的过程:
1.客户调用connect发送SYN分节
2.服务器收到SYN分节在未完成队列建立条目
3.直到三鹿握手的第三个分节(客户对服务器SYN的ACK)到达,此时该项目从未完成队列移动到已完成队列的队尾。
4.当进程调用accept时,已完成队列出队,当已完成队列为空时,accept函数阻塞,进程睡眠,直到已完成队列入队。 所以说,如果三路握手正常完成,未完成连接队列中的任何一项在其中存留的时间就是服务器在收到客户端的SYN和收到客户端的ACK这段时间(RTT)。
如图所示:
对于一个WEB服务器来说,RTT是187ms。 关于这两个队列还有需要注意的地方:当客户端发送SYN分节到达服务器时,如果此时服务器的未完成连接队列是满的,服务器将忽略这个SYN分节,服务器不会立即给客户端回应一个RST,因为客户端有自己的重传机制,如果服务器发送RST,那么客户度端的connect就会返回错误了。另外客户端无法区别RST究竟意味着“该端口没有服务器在监听”还是意味着“该端口有服务器在监听不过它的队列满了。” 6.accept函数 TCP服务器调用accept函数,函数声明如下: #include&sys/socket.h& int accept (int sockfd, struct sockaddr *cliaddr ,socklen_t * addrlen); 参数说明: sockfd:套接字描述符
cliaddr:对端(客户)的协议地址
addr:大小 当accept调用成功,将返回一个新的套接字描述符,例如: int connfd = Accept(listenfd,(SA*)NULL,NULL); 其中我们称listenfd为监听套接字描述符,称connfd为已连接套接字描述符。,区分这两个套接字十分重要,一个服务器进程通常只需要一个监听套接字,但是却能够有很多已连接套接字(比如通过fork创建子进程),也就是说每有一个客户端建立连接时就会创建一个connectfd,当连接结束时,相应的已连接套接字就会被关闭。 通过指针我们可以得到客户端的套接字信息,但是如果我们对这些不感兴趣就可以另他们为NULL,书中给出一个示例,服务器相应连接后,打印客户端的IP地址和端口号。
部分代码如下: #include &unp.h& #include &time.h& int main(int argc, char **argv) { //... for ( ; ; ) { len = sizeof(cliaddr); connfd = Accept(listenfd, (SA *) &cliaddr, &len);//已连接套接字 //cliaddr获取客户端协议地址信息。 printf(&connection from %s, port %d\n&, Inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)), ntohs(cliaddr.sin_port)); //... Close(connfd); } } 7.fork和exec函数 用fork创建子进程的方法并不陌生,这里将用fork编写并发服务器程序。 #include &unisted.h& pid_t fork(void); 在子进程中返回0,在父进程返回返回子进程ID,因为有这个父子的概念,所以fork函数调用一次却要返回两次。 关于fork函数的一些特性: 1.任何子进程只能有一个父进程,并且子进程可以通过getppid获取父进程ID
2.父进程中调用fork之前打开的描述符,在fork之后与子进程分享,在网络通信中也正是应用到这个特性。 说到上述第2个特性,我们知道服务器进程往往在死循环中等待客户端连接,利用特性2,当accept函数调用返回一个connfd时,子进程可以利用其进行读写,而父进程直接关闭即可。 简而言之:父进程创建描述符,子进程对其实际操作。 exec函数实际上是6个函数,他们的区别主要在于:
(a)待执行的程序文件是由文件名还是路径名指定。
(b)新程序的参数是一一列出来还是由一个指针数组来引用
(c)把调用进程的环境传递给新程序还是给新程序指定新的环境。
关于EXEC的详情可参考: linux下c语言编程exec函数使用 8.并发服务器 首先一个概念叫做“迭代服务器”,例如: for(;;) { connfd = Accept(listenfd,(SA*)NULL,NULL); ticks=time(NULL); snprintf(buff,sizeof(buff),&%.24s\r\n&,ctime(&ticks)); Write(connfd,buff,strlen(buff)); Close(connfd); } 当一个客户端连接过来时,服务器向客户端写入时间信息后,关闭已连接套接字,回到for循环顶部阻塞等待连接的到来,这样每次连接到来的时候,必须完成该次服务,因为它占用了服务器进程。但是由于简单的获取时间服务本身就很快,单次服务马上就完成了,所以也就影响不大,不过如果是十分耗时的服务就不一定了,我们并不希望服务器被单个客户长时间占用,而是希望服务器同时服务多个用户,于是在Unix中编写并发服务器程序最简单的办法就是fork一个子进程来服务每个客户。 pid_ listenfd=Socket(/*...*/); Bind(listenfd,/*...*/); Listen(listenfd,/*...*/); for(;;) { connfd = Accept(listenfd,(SA*)NULL,NULL); if((pid=fork())==0)//子进程 { close(listenfd);//关闭监听套接字 doit(connfd);//服务 close(connfd); exit(0); } close(connfd); } 这里我一直不理解的是,为什么在子进程里面要关闭监听套接字(listenfd)呢?
这就跟fork的相关知识有关: 1.首先fork并不是把父进程从头到尾执行一遍,否则这样不就无穷尽了。
2.父进程在调用fork处,整个父进程空间会原模原样地复制到子进程中,包括指令,变量值,程序调用栈,环境变量,缓冲区,等等。
3.在并发服务器的示例中,子进程会将已连接的套接字(connfd)和监听套接字(listenfd)拷贝到自己的进程空间。
4.对于套接字描述符来说,他们都有一个引用计数,fork之后由于描述符被复制,其引用计数都变成了2。
5.因此,我们在父进程中关闭connfd(因为我们在子进程中使用connfd),在子进程中关闭listenfd(因为我们在父进程中进行监听),此时他们的引用计数都变成了1。
6.然后,我们所期望的状态就是父进程中保留一个监听套接字继续等待客户端连接,在子进程中通过已连接的套接字对客户端请求进行服务。
7.最后在子进程中关闭connfd,或exit(0),使得connfd真正完成清理和资源释放。 9.close函数 close函数可以用来关闭套接字并终止TCP连接。 #include &unistd.h& int close(int sockfd); 从上节的并发服务器可以看到,close函数是对套接字描述符的引用计数减1,也就是说,如果调用close后,引用计数不为0,将不会引起TCP的四分组连接终止序列,这正是父进程与子进程共享已连接套接字的并发服务器所期望的。不过如果我们确实想在TCP连接上发送一个FIN,那么调用shutdown函数。 10.getsockname和getpeername函数 #include &sys/socket.h& int getsockname(int sockfd,struct sockaddr*localaddr,socklen_t *addrlen); int getpeername(int sockfd,struct sockaddr*peeraddr,socklen_t *addrlen); //若成功则为0,失败则为-1 这两个函数的作用:
1.首先我们知道在TCP客户端一般不使用bind函数,当connect返回后,getsockname可以返回客户端本地IP地址和本地端口号。
2.如果bind绑定了端口号0(内核选择),由于bind的参数是const型的,因此必须通过getsockname去得到内核赋予的本地端口号。
3.获取某个套接字的地址族
4.以通配IP地址bind的服务器上,accept成功返回之后,getsockname可以用于返回内核赋予该连接的本地IP地址。其中套接字描述符参数必须是已连接的套接字描述符。 11.总结 本章介绍了套接字编程的函数,客户端和服务器端都从socket开始,客户端随后调用connect,而服务器端先后调用bind,listen和accept,最后使用close来关闭描述符。
12:08&&&[]
,一次是子进程写入管道时,最后一次是在父进程从该管道中读出时。为避免这样的额外处理,父进程可以创建一个在它自身和子进程之间分享的共享内存区,然后把管道用作父子进程间的一个标志。 与网络编程相比,System V消息队列另一个缺失的特性是无法窥探一个消息,而这是recv,recvfrom,recvmsg
21:02&&&[]
意网络接口上接受客户连接8. 调用listen函数把该套接字转换成一个监听套接字,这样来自客户的外来连接就可以再该套接字上由内核接受
9. 当前时间和日期 time_ ticks = time(NULL); snprintf(buff, sizeof(buff), &quot
14:21&&&[]
《Unix编程艺术》读书笔记(1) 这两天开始阅读该书,下面是自己的体会,以及原文的摘录,虽然有些东西还无法完全吃透。 写优雅的代码来提高软件系统的透明性:(P134) Elegance is a combination of power and simplicity. Elegant code
16:56&&&[]
16:40&&&[]
process control
1 process identifiers
每一个进程都有一个唯一的非负整型做为标识符。
#include &unistd.h&
pid_t getpid();
pid_t getppid();
pit_t getuid();
pit_t geteuid();
pit_t getgid();
pit_t getegid();
getpid: 返回进程ID
getppid: 返回父进程ID
getuid: 获得进程的real user ID
geteuid: 获得进程的effective user ID
关于real user ID和effective user ID:
Advanced Programming in the UNIX Environment (2) 第三节,链接:
http://blog.csdn.net/alex_my/article/details/
getgid: 获取进程的real group ID
getegid: 获取进程的effective group ID
它们的用法在本文的后续章节中出现。
2 fork function
#include &unistd.h&
pid_t fork(void);
创建一个新的子进程,这个函数一次调用,两次返回,在原进程(即未来的父进程)中,返回的值为子进程ID,在子进程中,返回的值为0。子进程只能拥有一个副进程,可以通过getppid获取到父进程的ID。
理论上来说,子进程会复制一份父进程的数据空间,堆,栈。但是,往往在程序中,执行fork之后,常常在子进程中调用exec,该子进程完全由新的程序替换,覆盖了原先子进程复制的这部分数据空间,堆,栈,使得之前的复制白费力。因此,在大部分的实现中,子进程并不立马拷贝一份,而是使用一种&copy-on-write&,当哪个进程要改变某一小部分内容的时候,就将小部分内存拷贝一份,供其使用。
至于子进程具体继承了什么,不继承什么,可以查询man 2 fork。
继承部分:
real user ID, effective user ID, real group ID, effective group ID
supplementary group IDs
process group ID
session ID
controlling terminal
ser-user-ID and set-group-ID flags
current working directory
root directory
file mode creation mask
signal mask and dispositions
the close-on-exec flag for any open file descriptors
environment
attached shared memory segments
memory mappings
resource limit
不继承部分:
child's tms_utime, tms_stime, tms_cutime, and tms_cstime are set to zero
file locks
pending alarms are cleared for the child
the set of pending signals for the child is set to the empty set
程序用例:
#include &stdio.h&
#include &stdlib.h&
#include &unistd.h&
#include &errno.h&
#include &string.h&
int main(int argc, char* argv[])
& & &int n = 1;
& & &printf(&process id: %d \n&, getpid());
& & &printf(&test stdio buffer &);
& & &pid_t pid = fork();
& & &if(pid & 0)
& & & & & printf(&fork failed, error[ %d ] \t [ %s ]\n&, errno, strerror(errno));
& & & & & exit(EXIT_FAILURE);
& & &if(pid == 0)
& & & & & printf(&children, pid: %d \t ppid: %d \n&, getpid(), getppid());
& & & & & ++n;
& & & & & printf(&parent, pid: %d \t ppid: %d \n&, getpid(), getppid());
& & &printf(&n: %d \n&, n);
& & &return 0;
process id: 7328&
test stdio buffer parent, pid: 7328 ppid: 4766&
test stdio buffer children, pid: 7329 ppid: 7328&
在fork()前调用的语句printf(&test stdio buffer &)被执行了两次。这是因为,当标准输出是终端设备的时候,是行缓冲的,否则,是全缓冲。
当把&test stdio buffer&&填入输出缓冲区的时候,并没有flush到终端,接下来子进程复制了这部分,因此会输出两次。
当句子改为:printf(&test stdio buffer \n&)的时候,仅输出一次,因为换行引起flush。
3 vfork function
vfork产生一次新进程,与fork不同的是:
-1:父子进程共享数据空间,在程序用例中有体现
-2: 子进程先运行,直到子进程调用exec或者退出后,父进程才会运行,如果子进程在运行过程中,需要等待父进程接下来运行的某个状态或者数值,则会死锁。
修改下fork的程序用例:
#include &stdio.h&
#include &stdlib.h&
#include &unistd.h&
#include &errno.h&
#include &string.h&
int main(int argc, char* argv[])
& & &int n = 1;
& & &printf(&process id: %d \n&, getpid());
& & &pid_t pid = vfork();
& & &if(pid & 0)
& & & & & printf(&fork failed, error[ %d ] \t [ %s ]\n&, errno, strerror(errno));
& & & & & exit(EXIT_FAILURE);
& & &if(pid == 0)
& & & & & printf(&children, pid: %d \t ppid: %d \n&, getpid(), getppid());
& & & & & ++n;
& & & & &&printf(&n: %d \n&, n);
& & & & & _exit(0);
& & & & & printf(&parent, pid: %d \t ppid: %d \n&, getpid(), getppid());
& & & & &&printf(&n: %d \n&, n);
& & &exit(EXIT_SUCCESS);
process id: 7893&
children, pid: 7894 ppid: 7893&
parent, pid: 7893 ppid: 6718&
由于父子进程共享数据空间,因此,父进程中的n也为2(其实是同一个n)
需要注意的是,子进程中终止进程采用的是_exit(0)。由前面的章节得知,_exit并不会flush标准流。
如果子进程中,调用exit(0),而系统实现exit中会关闭标准I/O流,由于二者共享空间,那么父进程调用printf的时候,就不会产生输出。
不过,现在大多是的实现中,不会在exit中关闭标准I/O流,因为进程即将关闭,内核将关闭所有打开的文件描述符,因此,不必在exit中多此一举。所以,如果子进程中使用exit终止进程,编译运行,也会正常。
exit相关已经在BOOKS: Advanced Programming in the UNIX Environment (6)第一节有所描述。
进程退出,释放所占用的资源,包括打开的文件描述符,申请的内存等,但是仍然保留了一定的信息,包括进程号,终止状态,运行时间等。直到父进程调用wait/waitpid来获取才会释放。
如果一个进程,大量产生子进程,等待子进程结束之后,缺不主动调用wait/waitpid去释放子进程的保留信息,就会造成不良后果,比如进程号被占用(系统所能使用的进程号是有限的),积累到一定程度后,会造成进程由于进程号限制而创建失败。
有一些退出的情况:
-1: 当父进程在子进程之前退出,即子进程失去了父亲,成为了孤儿进程,这时候,init进程就会收养这些孤儿进程,并且循环调用wait来释放已经推出的子进程。
-2: 当子进程先于父进程退出,释放一定的资源后,还会留下一些信息,此时的子进程称为僵尸进程。这些信息由父进程调用wait/waitpid后才会释放。子进程在退出的时候,会发送SIGCHLD信号给父进程,父进程接到信号后,在信号处理函数中调用wait/waitpid处理这些僵尸进程。默认情况下,父进程是忽略这些信号的。
程序用例在下一节中。
5 wait and waitpid functions
无论一个进程是正常结束还是异常终止,内核都会发送SIGCHLD信号给其父进程。子进程的终止是一个异步事件,可能发生在父进程运行的任何时候。内核通过信号异步通知父进程,父进程接到该信号后,可以选择忽略和处理。默认情况下是忽略该信号。父进程在信号处理函数中,可以调用以下两个函数之一来释放子进程退出后还保留的信息。
#include &sys/wait.h&
pid_t wait(int* stat_loc);
pid_t waitpid(pid_t pid, int* stat_loc, int options);
wait: 阻塞当前进程,直到有信号来或者子进程结束,如果在调用时子进程已经结束,则wait()会立即返回子进程的结束状态值。子进程的状态值由stat_loc返回。返回值为已结束的子进程ID。
waitpid: 阻塞当前进程,直到有信号来或者子进程结束,子进程状态值由stat_loc返回。
参数pid可选值:
& -1: 等待进程组识别码为pid绝对值的任何子进程
= -1: 等待任何子进程,相当于wait()
= 0 : 等待进程组识别码与当前进程相同的任何子进程
& 0 : 等待指定进程ID的子进程
参数options可选值:
0 & & & & & :&
WCONTINUED &: 如果指定pid从作业控制暂停中继续,则返回。
WNOHANG & & :&如果指定Pid的进程没有结束,也不阻塞,立即返回。
WUNTRACED & : 如果子进程进入暂停状态,则马上返回,不理会其结束状态。
对于返回的状态值,可以调用以下宏进行进一步判断(真,非零值):
WIFEXITED & : 如果为正常结束的子进程返回的状态,则为真
WEXITSTATUS : 对于正常结束的子进程,可以取得子进程退出状态的低八位
WIFSIGNALED : 如果是信号结束的子进程状态,则为真
WTERMSIG & &: 对于信号结束的子进程,可以取得使子进程结束的信号编码
WIFSTOPPED &: 如果当前子进程是暂停而返回的,则为真
WSTOPSIG & &: 对于暂停而返回的子进程,可以取得使子进程暂停的信号编码
WIFCONTINUED: 如果当前子进程是从一个作业控制暂停中继续的,则为真
程序用例1:父进程先退出
#include &stdio.h&
#include &stdlib.h&
#include &unistd.h&
#include &errno.h&
#include &string.h& // strerror
int main(int argc, char* argv[])
pid_t pid = fork();
if(pid & 0)
printf(&fork failed. error[%d] \t %s \n&, errno, strerror(errno));
exit(EXIT_FAILURE);
if(pid == 0)
printf(&child process, pid: %d \t ppid: %d\n&, getpid(), getppid());
printf(&child process, pid: %d \t ppid: %d\n&, getpid(), getppid());
exit(EXIT_SUCCESS);
printf(&father process\n&);
printf(&father exit\n&);
exit(EXIT_SUCCESS);
father process
father exit
child process, pid: 10471& &ppid: 10470
child process, pid: 10471& &ppid: 1
父进程退出后,子进程被托管给了init(process ID为1)
程序用例2:子进程先退出,父进程不处理
#include &stdio.h&
#include &stdlib.h&
#include &unistd.h&
#include &errno.h&
#include &string.h& // strerror
int main(int argc, char* argv[])
pid_t pid = fork();
if(pid & 0)
printf(&fork failed. error[%d] \t %s \n&, errno, strerror(errno));
exit(EXIT_FAILURE);
if(pid == 0)
printf(&child process, pid: %d \t ppid: %d\n&, getpid(), getppid());
exit(EXIT_SUCCESS);
printf(&father process, pid: %d\n&, getpid());
sleep(10); // 方便查看僵尸进程
printf(&father exit\n&);
exit(EXIT_SUCCESS);
程序放后台运行,比如编译出程序为 test8.3, 则执行:./test8.3 &
当运行时,可以输入ps -l
father process, pid: 10727
child process, pid: 10731& &ppid: 10727
[alex_my@localhost Apue]$ ps -l
F S & UID & PID &PPID &C PRI &NI ADDR SZ WCHAN &TTY & & & & &TIME CMD
0 S & & & 0 - 29164 wait & pts/1 & &00:00:00 bash
0 S & 1 & 0 - &3122 hrtime pts/1 & &00:00:00 test8.3
1 Z & 1 & 0 - & & 0 exit & pts/1 & &00:00:00 test8.3 &defunct&
0 R & 1 & 0 - 30315 - & & &pts/1 & &00:00:00 ps
father exit
可以发现,子程序退出后,成为了僵尸进程,注意Z符号。
程序用例3:子进程先退出,父进程通过信号处理
clude &stdlib.h&
#include &unistd.h&
#include &errno.h&
#include &string.h& // strerror
#include &signal.h&
#include &sys/wait.h&
// 信号处理函数
static void sig_process(int signo)
pid_t pid = wait(&status);
printf(&child[%d] terminated\n&, pid);
int main(int argc, char* argv[])
signal(SIGCHLD, sig_process);
pid_t pid = fork();
if(pid & 0)
printf(&fork failed. error[%d] \t %s \n&, errno, strerror(errno));
exit(EXIT_FAILURE);
if(pid == 0)
printf(&child process, pid: %d \t ppid: %d\n&, getpid(), getppid());
exit(EXIT_SUCCESS);
printf(&father process, pid: %d\n&, getpid());
sleep(10); // 等待子进程退出及ps -l
printf(&father exit\n&);
exit(EXIT_SUCCESS);
father process, pid: 11013
child process, pid: 11017& &ppid: 11013
child[11017] terminated
father exit
[1]+ &Done & & & & & & & & & &./test8.3
[alex_my@localhost Apue]$ ps -l
F S & UID & PID &PPID &C PRI &NI ADDR SZ WCHAN &TTY & & & & &TIME CMD
0 S & & & 0 - 29164 wait & pts/1 & &00:00:00 bash
0 R & 1 & 0 - 30315 - & & &pts/1 & &00:00:00 ps
可以看见,没有发现Z开头的僵尸进程了。当然,如果你够快,在子进程结束到父进程处理之前输入ps -l 还是可以发现的。
比如在把父进程退出时间改为20秒,在信号处理函数里边添加一句 sleep(10), 则在子进程退出后10秒内输入ps -l,还是能看见僵尸进程的。
6 waitid function
#include &sys/wait.h&
int waitid(idtype_t idtype, id_t id, siginfo_t* infop, int options);
如同waitpid, 但id意义依赖于idtype。
idtype可选值:
P_PID : id为指定子进程ID
P_PGID: 指定进程组ID,该组下的子进程均符合条件
P_ALL : 任意子进程, id无效
options与waitpid中的参数options相同。
infop结构包含:
si_pid & : process ID
si_uid & : real user ID or 0
si_signo : SIGCHLD
si_status: exit status or signal
si_code &: CLD_EXITED(_exit), CLD_KILLED(killed by signal), CLD_DUMPED(killed by signal, and dump core),
CLD_STOPPED(stopped by signal), CLD_TRAPPED(traced child has trapped), CLD_CONTINUED(child continued by SIGCONT)
程序用例:
将前边代码中的信号处理函数修改如下:
static void sig_process(int signo)
sleep(10);
pid_t pid = waitid(P_ALL, 0, &info, 0);
printf(&child process, pid: %d \t ppid: %d\n&, getpid(), getppid());
printf(&child[%d] terminated\n&, pid);
printf(&info: si_pid: %d \t si_uid: %d\n&, info.si_pid, info.si_uid);
printf(&info: si_code: %d\n&, info.si_code);
然后运行,可以看到结果。
18:15&&&[]
近来读书,做些笔记,来年好翻翻。
本文所使用的操作系统为 CentOS7.0,如果不想 装双系统的可以装 虚拟机,可以参考这里:
http://blog.csdn.net/alex_my/article/details/
date and time
涉及到的函数列出如下,然后再举例运行,输出结果,比较直观。
时间这块资料有限,如果有误,还望指正。
#include &time.h&
time_t time(time_t*tloc);
int clock_getres(clockid_t clk_id, struct timespec* res);
int clock_gettime(clockid_t clk_id, struct timespec* tp);
int clock_settime(clockid_t clk_id, const struct timespec* tp);
////////////////////////////
#include &sys/time.h&
int gettimeofday(struct timeval* restrict tp, void* restrict tzp);
////////////////////////////
#include &time.h&
char* asctime(const struct tm* tm);
char* asctime_r(const struct tm* tm, char* buf);
char* ctime(const time_t* timep);
char* ctime_r(const time_t* timep, char* buf);
struct tm* gmtime(const time_t* timep);
struct tm* gmtime_r(const time_t* timep, struct tm* result);
struct tm* localtime(const time_t* timep);
struct tm* localtime_r(const time_t* timep, struct tm* result);
time_t mktime(struct tm* tm);
////////////////////////////
#include &time.h&
size_t strftime(char* s, size_t max, const char* format,&const struct tm* tm);
///////////////////////////
#define _XOPEN_SOURCE & & & /* See feature_test_macros(7) */
#include &time.h&
char* strptime(const char* s, const char* format, struct tm* tm);
#include &time.h&
time_t time(time_t* tloc);
Epoch: 指的是一个特定的时间, 日 00:00:00 UTC
time函数返回从Epoch到当前的秒数,如果参数tloc不为null,还会赋值到tloc中。
如果出错,则会返回-1。
#include &stdio.h&
#include &time.h&
int main(int argc, char* argv[])
& & &time_t t = time(NULL);
& & &printf(&time: %d\n&, static_cast&int&(t));
& & &return 0;
#include &time.h&
int clock_getres(clockid_t clk_id, struct timespec* res);
int clock_gettime(clockid_t clk_id, struct timespec* tp);
int clock_settime(clockid_t clk_id, const struct timespec* tp);
如果正常则返回0,错误返回-1。结果均填充在res(tp)中。
timespec的定义:
struct timespec&
&time_t & tv_ & & & &/* seconds */
& & &long & & tv_ & & & /* nanoseconds */
clk_id用于指定计时时钟的类型,有以下选项:
CLOCK_REALTIME: 系统实时时间,从Epoch计时,可以被用户更改以及adjtime和NTP影响。
CLOCK_REALTIME_COARSE: 系统实时时间,比起CLOCK_REALTIME有更快的获取速度,更低一些的精确度。
CLOCK_MONOTONIC: 从系统启动这一刻开始计时,即使系统时间被用户改变,也不受影响。系统休眠时不会计时。受adjtime和NTP影响。
CLOCK_MONOTONIC_COARSE: 如同CLOCK_MONOTONIC,但有更快的获取速度和更低一些的精确度。受NTP影响。
CLOCK_MONOTONIC_RAW: 与CLOCK_MONOTONIC一样,系统开启时计时,但不受NTP影响,受adjtime影响。
CLOCK_BOOTTIME: 从系统启动这一刻开始计时,包括休眠时间,受到settimeofday的影响。
CLOCK_PROCESS_CPUTIME_ID: 本进程开始到此刻调用的时间。
CLOCK_THREAD_CPUTIME_ID: 本线程开始到刺客调用的时间。
clock_getres获取指定类型时钟的精确度。
clock_gettime和clock_settime取得和设置clk_id指定的时间。
看过用例就明白了。
程序用例:clock_getres和clock_gettime
#include &stdio.h&
#include &time.h&
static void ShowTime(const char* desc, const timespec* res)
& & &printf(&%s: \t %d\t %d\n&, desc, res-&tv_sec, res-&tv_nsec);
static void TestTime(clockid_t clk_id)
& & &clock_getres(clk_id, &res);
& & &ShowTime(&clock_getres&, &res);
& & &clock_gettime(clk_id, &res);
& & &ShowTime(&clock_gettime&, &res);
int main(int argc, char* argv[])
& & &// 为了方便阅读,假设函数调用成功
& & &printf(&CLOCK_REALTIME: \n&);
& & &TestTime(CLOCK_REALTIME);
& & &printf(&\n\nCLOCK_REALTIME_COARSE:\n&);
& & &TestTime(CLOCK_REALTIME_COARSE);
& & &printf(&\n\nCLOCK_MONOTONIC:\n&);
& & &TestTime(CLOCK_MONOTONIC);
& & &return 0;
CLOCK_REALTIME:&
clock_getres: 0 1
clock_gettime:
CLOCK_REALTIME_COARSE:
clock_getres: 0 1000000
clock_gettime:
CLOCK_MONOTONIC:
clock_getres: 0 1
clock_gettime: 26
#include &sys/time.h&
int gettimeofday(struct timeval* restrict tp, void* restrict tzp);
一直返回0 & & & &&
参数2 tzp必须为NULL
struct timeval
& & &time_t tv_ & & & //秒 [long int]
& & &suseconds_t tv_ //微秒 [long int]
程序用例:&
#include &stdio.h&
#include &sys/time.h&
int main(int argc, char* argv[])
& & &int ret = gettimeofday(&tp, NULL);
& & &printf(&ret: %d\t sec: %d\t usec: %d\n&, ret, tp.tv_sec, tp.tv_usec);
& & &return 0;
ret: 0 sec:
usec: 95274
char* asctime(const struct tm* tm);
char* asctime_r(const struct tm* tm, char* buf); // buf: 26 bytes at least
char* ctime(const time_t* timep);
char* ctime_r(const time_t* timep, char* buf); &&// buf: 26 bytes at least
struct tm* gmtime(const time_t* timep);
struct tm* gmtime_r(const time_t* timep, struct tm* result);
struct tm* localtime(const time_t* timep);
struct tm* localtime_r(const time_t* timep, struct tm* result);
time_t mktime(struct tm* tm);
struct tm&
& & &int tm_ & & & & /* seconds */
& & &int tm_ & & & & /* minutes */
& & &int tm_ & & & &/* hours */
& & &int tm_ & & & &/* day of the month */
& & &int tm_ & & & & /* month */
& & &int tm_ & & & &/* year */
& & &int tm_ & & & &/* day of the week */
& & &int tm_ & & & &/* day in the year */
& & &int tm_ & & & /* daylight saving time */ /* 是否是夏令制 */
& & &/** the glibc version of struct tm has additional fields:
& & &/** long tm_
& & &/** const char* tm_
asctime/asctime_r & & &: 把tm所表示的日期和时间转为字符串, 且会自动转为本地时区
ctime/ctime_r & & & & &: 把日期和时间转为字符串
gmtime/gmtime_r & & & &: 把time_t转换为tm,未经时区转换
localtime/localtime_r &: 把time_t转换为tm,转为本地时区
mktime & & & & & & & & : 将tm转换为time_t,UTC
以下示例展示了以上时间的用法:
#include &stdio.h&
#include &time.h&
#include &sys/time.h&
static void ShowTM(const char* desc, const tm* t)
& & &printf(&%s:\n&, desc);
& & &printf(&%d-%02d-%02d %02d:%02d:%02d\n&,&
& & & & & & & & & & t-&tm_year, t-&tm_mon, t-&tm_mday, t-&tm_hour, t-&tm_min, t-&tm_sec);
& & &printf(&yday: %d \t wday: %d \t isdst: %d\n\n&,&
& & & & & & & & & & t-&tm_yday, t-&tm_wday, t-&tm_isdst);
int main(int argc, char* argv[])
& & &// time
& & &time_t t = time(NULL);
& & &printf(&time: %d\n\n&, static_cast&int&(t));
& & &// ctime and ctime_r
& & &char* c1 = ctime(&t);
& & &printf(&ctime: c1: %s\n&, c1);
& & &char c20[64];
& & &char* c2 = ctime_r(&t, c20);
& & &printf(&ctime_r: c2: %s \t c20: %s \n\n&);
& & &// gmtime and gmtime_r
& & &tm* t1 = gmtime(&t);
& & &ShowTM(&gmtime-t1&, t1);
& & &tm t20;
& & &tm* t2 = gmtime_r(&t, &t20);
& & &ShowTM(&gmtime_r-t2&, t2);
& & &ShowTM(&gmtime_r-t20&, &t20);
& & &// localtime and localtime_r
& & &tm* t3 = localtime(&t);
& & &ShowTM(&localtime-t3&, t3);
& & &tm t40;
& & &tm* t4 = localtime_r(&t, &t40);
& & &ShowTM(&localtime_r-t4&, t4);
& & &ShowTM(&localtime_r-t40&, &t40);
& & &// asctime and asctime_r
& & &char* asc1 = asctime(t1);
& & &printf(&asctime-gmtime: %s\n&, asc1);
& & &char asc20[128];
& & &char* asc2 = asctime_r(t1, asc20);
& & &printf(&asctime-gmtime: asc2: %s \t asc20: %s\n&, asc2, asc20);
& & &char* asc3 = asctime(t3);
& & &printf(&asctime-localtime: %s\n&, asc3);
& & &char asc40[128];
& & &char* asc4 = asctime_r(t3, asc40);
& & &printf(&asctime-localtime: asc4: %s \t asc40: %s\n&, asc4, asc40);
& & &// mktime
& & &time_t mkt1 = mktime(t1);
& & &printf(&mktime-gmtime: %d\n\n&, static_cast&int&(mkt1));
& & &time_t mkt2 = mktime(t3);
& & &printf(&mktime-localtime: %d\n&, static_cast&int&(mkt2));
& & &return 0;
ctime: c1: Mon Sep 15 16:42:53 2014
ctime_r: c2:&
gmtime-t1:
114-08-15 08:42:53
yday: 257& &wday: 1& &isdst: 0
gmtime_r-t2:
114-08-15 08:42:53
yday: 257& &wday: 1& &isdst: 0
gmtime_r-t20:
114-08-15 08:42:53
yday: 257& &wday: 1& &isdst: 0
localtime-t3:
114-08-15 16:42:53
yday: 257& &wday: 1& &isdst: 0
localtime_r-t4:
114-08-15 16:42:53
yday: 257& &wday: 1& &isdst: 0
localtime_r-t40:
114-08-15 16:42:53
yday: 257& &wday: 1& &isdst: 0
asctime-gmtime: Mon Sep 15 16:42:53 2014
asctime-gmtime: asc2: Mon Sep 15 16:42:53 2014
& &asc20: Mon Sep 15 16:42:53 2014
asctime-localtime: Mon Sep 15 16:42:53 2014
asctime-localtime: asc4: Mon Sep 15 16:42:53 2014
& &asc40: Mon Sep 15 16:42:53 2014
mktime-gmtime:
mktime-localtime:
#include &time.h&
size_t strftime(char* s, size_t max, const char* format,&const struct tm* tm);
strftime: 将tm按照format的格式,格式化输出到大小为max的字符串s中。
格式部分字符:
%a: 周几的英文缩写。
%A: 周几的英文全称。
%b: 月份的英文缩写。
%B: 月份的英文全称。
%c: 日期和时间, Tue Feb 10 19:45:20 2014。
%C: 年/100 [00, 99]。
%d: 每月的第几天 [01, 31] 。
%D: 日期 MM/DD/YY 09/15/14。
%e: 每月的第几天[1, 31], 个位数前添加空格。
%F: ISO 8601 日期格式 YYYY-MM-DD 。
%g: ISO 8601 年的最后2位,基于周的年 [00,99]。
%G: ISO 8601 基于周的年, 2014。
%h: 与%b相同。
%H: 小时(24小时制) [00, 23]。
%I: 小时(12小时制) [01, 12]。
%j: 一年的第几天, [000, 366]。
%m: 月份 [01, 12]。
%M: 分钟 [00, 59]。
%n: 换行符。
%p: AM/PM。
%r: 本地时间(12小时制) 09:15:21 PM。
%R: 与 %H:%M相同, 18:27。
%S: 秒 [00, 60]。
%t: 水平制表符。
%T: 与 %H:%M:%S相同。
%u: ISO 8601 [Monday = 1, 7]。
%U: 以周日作为标准,一年的第几周。在第一月第一周,如果该周一半(4天)以上属于该年,则作为本年第一周,否则做为上一年最后一周。
%V: ISO 8601 周数 [01, 53]。
%w: 一周的第几天 [Sunday = 0, 6]。
%W: 以星期一做为标准,一年的第几周。判断方法如同%U。
%x: 日期 09/15/14
%X: 时间 19:45:20
%y: 年的最后两位 [00, 99] 14
%Y: 年 2014
%z: ISO 8601格式的UTC偏移量
%Z: 时区名
程序用例:
#include &stdio.h&
#include &time.h&
int main(int argc, char* argv[])
& & &char desc[128] = {0};
& & &time_t curr = time(NULL);
& & &tm* t = localtime(&curr);
& & &int n = strftime(desc, 128, &It is: %D\n&, t);
& & &printf(&n: %d\t %s\n&, n, desc);
& & &return 0;
n: 16 &It is: 09/15/14
#include &time.h&
char* strptime(const char* s, const char* format, struct tm* tm);
strptime: 按照特定时间格式将字符串转换为时间类型。
成功返回s的最后位置。失败返回空指针。
程序用例:
#include &stdio.h&
#include &time.h&
int main(int argc, char* argv[])
& & &char buffer[256] = {0};
& & &strptime(& 21:44:45&, &%Y-%m-%d %H:%M:%S&, &t);
& & &strftime(buffer, 256, &%d %b %Y %H:%M&, &t);
& & &puts(buffer);
& & &return 0;
17:45&&&[]
近来读书,做些笔记,来年好翻翻。
本文所使用的操作系统为 CentOS7.0,如果不想 装双系统的可以装 虚拟机,可以参考这里:
http://blog.csdn.net/alex_my/article/details/
system data files
17:00&&&[]
近来读书,做些笔记,来年好翻翻。
本文所使用的操作系统为 CentOS7.0,如果不想装双系统的可以装虚拟机,可以参考这里:
http://blog.csdn.net/alex_my/article/details/
Standard I/O library
11:59&&&[]
近来读书,做些笔记,来年好翻翻。
本文所使用的操作系统为 CentOS7.0,如果不想装双系统的可以装虚拟机,可以参考这里:
http://blog.csdn.net/alex_my/article/details/
18:23&&&[]
近来读书,做些笔记,来年好翻翻。
本文所使用的操作系统为 CentOS7.0,如果不想装双系统的可以装虚拟机,可以参考这里:
http://blog.csdn.net/alex_my/article/details/
当然啦,直接装个再好不过
12:12&&&[]}

我要回帖

更多关于 出现错误未定义书签 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信