一,文件操作
文件操作顺序:打开(创建)—>读写—>关闭
文件的打开与创建
1.open()函数
describe:
(1)open(const char *pathname,int flags)
const char *pathname
是你打开的文件路径名,是一个字符串;
int flags
是flags可以去下面的一个值或者是几个值的组合.
O_RDONLY:以只读的方式打开文件.
O_WRONLY:以只写的方式打开文件.
O_RDWR:以读写的方式打开文件.
O_APPEND:以追加的方式打开文件.
O_CREAT:创建一个文件.
O_EXEC:如果使用了O_CREAT而且文件已经存在,就会发生一个错误.
O_NOBLOCK:以非阻塞的方式打开一个文件.
O_TRUNC:如果文件已经存在,则删除文件的内容.
前面三个标志只能使用任意的一个.如果使用了 O_CREATE标志,那么我们要使用open的第二种形式.还要指定mode标志,用来表示文件的访问权限.mode可以是以下情况的组合.即open(const char *pathname,int flags,mode_t mode)
(2)open(const char *pathname,int flags,mode_t mode)
前面两个参数配置和上面第一种相同,
mode_t mode
mode标志,用来表示文件的访问权限.mode可以是以下情况的组合.
—————————————————————–
S_IRUSR 用户可以读
S_IWUSR 用户可以写
S_IXUSR 用户可以执行 S_IRWXU 用户可以读写执行
—————————————————————–
S_IRGRP 组可以读
S_IWGRP 组可以写
S_IXGRP 组可以执行
S_IRWXG 组可以读写执行
—————————————————————–
S_IROTH 其他人可以读
S_IWOTH 其他人可以写
S_IXOTH 其他人可以执行
S_IRWXO 其他人可以读写执行
—————————————————————–
S_ISUID 设置用户执行ID S_ISGID 设置组的执行ID
我们也可以用数字来代表各个位的标志.Linux总共用5个数字来表示文件的各种权限.
00000.第一位表示设置用户ID.第二位表示设置组ID,第三位表示用户自己的权限位,第四位表示组的权限,最后一位表示其他人的权限.
每个数字可以取1(执行权限),2(写权限),4(读权限),0(什么也没有)或者是这几个值的和.例如
(3)返回值
如果我们打开文件成功,open会返回一个文件描述符fd。我们以后对文件的所有操作就可以对这个文件描述符进行操作了。如果open失败就return -1。
文件写入操作
文件打开了以后,我们就要对文件进行读写了.我们可以调用函数read和write进行文件的读写.
**1.write(int fd,const void *buf,size_t count)**斜体样式
fd是我们要进行读写操作的文件描述符,buf是我们要写入文件内容或读出文件内容的内存地址,注意:它是无类型的指针,它既可以是字符串 char *buf=“字符串”,也可以是结构体 struct { };count是我们要读写的字节数byte,这里可以用API函数sizeof(char * buf).
》》返回值
如果成功则返回字节数count,失败就return -1。
文件读出操作
文件读出操作和write类似,有区别的是:write的buf是从buf里面取内容写到fd指定的文件,read的buf是将fd指向的文件里的内容读取到buf中。count 均表示读写所带走的数据字节数。
文件光标移动操作
当我们对打开的文件进行write和read操作后,指针光标就会移动到文件的末端,这时若要继续读写操作就必须将光标移动到文件的开头,用到API函数lseek(int fd,off_t offset,int whence)。
1.lseek(int fd,off_t offset,int whence)
fd同样是操作的文件描述符,offset为正则向文件末尾移动(向前移),为负数则向文件头部(向后移)。
lseek()函数会重新定位被打开文件的位移量,根据参数offset以及whence的组合来决定:
SEEK_SET:
从文件头部开始偏移offset个字节。
SEEK_CUR:
从文件当前读写的指针位置开始,增加offset个字节的偏移量。
SEEK_END:
文件偏移量设置为文件的大小(文件末端)加上偏移量字节
》》返回值
返回值:新的偏移量(成功),-1(失败)。
补充:这里新的偏移量是指从文件的开头到你光标定位的位置的字节数。
利用该函数的返回值可以计算出文件内容的大小,即
》》实例
代码:
下面展示一些
内联代码片
。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
main()
{
int fd;
int n_write;
char *buf1="123456789";
char *readbuf;
readbuf=(char *)malloc(n_write);
char *buf="xiongqi handsome!";
fd=open("./file1",O_RDWR);//打开文件
if(fd==-1){
printf("open file1 failed?\n");
fd=open("./file1",O_RDWR|O_CREAT,0600);
}
if(fd!=-1){
printf("open file1:%d\n",fd);
}
// ssize_t write(int fd, const void *buf, size_t count);
n_write=write(fd,buf,strlen(buf));//将buf中的数据写入文件
lseek(fd,0,SEEK_END);//move to the file' tail
write(fd,buf1,strlen(buf1));//将buf1的数据继续写入文件
// close(fd);
//fd=open("./file1",O_RDWR);
//off_t lseek(int fd, off_t offset, int whence);
lseek(fd,0,SEEK_SET);//move to the file' head
// ssize_t read(int fd, void *buf, size_t count);
int n=read(fd,readbuf,n_write+strlen(buf1));//将文件的数据读到readbuf
printf("you writed %d bytes data,contex:%s\n",n,readbuf);
int filesize;
filesize=lseek(fd,0,SEEK_END);
printf("The file's size is %d bytes\n",filesize);
close(fd);
return 0;
}
》》运行结果
文件操作之cp指令编写
cp指令编程步骤:打开文件1—>读取文件1中的内容—>打开文件2—>文件2光标定位—>将内容写入文件2
》》实例
文件1内容
文件2内容
代码:
下面展示一些
内联代码片
。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
int fd1,fd2;
int n_write;
int sizeFile1;
int sizeFile2;
char *buf1;
char *buf2;
buf1=(char *)malloc(128);
buf2=(char *)malloc(128);
if(argc!=3){
printf("program error\n");
exit(-1);
}
fd1=open(argv[1],O_RDWR);
fd2=open(argv[2],O_RDWR|O_CREAT,0600);
printf("file1:%d,file2:%d\n",fd1,fd2);
sizeFile1=lseek(fd1,0,SEEK_END);//move to file1' tail
lseek(fd1,0,SEEK_SET);//move to file1' head
printf("file1'size:%d\n",sizeFile1);
read(fd1,buf1,sizeFile1);
printf("%s\n",buf1);
lseek(fd2,0,SEEK_END);//move to file2' tail
n_write=write(fd2,buf1,sizeFile1);
printf("%d\n",n_write);
close(fd1);
close(fd2);
return 0;
}
》》运行结果
文件操作之修改配置文件
配置文件编程步骤:打开目标文件—>读取目标文件—>查找要修改的内容(指针地址)—>指针移动到修改的地址—>赋值(注意:这里赋值是字符型数据)—>将修改的内容写到目标文件
》》实例
目标文件
代码:
下面展示一些
内联代码片
。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
int fd1;
int n_write;
int sizeFile1;
int sizeFile2;
char *buf1;
buf1=(char *)malloc(128);
if(argc!=2){
printf("program error\n");
exit(-1);
}
fd1=open(argv[1],O_RDWR);
printf("file1:%d\n",fd1);
sizeFile1=lseek(fd1,0,SEEK_END);//move to file' tail
printf("file1'size:%d\n",sizeFile1);
lseek(fd1,0,SEEK_SET);//move to file' head
read(fd1,buf1,sizeFile1);
printf("%s\n",buf1);
char *p;
p=strstr(buf1,"leng=");//seek for "leng="
printf("%s\n",p);
p=p+strlen("leng=");//addr moving
*p='8';//change value
lseek(fd1,0,SEEK_SET);//move to file' head
n_write=write(fd1,buf1,sizeFile1);
printf("%d\n",n_write);
close(fd1);
return 0;
}
》》运行结果
目标文件内容已改
文件操作之向文件写入整数,结构体
1.写入整数
》》实例
下面展示一些
内联代码片
。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int fd1;
int data1=12;
int *data2;
fd1=open("./file1",O_RDWR);
write(fd1,&data1,sizeof(int));//将数据data1(int)写入文件
lseek(fd1,0,SEEK_SET);//move to the file' head
read(fd1,data2,sizeof(int));//将文件中的数据读到data2
printf("read:%d\n",*data2);
close(fd1);
return 0;
}
》》运行结果
2.写入结构体
》》实例
下面展示一些
内联代码片
。
// An highlighted block
var foo = 'bar';#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
struct test
{
int a;
char ch;
};
int fd1;
struct test data1[2]={{123,'k'},{100,'5'}};
struct test *data2=(struct test *)malloc(2*sizeof(struct test));
fd1=open("./file1",O_RDWR);
write(fd1,&data1,sizeof(struct test)*2);//将结构体数组data1[2]写入文件
lseek(fd1,0,SEEK_SET);//move to the file' head
read(fd1,data2,sizeof(struct test)*2);//将文件里的数据读到结构体数组data2[2]
printf("read1:nu:%d,ch:%c\n",data2->a,data2->ch);
data2++;//array' addr++
printf("read2:nu:%d,ch:%c\n",data2->a,data2->ch);
close(fd1);
return 0;
}
》》运行结果
标准C库对应的文件操作
1, f open()
函数参数:
path:文件名,包括路径,如果不显式含有路径,则表示当前路径。例如,“D:\f1.txt”表示 D 盘根目录下的文件 f1.txt 文件。“f2.doc”表示当前目录下的文件 f2.doc。
mode:文件打开模式,指出对该文件可进行的操作。常见的打开模式如 “r” 表示只读,“w” 表示只写,“rw” 表示读写,“a” 表示追加写入。更多的打开模式如表 2 所示
》》返回值
返回值:打开成功,返回该文件对应的 FILE 类型的指针;打开失败,返回 NULL。故需定义 FILE 类型的指针变量,保存该函数的返回值。可根据该函数的返回值判断文件打开是否成功。
2,fwrite()
函数功能:将 ptr所指向内存中的nmemb个数据块写入 stream 指向的文件中。每个数据块的大小为 size。
返回值:返回实际写入的数据块(非字节)个数,如果该值比 nmemb小,则说明 buf 所指空间中的所有数据块已写完或有错误产生。
函数参数:
ptr:前加const的含义是buf所指的内存空间的数据块只读属性,避免程序中有意或无意的修改。
size:每个数据块所占的字节数。
nmemeb:预写入的数据块最大个数。
stream:文件指针,指向所读取的文件。
注意:使用 fread 和 fwrite 对文件读写操作时,一定要记住使用“二进制模式”打开文件,否则,可能会出现意想不到的错误。
3,fread()
函数功能:从 stream 指向的文件中读取 nmemb个数据块,每个数据块的大小为 size。把读取到的数据块存放到 ptr指针指向的内存空间中。
返回值:返回实际读取的数据块(非字节)个数,如果该值比 nmemb小,则说明已读到文件尾或有错误产生。
函数参数:
ptr:指向存放数据块的内存空间,该内存可以是数组空间,结构体,也可以是动态分配的内存。void类型指针,故可存放各种类型的数据,包括基本类型及自定义类型等。
size:每个数据块所占的字节数。
nmemb:预读取的数据块最大个数。
stream:文件指针,指向所读取的文件。
4,fclose()
函数参数:
fp:已打开的文件指针。
返回值:
该函数返回一个整型数。当文件关闭成功时, 返回0, 否则返回一个非零值。 可以根据函数的返回值判断文件是否关闭成功。
5,fseek()
第一个参数stream为文件指针
第二个参数offset为偏移量,整数表示正向偏移,负数表示负向偏移
第三个参数origin设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、 SEEK_END 或 SEEK_SET
SEEK_SET: 文件开头
SEEK_CUR: 当前位置
SEEK_END: 文件结尾
其中SEEK_SET,SEEK_CUR和SEEK_END依次为0,1和2.
简言之:
fseek(fp,100,0);把fp指针移动到离文件开头100字节处;
fseek(fp,100,1);把fp指针移动到离文件当前位置100字节处;
fseek(fp,100,2);把fp指针退回到离文件结尾100字节处。
————————————————
》》实例
下面展示一些
内联代码片
。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
//FILE *fopen(const char *path, const char *mode);
FILE *fp;
char *buf="xiong qi is hansome";
char readbuf[128]={0};
fp=fopen("./file1","w+");
//size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
fwrite(buf,sizeof(char)*strlen(buf),1,fp);
printf("start:%s\n",buf);
//int fseek(FILE *stream, long offset, int whence);
fseek(fp,0,SEEK_SET);//move to the file' head
//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
fread(readbuf,sizeof(char),strlen(buf),fp);
printf("read:%s\n",readbuf);
fclose(fp);
return 0;
}
》》运行结果
二, 进程
Linux操作系统学会进程的查看及管理是很重要的,了解系统的运行状态。就要了解系统进程。进程是cpu在内存中运行的程序指令,是动态执行的代码。
Linux是一个多用户,多任务的系统,可以同时运行多个用户的多个程序,就必然会产生很多的进程,而每个进程会有不同的状态。
》》Linux 进程状态了解
Linux进程状态:R (TASK_RUNNING),可执行状态。
Linux进程状态:S (TASK_INTERRUPTIBLE),可中断的睡眠状态。
Linux进程状态:D (TASK_UNINTERRUPTIBLE),不可中断的睡眠状态。
Linux进程状态:T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态。
Linux进程状态:Z (TASK_DEAD – EXIT_ZOMBIE),退出状态,进程成为僵尸进程。
Linux进程状态:X (TASK_DEAD – EXIT_DEAD),退出状态,进程即将被销毁。
如何查看Linux进程状态:
执行命令:ps -aux或者ps -aux|grep+可执行文件
fork()创建进程
一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
返回值
在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。
每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。
》》实例
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pr;
pr=fork();//create a child process
int data=10;
pid_t pid;
pid=getpid();
printf("this is process:%d\n",pid);
if(pr>0){
int data1=0;
data1=data-5;
printf("this is father:%d\n",getpid());
printf("data change:%d\n",data1);
}
else if(pr==0){
printf("this is child:%d\n",getpid());
printf("data change:%d\n",data+10);
}
printf("this is process:%d\n",getpid());
printf("data=%d\n",data);
return 0;
}
》》运行结果
fork是把进程当前的情况拷贝一份,执行fork时,进程已经执行完了pid_t pr;fork只拷贝下一个要执行的代码到新的进程。换而言之,就是fork语句之后父进程要跑的内容,子进程都得跟着跑。
当然也可以让父进程和子进程跑不一样的内容,只不过在创建子进程的时候得注意不能同时创建两个相同层次的子进程,多个子进程同时创建执行不同任务时容易跑飞,因为它们在争夺先后顺序,必须嵌套使用。而且在使用sleep睡眠函数时,会起不到效果,原因尚未查明,慎用。
》》实例
下面展示一些
内联代码片
。
#include <stdio.h>
int main()
{
int cmd;
int test;
int pro1,pro2;
while(1){
scanf("%d",&cmd);
if(cmd!=1){
printf("no found cmd\n");
}
else{
pro1=fork();
if(pro1==0){
while(1){
pro2=fork();
int i=0;
if(pro2==0){
while(1){
printf("this is a cmd test,i=%d\n",i++);
sleep(3);
}
}
printf("test is over\n");
sleep(6);
}
}
}
}
return 0;
}
》》运行结果
可以发现sleep语句在子进程里面根本没有起效
vfork()
vfork 保证子进程先运行,在它调用 exec 或 exit 之后父进程才可能被调度运行。如果在 调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
》》实例
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int cnt=0;
pid_t pr;
while(1){
pr=vfork(); //vfork()
if(pr>0){
int cnt1=6;
while(1){
printf("this is father:%d,cnt1=%d\n",getpid(),cnt1);
sleep(2);
cnt1--;
if(cnt1==0){
exit(0);
}
}
}
else if(pr==0){
int cnt2=0;
while(1){
printf("this is child:%d,cnt2=%d\n",getpid(),cnt2);
sleep(2);
cnt2++;
if(cnt2==3){
exit(0);
}
}
}
}
return 0;
}
》》运行结果
父子进程———wait(),exit()
1.wait()
进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程, wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:
pid = wait(NULL);
》》返回值
如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中,这是一个整数值(int),指出了子进程是正常退出还是 被非正常结束的(一个进程也可以被其他进程用信号结束,我们将在以后的文章中介绍),以及正常结束时的返回值,或被哪一个信号结束的等信息。由于这些信息 被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作,下面我们来学习一下其中最 常用的两个:
1.WIFEXITED(status)
这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。 (请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数–指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了。)
2. WEXITSTATUS(status)
当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。
》》实例
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
int cnt=0;
int status=0;
int pid;
pid_t pr;
while(1){
pr=fork(); //vfork()
if(pr>0){
while(1){
pid=wait(&status);//pid是子进程的pid
printf("this is father:%d,pid=%d,status=%d\n",getpid(),pid,WEXITSTATUS(status));
sleep(2);
exit(1);
}
}
else if(pr==0){
int cnt2=0;
while(1){
printf("this is child:%d,cnt2=%d\n",getpid(),cnt);
sleep(2);
cnt++;
if(cnt==3){
exit(5);//退出状态为5
}
}
}
}
return 0;
}
》》运行结果
》》实例2进程+配置文件
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
pid_t pr;
int cmd;
while(1){
printf("please input your cmd\n");
scanf("%d",&cmd);
if(cmd!=2){
printf("cmd error!\n");
}
else{
pr=fork();
if(pr==0){
int fd;
int len;
fd=open("../file/text",O_RDWR);
len=lseek(fd,0,SEEK_END);
char *buf=(char *)malloc(len*sizeof(char));
char *readbuf=(char *)malloc(len*sizeof(char));
lseek(fd,0,SEEK_SET);
read(fd,buf,len);
printf("%s\n",buf);
char *p;
int count;
p=strstr(buf,"leng=");
count=strlen("leng=");
p=p+count;
*p='9';
lseek(fd,0,SEEK_SET);
write(fd,buf,len);
lseek(fd,0,SEEK_SET);
read(fd,readbuf,len);
printf("%s\n",readbuf);
close(fd);
}
}
}
return 0;
}
》》运行结果
exec族函数
execl()其中后缀l代表list也就是参数列表的意思,第一参数path字符指针所指向要执行的文件路径, 接下来的参数代表执行该文件时传递的参数列表:argv[0],argv[1]… 最后一个参数须用空指针NULL作结束。
》》返回值
函数返回值 成功则不返回值, 失败返回-1, 失败原因存于errno中,可通过perror()打印。
execl函数特点:
当进程调用一种exec函数时,该进程完全由新程序代换,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。
》》实例进程+配置文件(execl执行shell文件)
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
pid_t pr;
int cmd;
while(1){
printf("please input your cmd\n");
scanf("%d",&cmd);
if(cmd!=2){
printf("cmd error!\n");
}
else{
pr=fork();
if(pr==0){
execl("../file/changedata","./changedata","../file/text",NULL);
exit(1);
}
}
}
return 0;
}
》》运行结果
配置结果
system()
1.system()函数介绍
system()函数调用/bin/sh来执行参数指定的命令,/bin/sh 一般是一个软连接,指向某个具体的shell,比如bash,-c选项是告诉shell从字符串command中读取命令; 在该command执行期间,SIGCHLD是被阻塞的,好比在说:hi,内核,这会不要给我送SIGCHLD信号,等我忙完再说; 在该command执行期间,SIGINT和SIGQUIT是被忽略的,意思是进程收到这两个信号后没有任何动作。
2.返回值
为了更好的理解system()函数返回值,需要了解其执行过程,实际上system()函数执行了三步操作:
1.fork一个子进程;
2.在子进程中调用exec函数去执行command;
3.在父进程中调用wait去等待子进程结束。
对于fork失败,system()函数返回-1。 如果exec执行成功,也即command顺利执行完毕,则返回command通过exit或return返回的值。 (注意,command顺利执行不代表执行成功,比如command:“rm debuglog.txt”,不管文件存不存在该command都顺利执行了) 如果exec执行失败,也即command没有顺利执行,比如被信号中断,或者command命令根本不存在,system()函数返回127. 如果command为NULL,则system()函数返回非0值,一般为1.
3.system源码
下面展示一些
内联代码片
。
int system(const char * cmdstring)
{
pid_t pid;
int status;
if(cmdstring == NULL)
{
return (1); //如果cmdstring为空,返回非零值,一般为1
}
if((pid = fork())<0)
{
status = -1; //fork失败,返回-1
}
else if(pid == 0)
{
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127); // exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的
进程就不存在啦~~
}
else //父进程
{
while(waitpid(pid, &status, 0) < 0)
{
if(errno != EINTR)
{
status = -1; //如果waitpid被信号中断,则返回-1
break;
}
}
}
return status; //如果waitpid成功,则返回子进程的返回状态
}
》》实例进程+配置文件(system)
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
pid_t pr;
int cmd;
while(1){
printf("please input your cmd\n");
scanf("%d",&cmd);
if(cmd!=2){
printf("cmd error!\n");
}
else{
pr=fork();
if(pr==0){
system("../file/changedata ../file/text");
exit(1);
}
}
}
return 0;
}
执行结果和上面一致。
popen()
函数说明
popen() 函数通过创建一个管道,调用 fork 产生一个子进程,执行一个 shell 以运行命令来开启一个进程。这个进程必须由 pclose() 函数关闭,而不是 fclose() 函数。pclose() 函数关闭标准 I/O 流,等待命令执行结束,然后返回 shell 的终止状态。如果 shell 不能被执行,则 pclose() 返回的终止状态与 shell 已执行 exit 一样。
type 参数
只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。
command 参数
是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到 bin/sh 并使用-c 标志,shell 将执行这个命令。 popen 的返回值是个标准 I/O 流,必须由 pclose 来终止。前面提到这个流是单向的。所以向这个流写内容相当于写入该命令的标准输入;命令的标准输出和调用 popen 的进程相同。与之相反的,从流中读数据相当于读取命令的标准输出;命令的标准输入和调用 popen 的进程相同。
》》返回值
如果调用 fork() 或 pipe() 失败,或者不能分配内存将返回NULL,否则返回标准 I/O 流。
返回错误 popen 没有为内存分配失败设置 errno 值。
如果调用 fork() 或 pipe() 时出现错误,errno 被设为相应的错误类型。 如果 type 参数不合法,errno将返回EINVAL。
popen()实际上就是把command 执行命令的结果指向FILE *文件 I/O 流,然后我们就可以对运行结果(这里指命令运行过程中打印的数据,其内部的运行效果并不会进行展示,也读不到)进行读取,再写出来。
》》实例 进程+文件配置(popen)
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
pid_t pr;
int cmd;
FILE *fp;//文件I/O流
char *backbuf=(char *)malloc(1024);
while(1){
printf("please input your cmd\n");
scanf("%d",&cmd);
if(cmd!=2){
printf("cmd error!\n");
}
else{
pr=fork();
if(pr==0){
fp=popen("../file/changedata ../file/text","r");
int n_read=fread(backbuf,1,1024,fp);
printf("read %d bytes data:%s",n_read,backbuf);
exit(1);
}
}
}
return 0;
}
》》运行结果与上面相同。
三, 进程通信
进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。
无名管道
特点:
1.半双工通信,即数据流只能在一个方向上流动
2.亲缘进程之前的通信
3.特殊的一种文件,不属于任何文件系统,只存在于在内存中
1.创建无名管道
当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。
返回值:若成功返回0,失败返回-1。
(1)使用fork()之后的半双工通信管道
(2)从父进程到子进程的通信管道
》》实例
下面展示一些
内联代码片
。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int pr;
int fd[2];
char readbuf[128];
if(pipe(fd)==-1){//创建无名管道
printf("pipe create failed\n");
exit(1);
}
pr=fork();
if(pr<0){
printf("create failed\n");
}
else if(pr>0){
printf("this is father,%d\n",getpid());
close(fd[0]);
write(fd[1],"hello this is your father",strlen("hello this is your father"));
}
else{
close(fd[1]);
read(fd[0],readbuf,128);
printf("this is child,%d\n",getpid());
printf("father say :%s\n",readbuf);
}
close(fd[0]);
close(fd[1]);
return 0;
}
》》运行结果
FIFO有名管道
1、FIFO也称命名管道,是一种特殊文件类型
2、命名管道的打开规则
(1)只读且阻塞方式
open(const char *pathname, O_RDONLY);
(2)只读且非阻塞方式
open(const char *pathname, O_RDONLY | O_NONBLOCK);
(3)只写且阻塞方式
open(const char *pathname, O_WRONLY);
(4)只写且非阻塞方式
open(const char *pathname, O_WRONLY | O_NONBLOCK);
3、特点
(1)可在无关进程间通信
(2)关联路径名,是一种存在于文件系统中的设备文件
1.创建FIFO
#include<sys/stat.h>
int mkfifo(const char
pathname, mode_t mode);
*
参数pathname指出想要创建的FIFO路径,参数mode指定创建的FIFO访问模式。这个访问会与当前进程的umask进程运算,以产生实际应用的权限模式。
》》返回值
返回值:成功,0;失败,-1。
》》实例
(1)read_fifo.c文件内容如下:
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main()
{
if(mkfifo("./file",0600)==0){//创建有名管道
printf("mkfifo successful\n");
}
else{
printf("create failed\n");
perror("why");
}
int fd;//
while(1){
char readbuf[128]={0};
fd=open("./file",O_RDONLY);//打开管道
read(fd,readbuf,128);//从管道读取信息
printf("read context:%s\n",readbuf);
sleep(2);
}
close(fd);
return 0;
}
(2)write_fifo.c文件内容如下:
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd;
fd=open("./file",O_WRONLY);//打开管道
while(1){
sleep(2);
write(fd,"xq123456789",strlen("xq123455789"));//向管道中写入信息
printf("write sucessful\n");
}
close(fd);
return 0;
}
》》运行结果
消息队列
1 、消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
2、消息队列跟命名管道有不少的相同之处,通过与命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。在命名管道中,发送数据用write,接收数据用read,则在消息队列中,发送数据用msgsnd,接收数据用msgrcv。而且它们对每个数据都有一个最大长度的限制。
3、与命名管道相比,消息队列的优势在于,1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。
#include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);
4 // 添加消息:成功返回0,失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
6 // 读取消息:成功返回消息数据的长度,失败返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
4、各个函数详解:
(1)创建或打开消息队列函数
int msgget(key_t, key, int msgflg);
与其他的IPC机制一样,程序必须提供一个键来命名某个特定的消息队列。msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。
在程序中若要使用消息队列,必须要能知道消息队列key,因为应用进程无法直接访问内核消息队列中的数据结构,因此需要一个消息队列的标识,让应用进程知道当前操作的是哪个消息队列,同时也要保证每个消息队列key值的唯一性。
申请一块内存,创建一个新的消息队列(数据结构msqid_ds),将其初始化后加入到msgque向量表中的某个空位置处,返回标示符。或者在msgque向量表中找键值为key的消息队列。
(2)添加消息函数
int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
msgid是由msgget函数返回的消息队列标识符。
msg_ptr是一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针msg_ptr所指向的消息结构一定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型。所以消息结构要定义成这样:
struct my_message{ long int message_type; /* The data you wish to transfer*/ };
msg_sz是msg_ptr指向的消息的长度,注意是消息的长度,而不是整个结构体的长度,也就是说msg_sz是不包括长整型消息类型成员变量的长度。
msgflg用于控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。
如果调用成功,消息数据的一分副本将被放到消息队列中,并返回0,失败时返回-1.
(3)从一个消息队列中获取消息:
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
msgid, msg_ptr, msg_st的作用也函数msgsnd函数的一样。
msgtype可以实现一种简单的接收优先级。如果msgtype为0,就获取队列中的第一个消息。如果它的值大于零,将获取具有相同消息类型的第一个信息。如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息。
msgflg用于控制当队列中没有相应类型的消息可以接收时将发生的事情。
调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1.
(4)消息队列控制函数:
int msgctl(int msgid, int command, struct msgid_ds *buf);
command是将要采取的动作,它可以取3个值:
IPC_STAT
:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。
IPC_SET
:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
IPC_RMID
:删除消息队列
buf是指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。msgid_ds结构至少包括以下成员:
struct msgid_ds { uid_t shm_perm.uid; uid_t shm_perm.gid; mode_t shm_perm.mode; };
成功时返回0,失败时返回-1.
5,ftok()函数
系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
ftok原型如下:
key_t ftok( char * fname, int id )
fname就时你指定的文件名(该文件必须是存在而且可以访问的),id是子序号,虽然为int,但是只有8个比特被使用(0-255)。
当成功执行的时候,一个key_t值将会被返回,否则 -1 被返回。
》》实例
(1)消息发送端:send.c文件内容如下
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <string.h>
#include <sys/msg.h>
int main()
{
key_t key;
key=ftok(".",'5');//get key
printf("key:%x\n",key);
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int msgid;
msgid=msgget(key,IPC_CREAT|0777);//创建消息队列
if(msgid==-1){
printf("create que failed\n");
exit(1);
}
struct msgbuf sendbuf={666,"hello Mary!"};
struct msgbuf rebuf;
int send_v=msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);//send information
if(send_v==0){
printf("send successful\n");
}
int re_v=msgrcv(msgid,&rebuf,128,888,0);//receive information
if(re_v!=-1){
printf("receive successful,context:%s\n",rebuf.mtext);
}
msgctl(msgid,IPC_RMID,NULL);//销毁消息队列
return 0;
}
(2)消息接收端:receive.c文件如下
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <string.h>
#include <sys/msg.h>
int main()
{
key_t key;
key=ftok(".",'5');
printf("key:%x\n",key);
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int msgid;
msgid=msgget(key,IPC_CREAT|0777);//创建消息队列
if(msgid==-1){
printf("create que failed\n");
exit(1);
}
struct msgbuf sendbuf={888,"hello Bob!"};
struct msgbuf rebuf;
int re_v=msgrcv(msgid,&rebuf,128,666,0);//receive information
if(re_v!=-1){
printf("receive successful,context:%s\n",rebuf.mtext);
}
int send_v=msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);//send information
if(send_v==0){
printf("send successful\n");
}
msgctl(msgid,IPC_RMID,NULL);//销毁消息队列
return 0;
}
》》运行结果
共享内存
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
1、特点
共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
因为多个进程可以同时操作,所以需要进行同步。
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
2,原型
#include <sys/shm.h>
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr);
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
当用shmget函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为0 。
当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。
shmdt函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。
shmctl函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。
》》实例
(1)send代码
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/ipc.h>
#include <string.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <sys/types.h>
int main()
{
key_t key;
key=ftok(".",23);//创建一个键值
int shmid;
char *shm;
shmid=shmget(key,1024*4,IPC_CREAT|0666);//创建共享内存
if(shmid==-1){
printf("sham create failed\n");
exit(1);
}
shm=shmat(shmid,0,0);//连接共享内存
strcpy(shm,"xqxy1998");//向共享内存写数据
printf("send successful\n");
sleep(5);
shmdt(shm);//断开共享内存连接
shmctl(shmid,IPC_RMID,NULL);//销毁共享内存
return 0;
}
(2)receive代码:
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/ipc.h>
#include <string.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <sys/types.h>
int main()
{
key_t key;
key=ftok(".",23);
int shmid;
char *shm;
shmid=shmget(key,1024*4,IPC_CREAT|0666);//创建共享内存
if(shmid==-1){
printf("sham create failed\n");
exit(1);
}
shm=shmat(shmid,0,0);//连接共享内存
printf("re:%s\n",shm);//打印共享内存中的数据
shmdt(shm);//断开共享内存连接
shmctl(shmid,IPC_RMID,NULL);//销毁共享内存
return 0;
}
》》运行结果
信号
1,
信号概述
1,信号的名字和编号:
每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
信号定义在
signal.h
头文件中,信号名都定义为正整数。
具体的信号名称可以使用
kill -l
来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。
2,信号的处理:
信号的处理有三种方法,分别是:忽略、捕捉和默认动作
》》
忽略信号
,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
》》
捕捉信号
,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
》》
系统默认动作
,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
具体的信号默认动作可以使用
man 7 signal
来查看系统的具体定义。在此,我就不详细展开了,需要查看的,可以自行查看。也可以参考 《UNIX 环境高级编程(第三部)》的 P251——P256中间对于每个信号有详细的说明。
信号处理函数的注册
入门版:函数
signal
高级版:函数
sigaction
信号处理发送函数
1.入门版:
kill
2.高级版:
sigqueue
2,信号注册函数——入门版
在正式开始了解这两个函数之前,可以先来思考一下,处理中断都需要处理什么问题。
按照我们之前思路来看,可以发送的信号类型是多种多样的,每种信号的处理可能不一定相同,那么,我们肯定需要知道到底发生了什么信号。
另外,虽然我们知道了系统发出来的是哪种信号,但是还有一点也很重要,就是系统产生了一个信号,是由谁来响应?
如果系统通过 ctrl+c 产生了一个 SIGINT(中断信号),显然不是所有程序同时结束,那么,信号一定需要有一个接收者。对于处理信号的程序来说,接收者就是自己。
根据函数原型可以看出由两部分组成,一个是真实处理信号的函数,另一个是注册函数了。
对于sighandler_t signal(int signum, sighandler_t handler);函数来说,signum 显然是信号的编号,handler 是中断函数的指针。
同样,typedef void (*sighandler_t)(int);中断函数的原型中,有一个参数是 int 类型,显然也是信号产生的类型,方便使用一个函数来处理多个信号。
》》实例
下面展示一些
内联代码片
。
#include <stdio.h>
#include <signal.h>
void handler(int signum)
{
switch(signum){
case 2:
printf("SIGINT\n");
break;
case 9:
printf("SIGKILL\n");
break;
case 30:
printf("SIGPWR\n");
break;
}
}
int main()
{
int pid=getpid();
signal(SIGINT,handler);
signal(SIGPWR,handler);
signal(SIGKILL,handler);
printf("pid:%d\n",pid);
while(1);
return 0;
}
》》运行结果
可以看出**“kill -9”**的命令不能被信号处理函数执行,因为它的权限比较高。
3,信号发送函数——入门版
正如我之前所说的,信号的处理需要有接受者,显然发送者必须要知道发给谁,根据 kill 函数的远行可以看到,pid 就是接受者的 pid,sig 则是发送的信号的类型。
》》实例
下面展示一些
内联代码片
。
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc, char **argv)
{
int pid;
int sig;
pid=atoi(argv[2]);
sig=atoi(argv[1]);
//int kill(pid_t pid, int sig);
kill(pid,sig);
while(1);
return 0;
}
》》运行结果
用另外一种方式实现以上功能(system)
下面展示一些
内联代码片
。
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
int main(int argc, char **argv)
{
int pid;
int sig;
char cmd[128];
pid=atoi(argv[2]);
sig=atoi(argv[1]);
//int kill(pid_t pid, int sig);
sprintf(cmd,"kill -%d %d",sig,pid);
//kill(pid,sig);
system(cmd);
while(1);
return 0;
}
4,信号注册函数——高级版
我们已经成功完成了信号的收发,那么为什么会有高级版出现呢?其实之前的信号存在一个问题就是,虽然发送和接收到了信号,可是总感觉少些什么,既然都已经把信号发送过去了,为何不能再携带一些数据呢?
正是如此,我们需要另外的函数来通过信号传递的过程中,携带一些数据。咱么先来看看发送的函数吧。
下面展示一些
内联代码片
。
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
sigset_t sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
int sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
};
//回调函数句柄sa_handler、sa_sigaction只能任选其一
sigaction 是一个系统调用,根据这个函数原型,我们不难看出,在函数原型中,第一个参数signum应该就是注册的信号的编号;第二个参数act如果不为空说明需要对该信号有新的配置;第三个参数oldact如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。
在这里额外说一下struct sigaction结构体中的 sa_mask 成员,设置在其的信号集中的信号,会在捕捉函数调用前设置为阻塞,并在捕捉函数返回时恢复默认原有设置。这样的目的是,在调用信号处理函数时,就可以阻塞默写信号了。在信号处理函数被调用时,操作系统会建立新的信号阻塞字,包括正在被递送的信号。因此,可以保证在处理一个给定信号时,如果这个种信号再次发生,那么他会被阻塞到对之前一个信号的处理结束为止。
sigaction 的时效性:当对某一个信号设置了指定的动作的时候,那么,直到再次显式调用 sigaction并改变动作之前都会一直有效。
关于结构体中的 flag 属性的详细配置,在此不做详细的说明了,只说明其中一点。如果设置为 SA_SIGINFO 属性时,说明了信号处理程序带有附加信息,也就是会调用 sa_sigaction 这个函数指针所指向的信号处理函数。否则,系统会默认使用 sa_handler 所指向的信号处理函数。在此,还要特别说明一下,sa_sigaction 和 sa_handler 使用的是同一块内存空间,相当于 union,所以只能设置其中的一个,不能两个都同时设置。
关于void (*sa_sigaction)(int, siginfo_t *, void *);处理函数来说还需要有一些说明。void *是接收到信号所携带的额外数据;而struct siginfo_t 这个结构体主要适用于记录接收信号的一些相关信息。
下面展示一些
内联代码片
。
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
int si_band; /* Band event */
int si_fd; /* File descriptor */
}
其中的成员很多,si_signo 和 si_code 是必须实现的两个成员。可以通过这个结构体获取到信号的相关信息。
关于发送过来的数据是存在两个地方的,sigval_t si_value这个成员中有保存了发送过来的信息;同时,在si_int或者si_ptr成员中也保存了对应的数据。
下面展示一些
内联代码片
。
union sigval {
int sival_int;
void *sival_ptr;
};
5,信号发送函数——高级版
下面展示一些
内联代码片
。
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
使用这个函数之前,必须要有几个操作需要完成
1,使用 sigaction 函数安装信号处理程序时,制定了 SA_SIGINFO 的标志。
2,sigaction 结构体中的 sa_sigaction 成员提供了信号捕捉函数。如果实现的时 sa_handler 成员,那么将无法获取额外携带的数据。
3,sigqueue 函数只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值。
sigqueue 函数不但可以发送额外的数据,还可以让信号进行排队(操作系统必须实现了 POSIX.1的实时扩展),对于设置了阻塞的信号,使用 sigqueue 发送多个同一信号,在解除阻塞时,接受者会接收到发送的信号队列中的信号,而不是直接收到一次。
但是,信号不能无限的排队,信号排队的最大值受到SIGQUEUE_MAX的限制,达到最大限制后,sigqueue 会失败,errno 会被设置为 EAGAIN。
》》实例,信号收发+附加信息
发送方
下面展示一些
内联代码片
。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int signum=atoi(argv[1]);//信号数字代码
int pid=atoi(argv[2]);//接收方进程pid
union sigval value;
value.sival_int=100;//附加信息
printf("my pid:%d\n",getpid());
sigqueue(pid,signum,value);
while(1);
return 0;
}
接收方
下面展示一些
内联代码片
。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void handler(int signum, siginfo_t *info, void *context)//接受处理函数
{
printf("re signum:%d\n",signum);
if(context!=NULL){
printf("information from %d\n",info->si_pid);//打印发送方进程pid
printf("data:%d\n",info->si_int);//打印附加信息
printf("dat1:%d\n",info->si_value.sival_int);
}
}
int main()
{
printf("my pid: %d\n",getpid());
struct sigaction act;
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=handler;
sigaction(SIGQUIT,&act,NULL);
while(1);
return 0;
}
》》运行结果
上述的是传递附加信息是整数型,比如得到发送方的pid,那怎么传送字符串信息呢,这个可以参考以下链接:
《Linuxc 信号的使用6 传附带的字符串变量》, 一起来围观吧
链接:
link
.
信号量
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
1、特点
1,信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
2,信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
3,每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
支持信号量组。
2、原型
最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。
Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。
下面展示一些
内联代码片
。
1 #include <sys/sem.h>
2 // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
3 int semget(key_t key, int num_sems, int sem_flags);
4 // 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
5 int semop(int semid, struct sembuf semoparray[], size_t numops);
6 // 控制信号量的相关信息
7 int semctl(int semid, int sem_num, int cmd, ...);
1,int semget(key_t key, int num_sems, int sem_flags)
第一个参数
key
是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用
semget
函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
第二个参数
num_sems
指定需要的信号量数目,它的值几乎总是1。
第三个参数
sem_flags
是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值
IPC_CREAT
做按位或操作。设置了
IPC_CREAT
标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而
IPC_CREAT | IPC_EXCL
则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
2,int semop(int semid, struct sembuf semoparray[], size_t numops)
它的作用是改变信号量的值。sem_id是由semget返回的信号量标识符,sembuf结构的定义如下:
下面展示一些
内联代码片
。
struct sembuf{
short sem_num;//除非使用一组信号量,否则它为0
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
3,int semctl(int semid, int sem_num, int cmd, …)
在semctl函数中的命令有多种,这里就说两个常用的:
SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
IPC_RMID:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。
3, 实例—父子进程运行先后顺序
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void pGetKey(int semid)//P操作,等待信号
{
struct sembuf sop;
sop.sem_num=0;
sop.sem_op=-1;
sop.sem_flg=SEM_UNDO;
semop(semid,&sop,1);
printf("gett a key\n");
}
void vPutKey(int semid)//V操作,发送信号
{
struct sembuf sop;
sop.sem_num=0;
sop.sem_op=1;
sop.sem_flg=SEM_UNDO;
semop(semid,&sop,1);
printf("put a key\n");
}
int main()
{
int semid;
key_t key;
key=ftok(".",5);
semid=semget(key,1,IPC_CREAT|0666);//创建信号量
union semun tem;
tem.val=0;
semctl(semid,0,SETVAL,tem);//初始化信号量
int pid=fork();
if(pid>0){
pGetKey(semid);//等待信号
printf("this is father\n");
vPutKey(semid);//发送信号
semctl(semid,0,IPC_RMID);
}
else if(pid==0){
printf("this is child\n");
vPutKey(semid);//发送信号
}
else{
printf("fork error\n");
}
return 0;
}
》》运行结果
这里的P操作(pGetKey()函数)只有在V操作(vPutKey())发送信号之后才会执行下面的语句。
4,实例—共享内存访问
发送方
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/msg.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int sem_creat(key_t key)//创建信号量
{
int semid;
semid=semget(key,1,IPC_CREAT|0666);
if(semid==-1){
printf("sem failed\n");
exit(1);
}
return semid;
}
void sem_init(int semid,union semun temp)//信号量初始化
{
temp.val=1;
semctl(semid,0,SETVAL,temp);
}
int shm_creat(key_t key)//创建共享内存
{
int shamid;
shamid=shmget(key,1024*4,IPC_CREAT|0666);
if(shamid==-1){
printf("shm error\n");
exit(1);
}
return shamid;
}
void pGetKey(int semid)//P操作---等待信号
{
//int semop(int semid, struct sembuf *sops, unsigned nsops);
struct sembuf sop;
sop.sem_num=0;
sop.sem_op=-1;
sop.sem_flg=SEM_UNDO;
semop(semid,&sop,1);
}
void vPutKey(int semid)//V操作---发送信号
{
//int semop(int semid, struct sembuf *sops, unsigned nsops);
struct sembuf sop;
sop.sem_num=0;
sop.sem_op=1;
sop.sem_flg=SEM_UNDO;
semop(semid,&sop,1);
}
int msg_creat(key_t key)//创建消息队列
{
//int msgget(key_t key, int msgflg);
int msgid;
msgid=msgget(key,IPC_CREAT|0777);
if(msgid==-1){
printf("msg error\n");
exit(1);
}
return msgid;
}
int main(int argc,char **argv)
{
key_t key1,key2,key3;
char *p;
union semun temp;
struct msgbuf msginfo;
msginfo.mtype=666;
strcpy(msginfo.mtext,argv[1]);
key1=ftok(".",25);
key2=ftok(".",45);
key3=ftok(".",20);
int msgid;
int shamid;
int semid;
shamid=shm_creat(key1);
msgid=msg_creat(key3);
semid=sem_creat(key2);
sem_init(semid,temp);
int send_v=msgsnd(msgid,&msginfo,strlen(msginfo.mtext)+strlen(" "),0);
if(send_v==0){
printf("send successful\n");
}
p=shmat(shamid,0,0);
strcpy(p,"xq hello");
pGetKey(semid);
sleep(10);
vPutKey(semid);
shmdt(p);
shmctl(shamid,IPC_RMID,NULL);
semctl(semid,0,IPC_RMID);
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
接收方
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/msg.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int sem_creat(key_t key)
{
int semid;
semid=semget(key,1,IPC_CREAT|0666);
if(semid==-1){
printf("sem failed\n");
exit(1);
}
return semid;
}
void sem_init(int semid,union semun temp)
{
temp.val=1;
semctl(semid,0,SETVAL,temp);
}
int shm_creat(key_t key)
{
int shamid;
shamid=shmget(key,1024*4,IPC_CREAT|0666);
if(shamid==-1){
printf("shm error\n");
exit(1);
}
return shamid;
}
void pGetKey(int semid)//P操作
{
//int semop(int semid, struct sembuf *sops, unsigned nsops);
struct sembuf sop;
sop.sem_num=0;
sop.sem_op=-1;
sop.sem_flg=SEM_UNDO;
semop(semid,&sop,1);
}
void vPutKey(int semid)//V操作
{
//int semop(int semid, struct sembuf *sops, unsigned nsops);
struct sembuf sop;
sop.sem_num=0;
sop.sem_op=1;
sop.sem_flg=SEM_UNDO;
semop(semid,&sop,1);
}
int msg_creat(key_t key)
{
//int msgget(key_t key, int msgflg);
int msgid;
msgid=msgget(key,IPC_CREAT|0777);
if(msgid==-1){
printf("msg error\n");
exit(1);
}
return msgid;
}
int main(int argc,char **argv)
{
key_t key1,key2,key3;
char *p;
union semun temp;
struct msgbuf msginfo;
key1=ftok(".",25);
key2=ftok(".",45);
key3=ftok(".",20);
int msgid;
int shamid;
int semid;
semid=sem_creat(key2);//创建信号量
sem_init(semid,temp);//初始化信号量
shamid=shm_creat(key1);//创建共享内存
p=shmat(shamid,0,0);//连接共享内存
msgid=msg_creat(key3);//创建消息队列
int re_v=msgrcv(msgid,&msginfo,strlen(msginfo.mtext)+strlen(" "),666,0);
if(re_v!=-1){
printf("receive cmd:%s\n",msginfo.mtext);
printf("receive sucessful\n");
}
else{
printf("receive failed\n");
}
printf("nei:%s\n",p);
if(strcmp(msginfo.mtext,"read")==0){
pGetKey(semid);
printf("context:%s\n",p);
vPutKey(semid);
}
shmdt(p);
shmctl(shamid,IPC_RMID,NULL);
semctl(semid,0,IPC_RMID);
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
》》运行结果
四,线程
Linux 线程介绍
进程和线程
典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。
进程是程序执行时的一个实例,是担当分配系统资源(CPU时间、内存等)的基本单位。在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程包含了表示进程内执行环境必须的信息,其中包括进程中表示线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno常量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。在Unix和类Unix操作系统中线程也被称为轻量级进程(lightweight processes),但轻量级进程更多指的是内核线程(kernel thread),而把用户线程(user thread)称为线程。
“进程——资源分配的最小单位,线程——程序执行的最小单位”
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
使用线程的理由
从上面我们知道了进程与线程的区别,其实这些区别也就是我们使用线程的理由。总的来说就是:进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)。
使用多线程的理由之一
是和进程相比,它是一种非常”节俭”的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种”昂贵”的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
使用多线程的理由之二
是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
》》提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
》》使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
》》改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
线程创建,等待,退出
多线程开发在 Linux 平台上已经有成熟的 pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中,线程操作又分线程的创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别是创建,销毁,加锁和解锁。条件操作有 5 种操作:创建,销毁,触发,广播和等待。其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。详细请见下表:
1. 线程创建
int pthread_create(pthread_t * tid, const pthread_attr_t * attr, void * ( * func) (void * ), void * arg);
其返回值是一个整数,若创建进程成功返回0,否则,返回其他错误代码,也是正整数。
创建线程需要的参数:
》》线程变量名:pthread_t *类型,是标示线程的 id,一般是无符号整形,这里也可以是引用类型,目的是用于返回创建线程的 ID
》》线程的属性指针:制定线程的属性,比如线程优先级,初始栈大小等,通常情况使用的都是指针。
》》创建线程的程序代码:一般是函数指针,进程创建后执行该函数指针只想的函数。
》》程序代码的参数:若线程执行的函数包含由若干个参数,需要将这些参数封装成结构体,并传递给它指针。
2.结束线程
结束进程的函数定义如下:
void pthread_exit (void *status);
参数是指针类型,用于存储线程结束后返回状态。
3.线程等待
线程创建后怎么执行,新线程和老线程谁先执行这些不是程序来决定,而是由操作系统进行调度的,但是在编程的时候我们常常需要多个线程配合工作,比如在结束某个线程之前,需要等待另外一个线程的处理结果(返回状态等信息),这时候就需要使用线程等待函数。
比如说:当主线程退出以后,如果还有线程在执行,那么该线程也会随之退出,因此我们需要在主线程里增加一个线程等待函数。这个函数的定义如下:
功能:以阻塞的方式回收新线程,可以得到线程的退出码,并回收其资源
如果不使用pthread_join回收线程,有可能造成和僵尸进程一样的问题,造成内存泄漏;
#include <pthread.h>
int pthread_join(pthread_t thread, //要等待的线程ID
void **retval);//用于接收线程退出的退出码 ,即pthread_exit (void *status),该函数退出的状态回收,如果不关心返回的状态,可以设置为NULL
调用这个函数,如果创建的线程没有返回状态的话,会一直阻塞在这边。
》》第一个参数表示要等待的进程的id;
》》第二参数表示要等待的进程的返回状态,是个二级指针。
4.其他关于进程的函数
返回当前线程ID
pthread_t pthread_self (void);
用于返回当前进程的ID
制定线程变成分裂状态
int pthread_detach (pthread_t tid);
参数是指定线程的ID,指定的ID的线程变成分离状态;若指定线程是分离状态,则 如果线程退出,那么它所有的资源都将释放,如果线程不是分离状态,线程必须保留它的线程ID、退出状态,直到其他线程对他调用的pthread_join()函数
》》实例
下面展示一些
内联代码片
。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
struct paramater
{
int num;
char ch[128];
};
void *fun1 (void *param)//线程执行函数
{
static char *status="t1 run out";
static int sta=16;
printf("t1:num:%d,char:%s\n",((struct paramater *)param)->num,((struct paramater *)param)->ch);
printf("tid:%d\n",(unsigned int)pthread_self());
pthread_exit((void *)status);//线程退出
}
int main()
{
pthread_t t1;
char *re;
int *re1;
struct paramater arg;
arg.num=56;
strcpy(arg.ch,"this is a thread");
int rec;
rec=pthread_create(&t1,NULL,fun1,(void *)&arg);//创建线程
if(rec==0){
printf("thread successful\n");
}
printf("main:%d\n",(unsigned int)pthread_self());
pthread_join (t1,(void **)&re);//等待线程退出
printf("return:%s\n",re);
return 0;
}
》》运行结果
线程共享内存
一个进程里面有多个线程,叫做线程组。多个线程访问的内存空间是同一个,包括数据段,堆,栈,参数等等。既可以访问进程里的任意一个变量,也可以改变其的值。
》》实例
下面展示一些
内联代码片
。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
struct paramater
{
int num;
char ch[128];
};
int data=0;
void *fun1 (void *param)//线程1执行函数
{
static char *status="t1 run out";
static int sta=16;
printf("t1:num:%d,char:%s\n",((struct paramater *)param)->num,((struct paramater *)param)->ch);
printf("t2:tid:%d\n",(unsigned int)pthread_self());//打印当前线程id
while(1){
printf("t1:data:%d\n",data++);
sleep(2);
if(data>5)
break;
}
printf("thread1 run out\n");
pthread_exit((void *)&sta);
}
void *fun2 (void *param)//线程2执行函数
{
static char *status="t1 run out";
static int sta=10;
printf("t2:num:%d,char:%s\n",((struct paramater *)param)->num,((struct paramater *)param)->ch);
printf("t2:tid:%d\n",(unsigned int)pthread_self());
while(1){
printf("t2:data:%d\n",data++);
sleep(2);
if(data>9)
break;
}
printf("thread2 run out\n");
pthread_exit((void *)&sta);
}
int main()
{
pthread_t t1;
pthread_t t2;
char *re;
int *re1;
int *re2;
struct paramater arg;
arg.num=56;
strcpy(arg.ch,"this is a thread");
int rec=0;
int rec1=0;
rec=pthread_create(&t1,NULL,fun1,(void *)&arg);//创建线程1
if(rec==0){
printf("thread successful\n");
}
rec1=pthread_create(&t2,NULL,fun2,(void *)&arg);//创建线程2
if(rec1==0){
printf("thread successful\n");
}
while(1){//主线程
printf("main:data:%d\n",data++);
sleep(2);
}
printf("main:%d\n",(unsigned int)pthread_self());
pthread_join (t1,(void **)&re1);//等待线程退出
pthread_join (t2,(void **)&re2);
printf("t1:return:%d\n",*re1);
printf("t2:return:%d\n",*re2);
return 0;
}
》》运行结果
互斥量解锁加锁
互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为可运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去等待它重新变为可用。在这种方式下,每次只有一个线程可以向前运行。
在设计时需要规定所有的线程必须遵守相同的数据访问规则。只有这样,互斥机制才能正常工作。操作系统并不会做数据访问的串行化。如果允许其中的某个线程在没有得到锁的情况下也可以访问共享资源,那么即使其它的线程在使用共享资源前都获取了锁,也还是会出现数据不一致的问题。
互斥变量用pthread_mutex_t数据类型表示。在使用互斥变量前必须对它进行初始化,可以把它置为常量
PTHREAD_MUTEX_INITIALIZER
(只对静态分配的互斥量),也可以通过调用
pthread_mutex_init
函数进行初始化。如果动态地分配互斥量(例如通过调用malloc函数),那么在释放内存前需要调用
pthread_mutex_destroy
。
下面展示一些
内联代码片
。
//互斥锁的接口
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//函数应销毁mutex引用的mutex对象
//注意!!!
//销毁已解锁的已初始化互斥体应是安全的。试图销毁锁定的互斥体会导致未定义的行为。
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
//mutex:互斥锁变量
//attr:属性,通常为NULL
//应使用attr指定的属性初始化mutex引用的mutex
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//宏PTHREAD_MUTEX_INITIALIZER来静态的初始化锁
//互斥锁变量不一定非要全局变量--只要保证要互斥的线程都能访问到就行
int pthread_mutex_lock(pthread_mutex_t *mutex);
//锁定mutex引用的mutex对象。如果互斥体已被锁定,则调用线程应阻塞,直到互斥体可用。此操作将返回互斥对象引用的互斥对象处于锁定状态,调用线程作为其所有者。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//函数应等同于pthread_mutex_lock(),但如果mutex引用的mutex对象当前被锁定(由任何线程,包括当前线程),则调用应立即返回。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//函数应释放mutex引用的mutex对象。互斥体的释放方式取决于互斥体的type属性。如果在调用pthread_mutex_unlock()时,mutex引用的mutex对象上有线程被阻塞,导致mutex可用,调度策略应确定哪个线程应获取mutex。
》》实例
下面展示一些
内联代码片
。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
struct paramater
{
int num;
char ch[128];
};
int data=0;
pthread_mutex_t mutex;//定义互斥量
void *fun1 (void *param)
{
int i=0;
pthread_mutex_lock(&mutex);//锁定互斥量
static int sta=16;
for(i=0;i<3;i++){
printf("t1:num:%d,char:%s\n",((struct paramater *)param)->num,((struct paramater *)param)->ch);
printf("t2:tid:%d\n",(unsigned int)pthread_self());
sleep(2);
}
pthread_mutex_unlock(&mutex);//解锁互斥量
// pthread_exit((void *)&sta);
//pthread_exit((void *)&sta);
}
void *fun2 (void *param)
{
int j=0;
pthread_mutex_lock(&mutex);//锁定互斥量
static int sta1=10;
for(j=0;j<5;j++){
printf("t2:num:%d,char:%s\n",((struct paramater *)param)->num,((struct paramater *)param)->ch);
printf("t2:tid:%d\n",(unsigned int)pthread_self());
sleep(2);
}
pthread_mutex_unlock(&mutex);//解锁互斥量
// pthread_exit((void *)&sta1);
//pthread_exit((void *)&sta);
}
void *fun3 (void *param)
{
pthread_mutex_lock(&mutex);//锁定互斥量
static int sta2=15;
printf("t3:num:%d,char:%s\n",((struct paramater *)param)->num,((struct paramater *)param)->ch);
printf("t3:tid:%d\n",(unsigned int)pthread_self());
pthread_mutex_unlock(&mutex);//解锁互斥量
// pthread_exit((void *)&sta2);
}
int main()
{
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
pthread_t t1;
pthread_t t3;
pthread_t t2;
// memset(re,0,128);
int rec=0;
int rec1=0;
int rec2=0;
int *re1;
int *re2;
int *re3;
struct paramater arg;
arg.num=56;
strcpy(arg.ch,"this is a thread");
pthread_mutex_init(&mutex,NULL);//初始化互斥量
rec=pthread_create(&t1,NULL,fun1,(void *)&arg);//创建线程
if(rec==0){
printf("thread successful\n");
}
rec1=pthread_create(&t2,NULL,fun2,(void *)&arg);
if(rec1==0){
printf("thread successful\n");
}
rec2=pthread_create(&t3,NULL,fun3,(void *)&arg);
if(rec2==0){
printf("thread successful\n");
}
printf("main:%d\n",(unsigned int)pthread_self());
pthread_join (t1,NULL);
pthread_join (t2,NULL);
pthread_join (t3,NULL);
pthread_mutex_destroy(&mutex);//销毁互斥量
return 0;
}
》》运行结果
加解锁使用共享内存
和信号量的P操作和V操作类似,只有在解锁之后,后面阻塞的线程才可以进行操作(上锁),而且上锁之后,其他线程只能等待。
死锁
在进行加锁的过程中很有可能发生死锁的情况下。
在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他的进程所占用不会释放的资源而处于一种永久等待的状态
死锁的四个条件(重点)
1、互斥条件:一个资源一次只能被一个执行流使用
我操作的时候别人不能操作
2、请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不变
拿着手里的,但是请求其他的,其他的请求不到,手里拿着的也不放开
3、不可剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺
我的锁,别人不能释放
4、循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系
指在发生死锁时,必然存在一个进程资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,Pn正在等待已被P0占用的资源
死锁的产生与处理
当加锁或者解锁顺序不同时会发生死锁的情况;对锁资源的竞争以及进程/线程的加锁的推进顺序b不当
当以上四种条件被破坏时,可以预防死锁的产生
避免死锁的方法可以通过:死锁检测算法,银行家算法(推荐王道视频学习)
线程同步————线程条件
条件变量是线程同步的一种手段,条件变量用来自动阻塞一个线程,直到条件满足被触发为止。通常情况下条件变量和互斥锁同时使用
条件变量使我们可以睡眠等待某种条件出现。条件变量利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:
1、一个/多个线程等待“条件变量的条件成立”而挂起;线程1如果操作条件满足,则操作,否则进行等待。
2、另一个线程使“条件成立”信号;线程2促使条件满足,唤醒等待的线程。
如果没有资源则等待(死等),生产资源后唤醒等待。
下面展示一些
内联代码片
。
//条件变量的接口
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
//条件变量初始化,一般attr默认为NULL
//使用attr引用的属性初始化cond引用的条件变量。如果attr为空,则使用默认条件变量属性;效果与传递默认条件变量属性对象的地址相同。初始化成功后,条件变量的状态将被初始化。
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//静态初始化条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
//销毁由cond指定的给定的条件变量
//销毁当前未阻塞线程的初始化条件变量是安全的。
//试图销毁当前阻止其他线程的条件变量会导致未定义的行为。
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
//abstime:限时等待时长,限时等待时长,超时后则返回
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
//解锁后的挂起操作(原子操作),有可能还没来得及挂起就已经有人唤醒--白唤醒--导致了死锁
int pthread_cond_signal(pthread_cond_t *cond);
//唤醒至少一个等待的
int pthread_cond_boardcast(pthread_cond_t *cond);
//广播唤醒,唤醒所有等待的人
条件变量的步骤:
1、定义条件变量
2、初始化条件变量
3、等待\唤醒定义的条件变量
4、销毁条件变量
》》实例
下面展示一些
内联代码片
。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
struct paramater
{
int num;
char ch[128];
};
int data=0;
pthread_cond_t cond;//定义条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//初始化互斥量
void *fun1 (void *param)
{
int cnt=0;
static int j=1;
static char *status="t1 run out";
static int sta=16;
// printf("t1:num:%d,char:%s\n",((struct paramater *)param)->num,((struct paramater *)param)->ch);
// printf("t2:tid:%d\n",(unsigned int)pthread_self());
while(1){
pthread_cond_wait(&cond,&mutex);//条件阻塞
printf("this is %d time\n",j++);
printf("t1:data:%d----------------------------\n",data);
data=0;
pthread_mutex_unlock(&mutex);
cnt++;
if(cnt==5){
exit(1);
}
sleep(2);
}
//pthread_exit((void *)&sta);
}
void *fun2 (void *param)
{
static char *status="t1 run out";
static int sta=10;
// printf("t2:num:%d,char:%s\n",((struct paramater *)param)->num,((struct paramater *)param)->ch);
// printf("t2:tid:%d\n",(unsigned int)pthread_self());
while(1){
pthread_mutex_lock(&mutex);//锁定互斥量
printf("t2:data:%d\n",data++);
pthread_mutex_unlock(&mutex);//解锁互斥量
if(data==3){
pthread_cond_signal(&cond);
}
sleep(2);
}
//pthread_exit((void *)&sta);
}
int main()
{
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
pthread_t t1;
pthread_t t2;
char *re;
int *re1;
int *re2;
pthread_cond_init(&cond,NULL);//初始化条件变量
// memset(re,0,128);
struct paramater arg;
arg.num=56;
strcpy(arg.ch,"this is a thread");
int rec=0;
int rec1=0;
rec=pthread_create(&t1,NULL,fun1,(void *)&arg);//创建线程
if(rec==0){
// printf("thread successful\n");
}
rec1=pthread_create(&t2,NULL,fun2,(void *)&arg);
if(rec1==0){
// printf("thread successful\n");
}
while(1){
// printf("main:data:%d\n",data);
sleep(2);
}
printf("main:%d\n",(unsigned int)pthread_self());
pthread_join (t1,(void **)&re1);
pthread_join (t2,(void **)&re2);
printf("t1:return:%d\n",*re1);
printf("t2:return:%d\n",*re2);
pthread_mutex_destory(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
》》运行结果
五,网络编程
Socket定义
网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用—Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。常用 的Socket类型有两种:流式Socket—SOCK_STREAM和数据报式Socket—SOCK_DGRAM。流式是一种面向连接的Socket,针对于
面向连接的TCP
服务应用;数据报式Socket是一种无连接的Socket,对应于
无连接的UDP
服务应用。
Socket编程相关数据类型定义
计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先。Intenet上数据以高位字节优先顺序在网络上传输,所以对于在内部是以低位字节优先方式存储数据的机器,在Internet上传输数据时就需要进行转换。
socket 特点
- 不同主机进程间的通信——Socket
1)网络中的数据传输是一种I/O操作
2)read、write、close操作可应用于Socket描述符
3)Socket是一种文件描述符,代表了一个通信管道的一个端点
4)在Socket类型的文件描述符上,可以完成建立连接,数据传输等操作
5)常用的Socket类型有两种:
流式Socket:SOCK_STREAM , 提供面向连接的Socket(TCP)
数据报式Socket:SOCK_DGRAM , 提供面向无连接的Socket(UDP)
字节序
概述
字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。
1. Little endian:将低序字节存储在起始地址
2. Big endian:将高序字节存储在起始地址
例子:在内存中双字0x01020304(DWORD)的存储方式
内存地址
4000&4001&4002&4003
LE 04 03 02 01
BE 01 02 03 04
x86系列CPU都是little-endian的字节序.
网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big endian排序方式。
socket编程步骤
TCP:
UDP:
linux socket编程API
1,字节序转换函数
1)主机字节序数据转换成网络字节序数据
uint16_t htons(uint16_t host16bit) 把16位值从主机字节序转到网络字节序
uint32_t htonl(uint32_t host32bit) 把32位值从主机字节序转到网络字节序
功能:
将32或16位主机字节序数据转换成网络字节序数据
参数:
uint32_t: unsigned int
hostint32:待转换的32位主机字节序数据
返回值:
成功:返回网络字节序的值
2)网络字节序数据转换成主机字节序数据
uint16_t ntohs(uint16_t net16bit) //把16位值从网络字节序转到主机字节序
uint32_t ntohs(uint32_t net32bit) // 把32位值从网络字节序转到主机字节序
功能:
将32或16位网络字节序数据转换成主机字节序数据
参数:
uint32_t: unsignedint
netint32: 待转换的32位网络字节序数据
返回值:
成功:返回主机字节序的值
2,地址转换函数
1)int inet_pton(int family, const char* strptr,void *addrptr);
功能:
将点分十进制数串转换成32位无符号整数
参数:
family 协议族
strptr 点分十进制数串
addrptr 32位无符号整数的地址
返回值:
成功:1
失败:其它
头文件:#include <arpa/inet.h>
2)const char
inet_ntop(int family, const void
addrptr, char *strptr, size_t len);
功能:
将32位无符号整数转换成点分十进制数串
参数:
family 协议族
addrptr 32位无符号整数
strptr 点分十进制数串
len strptr缓存区长度
len的宏定义
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46 //for ipv6
返回值:
成功:则返回字符串的首地址
失败:返回NULL
头文件:#include <arpa/inet.h>
3,通用套接字地址结构sockaddr
套接字数据结构用于保存套接字信息,与使用该结构的网络协议有关,每一种网络协议都有其本身的网络地址数据结构,都是以sockaddr_开头的,不同的网络协议有不同的后缀,如IPv4对应的是skocaddr_in
1)地址标识了特定通信域中的套接字端点,
地址格式与特定通信域相关,
为使不同格式地址能被传入套接字函数,地址被强制转换成通用套接字地址结构
通用套接字地址结构如下:
下面展示一些
内联代码片
。
struct sockaddr
{
sa_family_t sa_family; //2 字节,地址族 AF_xxx
char sa_data[14]; //14 字节的协议地 址,包含套接字IP和端口号
};
头文件:#include <netinet/in.h>
4,套接字地址结构sockaddr_in
1)在IPv4因特网域(AF_INET)中,套接字地址结构用sockaddr_in命名、
下面展示一些
内联代码片
。
struct sockaddr_in
{
sa_family_t sin_family; //2字节,地址族
in_port_t sin_port; //2字节,端口号
struct in_addr sin_addr; //4字节,IP地址
unsigned char sin_zero[8]; //8字节,
};
struct in_addr
{
in_addr_t s_addr; //4字节
};
sin_zero说明:用来将sockaddr_in结构填充到与sockaddr同样长度,可用bzero()或memset()函数将其置为0
头文件:#include <netinet/in.h>
5,创建套接字
创建套接字是进行任何网络通信时必须做的第一步
1.int socket(int family, int type,int protocol);
功能:
创建一个用于网络通信的I/O描述符(套接字)
参数:
family:协议族
AF_INET,AF_INET6,AF_LOCAL,AF_ROUTE,AF_KEY
常用值 AF_INET 互联网协议族
type:套接字类型
SOCK_STREAM(流式套接字)
SOCK_DGRAM(数据包套接字)
SOCK_RAW (原始套接字)
SOCK_SEQPACKET
protocol:协议类别。表示为给定域和套接字类型选择默认协议,当对同一域和套接字类型支持多个协议时,可以通过该字段来选择一个特定协议,通常默认为0.上面设置的socket类型,在执行的时候也会有默认的协议类型提供,比如SOCK_STREAM就TCP协议。
0,IPPROTO_TCP,IPPROTO_UDP,IPPROTO_SCTP
常用值 0
》》返回值:套接字
socket创建的套接字特点:
使用socket创建套接字时,系统不会分配端口
使用socket创建的是主动套接字,但作为服务器,
需要被动等待别人的连接
头文件:#include<sys/socket.h>
6,服务端API
做为服务器需要具备的条件
1)具备一个可以确知的地址,以便让别人找到我
2)让操作系统知道你是一个服务器,而不是一个客户端
3)等待连接的到来
对于面向连接的TCP协议来说,连接的建立才真正意味着数据通信的开始
1.int bind(int sockfd,const struct sockaddr * myaddr,socklen_t addrlen);
功能:
将本地协议地址与sockfd绑定,套接字配置
参数:
sockfd: socket套接字
myaddr: 指向特定于协议的地址结构指针,
addrlen:该地址结构的长度
返回值:
成功:返回0
失败:其他
头文件:#include<sys/socket.h>
myaddr说明:
myaddr其实是指向含有本机IP地址及端口号等信息的sockaddr类型的指针
my_addr.sin_port= 0;//系统随机选择一个未使用的端口
my_addr.sin_addr= INADDR_ANY;//填入本机IP地址
注意:在使用bind函数使,需要将sin_port转换为网络字节优先
2. int listen(int sockfd, int backlog);
功能:
listen使套接字处于被动监听模式
使操作系统为该套接字设置一个连接队列,来记录所有连接到该套接字的连接
参数:
sockfd: socket监听套接字
backlog:连接队列的长度,输入队列已满时,将拒绝连接请求
返回值:
成功:返回0
失败:其他
头文件:#include <sys/socket.h>
4.int accept(int sockfd,struct sockaddr * cliaddr,socklen_t * addrlen);
功能:
从已连接队列中取出一个已经建立的连接,如果没有任何连接可用,则进入睡眠等待
在建立好输入队列后,服务器就调用accept函数,然后进入睡眠并等待客户端的连接请求
收到连接请求时,sockfd将将建立一个新的套接字,并把新套接字和请求连接进程的地址联系起来,收到服务套接字的初始套接字仍可以继续在以前的套接字上监听,同时也可以在新的套接字描述符上进行数据传输
参数:
sockfd: socket监听套接字
cliaddr: 用于存放客户端套接字地址结构
addrlen:套接字地址结构体长度
返回值:已连接套接字
注意:accept函数返回的是一个已连接套接字,这个套接字代表当前这个连接
7, 客户端API
作为客户端需要具备的条件:知道服务器的IP地址以及端口号
1.int connect(int sockfd,const struct sockaddr * addr,socklen_t len);
功能:
主动跟服务器建立链接
连接建立成功后才可以开始传输数据(对于TCP协议)
用于tcp客户端,udp不使用
参数:
sockfd:socket套接字
addr: 需连接的服务器地址结构
addrlen:地址结构体长度
返回值:
成功:0
失败:其他
注意:connect函数建立连接之后不会产生新的套接字
头文件:#include <sys/socket.h>
8,数据传输
当连接建立后,通信的两端便具备两个套接字。
套接字也是一种文件描述符,所以read、write函数可以用于从这个连接中取出或向其写入数据。
1.ssize_t send(int sockfd, const void * buf,size_t nbytes, int flags);
功能:
用于发送数据
注意:不能用TCP协议发送0长度的数据包
参数:
sockfd: socket套接字
buf: 待发送数据缓存区的地址
nbytes: 发送缓存区大小(以字节为单位)
flags: 套接字标志(常为0)
返回值:成功发送的字节数
头文件:#include <sys/socket.h>
注意:send发送的字节数可能和请求的字节数不一致,或小或大
2.ssize_t recv(int sockfd, void * buf,size_t nbytes,int flags);
功能:
用于接收网络数据
参数:
sockfd: 套接字
buf: 指向接收网络数据的缓冲区
nbytes: 接收缓冲区的大小(以字节为单位)
flags: 套接字标志(常为0)
返回值:成功接收到字节数
头文件:#include <sys/socket.h>
9,关闭连接
1. int close(int fd);
参数:fd是调用socket函数返回的套接字描述符
头文件:#include<unistd.h>
2. int shutdown(int s,int how)
功能:
允许只停止某个方向的数据传输,另一个方向的继续传输
参数:
s 需要关闭的套接字描述符
how:
0 停止继续接收数据
1 停止继续发送数据
2 停止发送和接收数据
返回值:
成功 0
失败 -1
头文件: #include <sys/socket.h>
3.使用close函数即可关闭套接字
1)关闭一个代表已连接套接字,将导致另一端接收到一个0长度的数据包
2)做服务器时
关闭socket创建的监听套接字,将导致服务器无法继续接受新的连接,但不 会影响已经建立的连接
关闭accept返回的已连接套接字,将导致它所代表的连接被关闭,但不会影 响服务器的监听
3)做客户端时
关闭连接就是关闭连接,不意味着其他
服务端编写
》》实例
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc,char **argv)
{
char msg[128]={0};
char readbuf[128]={0};
struct sockaddr_in server_sockaddr;
struct sockaddr_in client_sockaddr;
memset(&server_sockaddr,0,sizeof(struct sockaddr_in));
memset(&client_sockaddr,0,sizeof(struct sockaddr_in));//初始化网络地址
int server_socktid=socket(AF_INET,SOCK_STREAM,0);//创建socket
if(server_socktid==-1){
perror("socket");
}
server_sockaddr.sin_family=AF_INET;
server_sockaddr.sin_port=htons(atoi(argv[2]));
server_sockaddr.sin_addr.s_addr=inet_addr(argv[1]);
bind(server_socktid,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr));//绑定服务端网络地址
listen(server_socktid,1);//监听是否有客户端接入
while(1){
int c_len=sizeof(client_sockaddr);
int c_sockid=accept(server_socktid,(struct sockaddr *)&client_sockaddr,&c_len);//接受客户端连接,获取客户端socket描述符
if(c_sockid==-1){
printf("connect failed\n");
perror("accept");
}
else{
printf("connect successful:%s\n",inet_ntoa(client_sockaddr.sin_addr));
}
}
return 0;
}
客户端编写
下面展示一些
内联代码片
。
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc,char **argv)
{
char msg[128]={0};
char readbuf[128]={0};
struct sockaddr_in client_addr;
client_addr.sin_family=AF_INET;
client_addr.sin_port=htons(atoi(argv[2]));
client_addr.sin_addr.s_addr=inet_addr(argv[1]);
int c_soid=socket(AF_INET,SOCK_STREAM,0);//创建socket
if(c_soid==-1){
perror("socket");
}
int con=connect(c_soid,(struct sockaddr *)&client_addr,sizeof(struct sockaddr_in));//请求与服务端连接
if(con==0){
printf("connect successful\n");
}
else{
perror("connect");
}
return 0;
}
》》运行结果
双方聊天
1,服务端
下面展示一些
内联代码片
。
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc,char **argv)
{
char msg[128]={0};
char readbuf[128]={0};
struct sockaddr_in server_sockaddr;
struct sockaddr_in client_sockaddr;
memset(&server_sockaddr,0,sizeof(struct sockaddr_in));
memset(&client_sockaddr,0,sizeof(struct sockaddr_in));
int server_socktid=socket(AF_INET,SOCK_STREAM,0);//创建socket
if(server_socktid==-1){
perror("socket");
}
server_sockaddr.sin_family=AF_INET;
server_sockaddr.sin_port=htons(atoi(argv[2]));//inet_pton(int family, const char* strptr,void *addrptr);
server_sockaddr.sin_addr.s_addr=inet_addr(argv[1]);
bind(server_socktid,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr));//绑定服务端网络地址
listen(server_socktid,1);//监听客户端请求
while(1){
int c_len=sizeof(client_sockaddr);
int c_sockid=accept(server_socktid,(struct sockaddr *)&client_sockaddr,&c_len);//接受客户端连接,返回客户端socket描述符
if(c_sockid==-1){
printf("connect failed\n");
perror("accept");
}
else{
printf("connect successful:%s\n",inet_ntoa(client_sockaddr.sin_addr));
}
if(fork()==0){
while(1){
if(fork()==0){
while(1){
memset(readbuf,0,sizeof(readbuf));//初始化读取内存区
read(c_sockid,readbuf,sizeof(readbuf));//读取回馈消息
printf("%s\n",readbuf);
}
}
memset(msg,0,sizeof(msg));//初始化消息内存区
printf(">>\n");
gets(msg);
write(c_sockid,msg,sizeof(msg));//发送消息
}
}
}
return 0;
}
2,客户端
下面展示一些
内联代码片
。
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc,char **argv)
{
char msg[128]={0};
char readbuf[128]={0};
struct sockaddr_in client_addr;
client_addr.sin_family=AF_INET;
client_addr.sin_port=htons(atoi(argv[2]));
client_addr.sin_addr.s_addr=inet_addr(argv[1]);
int c_soid=socket(AF_INET,SOCK_STREAM,0);//创建socket
if(c_soid==-1){
perror("socket");
}
int con=connect(c_soid,(struct sockaddr *)&client_addr,sizeof(struct sockaddr_in));//请求连接服务端
if(con==0){
printf("connect successful\n");
}
else{
perror("connect");
}
while(1){
if(fork()==0){
while(1){
memset(readbuf,0,sizeof(readbuf));//初始化读取接收内存区
read(c_soid,readbuf,sizeof(readbuf));//读取回馈消息
printf("%s\n",readbuf);
}
}
memset(msg,0,sizeof(msg));//初始化发送消息内存区
printf(">>\n");
gets(msg);
write(c_soid,msg,sizeof(msg));//发送消息
}
return 0;
}
》》运行结果