一个简单的客户端与服务器通讯_第1页
一个简单的客户端与服务器通讯_第2页
一个简单的客户端与服务器通讯_第3页
一个简单的客户端与服务器通讯_第4页
一个简单的客户端与服务器通讯_第5页
已阅读5页,还剩8页未读 继续免费阅读

下载本文档

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

文档简介

1、Linux网络编程简单的客户端和服务器通讯程序开发入门(2)?2007-09-19 21:15:32分类:燙/C+zieckey简介:本文详细介绍了Linux下BS结构的客户端服务器通讯程序的开发入门,其中对重要的网络函数和结构体作了详细的说明和分析,最后给出一个简单的客户端和服务器通讯程序示例以加深理解。2. 初等网络函数介绍(TCP)Linux系统是通过提供套接字(socket)来进行网络编程的.网络程序通过socket和其它几个函数的调用,会返回一个通讯的文件描述符,我们可以将这个描述符看成普通的文件的描述符来操作,这就是linux的设备无关性的好处.我们可以通过向描述符读写操作实现网络

2、之间的数据交流.2.1 socketint 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等)

3、SOCK_STREAM表明我们用的是TCP协议,这样会提供按顺序的,可靠,双向,面向连接的比特流. SOCK_DGRAM 表明我们用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信.protocol:由于我们指定了type,所以这个地方我们一般只要用0来代替就可以了 socket为网络通讯做基本的准备.成功时返回文件描述符,失败时返回-1,看error可知道出错的详细情况.2.2 bind? 一旦你有一个套接字,你可能要将套接字和机器上的一定的端口关联 起来。(如果你想用listen()来侦听一定端口的数据,这是必要一步)如果你只想用 connect(),那么这个步 骤没有必要。但是无

4、论如何,请继续读下去。这里是系统调用 bind() 的大概:int bind(int sockfd, struct sockaddr *my_addr, int addrlen)sockfd:是由socket调用返回的文件描述符.addrlen:是sockaddr结构的长度.my_addr:是一个指向sockaddr的指针. 在中有 sockaddr的定义struct sockaddrunisgned short as_family;char sa_data14;不过由于系统的兼容性,我们一般不用这个头文件,而使用另外一个结构(struct sockaddr_in) 来代替.在中有sockad

5、dr_in的定义struct sockaddr_inunsigned 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牋牋牋

6、牋牋牋牋牋牋牋牋? 假如你不希望与远程的一个地址相连,或者说, 仅仅是将它踢开,那你就需要等待接入请求并且用各种方法处理它们。处 理过程分两步:首先,你听-listen(),然后,你接受-accept() (请看下面的 内容)。除了要一点解释外,系统调用 listen 也相当简单。int listen(int sockfd, int backlog);sockfd 是调用 socket() 返回的套接字文件描述符。backlog 是在进入 队列中允许的连接数目。什么意思呢? 进入的连接是在队列中一直等待直 到你接受 (accept() 请看下面的文章)连接。它们的数目限制于队列的允许。 大多数

7、系统的允许数目是20,你也可以设置为5到10。和别的函数一样,在发生错误的时候返回-1,并设置全局错误变量 errno。你可能想象到了,在你调用 listen() 前你或者要调用 bind() 或者让内 核随便选择一个端口。如果你想侦听进入的连接,那么系统调用的顺序可 能是这样的:socket();? bind();listen();? /* accept() 应该在这 */2.4 accept? 准备好了,系统调用 accept() 会有点古怪的地方的!你可以想象发生 这样的事情:有人从很远的地方通过一个你在侦听 (listen() 的端口连接 (connect() 到你的机器。它的连接将加

8、入到等待接受 (accept() 的队列 中。你调用accept() 告诉它你有空闲的连接。它将返回一个新的套接字文 件描述符!这样你就有两个套接字了,原来的一个还在侦听你的那个端口, 新的在准备发送 (send() 和接收 ( recv() 数据。这就是这个过程!int accept(int sockfd, struct sockaddr *addr,int *addrlen)sockfd:是listen后的文件描述符.addr, addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了.?bind,listen和accept是服务器端用的函数,accept调用时,服务器端的程

9、序会一直阻塞到有一个 客户程序发出了连接.?accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了. 失败时返回-12.5 connectint connect(int sockfd, struct sockaddr * serv_addr,int addrlen)sockfd:socket返回的文件描述符.serv_addr:储存了服务器端的连接信息.其中sin_add是服务端的地址addrlen:serv_addr的长度connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符 失败时返回-1.牋? 现在我们假设你是

10、个 telnet 程序。你的用户命令你得到套接字的文件 描述符。你听从命令调用了socket()。下一步,你的用户告诉你通过端口 23(标准 telnet 端口)连接到"0"。你该怎么做呢? 幸运的是,你正在阅读connect()-如何连接到远程主机这一章。你可 不想让你的用户失望。connect() 系统调用是这样的:#include <sys/types.h>#include <sys/socket.h>int connect(int sockfd, struct sockaddr *serv_addr, int addrl

11、en);sockfd 是系统调用 socket() 返回的套接字文件描述符。serv_addr 是 保存着目的地端口和 IP 地址的数据结构 structsockaddr。addrlen 设置 为 sizeof(struct sockaddr)。想知道得更多吗?让我们来看个例子:#include <string.h>#include <sys/types.h>#include <sys/socket.h>#define DEST_IP "0"? #define DEST_PORT 23main()牋 int sock

12、fd;struct sockaddr_in dest_addr; /* 目的地址*/sockfd = socket(AF_INET, SOCK_STREAM, 0); /* 错误检查 */dest_addr.sin_family = AF_INET; /* host byte order */dest_addr.sin_port = htons(DEST_PORT); /* short, network byte order */dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);bzero(&(dest_addr.sin_zero),; /*

13、 zero the rest of the struct */* don't forget to error check the connect()! */connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr);牋 .2.6重要的结构题介绍首先是简单的一个:socket描述符。它是下面的类型:int仅仅是一个常见的 int。从现在起,事情变得不可思议了,而你所需做的就是继续看下去。注 意这样的事实:有两种字节排列顺序:重要的字节 (有时叫 "octet",即八 位位组) 在

14、前面,或者不重要的字节在前面。前一种叫“网络字节顺序 (Network Byte Order)”。有些机器在内部是按照这个顺序储存数据,而另外 一些则不然。当我说某数据必须按照 NBO 顺序,那么你要调用函数(例如 htons() )来将它从本机字节顺序 (Host Byte Order) 转换过来。如果我没有 提到 NBO, 那么就让它保持本机字节顺序。我的第一个结构(在这个技术手册TM中)-struct sockaddr.。这个结构 为许多类型的套接字储存套接字地址信息:struct sockaddr 牋 unsigned short sa_family; /* 地址家族, AF_xxx

15、*/牋 char sa_data14; /*14字节协议地址*/牋 ;sa_family 能够是各种各样的类型,但是在这篇文章中都是 "AF_INET"。 sa_data包含套接字中的目标地址和端口信息。这好像有点 不明智。为了处理struct sockaddr,程序员创造了一个并列的结构: struct sockaddr_in ("in" 代表 "Internet"。)struct sockaddr_in 牋 short int sin_family; /* 通信类型 */牋 unsigned short int sin_port;

16、 /* 端口 */牋 struct in_addr sin_addr; /* Internet 地址 */牋 unsigned char sin_zero8; /* 与sockaddr结构的长度相同*/牋 ;用这个数据结构可以轻松处理套接字地址的基本元素。注意 sin_zero (它被加入到这个结构,并且长度和 structsockaddr 一样) 应该使用函数 bzero() 或 memset() 来全部置零。 同时,这一重要的字节,一个指向 sockaddr_in结构体的指针也可以被指向结构体sockaddr并且代替它。这 样的话即使 socket() 想要的是 struct sockad

17、dr *,你仍然可以使用 struct sockaddr_in,并且在最后转换。同时,注意 sin_family 和 struct sockaddr 中的 sa_family 一致并能够设置为 "AF_INET"。最后,sin_port和 sin_addr 必须是网络字节顺序 (Network Byte Order)!你也许会反对道:"但是,怎么让整个数据结构 struct in_addr sin_addr 按照网络字节顺序呢?" 要知道这个问题的答案,我们就要仔细的看一看这 个数据结构: struct in_addr, 有这样一个联合 (unions)

18、:/* Internet 地址 (一个与历史有关的结构) */牋 struct in_addr 牋 unsigned long s_addr;牋 ;它曾经是个最坏的联合,但是现在那些日子过去了。如果你声明 "ina" 是数据结构 struct sockaddr_in 的实例,那么"ina.sin_addr.s_addr" 就储 存4字节的 IP 地址(使用网络字节顺序)。如果你不幸的系统使用的还是恐 怖的联合struct in_addr ,你还是可以放心4字节的 IP地址并且和上面 我说的一样(这是因为使用了“#define”。)struct hoste

19、nt结构体这个数据结构是这样的:?牋 爏truct hostent ?牋 燾har *h_name;?牋 燾har *h_aliases;?牋 爄nt h_addrtype;?牋 爄nt h_length;?牋 燾har *h_addr_list;?牋 爙;?牋 ?#define h_addr h_addr_list0 ?这里是这个数据结构的详细资料: ?struct hostent: ?h_name ? 地址的正式名称。?h_aliases ? 空字节-地址的预备名称的指针。?h_addrtype ?地址类型; 通常是AF_INET。 ?h_length ? 地址的比特长度。?h_addr_

20、list ? 零字节-主机网络地址指针。网络字节顺序。?h_addr - h_addr_list中的第一地址。?2.7本机转换? 我们现在到了新的章节。我们曾经讲了很多网络到本机字节顺序的转 换,现在可以实践了!你能够转换两种类型: short (两个字节)和 long (四个字节)。这个函 数对于变量类型 unsigned 也适用。假设你想将short 从本机字节顺序转 换为网络字节顺序。用 "h" 表示 "本机 (host)",接着是 "to",然后用 "n" 表 示 "网络(network)&quo

21、t;,最后用 "s" 表示 "short": h-to-n-s, 或者 htons() ("Host to Network Short")。太简单了.如果不是太傻的话,你一定想到了由"n","h","s",和 "l"形成的正确 组合,例如这里肯定没有stolh() ("Short toLong Host") 函数,不仅在这里 没有,所有场合都没有。但是这里有:htons()-"Host to Network Short&quo

22、t;? htonl()-"Host to Network Long"? ntohs()-"Network to Host Short"? ntohl()-"Network to Host Long"现在,你可能想你已经知道它们了。你也可能想:“如果我想改变 char 的顺序要怎么办呢?” 但是你也许马上就想到,“用不着考虑的”。你也许 会想到:我的 68000 机器已经使用了网络字节顺序,我没有必要去调用 htonl() 转换 IP 地址。你可能是对的,但是当你移植你的程序到别的机器 上的时候,你的程序将失败。可移植性!这里是 Uni

23、x 世界!记住:在你 将数据放到网络上的时候,确信它们是网络字节顺序的。最后一点:为什么在数据结构 struct sockaddr_in 中, sin_addr 和 sin_port 需要转换为网络字节顺序,而sin_family 需不需要呢? 答案是: sin_addr 和 sin_port 分别封装在包的 IP 和 UDP 层。因此,它们必须要 是网络字节顺序。但是 sin_family 域只是被内核 (kernel) 使用来决定在数 据结构中包含什么类型的地址,所以它必须是本机字节顺序。同时, sin_family 没有发送到网络上,它们可以是本机字节顺序。2.8 实例服务器端程序/*

24、服务器程序 (server.c) */*牋 燦ame:server.c*牋 燯sed to study the network programming in Linux OS.*牋 燬howing how to use the functions,* like socket,bind,listen,accept and write.*牋 燭his is the server program.*牋 燗uthor:zeickey*牋 燚ate:2006/9/16牋 牋? ?*牋 燙opyright (c) 2006,All Rights Reserved!*/#include <sys/ty

25、pes.h>#include <sys/socket.h>#include <stdio.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <errno.h>#include <string.h>int main(int argc, char *argv)牋 爄nt sockfd,new_fd;牋 爏truct sockaddr_in server_addr;牋 爏truct sockaddr_in cli

26、ent_addr;牋 爄nt sin_size,portnumber;牋 燾har hello="Hello! Are You Fine?n"牋 ?牋? if(argc!=2)牋? 牋? 牋? fprintf(stderr,"Usage:%s portnumberan",argv0);牋? 牋? return 1;牋? 牋牋牋? if( (portnumber = atoi(argv1) < 0 )牋? 牋? 牋? fprintf(stderr,"Usage:%s portnumberan",argv0);牋? 牋? retur

27、n 1;牋? 牋牋牋? /* 服务器端开始建立socket描述符 */牋? if( (sockfd = socket(AF_INET,SOCK_STREAM, 0) = -1 )牋? 牋? 牋? fprintf(stderr,"Socket error:%sna",strerror(errno);牋? 牋? return 1;牋? 牋牋牋? /* 服务器端填充 sockaddr结构 */牋? /bzero(&server_addr, sizeof(struct sockaddr_in);牋? memset(&server_addr, 0, sizeof(st

28、ruct sockaddr_in);牋? server_addr.sin_family = AF_INET;牋? server_addr.sin_addr.s_addr = htonl(INADDR_ANY);牋? server_addr.sin_port = htons(portnumber);牋牋牋? /* 捆绑sockfd描述符,为下面的listen函数作准备 */牋? if( bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr)=-1 )牋? 牋? 牋? fprintf(stderr,&quo

29、t;Bind error:%sna",strerror(errno);牋? 牋? return 1;牋? 牋牋牋? /* 监听sockfd描述符 */牋? if( -1 = listen(sockfd,5) )牋? 牋? 牋? fprintf(stderr,"Listen error:%sna",strerror(errno);牋? 牋? return 1;牋? 牋牋牋? while(1)牋? 牋? 牋? /* 服务器阻塞,直到客户程序建立连接 */牋? 牋? sin_size=sizeof(struct sockaddr_in);牋? 牋? /if( (new_

30、fd = accept(sockfd, (struct sockaddr *)(&client_addr), &sin_size) = -1)牋? 牋? new_fd = accept(sockfd, (struct sockaddr *)(&client_addr), &sin_size);牋? 牋? if( -1 = new_fd )牋? 牋? 牋? 牋? 牋? fprintf(stderr,"Accept error:%sna",strerror(errno);牋? 牋? 牋? return 1;牋? 牋? 牋? 牋牋牋? 牋? fpr

31、intf(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);牋? 牋? 牋? return 1;牋? 牋? 牋? 牋? /* 这个通讯已经结束 */牋? 牋? close(new_fd);牋? 牋? /* 循环下一个 */牋? 牋? close(

32、sockfd);牋? return 0;客户端程序/* 客户端程序 client.c */*牋? Name:client.c*牋? Used to study the network programming in Linux OS.*牋? Showing how to use the functions,* like socket,bind,listen,accept and write.*牋? This is the client program.*牋? Author:zeickey*牋? Date:2006/9/16牋? 牋牋*牋? Copyright (c) 2006,All Right

33、s Reserved!*/#include <sys/types.h>#include <sys/socket.h>#include <stdio.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <errno.h>int main(int argc, char *argv)牋? int sockfd;牋? char buffer1024;牋? struct sockaddr_in server_addr;牋?

34、/struct hostent *host;牋? char *ip;牋? int portnumber,nbytes;牋牋牋? if(argc!=3)牋? 牋? 牋? fprintf(stderr,"Usage:%s ip portnumberan",argv0);牋? 牋? return 1;牋? 牋牋牋? /if(host=gethostbyname(argv1)=NULL)牋? printf("agrv1 = %sn",argv1);牋? if( strlen(ip=argv1)< 7 )牋? 牋? 牋? fprintf(stderr,&qu

35、ot;Get Ip address errorn");牋? 牋? return 1;牋? 牋牋牋? if(portnumber=atoi(argv2)<0)牋? 牋? 牋? fprintf(stderr,"Usage:%s hostname portnumberan",argv0);牋? 牋? return 1;牋? 牋牋牋? /* 客户程序开始建立 sockfd描述符 */牋? if(sockfd=socket(AF_INET,SOCK_STREAM,0)=-1)牋? 牋? 牋? fprintf(stderr,"Socket Error:%san",strerror(errno);牋? 牋? return 1;牋? 牋牋牋? /* 客户程序填充服务端的资料 */牋? bzero(&server_addr, sizeof(server_addr);牋? server_addr.sin_family = AF_INET;牋? server_addr.sin_port = htons(portnumber);牋? /

温馨提示

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

评论

0/150

提交评论