Linux网络编程.doc_第1页
Linux网络编程.doc_第2页
Linux网络编程.doc_第3页
Linux网络编程.doc_第4页
Linux网络编程.doc_第5页
已阅读5页,还剩31页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Linux C编程-make命令的使用 摘要:在开发一个系统时,一般是将一个系统分成几个模块,这样做提高了系统的可维护性,但由于各个模块间不可避免存在关联,所以当一个模块改动后,其他模块也许会有所更新,当然对小系统来说,手工编译连接是没问题,但是如果是一个大系统,存在很多个模块,那么手工编译的方法就不适用了。为此,在Linux系统中,专门提供了一个make命令来自动维护目标文件,与手工编译和连接相比,make命令的优点在于他只更新修改过的文件(在Linux中,一个文件被创建或更新后有一个最后修改时间,make命令就是通过这个最后修改时间来判断此文件是否被修改),而对没修改的文件则置之不理,并且make命令不会漏掉一个需要更新的文件。文件和文件间或模块或模块间有可能存在倚赖关系,make命令也是依据这种依赖关系来进行维护的,所以我们有必要了解什么是依赖关系;打个最比喻:如果我们想玩游戏,必须有游戏光碟和电脑(这两者间存在依赖关系),而有游戏光碟和电脑的前提条件是必须经济条件允许,另外当你有了游戏光碟后还要根据你的心情来选择是玩哪种游戏;如下图:make命令当然不会自己知道这些依赖关系,而需要程序员将这些依赖关系写入一个叫makefile的文件中。Makefile文件中包含着一些目标,通常目标就是文件名,对每一个目标,提供了实现这个目标的一组命令以及和这个目标有依赖关系的其他目标或文件名,以下是一个简单的Makefile的简单例子:#一个简单的Makefileprog:prog1.o prog2.ogcc prog1.o prog2.o -o progprog1.o:prog1.c lib.hgcc -c -I. -o prog1.o prog1.cprog2.o:prog2.cgcc -c prog2.c以上Mamefile中定义了三个目标:prog、prog1和prog2,分号后是依赖文件列表,中间用一个分号隔开; 对于第一个目标文件prog来说,他有两个依赖文件:prog1.o和prog2.o,任何一个依赖文件更新,prog也要随之更新,命令gcc prog1.o prog2.o -o prog是生成prog的命令。make检查目标是否需要更新时采用递归的方法,递归从底层向上对过时目标进行更新,只有当一个目标所依赖的所有目标都为最新时,这个目标才会被更新。以上面的Makefile为例,我们修改了prog2.c,执行make时,由于目标prog依赖prog1.o和prog2.o,所以要先检查prog1.o和prog2.o是否过时,目标prog1.o依赖prog1.c和lib.h,由于我们并没修改这两个文件,所以他们都没有过期,接下来再检查目标prog2.o,他依赖prog2.c,由于我们修改了prog2.c,所以prog2.c比目标文件prog2.o要新,即prog2.o过期,而导致了依赖prog2.o的所有目标都过时;这样make会先更新prog2.o再更新prog。 如果某一行过长,已经到了文本编辑器的右边界,可用一个反斜杠()做换行符,反斜杠所连接的所有行都会被当成一行来处理;另外在Makefile中涉及的文件名允许使用通配符(?或*)。 有时候为了简化命令的书写,可以在Makefile中定义一些宏和使用缩写,下面是几个常用的缩写: $ 代表该目标的全名 $* 代表已经删除了后缀的目标名 $ 代表该目标的第一个相关目标名 现在就可以使用缩写对以上Makefile做相应的修改:#使用缩写的Makefileprog:prog1.o prog2.ogcc prog1.o prog2.o -o $prog1.o:prog1.c lib.hgcc -c -I. -o $ $prog2.o:prog2.cgcc -c $*.c 在一个项目中,可能几个目标中使用同一个文件a.c,如果以后这个文件被修改,那么需要修改Makefile中所有的a.c,这样就比较麻烦,可以定义宏来解决这个问题,宏可以使Makefile更加清晰:#使用缩写和宏的MakefileMARCO = prog1.o prog2.oprog:$(MARCO)gcc prog1.o prog2.o -o $prog1.o:prog1.c lib.hgcc -c -I. -o $ $0)printf(this is parent,child has pid %d ,pid);elseprintf(fork fail );调用fock创建子进程后,父进程中所有打开的描述字在子进程中是共享的,这个特性在网络服务器中广泛使用,例如父进程通过socket函数返回一个套接字,然后调用fock函数创建字进程,这个子进程就可以直接对这个已经存在的套接字进行操作。fock的另一个典型应用是创建一个子进程调用exec函数来代替自己去执行新的程序。3、进程的执行UNIX系统执行以文件形式存储在磁盘上的可执行程序的唯一方法是用一个现有的进程去调用六个exec函数之一,六个exec函数定义如下:#include int execl(const char *pathname,const char *arg0,./* (char *)0*/);int execv(const char *pathname,char *const argv);int execle(const char *pathname,const char *arg0,. / (char *)0,char *const envp*/);int execve(const char * pathname,char *const argv,char *const envp);int execlp(const char *filename,const char *arg0,./* (char *)0*/);int execvp(const char *filename,char *const argv);在以上六个exec函数中,第一个参数如果为pathname,则说明被执行程序是由路径名指定,如果为filename,则说明是由文件名指定;第二个参数如果为数组,说明被执行程序的参数是由一个数组来索引(数组必须含有一个空指针来表示结束),否则需要将参数一一列出;execle()及execve()的envp指针数组表示这两个函数显示指定一个环境表(这个数组必须以一个空指针结束),而另外四个函数则用外部变量environ的当前值来创建一个环境表传递给新程序。exec函数把新程序装入调用进程的内存空间,来改变调用进程的执行代码。当exec函数执行成功,调用进程将被覆盖,相当于产生一个新进程,但是新进程的进程表识符号和调用进程的进程标识符相同,所以exec并没象fock函数一样创建一个新进程,而是用新进程取代了调用进程。下面是一个fork()结合exec()的程序: #include #include main() int pid;pid = fork();switch(pid) case -1:perror(fork failed);exit(1);case 0:execl(/bin/ls,ls,-l,NULL);perror(execl failed);exit(1);二、守护进程1、什么是守护进程守护进程是脱离于终端并且在后台运行的进程。守护进程脱离于终端是为了避免进程在执行中的信息不在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。2、守护进程的启动守护进程一般可以通过以下方式启动:A:在系统启动时由启动脚本启动,这些启动脚本通常放在/etc/rc.d目录下 B:利用inetd超级服务器启动,大部分网络服务都是这样启动的,如ftp、telnet等 C:另外,由cron定时启动以及在终端用nohup启动的进程也是守护进程 3、守护进程的错误输出由于守护进程是脱离于任何终端的,所以他的任何信息都无法直接输出到标准输出设备和标准错误输出设备中,在Linux系统中提供了syslog()系统调用来解决这个问题。syslog()在shslog.h定义如下:#include void syslog(int priority,char *format,.);其中参数priority指明了进程要写入信息的等级和用途。等级见表1,用途见表2:第二个参数是一个格式串,指定了记录输出的格式,在这个串的最后需要指定一个%m,对应errno错误码。4、守护进程的建立首先fock一个子进程并关闭父进程,如果此进程是作为一个shell命令在控制台执行的,当父进程被终止时,shell就认为该命令已结束,子进程自动转为后台运行,由于子进程在父进程那里继承了组标识符同时又拥有自己的进程标识符,就保证了子进程不是一个进程组的首进程;接着调用setsid()创建一个新进程组,调用进程成为该进程组的首进程,这就使该进程脱离于原终端;接下来重新调用fock(),使进程不再是进程组的首进程,从而忽略SIGHUP信号,以避免在某些情况下进程以外打开终端而重新与终端发生联系;最后改变工作目录,关闭全部已经打开的文件句柄,并打开log系统,到此守护进程成功建立。Linux网络编程-2. 初等网络函数介绍(TCP)Linux系统是通过提供套接字(socket)来进行网络编程的.网络程序通过socket和其它几个函数的调用,会返回一个通讯的文件描述符,我们可以将这个描述符看成普通的文件的描述符来操作,这就是linux的设备无关性的好处.我们可以通过向描述符读写操作实现网络之间的数据交流. 2.1 socket int socket(int domain, int type,int protocol) domain:说明我们网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等). AF_UNIX只能够用于单一的Unix系统进程间通信,而AF_INET是针对Internet的,因而可以允许在远程 主机之间通信(当我们 man socket时发现 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.2 bind int bind(int sockfd, struct sockaddr *my_addr, int addrlen) sockfd:是由socket调用返回的文件描述符. addrlen:是sockaddr结构的长度. my_addr:是一个指向sockaddr的指针. 在中有 sockaddr的定义 struct sockaddr unisgned short as_family; char sa_data14; ; 不过由于系统的兼容性,我们一般不用这个头文件,而使用另外一个结构(struct sockaddr_in) 来代替.在中有sockaddr_in的定义 struct sockaddr_in unsigned short sin_family; unsigned short int sin_port; struct in_addr sin_addr; unsigned char sin_zero8; 我们主要使用Internet所以sin_family一般为AF_INET,sin_addr设置为INADDR_ANY表示可以和任何的主机通信,sin_port是我们要监听的端口号.sin_zero8是用来填充的. bind将本地的端口同socket返回的文件描述符捆绑在一起.成功是返回0,失败的情况和socket一样 2.3 listen int listen(int sockfd,int backlog) sockfd:是bind后的文件描述符. backlog:设置请求排队的最大长度.当有多个客户端程序和服务端相连时, 使用这个表示可以介绍的排队长度. listen函数将bind的文件描述符变为监听套接字.返回的情况和bind一样. 2.4 accept int accept(int sockfd, struct sockaddr *addr,int *addrlen) sockfd:是listen后的文件描述符. addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了. bind,listen和accept是服务器端用的函数,accept调用时,服务器端的程序会一直阻塞到有一个 客户程序发出了连接. accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了. 失败时返回-1 2.5 connect int connect(int sockfd, struct sockaddr * serv_addr,int addrlen) sockfd:socket返回的文件描述符. serv_addr:储存了服务器端的连接信息.其中sin_add是服务端的地址 addrlen:serv_addr的长度 connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符失败时返回-1. 2.6 实例 服务器端程序 /* 服务器程序 (server.c) */ int main(int argc, char *argv) int sockfd,new_fd; struct sockaddr_in server_addr; struct sockaddr_in client_addr; int sin_size,portnumber; char hello=Hello! Are You Fine?n; if(argc!=2) fprintf(stderr,Usage:%s portnumberan,argv0); exit(1); if(portnumber=atoi(argv1)0) fprintf(stderr,Usage:%s portnumberan,argv0); exit(1); /* 服务器端开始建立socket描述符 */ if(sockfd=socket(AF_INET,SOCK_STREAM,0)=-1) fprintf(stderr,Socket error:%sna,strerror(errno); exit(1); /* 服务器端填充 sockaddr结构 */ bzero(&server_addr,sizeof(struct sockaddr_in); server_addr.sin_family=AF_INET; server_addr.sin_addr.s_addr=htonl(INADDR_ANY); server_addr.sin_port=htons(portnumber); /* 捆绑sockfd描述符 */ if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr)=-1) fprintf(stderr,Bind error:%sna,strerror(errno); exit(1); /* 监听sockfd描述符 */ if(listen(sockfd,5)=-1) fprintf(stderr,Listen error:%sna,strerror(errno); exit(1); while(1) /* 服务器阻塞,直到客户程序建立连接 */ sin_size=sizeof(struct sockaddr_in); if(new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size)=-1) fprintf(stderr,Accept error:%sna,strerror(errno); exit(1); fprintf(stderr,Server get connection from %sn, inet_ntoa(client_addr.sin_addr); if(write(new_fd,hello,strlen(hello)=-1) fprintf(stderr,Write Error:%sn,strerror(errno); exit(1); /* 这个通讯已经结束 */ close(new_fd); /* 循环下一个 */ close(sockfd); exit(0); 客户端程序 /* 客户端程序 client.c */ int main(int argc, char *argv) int sockfd; char buffer1024; struct sockaddr_in server_addr; struct hostent *host; int portnumber,nbytes; if(argc!=3) fprintf(stderr,Usage:%s hostname portnumberan,argv0); exit(1); if(host=gethostbyname(argv1)=NULL) fprintf(stderr,Gethostname errorn); exit(1); if(portnumber=atoi(argv2)h_addr); /* 客户程序发起连接请求 */ if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr)=-1) fprintf(stderr,Connect Error:%san,strerror(errno); exit(1); /* 连接成功了 */ if(nbytes=read(sockfd,buffer,1024)=-1) fprintf(stderr,Read Error:%sn,strerror(errno); exit(1); buffernbytes=0; printf(I have received:%sn,buffer); /* 结束通讯 */ close(sockfd); exit(0); MakeFile 这里我们使用GNU 的make实用程序来编译. 关于make的详细说明见 Make 使用介绍 # Makefile # all:server client server:server.c gcc $ -o $ client:client.c gcc $ -o $ 运行make后会产生两个程序server(服务器端)和client(客户端) 先运行./server portnumber& (portnumber随便取一个大于1204且不在/etc/services中出现的号码 就用8888好了),然后运行 ./client localhost 8888 看看有什么结果. (你也可以用telnet和netstat试一试.) 上面是一个最简单的网络程序,不过是不是也有点烦.上面有许多函数我们还没有解释. 我会在下一章进行的详细的说明. 2.7 总结 总的来说网络程序是由两个部分组成的-客户端和服务器端.它们的建立步骤一般是: 服务器端 socket-bind-listen-accept 客户端 socket-connectLinux网络编程-3. 服务器和客户机的信息函数 这一章我们来学习转换和网络方面的信息函数. 3.1 字节转换函数 在网络上面有着许多类型的机器,这些机器在表示数据的字节顺序是不同的, 比如i386芯片是低字节在内存地址的低端,高字节在高端,而alpha芯片却相反. 为了统一起来,在Linux下面,有专门的字节转换函数. unsigned long int htonl(unsigned long int hostlong) unsigned short int htons(unisgned short int hostshort) unsigned long int ntohl(unsigned long int netlong) unsigned short int ntohs(unsigned short int netshort) 在这四个转换函数中,h 代表host, n 代表 network.s 代表short l 代表long 第一个函数的意义是将本机器上的long数据转化为网络上的long. 其他几个函数的意义也差不多. 3.2 IP和域名的转换 在网络上标志一台机器可以用IP或者是用域名.那么我们怎么去进行转换呢? struct hostent *gethostbyname(const char *hostname) struct hostent *gethostbyaddr(const char *addr,int len,int type) 在中有struct hostent的定义 struct hostent char *h_name; /* 主机的正式名称 */ char *h_aliases; /* 主机的别名 */ int h_addrtype; /* 主机的地址类型 AF_INET*/ int h_length; /* 主机的地址长度 对于IP4 是4字节32位*/ char *h_addr_list; /* 主机的IP地址列表 */ #define h_addr h_addr_list0 /* 主机的第一个IP地址*/ gethostbyname可以将机器名(如 )转换为一个结构指针.在这个结构里面储存了域名的信息 gethostbyaddr可以将一个32位的IP地址(C0A80001)转换为结构指针. 这两个函数失败时返回NULL 且设置h_errno错误变量,调用h_strerror()可以得到详细的出错信息 3.3 字符串的IP和32位的IP转换. 在网络上面我们用的IP都是数字加点()构成的, 而在struct in_addr结构中用的是32位的IP, 我们上面那个32位IP(C0A80001)是的 为了转换我们可以使用下面两个函数 int inet_aton(const char *cp,struct in_addr *inp) char *inet_ntoa(struct in_addr in) 函数里面 a 代表 ascii n 代表network.第一个函数表示将a.b.c.d的IP转换为32位的IP,存储在 inp指针里面.第二个是将32位IP转换为a.b.c.d的格式. 3.4 服务信息函数 在网络程序里面我们有时候需要知道端口.IP和服务信息.这个时候我们可以使用以下几个函数 int getsockname(int sockfd,struct sockaddr *localaddr,int *addrlen) int getpeername(int sockfd,struct sockaddr *peeraddr, int *addrlen) struct servent *getservbyname(const char *servname,const char *protoname) struct servent *getservbyport(int port,const char *protoname) struct servent char *s_name; /* 正式服务名 */ char *s_aliases; /* 别名列表 */ int s_port; /* 端口号 */ char *s_proto; /* 使用的协议 */ 一般我们很少用这几个函数.对应客户端,当我们要得到连接的端口号时在connect调用成功后使用可得到 系统分配的端口号.对于服务端,我们用INADDR_ANY填充后,为了得到连接的IP我们可以在accept调用成功后 使用而得到IP地址. 在网络上有许多的默认端口和服务,比如端口21对ftp80对应WWW.为了得到指定的端口号的服务 我们可以调用第四个函数,相反为了得到端口号可以调用第三个函数. 3.5 一个例子 #include int main(int argc ,char *argv) struct sockaddr_in addr; struct hostent *host; char *alias; if(argch_name); printf(Name aliases:); for(alias=host-h_aliases;*alias!=NULL;alias+) printf(%s ,*alias); printf(nIp address:); for(alias=host-h_addr_list;*alias!=NULL;alias+) printf(%s ,inet_ntoa(*(struct in_addr *)(*alias); 在这个例子里面,为了判断用户输入的是IP还是域名我们调用了两个函数,第一次我们假设输入的是IP所以调用inet_aton, 失败的时候,再调用gethostbyname而得到信息.Linux网络编程-4. 完整的读写函数 一旦我们建立了连接,我们的下一步就是进行通信了.在Linux下面把我们前面建立的通道看成是文件描述符,这样服务器端和客户端进行通信时候,只要往文件描述符里面读写东西了. 就象我们往文件读写一样. 4.1 写函数write ssize_t write(int fd,const void *buf,size_t nbytes) write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数.失败时返回-1. 并设置errno变量. 在网络程序中,当我们向套接字文件描述符写时有俩种可能. 1)write的返回值大于0,表示写了部分或者是全部的数据. 2)返回的值小于0,此时出现了错误.我们要根据错误类型来处理. 如果错误为EINTR表示在写的时候出现了中断错误. 如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接). 为了处理以上的情况,我们自己编写一个写函数来处理这几种情况. int my_write(int fd,void *buffer,int length) int bytes_left; int written_bytes; char *ptr; ptr=buffer; bytes_left=length; while(bytes_left0) /* 开始写*/ written_bytes=write(fd,ptr,bytes_left); if(written_bytes0) bytes_read=read(fd,ptr,bytes_read); if(bytes_read0) if(errno=EINTR) bytes_read=0; else return(-1); else if(bytes_read=0) break; bytes_left-=bytes_read; ptr+=bytes_read; return(length-bytes_left); 4.3 数据的传递 有了上面的两个函数,我们就可以向客户端或者是服务端传递数据了.比如我们要传递一个结构.可以使用如下方式 /* 客户端向服务端写 */ struct my_struct my_struct_client; write(fd,(void *)&my_struct_client,sizeof(struct my_struct); /* 服务端的读*/ char buffersizeof(struct my_struct); struct *my_struct_server; read(fd,(void *)buffer,sizeof(struct my_struct); my_struct_server=(struct my_struct *)buffer; 在网络上传递数据时我们一般都是把数据转化为char类型的数据传递.接收的时候也是一样的 注意的是我们没有必要在网络上传递指针(因为传递指针是没有任何意义的,我们必须传递指针所指向的内容)Linux网络编程-5. 用户数据报发送我们前面已经学习网络程序的一个很大的部分,由这个部分的知识,我们实际上可以写出大部分的基于TCP协议的网络程序了.现在在Linux下的大部分程序都是用我们上面所学的知识来写的.我们可以去找一些源程序来参考一下.这一章,我们简单的学习一下基于UDP协议的网络程序. 5.1 两个常用的函数 int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr * from int *fromlen) int sendto(int sockfd,const void *msg,int len,unsigned int flags,struct sockaddr *to int tolen) sockfd,buf,len的意义和read,write一样,分别表示套接字描述符,发送或接收的缓冲区及大小.recvfrom负责从sockfd接收数据,如果from不是NULL,那么在from里面存储了信息来源的情况,如果对信息的来源不感兴趣,可以将from和fromlen设置为NULL.sendto负责向to发送信息.此时在to里面存储了收信息方的详细资料. 5.2 一个实例 /* 服务端程序 server.c */ #include #define SERVER_PORT 8888 #define MAX_MSG_SIZE 1024 void udps_respon(int sockfd) struct sockaddr_in addr; int addrlen,n; char msgMAX_MSG_SIZE; while(1) /* 从网络上度,写到网络上面去 */ n=recvfrom(sockfd,msg,MAX_MSG_SIZE,0, (struct sockaddr*)&addr,&addrlen); msgn=0; /* 显示服务端已经收到了信息 */ fprintf(stdout,I have received %s,msg); sendto(sockfd,msg,n,0,(struct sockaddr*)&addr,addrlen); int main(void) int sockfd; struct sockaddr_in addr; sockfd=socket(AF_INET,SOCK_DGRAM,0); if(sockfd0) fprintf(stderr,Socket Error:%sn,strerror(errno); exit(1); bzero(&addr,sizeof(struct sockaddr_in); addr.sin_family=AF_INET; addr.sin_addr.s_addr=htonl(INADDR_ANY); addr.sin_port=htons(SERVER_PORT); if

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论