linux应用程序常用的函数
来源:互联网 发布:电脑重置网络命令 编辑:程序博客网 时间:2024/05/17 22:03
Linux应用程序常用函数
在linux应用程序设计中,通常不需要去了底层的驱动是怎么实现的,只需要利用系统提供的接口函数,就可以去访问底层设备。这篇文档是记录自己学习《嵌入式linux应用程序开发详解》,其中主要介绍在应用程序中一些常用函数,并通过实例来介绍这些函数的用法。其中主要介绍了基于文件IO的编程,例如打开关闭,读写等等;进程及进程间通信;最后介绍了基于网络的的socket编程。
一:文件 I/O编程
通过上面的系统框图,可以发现,应用程序有两种方式进行系统调用,直接调用和通过C库函数调用,也即是利用linux系统提供的文件IO和标准C库函函数的IO来进行系统调用。先说说基于linux系统的文件IO操作。
㈠不带缓存的文件IO操作
1. open()函数
功能描述:用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。
所需头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
函数原型:intopen(const char *pathname,int flags,int perms)
参数:
pathname:被打开的文件名(可包括路径名如"dev/ttyS0")
flags:文件打开方式,
O_RDONLY:以只读方式打开文件
O_WRONLY:以只写方式打开文件
O_RDWR:以读写方式打开文件
O_CREAT:如果改文件不存在,就创建一个新的文件,并用第三个参数为其设置权限
O_EXCL:如果使用O_CREAT时文件存在,则返回错误消息。这一参数可测试文件是否存在。此时open是原子操作,防止多个进程同时创建同一个文件
O_NOCTTY:使用本参数时,若文件为终端,那么该终端不会成为调用open()的那个进程的控制终端
O_TRUNC:若文件已经存在,那么会删除文件中的全部原有数据,并且设置文件大小为0
O_APPEND:以添加方式打开文件,在打开文件的同时,文件指针指向文件的末尾,即将写入的数据添加到文件的末尾
O_NONBLOCK: 如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I/O操作设置非阻塞方式。
O_SYNC:使每次write都等到物理I/O操作完成。
O_RSYNC:read 等待所有写入同一区域的写操作完成后再进行
在open()函数中,falgs参数可以通过“|”组合构成,但前3个标准常量(O_RDONLY,O_WRONLY,和O_RDWR)不能互相组合。
perms:被打开文件的存取权限,可以用两种方法表示,可以用一组宏定义:S_I(R/W/X)(USR/GRP/OTH),其中R/W/X表示读写执行权限,
USR/GRP/OTH分别表示文件的所有者/文件所属组/其他用户,如S_IRUUR|S_IWUUR|S_IXUUR,(-rex------),也可用八进制800表示同样的权限
返回值:
成功:返回文件描述符
失败:返回-1
2. close()函数
功能描述:用于关闭一个被打开的的文件
所需头文件:#include<unistd.h>
函数原型:intclose(int fd)
参数:fd文件描述符
函数返回值:0成功,-1出错
3. read()函数
功能描述:从文件读取数据。
所需头文件:#include<unistd.h>
函数原型:ssize_tread(int fd, void *buf, size_t count);
参数:
fd:将要读取数据的文件描述词。
buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。
count:表示调用一次read操作,应该读多少数量的字符。
返回值:返回所读取的字节数;0(读到EOF);-1(出错)。
以下几种情况会导致读取到的字节数小于count:
A. 读取普通文件时,读到文件末尾还不够count字节。例如:如果文件只有30 字节,而我们想读取 100
字节,那么实际读到的只有30字节,read 函数返回 30。此时再使用 read 函数作用于这个文件会导致 read返回 0 。
B. 从终端设备(terminal device)读取时,一般情况下每次只能读取一行。
C. 从网络读取时,网络缓存可能导致读取的字节数小于 count字节。
D. 读取 pipe或者 FIFO 时,pipe或 FIFO 里的字节数可能小于count。
E. 从面向记录(record-oriented)的设备读取时,某些面向记录的设备(如磁带)每次最多只能返回一个记录。
F. 在读取了部分数据时被信号中断。
读操作始于 cfo。在成功返回之前,cfo 增加,增量为实际读取到的字节数。
4. write()函数
功能描述:向文件写入数据。
所需头文件:#include<unistd.h>
函数原型:ssize_twrite(int fd, void *buf, size_t count);
返回值:写入文件的字节数(成功);-1(出错)
功能:write函数向 filedes 中写入count字节数据,数据来源为 buf 。返回值一般总是等于 count,否则就是出错了。常见的出错原因是磁盘空间满了或者超过了文件大小限制。
对于普通文件,写操作始于cfo。如果打开文件时使用了O_APPEND,则每次写操作都将数据写入文件末尾。成功写入后,cfo增加,增量为实际写入的字节数。
5. lseek()函数
功能描述:用于在指定的文件描述符中将将文件指针定位到相应位置。
所需头文件:#include<unistd.h>,#include<sys/types.h>
函数原型:off_tlseek(int fd, off_t offset,int whence);
参数:
fd;文件描述符
offset:偏移量,每一个读写操作所需要移动的距离,单位是字节,可正可负(向前移,向后移)
whence:
SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小
SEEK_CUR:当前位置为指针的位置,新位置为当前位置加上偏移量
SEEK_END:当前位置为文件的结尾,新位置为文件大小加上偏移量的大小
返回值:
成功:返回当前位移
失败:返回-1
㈡标准IO操作
下面的这图能很好的反映,用户通过文件IO和标准IO进行系统调用的区别。
标准IO操作:标准I/O是ANSIC建立的一个标准I/O模型,是一个标准函数包和stdio.h头文件中的定义,具有一定的可移植性。
文件IO操作:称之为不带缓存的IO(unbuffered I/O)。不带缓存指的是每个read,write都调用内核中的一个系统调用。也就是一般所说的低级I/O——操作系统提供的基本IO服务,与os绑定,特定于linix或unix平台。
首先:两者一个显著的不同点在于,标准I/O默认采用了缓冲机制,比如调用fopen函数,不仅打开一个文件,而且建立了一个缓冲区(读写模式下将建立两个缓冲区),还创建了一个包含文件和缓冲区相关数据的数据结构。低级I/O一般没有采用缓冲,需要自己创建缓冲区,不过其实在linix或unix系统中,都是有使用称为内核缓冲的技术用于提高效率,读写调用是在内核缓冲区和进程缓冲区之间进行的数据复制。
其次从操作的设备上来区分,文件I/O主要针对文件操作,读写硬盘等,它操作的是文件描述符,标准I/O针对的是控制台,打印输出到屏幕等,它操作的是字符流。对于不同设备得特性不一样,必须有不同api访问才最高效。
标准IO的一些常用函数Fopen(),fclose(),fread(),fwrite();
㈢如何避免竞争
上面只是简单介绍文件操作的基本操作,如果当多个用户共同使用和操作一个文件时,就回导致对共享资源的的竞争,该怎么解决竞争呢?Linux最常用的办法就是给文件上锁。文件锁又包括建议性锁和强制性锁。实现上锁的方法有lock和fcntl,lock用于对文件施加建议性锁,而fcntl用于对文件施加强制性锁。同时,fcntl还能对文件的某一记录进行上锁,也就是记录锁,记录锁可以分为读取锁和写入锁,其中读取锁为共享锁,写入锁为互斥锁。
int fcntl(int fd,int cmd,struct flock * lock);
fcntl()用来操作文件描述符的一些特性。参数fd代表欲设置的文件描述词,参数cmd代表欲操作的指令。
有以下几种情况:
F_DUPFD用来查找大于或等于参数arg的最小且仍未使用的文件描述词,并且复制参数fd的文件描述词。执行成功则返回新复制的文件描述词。请参考dup2()。
F_GETFD取得close-on-exec旗标。若此旗标的FD_CLOEXEC位为0,代表在调用exec()相关函数时文件将不会关闭。
F_SETFD 设置close-on-exec旗标。该旗标以参数arg 的FD_CLOEXEC位决定。 F_GETFL取得文件描述词状态旗标,此旗标为open()的参数flags。
F_SETFL 设置文件描述词状态旗标,参数arg为新旗标,但只允许O_APPEND、O_NONBLOCK和O_ASYNC位的改变,其他位的改变将不受影响。
F_GETLK 取得文件锁定的状态。
F_SETLK 设置文件锁定的状态。此时flcok结构的l_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES或EAGAIN。 F_SETLKW F_SETLK作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。
参数lock指针为flock结构指针,定义如下
struct flcok {
short intl_type;
short intl_whence;
off_t l_start;
off_t l_len;
pid_t l_pid; };
l_type 有三种状态:
F_RDLCK 建立一个供读取用的锁定
F_WRLCK 建立一个供写入用的锁定
F_UNLCK 删除之前建立的锁定
l_whence 也有三种方式:
SEEK_SET 以文件开头为锁定的起始位置。
SEEK_CUR 以目前文件读写位置为锁定的起始位置
SEEK_END 以文件结尾为锁定的起始位置。
返回值成功则返回0,若有错误则返回-1,错误原因存于errno.
Fcntl实例
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/file.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
/*利用fcntl创建记录锁*/
voidlock_set(int fd,int type)
{
struct flock lock;
lock.l_whence = SEEK_SET;
lock.l_len = 0;
lock.l_start = 0;
while(1)
{
lock.l_type = type;
/*根据不同的type给文件上锁*/
if((fcntl(fd, F_SETLK,&lock)) == 0)
{
if(lock.l_type ==F_RDLCK)
printf("read lock is set by %d\n", getpid());
else if(lock.l_type ==F_WRLCK )
printf("write lock is set by %d\n", getpid());
else if(lock.l_type ==F_UNLCK)
printf("release lock is set by %d\n", getpid());
return;
}
/*判断文件是否可以上锁*/
fcntl(fd, F_GETLK, &lock);
if(lock.l_type != F_UNLCK)
{
if(lock.l_type ==F_RDLCK)/*该文件已有读入琐*/
printf("read lock is set by %d\n", lock.l_pid);
else if(lock.l_type ==F_WRLCK )/*该文件已有写入锁*/
printf("write lock already set by %d\n", lock.l_pid);
}
}
}
int main(void)
{
int fd;
fd =open("/home/fany/application/io/hello.c", O_RDWR, 0666);
if(fd < 0)
{
perror("openfailed\n");
exit(1);
}
/*给文件加锁*/
lock_set(fd,F_RDLCK);
getchar();
/*给文件解锁*/
lock_set(fd,F_UNLCK);
getchar();
close(fd);
}
㈣select函数实例
Fcntl函数解决了因争抢共享资源而发生的竞态,而select函数是处理IO复用的一个高效的方法。他可以设置每一个所关心的文件描述符的条件,希望等待时间,从select返回时,内核会通知用户已准备好的文件描述符的数量,已准备好的条件等。
int select(intmaxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout);
先说明两个结构体:
第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合FD_ZERO(fd_set *),将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set *),将一个给定的文件描述符从集合中删除FD_CLR(int ,fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。一会儿举例说明。
第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。
具体解释select的参数:
int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。
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应用实例
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
intfd0,fd1;
charbuf[7];
inti,rc,maxfd;
fd_setinset0,inset1;
structtimeval tv;
/*按一定权限打开hello1.c*/
if((fd0= open("/home/fany/application/io/hello1.c", O_RDWR | O_CREAT, 0666))< 0)
perror("open hello1.c failed\n");
/*按一定权限打开hello2.c*/
if((fd1= open("/home/fany/application/io/hello2.c", O_RDWR | O_CREAT, 0666))< 0)
perror("open hello1.c failed\n");
if((rc= write(fd0, "Hello!\0", 7)) < 0)
perror("write file failed \n");
else
printf("rc = %d\n", rc);
lseek(fd0,0, SEEK_SET);
/*取出文件描述符中的较大者*/
maxfd= fd0 > fd1? fd0 : fd1;
/*初始化集合集0*/
FD_ZERO(&inset0);
FD_SET(fd0,&inset0);
/*初始化集合集1*/
FD_ZERO(&inset1);
FD_SET(fd1,&inset1);
tv.tv_sec= 2;
tv.tv_usec= 0;
/*测试描述符是否就绪*/
while((FD_ISSET(fd0,&inset0))|| (FD_ISSET(fd1,&inset1)))
{
if(select(maxfd + 1, &inset0,&inset1, NULL, &tv) < 0)
perror("selectfailed\n");
else
{
if(FD_ISSET(fd0,&inset0))
{
rc = read(fd0, buf, 7);
if(rc > 0)
{
buf[rc] = '\0';
printf("read %s, rc = %d\n",buf, rc);
}
}
if(FD_ISSET(fd1,&inset1))
{
rc = write(fd1, buf, 7);
if(rc > 0)
{
buf[rc] = '\0';
printf("write %s, rc = %d\n",buf, rc);
}
sleep(5);
}
}
}
exit(0);
}
二:Linux进程
在前面主要学习基于文件IO的函数,他们有一个共同点是基于文件的编程,既是具体的设备,而进程则是系统调度的单位,是一个程序的执行过程,是动态的。有点类似在windows下启动启动任务管理器看到的进程。
1:创建进程fork()
Fork函数用于在已经存在的进程中创建一个新进程,新进程为子进程,而原进程为父进程。调用该函数后,返回两个值,其中父进程的返回值是子进程的进程号,而子进程的返回值则是0.
fork函数实例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
pid_tresult;
result= fork();
if(result== -1)
printf("fork error!\n");
elseif(result == 0)
printf("the child process idis:%d\n", getpid());
elseif(result > 0)
printf("the parent process idis:%d\n",getppid());
exit(0);
return0;
}
2:exec函数族
利用fork函数可以在一个已存在的进程中创建子进程,但是新创建的子进程该如何执行呢?Exec函数族就提供了一个在进程中启动另一个进程的方法。
exec用被执行的程序完全替换调用它的程序的影像。fork创建一个新的进程就产生了一个新的PID,exec启动一个新程序,替换原有的进程,因此这个新的被exec执行的进程的PID不会改变,和调用exec函数的进程一样。
下面来看下exec函数族:
#include<unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char*const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *constenvp[]);
exec函数族装入并运行程序pathname,并将参数arg0(arg1,arg2,argv[],envp[])传递给子程序,出错返回-1。在exec函数族中,后缀l、v、p、e添加到exec后,所指定的函数将具有某种操作能力有后缀:
以execl函数为例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
if(fork()== 0)
execl("/bin/ps","ps", "-ef", NULL);
return0;
}
3:wait函数
Wait函数是用于使父进程阻塞,直到一个子进程结束或者该子进程接到了一个指定的信号为止。Waitpid的作用和wait一样
头文件
#include<sys/types.h>
#include<sys/wait.h>
定义函数pid_t wait (int * status);
函数说明:
wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用 wait()时子进程已经结束,则 wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数 status返回,而子进程的进程识别码也会一起返回。如果不在意结束状态值,则参数 status可以设成 NULL。
子进程的结束状态值请参考 waitpid( ),如果执行成功则返回子进程识别码(PID) ,如果有错误发生则返回返回值-1。失败原因存于 errno中。
waitpid(等待子进程中断或结束)
表头文件
#include<sys/types.h>
#include<sys/wait.h>
定义函数 pid_twaitpid(pid_t pid,int * status,int options);
函数说明:
waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用 wait()时子进程已经结束,则 wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数 status返回,而子进程的进程识别码也会一快返回。如果不在意结束状态值,则参数 status可以设成 NULL。
Waitpid函数实例
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t pc,pr;
pc = fork();
if(pc < 0)
printf("forkerror!\n");
else if(pc == 0) //对子进程的操作
{
sleep(5);
printf("thechild id is %d\n", pc);
exit(0);
}
else if(pc > 0) //对父进程的操作
{
do
{
pr = waitpid(pc, NULL, WNOHANG);
if(pr == 0)
{
printf("Thechild process has not exited!\n");
sleep(1);
}
}
while(pr== 0);
printf("Thechild process exited %d\n", pr);
}
return 0;
}
4:如何创建守护进程
守护进程,是linux中的后台服务进程,是一个生存周期较长的进程,通常独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。有点类似windows下的定时关机。
守护进程实例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
#include <sys/types.h>
#include<sys/wait.h>
int main()
{
int i,fd;
pid_t pd;
/*第一步:创建子进程,父进程退出*/
pd = fork();
if(pd > 0)
exit(0);
/*第二步:在子进程中创建新会话*/
setsid();
/*第三步:改变当前目录为根目录*/
chdir("/");
/*第四步:设置文件权限码*/
umask(0);
/*第五步:关闭文件描述符*/
for(i = 0; i < 65535; i++)
close(i);
while(1){
/*守护进程创建完毕,开始进入守护进程工作*/
if((fd = open("/tmp/dameon.log",O_CREAT | O_WRONLY | O_APPEND, 0600)) < 0)
printf("openfailed!\n");
write(fd, "this is a dameon!",17);
close(fd);
sleep(1);
}
}
三:进程间通信
在上面介绍了进程的基本慨念和相关函数,但是进程间只是彼此独立的,该如何实现进程间的通信呢?Linux中提供了一下几种进程间通信的方法
管道和有名管道:管道用于具有亲缘关系进程间通信;而有名管道除拥有管道的功能外,还允许无亲缘关系进程间的通信。
信号:有点像中断,用于通知接受有某事件发生。
消息队列:消息队列是消息的链表。
共享内存:是的多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享数据的更新。
信号量:作为进程间以及同一进程不同线程之间的同步手段。
套接字:一般用于不同机器间的进程通信。
1:管道
管道是基于文件描述符的通信方式,当一个管道建立时,它会建立两个文件描述符fds[0]f和ds[1],fds[0]用于读管道,fds[1]用于写管道,这样就形成了一个半双工的通道。
管道创建和读写实例
#include <unistd.h>
#include <error.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
int main()
{
int pipe_fd[2];
pid_t pid;
int r_num;
char r_buf[10];
/*创建一无名管道*/
if(pipe(pipe_fd) < 0){
printf("pipe creat error!\n");
return -1;
}
/*创建一进程*/
pid = fork();
/*关闭子进程的写描述符和父进程的读描述符*/
if(pid == 0)
{
close(pipe_fd[1]);
sleep(1);
if((r_num = read(pipe_fd[0], r_buf, 10)) > 0)
printf("%d numbersread form pipe %s\n", r_num, r_buf);
close(pipe_fd[0]);
exit(0);
}
else if(pid > 0)
{
close(pipe_fd[0]);
if((write(pipe_fd[1], "Hello", 5)) != -1)
printf("parentprocess write success!\n");
close(pipe_fd[1]);
waitpid(pid, NULL, 0);
exit(0);
}
return 0;
}
2:信号通信
信号,它是在软件层次上对中断机制的一种模拟,是一种异步机制。可以直接进行用户空间与内核空间的交互。一个信号可分为4各阶段:信号产生,信号注册,信号注销,执行信号处理函数。
信号发送函数:
①int kill(pid_t pid, int signo);
pid> 0 将信号发送给进程ID为pid的进程。
pid == 0 将信号发送给其进程组ID等于发送进程的进程组ID,而且发送进程有许可权向其发送信号的所有进程。
②int raise(int signo);
raise(signo)等价于kill(getpid(),signo);
③unsigned int alarm(unsigned int seconds);
alarm函数可以设置一个计时器,计时器超时时,产生SIGALRM信号。
④int pause(void);
pause函数使调用进程挂起直至捕捉到一个信号。只有执行了一个信号处理程序并从其返回时,pause才返回。在这种情况下,pause返回-1,并将errno设置为EINTR。
信号发送函数实例
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int ret;
pid = fork();
if(pid < 0)
printf("fork error!\n");
if(pid == 0)
{
printf("the child id is %d\n", getpid());
/*使子进程暂停*/
raise(SIGSTOP);
exit(0);
}
else if(pid > 0)
{
printf("the parent id is %d\n", getppid());
/*父进程等待,直到接到指定子进程结束的信号*/
if((waitpid(pid, NULL, WNOHANG)) == 0)
{
/*向子进程发送结束的信号*/
if((ret =kill(pid, SIGKILL)) == 0)
printf("%d send SIGKILL to childsuccess!\n", pid);
else
printf("%d send SIGKILL to childfailed!\n", pid);
}
}
return 0;
}
信号处理函数
表头文件 #include<signal.h>
定义函数void(*signal(int signum,void(* handler)(int)))(int);
函数说明 signal()会依参数signum指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。如果参数handler不是函数指针,则必须是下列两个常数之一:
SIG_IGN忽略参数signum指定的信号。
SIG_DFL将参数signum 指定的信号重设为核心预设的信号处理方式。
返回值返回先前的信号处理函数指针,如果有错误则返回SIG_ERR(-1)。
信号处理函数实例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
void my_sign(int sign)
{
if(sign == SIGINT) printf("I have get SIGINT!\n");
else if(sign == SIGQUIT) printf("I have get SIGQUIT!\n");
}
int main()
{
printf("Waiting forsignal SIGINT or SIGQUIT!\n");
signal(SIGINT, my_sign);
signal(SIGQUIT, my_sign);
pause();
exit(0);
}
3:共享内存
共享内存是最为高效的进程间通信方式,因为进程可以直接读写内存,不需要任何数据的拷贝。要使用共享内存,应该有如下步骤:
1.开辟一块共享内存 shmget()
2.允许本进程使用共某块共享内存 shmat()
3.写入/读出
4.禁止本进程使用这块共享内存 shmdt()
5.删除这块共享内存 shmctl()或者命令行下ipcrm
操作共享内存,我们用到了下面的函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
intshmget( key_t shmkey , int shmsiz , int flag );
void *shmat( int shmid , char*shmaddr , int shmflag );
int shmdt( char *shmaddr );
shmget()是用来开辟/指向一块共享内存的函数。参数定义如下:
key_t shmkey 是这块共享内存的标识符。如果是父子关系的进程间通信的话,这个标识符用IPC_PRIVATE来代替。但是刚才我们的两个进程没有任何关系,所以就用ftok()算出来一个标识符使用了。
int shmsiz 是这块内存的大小.
int flag 是这块内存的模式(mode)以及权限标识。
模式可取如下值:新建:IPC_CREAT
使用已开辟的内存:IPC_ALLOC
如果标识符以存在,则返回错误值:IPC_EXCL
然后将“模式”和“权限标识”进行“或”运算,做为第三个参数。如: IPC_CREAT | IPC_EXCL | 0666
这个函数成功时返回共享内存的ID,失败时返回-1。
shmat()是用来允许本进程访问一块共享内存的函数。
int shmid是那块共享内存的ID。
char *shmaddr是共享内存的起始地址
int shmflag是本进程对该内存的操作模式。如果是SHM_RDONLY的话,就是只读模式。 其它的是读写模式,成功时,这个函数返回共享内存的起始地址。失败时返回-1。
shmdt()与shmat()相反,是用来禁止本进程访问一块共享内存的函数。
参数char *shmaddr是那块共享内存的起始地址。
成功时返回0。失败时返回-1。
共享内存函数实例
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#define BUFSIZE 1024
int main()
{
int shmid;
char *shmadd;
/*创建一共享内存区*/
shmid =shmget(IPC_PRIVATE, BUFSIZE, 0666);
if(shmid < 0) printf("creat shared memoryfailed!\n");
else printf("created shared memory:%d\n", shmid);
system("ipcs-m");
/*映射共享内存*/
if((shmadd = shmat(shmid,0, 0)) < (char*)0)
printf("attach shared memory failed!\n");
else
printf("attach shared memory success!\n");
system("ipcs-m");
/*删除共享内存区*/
if((shmdt(shmadd)) <0)
printf("del shared memory failed!\n");
else
printf("del shared memory success!\n");
system("ipcs-m");
exit(0);
}
四:linux网络编程
TCP(传输控制协议)和UDP(用户数据报协议是网络体系结构TCP/IP模型中传输层一层中的两个不同的通信协议。
TCP:传输控制协议,一种面向连接的协议,给用户进程提供可靠的全双工的字节流,TCP套接口是字节流套接口(streamsocket)的一种。
UDP:用户数据报协议。UDP是一种无连接协议。UDP套接口是数据报套接口(datagram socket)的一种。
㈠TCP和UDP介绍
⑴基本TCP客户—服务器程序设计基本框架
说明:(三路握手)
1.客户端发送一个SYN段(同步序号)指明客户打算连接的服务器端口,以及初始化序号(ISN)。
2.服务器发回包含服务器的初始序号的SYN报文段作为应答。同时,将确认序号(ACK)设置为客户的ISN加1以对客户的SYN报文段进行确认。一个SYN将占用一个序号。
3.客户必须将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认。
⑵基本TCP客户—服务器程序设计基本框架流程图
⑶UDP和TCP的对比:
从上面的流程图比较我们可以很明显的看出UDP没有三次握手过程。
简单点说。UDP处理的细节比TCP少。UDP不能保证消息被传送到(它也报告消息没有传送到)目的地。UDP也不保证数据包的传送顺序。UDP把数据发出去后只能希望它能够抵达目的地。
TCP优缺点:
1.TCP提供以认可的方式显式地创建和终止连接。
2.TCP保证可靠的、顺序的(数据包以发送的顺序接收)以及不会重复的数据传输。
3.TCP处理流控制。
4.允许数据优先
5.如果数据没有传送到,则TCP套接口返回一个出错状态条件。
6.TCP通过保持连续并将数据块分成更小的分片来处理大数据块。—无需程序员知道
缺点: TCP在转移数据时必须创建(并保持)一个连接。这个连接给通信进程增加了开销,让它比UDP速度要慢。
UDP优缺点:
1.UDP不要求保持一个连接
2.UDP没有因接收方认可收到数据包(或者当数据包没有正确抵达而自动重传)而带来的开销。
3.设计UDP的目的是用于短应用和控制消息
4.在一个数据包连接一个数据包的基础上,UDP要求的网络带宽比TCP更小。
㈡Socket编程
Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。要学Internet上的TCP/IP网络编程,必须理解Socket接口。
Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话,就很容易了解Socket了。网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。
socket调用库函数主要有:
创建套接字:Socket(af,type,protocol)
建立地址和套接字的联系:bind(sockid,local addr, addrlen)
服务器端侦听客户端的请求:listen(Sockid ,quenlen)
建立服务器/客户端的连接 (面向连接TCP)
客户端请求连接:Connect(sockid,destaddr, addrlen)
服务器端等待从编号为Sockid的Socket上接收客户连接请求
newsockid=accept(Sockid,Clientaddr, paddrlen)
发送/接收数据
面向连接:send(sockid,buff, bufflen);recv( )
面向无连接:sendto(sockid,buff,…,addrlen);recvfrom( )
释放套接字:close(sockid)
㈢socket编程实例
TCP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt(); *可选
3、绑定IP地址、端口等信息到socket上,用函数bind();
4、开启监听,用函数listen();
5、接收客户端上来的连接,用函数accept();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;
8、关闭监听;
/*************************************************************************/
/*文件:program_20_1.c */
/*简介:TCPServer示例。 */
/*************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main(int argc, char *argv[])
{
int sockfd,new_fd;
struct sockaddr_inserver_addr;
struct sockaddr_inclient_addr;
int sin_size,portnumber;
const charhello[]="Hello\n";
if(argc!=2)
{
fprintf(stderr,"Usage:%sportnumber\a\n",argv[0]);
exit(1);
}
if((portnumber=atoi(argv[1]))<0)
{
fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);
exit(1);
}
/* 服务器端开始建立socket描述符 */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
exit(1);
}
/* 服务器端填充 sockaddr结构 */
bzero(&server_addr,sizeof(struct sockaddr_in));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
server_addr.sin_port=htons(portnumber);
/* 捆绑sockfd描述符 */
if(bind(sockfd,(structsockaddr *)(&server_addr),sizeof(struct sockaddr))==
-1)
{
fprintf(stderr,"Binderror:%s\n\a",strerror(errno));
exit(1);
}
/* 监听sockfd描述符 */
if(listen(sockfd,5)==-1)
{
fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
exit(1);
}
while(1)
{
/* 服务器阻塞,直到客户程序建立连接 */
sin_size=sizeof(structsockaddr_in);
if((new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size))==-1)
{
fprintf(stderr,"Accept error:%s\n\a",strerror(errno));
exit(1);
}
fprintf(stderr,"Serverget connection from %s\n",
inet_ntoa(client_addr.sin_addr));
if(write(new_fd,hello,strlen(hello))==-1)
{
fprintf(stderr,"WriteError:%s\n",strerror(errno));
exit(1);
}
/*这个通讯已经结束 */
close(new_fd);
/* 循环下一个 */
}
close(sockfd);
exit(0);
}
TCP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();*可选
3、绑定IP地址、端口等信息到socket上,用函数bind();*可选
4、设置要连接的对方的IP地址和端口等属性;
5、连接服务器,用函数connect();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;
/*************************************************************************/
/*文件:program_20_2.c */
/*简介:TCPClient示例。 */
/*************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main(int argc, char *argv[])
{
int sockfd;
char buffer[1024];
struct sockaddr_in server_addr;
struct hostent *host;
int portnumber,nbytes;
if(argc!=3)
{
fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
exit(1);
}
if((host=gethostbyname(argv[1]))==NULL)
{
herror ("Get hostname error\n");
exit(1);
}
if((portnumber=atoi(argv[2]))<0)
{
fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
exit(1);
}
/* 客户程序开始建立sockfd描述符 */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
exit(1);
}
/* 客户程序填充服务端的资料*/
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(portnumber);
server_addr.sin_addr=*((struct in_addr *)host->h_addr);
/* 客户程序发起连接请求 */
if(connect(sockfd,(struct sockaddr *)(&server_addr),\
sizeof(structsockaddr))==-1)
{
fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
exit(1);
}
/* 连接成功了 */
if((nbytes=read(sockfd,buffer,1024))==-1)
{
fprintf(stderr,"ReadError:%s\n",strerror(errno));
exit(1);
}
buffer[nbytes]='\0';
printf("I have received:%s\n",buffer);
/* 结束通讯 */
close(sockfd);
exit(0);
}
与之对应的UDP编程步骤要简单许多,分别如下:
UDP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();*可选
3、绑定IP地址、端口等信息到socket上,用函数bind();
4、循环接收数据,用函数recvfrom();
5、关闭网络连接;
/*************************************************************************/
/*文件:program_20_3.c */
/*简介:UDPServer示例。 */
/*************************************************************************/
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 2000 /*监听端口 */
#define MAXDATASIZE 100 /*缓冲区的大小 */
int main()
{
int sockfd;
/* 服务器的地址信息 */
struct sockaddr_in server;
/* 客户端的地址信息 */
struct sockaddr_in client;
int sin_size;
int num;
/* 接收缓冲区 */
charmsg[MAXDATASIZE];
/* 创建UDP套接字 */
if ((sockfd =socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("Creatingsocket failed.");
exit(1);
}
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr =htonl (INADDR_ANY);
if (bind(sockfd, (structsockaddr *)&server, \
sizeof(structsockaddr)) == -1)
{
/* handle exception */
perror("Binderror.");
exit(1);
}
sin_size=sizeof(structsockaddr_in);
while (1)
{
num =recvfrom(sockfd,msg,MAXDATASIZE,0,\
(struct sockaddr*)&client,&sin_size);
if (num < 0)
{
perror("recvfrom error\n");
exit(1);
}
msg[num] = '\0';
printf("You got amessage (%s) from %s\n",msg,\
inet_ntoa(client.sin_addr));
/*向客户端发送消息*/
sendto(sockfd,"Welcome to my server.\n",23,0,\
(struct sockaddr*)&client,sin_size);
/*若消息是quit,则退出服务器程序*/
if(!strcmp(msg,"quit")) break;
}
/*关闭套接字*/
close(sockfd);
return 0;
}
UDP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();*可选
3、绑定IP地址、端口等信息到socket上,用函数bind();*可选
4、设置对方的IP地址和端口等属性;
5、发送数据,用函数sendto();
6、关闭网络连接;
/*************************************************************************/
/*文件:program_20_4.c */
/*简介:UDPClient示例。 */
/*************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#define PORT 2000 /*监听端口 */
#define MAXDATASIZE 100 /*缓冲区的大小 */
int main(int argc, char *argv[])
{
int fd, numbytes;
/* 存储接收到的消息 */
char buf[MAXDATASIZE];
/* 存储远程服务器的信息 */
struct hostent *he;
/* 服务器的地址信息 */
struct sockaddr_inserver,reply;
if (argc !=3)
{ /*用法 */
printf("Usage: %s <IP Address><message>\n",argv[0]);
exit(1);
}
if((he=gethostbyname(argv[1]))==NULL)
{
printf("gethostbyname()error\n");
exit(1);
}
if ((fd=socket(AF_INET,SOCK_DGRAM, 0))==-1)
{
printf("socket() error\n");
exit(1);
}
bzero(&server,sizeof(server));
server.sin_family =AF_INET;
server.sin_port =htons(PORT);
server.sin_addr =*((struct in_addr *)he->h_addr);
/*发送消息至服务器*/
sendto(fd, argv[2],strlen(argv[2]),0,\
(struct sockaddr*)&server,sizeof(struct sockaddr));
while (1)
{
int len;
/*接收来自服务器的消息*/
if((numbytes=recvfrom(fd,buf,MAXDATASIZE,0,\
(struct sockaddr*)&reply,&len)) == -1)
{
printf("recvfrom()error\n");
exit(1);
}
if (len != sizeof(structsockaddr) || \
memcmp((const void*)&server, (const void *)&reply,len) != 0)
{
printf("Receivemessage from other server.\n");
buf[numbytes]='\0';
printf("ServerMessage: %s\n",buf);
break;
}
}
/*关闭套接字*/
close(fd);
return 0;
}
- linux应用程序常用的函数
- 嵌入式Linux应用程序常用到的几个函数
- Linux下推荐的常用应用程序列表
- Linux下推荐的常用应用程序列表
- linux驱动程序与应用程序函数的联系
- linux驱动程序与应用程序函数的联系
- linux操作系统与应用程序的main函数
- Linux编程常用的函数
- LINUX编程常用的函数
- Linux编程常用的函数
- linux的常用时间函数
- Linux编程常用的函数
- linux编程常用的函数
- linux常用的时间函数
- [ 转]Linux下推荐的常用应用程序列表
- 在linux中 应用程序如何调用模块内的函数
- 在linux中 应用程序如何调用模块内的函数
- 在linux中应用程序如何调用模块内的函数
- 内存地址对齐及大小端 (转)
- 图标 DIY 模板,轻松创建 iOS 应用图标
- spring注入类型要点
- XCode 4创建ipa文件及提交应用程序
- PE产业介绍
- linux应用程序常用的函数
- java动态代理上是否能再进行一层代理
- 如何引用一个已经定义过的全局变量 与 全局变量可不可以定义在可被多个.C文件包含的头文件中
- windows hosts setup
- linux文件编程
- 权重萎靡资金不买账
- SSH Secure Shell Client 客户端乱码解决方法
- shell编程if/then备忘
- Linux vmstat命令实战详解