Linux网络编程-实现一个局域网的电子邮件系统_第1页
Linux网络编程-实现一个局域网的电子邮件系统_第2页
Linux网络编程-实现一个局域网的电子邮件系统_第3页
Linux网络编程-实现一个局域网的电子邮件系统_第4页
Linux网络编程-实现一个局域网的电子邮件系统_第5页
已阅读5页,还剩44页未读 继续免费阅读

下载本文档

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

文档简介

Linux网络编程——实现一个局域网的电子邮件系统摘要计算机网络的迅速发展,对人类社会诸多领域产生了巨大的影响。在诸多支持网络的操作系统中,LINUX以其优秀的性能越来越引起人们的关注。本次毕业设计的任务为分析LINUX网络编程环境,并实现一个LINUX局域网的电子邮件系统。本毕业设计论文分为两大部分。第一部分对LINUX以及LINUX网络编程环境作了一个介绍。详细分析了编程中将用到的BSD套接字函数族的使用。第二部分记录了本人与合作者唐志军开发的邮件系统的实现过程,从总体分析、模块划分到详细设计,并在后面附了本人实现的C语言源代码。关键字:Linux,网络编程,套接字,服务器,客户端,电子邮件系统AbstractTherapiddevelopmentofthecomputernetworkshasbeenmakingagreatinfluenceinmanyfieldsofhumansociety.Amongagreatmanynetworksoperatingsystems,Linuxisobtainingmoreandmoreattentionfrompeoplebyitsexcellentbehavior.Inthisgraduatedesign,ourtaskistodevelopae-mailsystembasedonalanwithLinuxasitsoperatingsystem.Thisarticleincludestwoparts.Inpartone,wewillmakeanintroductiontoLinuxoperatingsystemandthenetworkprogrammingenvironmentinLinux.Inparticular,theBSDsocketfunctionsareanalyzedingreatdetail,fortheywillbeusedinlaterprogrammingwork.Inparttwo,wepresenttheimplementationofourmailsystem,fromwholedesigningtodetaildesigning,andtheCprogramsourcefileswrittenbymeisattachedtotheendofthisarticle.Keywords:Linux,networkprogramming,socket,server,client,e-mailsystem目录第一部分环境分析第一章LINUX的发展历史及趋势…….3第二章LINUX网络编程环境分析第一节套接字简介…………4第二节socket编程的基本流程…………….4第二节BSD套接字函数介绍………………5第二部分邮件系统实现第三章邮件系统介绍第一节概述…………………13第二节模块划分……………14第三节服务器程序和客户端程序的详细设计与分析……16设计体会与致谢……………….…29参考文献………….30附录服务器程序和客户端程序的源代码…………………..31第一章LINUX的发展历史及趋势在迅猛发展的国际互联网上,有这样一群人,他们是一支由编程高手、业余计算机玩家和黑客们组成的奇怪队伍,完全独立的开发出在功能上毫不逊色于微软的全新的免费UNIX操作系统——Linux,成为网络上一支不可轻视的力量,短短几年时间就成了微软的一个强劲对手。LINUX是什么?按照LINUX开发者的说法,LINUX是一个遵循POSIX标准的免费操作系统。要追述LINUX的发展历史,要先从UNIX谈起。1969~1970年间,美国电报电话公司(AT&T)Bell实验室DennisRitchie和KenThompson首先PDP-7机器上实现了UNIX系统。之后又不断推出新的版本。由于UNIX的一下特征:开放性、多用户多任务环境、功能强大、效率高、提供丰富的网络功能等,UNIX操作系统取得了巨大的成功,广泛应用于金融、军事等社会各领域。1987年,为了教学目的,计算机科学家AndrewSTanenbaum开发了一套功能简单易懂的类UNIX操作系统Minix。1991年,一位来自芬兰赫尔辛基大学的年轻人LinusBenedictTorvalds在实习Minix时发现它功能还很不完善,于是决心自己写一个保护模式下的操作系统,这就是Linux的原型。之后随着不断的完善,和GNU软件的支持,LINUX已经是一个功能强大的类UNIX操作系统了。它继承了UNIX的所有优点,而且具有源代码开放的最大特色,过去十年中它取得了惊人的成就,而且将更快的发展下去。Linux目前已经进入了许多主流公司的事业,电信、金融、政府、军事等领域也广泛采用它。目前LINUX的发展趋势为分布式和嵌入式。当今计算机科学的比较前沿的课题集群系统超级服务器就多采用LINUX操作系统。可见LINUX在网络和分布式系统的应用将是很有前景的。这次毕业设计就是探讨LINUX的网络编程开发环境,并尝试实现一个基于Linux局域网的电子邮件系统,为将来在这方面的发展打下一个基础。第二章LINUX网络编程环境分析第一节套接字简介网络程序设计全靠套接字(SOCKET)接收和发送信息。什么是套接字?套接字的英文原意是“孔”或“插座”,作为BSDUNIX的进程通信机制,取后一种意义。套接字实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则没有办法建立联系并相互通信的。正如打电话之前,双方必须拥有各自的电话机一样。在电话系统中,一般用户只能感受到本地电话机和对方电话号码的存在,建立通话的过程、话音传输过程等技术细节对他是透明的额。这与套接字机制非常相似。套接字利用网络通信设施进行通信,但他对通信设施的细节毫不关心。在UNIX系统中,任何对I/O的操作都是通过读或写一个文件描述符来实现的。一个文件描述符只是一个简单的整形数值,它代表一个被打开的文件(这里的文件指广义的UNIX文件)。套接字在系统中以文件描述符的形式存在。一个套接字可以这样来解释:它是通过标准的UNIX文件描述符和其他程序通信的一个方法。一个套接字对应一个文件描述符,由操作系统分配。服务器进程或客户进程可以象操作文件一样操作它。进行诸如创建、读、写、删除等操作,从而实现网络通信。事实上,利用套接字通信的基本过程就是先调用socket()函数,它返回一个套接字描述符,再对这个描述符进行一些操作如:系统函数send(),recv(),read(),write()等等。套接字是面向客户—服务器模型设计,针对客户和服务器提供不同的套接字操作。客户随机申请一个套接字号,服务器拥有全局公认的套接字号,任何客户可以向它发出连接请求和信息请求。套接字有三种类型:流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM)及原始套接字。流式套接字可以提供可靠的、面向连接的通信流。通过流式套接字接收的数据顺序和发送的数据顺序是一致的。数据报套接字定义了一种无连接的服务,数据通过相互独立报文进行传输,是无序的,并且不保证可靠、无差错。原始套接字允许对底层协议如IP和ICMP直接访问,主要用于新的网络协议的实现的测试等。第二节socket编程的基本流程Socket编程的的基本模式是Client/Server。Server端首先调用socket创建一个一定类型的socket。然后通过bind函数将这个socket绑定到一个Client知道的端口上,接着server调用accept函数设置倾听队列的长度,为接收来自Client端的请求做准备,而后Server调用Accept,开始在所绑定的端口倾听来自Client端的连接请求。如果Socket被设置成阻塞方式,Accept调用将被阻塞,进程被挂起,直到Server收到来自Client的请求后,Accept才返回。Client端通过socket调用创建一个一定类型socket(应当Server的socket类型相同)。然后调用connect函数向Server所在主机发出连接请求,连接时,需要指定Server所在主机的IP地址和正在倾听的端口号。Server收到Client的连接请求后,向Client发回应。Client端收到回应后,又向Server发回应信号,并从connect函数返回,返回值是一个打开的socket描述符。稍后,Server收到回应后从accept函数返回,返回值也是一个打开的socket描述符。以上过程体现了TCP协议三次握手建立连接的思想。然后双方可以通过操作各自的描述符来进行传输数据。一方使用write将要发送的数据写到TCP的缓冲区,由TCP层负责写向网络,另一端通过read将数据从TCP缓冲区中读出。read和write系统调用和对文件的读写相似。当数据传输完毕,双方都调用close()关闭各自套接字,终止通信。第三节BSD套接字基本函数介绍2.3.1函数socket()函数socket()用于创建一个socket描述符,其定义如下:#include<sys/types.h>#include<sys/socket.h>intsocket(intdomain,inttype,intprotocol);参数domain用于指定要创建的套接字使用的协议簇,可设置为AF_INET(TCP/IP协议簇),AF_UNIX(UNIX域协议簇)或AF_ISO(ISO协议簇).参数type指定套接字的类型,可以为SOCK_STREAM(流套接字),SOCK_DGRAM(数据报套接字),SOCK_RAW(原始套接字)。参数protocol一般可设置成0,表示通过指定的协议簇和需要的套接字的类型,就是系统可以确定程序需要使用的具体协议,比如流套接字和数据报套接字分别对应的TCP和UDP协议。以下代码将创建一个TCP的套接字描述符:intsock_fd=socket(AF_INET,SOCK_STREAM,0);if(sock_fd<0){perror("socketcreatingerror");exit(1);}如果socket调用失败,将返回-1,并将设置errno,以标识错误原因。2.3.2函数connect()*函数定义函数connect()用于向服务器发出连接请求。其定义如下:#include<sys/types.h>#include<sys/socket.h>intconnect(intsockfd,structsockaddr*servaddr,intaddren);参数sockfd是在socket()函数中返回的套接字描述符。参数servaddr用于指定服务器的地址和服务器端使用的传输端口号,以及地址类型。参数addrlen指定这个套接字地址的长度。*套接字地址结构structsockaddr定义了一种通用的套接字地址,其定义如下:structsockaddr{unsignedshortsa_family;/*地址类型*/charsa_data[14];/*14字节的协议地址*/};其中,sa_family为套接字的协议簇地址类型,比如TCP/IP协议簇的地址类型为AF_INET;参数sa_data中存储具体的地址内容。每一种具体的协议簇都将定义自己的协议地址类型,TCP/IP协议簇定义了sockaddr_in描述自身的协议地址:structin_addr{_u32s_addr;/*unsignedlong32比特的IP地址*/};structsockaddr_in{shortintsin_family;/*地址类型*/unsignedshortintsin_port;/*端口号*/structin_addrsin_addr;/*Internet地址*/unsignedchar_pad[_SOCK_SIZE-sizeof(shortint)-sizeof(unsignedshortint)-sizeof(structin_addr)];/*填充比特*/};在编写TCP和UDP的代码时,通常直接使用structsockaddr_in结构来赋值,然后将它强制转化成套接字的地址结构。*网络字序及其相关函数族由于不同计算机内部对变量的字节存放的顺序可能不同。有的系统设计时,将变量的高端放在高字节,有的恰恰相反。所以,如果在将数据发到网络前不进行调整的话,a=0x0001将有可能被理解为a=0x0100。为了消除这种差异,协议规定在Internet上使用的网络字节顺序采用顺序存放。在编写程序时,无论本地字序是否与网络字序相同,都应调用相应的函数进行转化。Linux系统提供了4个库函数来进行字节顺序的转化:#include<netinet/in.h>unsignedlonginthtonl(unsignedlonginthostlong);unsignedshortinthtons(unsignedshortinthostshort);unsignedlongintntohl(unsingedlongintnetlong);unsignedshortintntohs(unsignedshortintnetshort);htonl代表hosttonetworklong对unsingedlongint型的变量转化成网络中使用的字节顺序。htons表示hosttonetworkshort,对unsignedshortint类型的变量进行转化。ntohs和ntohl的功能和htons、htonl相反。*connect()函数使用示例:structsockaddr_inserv_addr;intret,sock_fd;bzero(&serv_addr,sizeof(structsockaddr_in));serv_addr.sin_family=AF_INET;serv_addr.sin_port=htons(SERVER_PORT);ret=inet_aton("",&serv_addr.sin_addr);if(ret<0){perror("convertIPaddresserror");exit(1);}ret=connect(sock_fd,(structsockaddr*)&serv_addr,sizeof(structsockaddr_in));if(ret<0){perror("connecttoservererror");exit(1);}和socket()函数相似,如果连接失败,将返回-1,并使用errno还表示错误的原因。2.3.3函数band()bind()将一个未名名的套接字一个名字,在函数socket()中,核心只是创建了一个套接字结构,但是这个套接字将工作在哪个传输层端口上,核心并没有指定。如果进程需要使用某一个固定端口,则需要程序来提供端口信息。在server进程中,由于进程通常使用的是一个熟知端口,所以需要调用bind()向系统登记一个固定端口。bind()函数定义如下:#include<sys/types.h>#include<sys/socket.h>intbind(intsockfd,structsockaddr*my_addr,intaddrlen);sockfd是由socket()函数返回的套接字描述符。my_addr是一个指向structsockaddr的指针,包含有关的地址信息:名称、端口和IP地址。addrlen为地址长度,可设置为sizeof(structsockaddr)。下面几行代码显示了bind()的使用:structsockaddr_inserv_addr;bzero(&serv_addr,sizeof(structsockaddr_in);serv_addr.sin_family=AF_INET;serv_addr.sin_port=htons(SERVER_PORT);serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);ret=(bind(sock_fd,(structsockaddr*)&serv_addr,sizeof(serv_addr));if(ret<0){perror("bindtoSERVER_PORTerror");exit(1);}这里的INADDR_ANY的含义是任何网络设备接口。对于一台只有一个IP地址的主机,它就对应于它的IP地址。2.3.4函数listen()函数listen()把套接字转化为一个被动倾听套接字,并在套接字指定的端口上开始倾听。函数形式如下:#include<sys/socket.h>intlisten(intsockfd,intbacklog);参数sockfd是在bind之后已经命名的套接字,backlog指定连接请求队列的最大长度。如果函数成功返回0,否则返回-1。由函数socket创建的是主动的套接字,也就是说是可以通过调用connect主动请求连接到某个服务器进程的套接字。但是作为服务器进程,通常应当在某个熟知端口上等待,所以需要调用listen向系统申明自己希望在哪个端口倾听,哪个端口上的TCP连接状态将被设置成LISTEN。2.3.5函数accept()函数accept()从完全建立连接的队列中接受一个连接。函数的形式如下:#include<sys/socket.h>intaccept(intsockfd,structsockaddr*addr,int*addrlen);参数sockfd是被设置为倾听的被动套接字描述符,参数addr是指向套接字地址结构的指针,它将保持连接对端的地址信息。参数addrlen是对端套接字的长度。如果程序对客户进程的地址不感兴趣,则可以将addr和addrlen设置为NULL。当accept()成功返回时,将返回一个新的套接字描述符。这个套接字不同于服务进程用于倾听的被动套接字描述符(listensocketdescriptor),被动套接字只能用于接收客户进程的连接请求。而这个新的套接字描述符就象打开一个新文件返回文件的描述符一样,进程可以使用这个新的套接字描述符向客户端写数据或者从对端接受数据。这个新套接字描述符称为连接套接字描述符(connectedsocketdescriptor)。2.3.6函数read()和write()read()和write()用于数据的接收和发送。其形式为:intread(intfd,char*buf,intlen);intwrite(intfd,char*buf,intlen);参数是由connect返回(客户进程)或者accept(服务器进程)的连接套接字描述符。read中的buf是应用的发送缓冲区,而write中的buf是应用的接收缓冲区。参数len是用于指定希望发送或接收的数据的字节数。如果read函数成功,将返回实际读取的数据的字节数。如果read返回0,则表示对端已经关闭了写管道,相当于收到文件结束符EOF。如果read出错,则返回-1,并设置errno。write和read相似,将返回实际写出的数据的字节数。2.3.7函数recv()和send()函数recv()的功能和read()相似,它在read()的功能的基础上,增加了4个参数用来对套接字的读操作进行控制,函数形式如下:#include<sys/types.h>#include<sys/socket.h>intrecv(intsockfd,void*buf,intlen,intflags);函数send()的功能和write()相似,它在write()的功能的基础上,增加了4个参数用来对套接字的写操作进行控制,函数形式如下:#include<sys/types.h>#include<sys/socket.h>intsend(intsockfd,void*buf,intlen,intflags);2.3.7函数close()函数close()用于关闭一个套接字描述符。套接字描述符和文件描述符的操作相似。其形式如下:#include<unistd.h>intclose(intsockfd);通常close在关闭一个TCP连接时,close将立即返回。进程将不能再使用套接字描述符来访问套接字,但是TCP可能并没有删除套接字结构,因为可能在发送数据缓冲区还有数据没有发送完。TCP将继续发送剩余的数据,并在最后的数据段附加FIN控制信息。基本的套接字函数就是上述几个,用以上几个函数就能进行基本的服务器和客户端的通信了。下面给出一个对以上函数应用的简单的例子:实现由服务器向客户端发送一个字符串“HELLOWORLD!”。这是套接字网络编程最简单的应用,后面实现的邮件系统就是在此基础上做一些改动和功能上的添加。服务器代码:#include<stdlib.h>#include<errno.h>#include<string.h>#include<sys/types.h>#include<netinet/in.h>#include<sys/socket.h>#include<sys/wait.h>#defineMYPORT4050#defineBACKLOG10main(){intsockfd,new_fd;structsockaddr_inmy_addr;structsockaddr_intheir_addr;intsin_size;if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){perror("socket");exit(1);}else{printf("socketcreatedsuccessfully!\n");}my_addr.sin_family=AF_INET;my_addr.sin_port=htons(MYPORT);my_addr.sin_addr.s_addr=INADDR_ANY;bzero(&(my_addr.sin_zero),8);if(bind(sockfd,(structsockaddr*)&my_addr,sizeof(structsockaddr))==-1){perror("bindB");exit(1);}elseprintf("addressbindedsuccessfully!\n");if(listen(sockfd,BACKLOG)==-1){perror("listen");exit(1);}elseprintf("startlisteningtheclients'connectrequest......\n");while(1){sin_size=sizeof(structsockaddr_in);if((new_fd=accept(sockfd,(structsockaddr*)&their_addr,&sin_size))==-1){perror("accept");continue;}printf("server:gotconnectionfrom%s\n",inet_ntoa(their_addr.sin_addr));if(!fork())if(send(new_fd,"Hello,World!\n",14,0)==-1){perror("send");close(new_fd);exit(0);}close(new_fd);}while(waitpid(-1,NULL,WNOHANG)>0);}客户端程序:/*头文件同上*/#defineMAXDATASIZE100intmain(intargc,char**argv){intsockfd,numbytes,servport;structsockaddr_inservaddr;charbuf[MAXDATASIZE];if(argc!=3){fprintf(stderr,"usage:hellocli<server'sipaddress><server'sport>");exit(1);}sockfd=socket(AF_INET,SOCK_STREAM,0);bzero(&servaddr,sizeof(structsockaddr_in));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(atoi(argv[2]));inet_aton(argv[1],&servaddr.sin_addr);connect(sockfd,(structsockaddr*)&servaddr,sizeof(structsockaddr));if((numbytes=recv(sockfd,buf,MAXDATASIZE,0))==-1){perror("recv");exit(1);}buf[numbytes]='\0';printf("Receivefromserver%s:\n%s",argv[1],buf);printf("totally%dbytesreceived\n\n",numbytes);close(sockfd);printf("......localsocketclosed\n");return0;}第二部分第三章邮件系统的实现第一节概述(问题定义、需求分析和可行性分析)前几章对LINUX系统提供的网络编程接口进行了分析,为第二部分的内容——实现一个C/S模型的局域网内的电子邮件系统——打下了基础。接下来的章节将给出该邮件系统的概述、总体设计、详细设计和本人编写的服务器程序和客户端程序的源代码。为方便起见,将要实现的邮件系统命名为DTMS。由于是为了实习目的模拟现实的邮件系统,该邮件系统较现实中的邮件系统功能上有较大的简化。具备的是邮件系统必备的一些基本功能。如:申请新邮箱、注销邮箱、发邮件、阅读邮件、删除邮件、保存邮件。从服务器和客户端的角度,它们分别需实现以下功能:服务器:1、保存用户信息(帐号密码对)2、验证登录用户密码3、邮件接受转发功能(接受发信用户发来的邮件,将邮件发给收信用户)4、邮件管理功能(保存,打开,删除等)客户端:1、选择需要的服务2、读邮件,删邮件,编辑邮件,发送邮件另外,在以下四种场合,服务器和客户端需共同完成通信的功能:1、客户端向服务器发送服务请求、密码等信息2、服务器向客户端发送响应信息(服务完成、密码验证信息等)3、客户端向服务器传送邮件4、服务器向客户端传送邮件服务器和客户端分别需实现的功能可以采用LINUX系统提供的文件操作系统调用实现。服务器和客户端共同完成的通信功能可使用LINUX系统提供的BSDSOCKET网络编程接口实现。总之,对上述功能,该系统的实现在目前的知识基础上是可行的。第二节模块划分整个系统由服务器程序和客户端程序两大部分组成。服务器程序由两大模块组成:通信模块和服务器文件处理模块;客户端程序也由两大模块组成:通信模块和界面提供模块。基本结构如下:服务器主程序调用以下两大模块:1、服务器文件处理模块⑴信件管理子模块(打开邮件列表,打开邮件,删除邮件)⑵用户信息管理子模块(保存帐号密码对,验证密码)2、服务器通信模块(服务器向客户传邮件,服务器向客户传服务响应信息)客户端主程序调用以下两大模块1、客户端界面提供模块2、客户端通信模块(客户向服务器传邮件,客户向服务器传服务请求信息)我们实现的邮件系统的运行大致过程为:服务器和客户机分别进入主程序,调用各自通信模块建立连接。同时,客户端调用界面提供模块提供菜单式的操作界面,等待用户根据菜单提示选择需要的服务,通信模块将用户选择的服务用字符串的形式发送给服务器,服务器根据收到的字符串判断用户需要的哪种服务,调用文件处理模块中的相应服务子程序(函数)来提供特定服务。并同时在需要时调用通信模块向客户端发回应信号或传送文件。下面对各模块的功能和实现方案详细解释说明如下:1、服务器文件处理模块:包括两个子模块,信件管理模块和用户信息管理子模块。这两个子模块功能上是独立的,各自包含一些服务子程序。实现的方法是一样的,同为利用LINUX提供的文件操作系统调用。为满足邮件系统提供的基本功能,文件处理模块需具备以下文件:(1)用户信息文件一个:保存各用户的帐号密码信息,以记录形式保存。(2)邮件列表文件若干:为各用户保存各自的邮件列表,以记录形式保存,记录包括寄信者,邮件编号,发信时间,主题等项。每个用户拥有一个邮件列表文件。(3)邮件文件若干:保存用户的邮件内容。2、信件管理子模块:该模块包含打开邮件、保存邮件、删除邮件功能,分别由一个函数实现。(1)打开邮件列表:在用户登录成功时,即用户输入用户名密码得到正确验证时调用此功能。用一个函数读取该用户的邮件信息文件,产生一个邮件列表的临时文件,并通过通信模块将该临时文件的内容传至客户机。(2)打开邮件:在收到客户“读邮件”命令时调用此功能。根据用户名以及用户传过来的邮件号码,打开相应邮件,通过通信模块将邮件内容传给客户机。(3)删除邮件:在收到客户“删除邮件”命令时调用此功能。根据用户名及用户传过来的待删邮件的号码利用系统调用将相应邮件删除。并修改保存该用户邮件列表的文件。3、用户信息管理子模块:该模块包含保存、删除帐号密码对,验证密码三个功能。(1)保存用户密码对:在新用户注册时,即用户选择“注册”命令,并将用户名和密码传到服务器时调用此功能,将新用户的信息——用户密码对添加到用户信息文件中。(2)验证密码:在用户登录,并发送帐号密码到服务器后调用此功能。实现原理为根据欲登录用户发过来的用户密码对查找服务器上的用户信息文件。若查找成功,发提示信息到客户端,并转入调用打开邮件列表的服务程序。若查找失败,发失败提示信息到客户端,等待用户重新输入帐号密码。4、服务器端通信模块服务器程序在启动时便调用SOCKET函数族,创建套接字,绑定端口,开始等待客户端的连接。当有用户进入邮箱,即建立连接。并根据用户的不同服务请求,利用建立好连接的套接字传送或接收数据。在以下四个场合需要调用服务器端的read/write或send/revc函数与客户端通信:(1)服务器向客户端发服务响应信息(字符串形式)(2)服务器向客户传送邮件(服务器打开邮件文件,传输文本,客户接收文本写入本地新文件)(3)客户端向服务器发服务请求(字符串形式)(4)客户端向服务器传送邮件(客户打开邮件文件,传输文本,服务器接收文本写入本地新文件)5、客户端界面提供模块该模块提供用户使用邮件系统的界面。采用字符菜单界面的形式。客户端主程序启动时,一边建立与服务器的连接,同时提供用户操作界面。在不同时段(状态),将提供不同的界面。根据用户不同的操作请求,不断调用该模块提供不同的界面,可细分为以下几种:(1)系统初始界面:显示已经入邮件系统,提供“注册”,“登录”命令选择(2)输入帐号密码界面:提示用户输入帐号和密码,用户选择“注册”或“登录”都进入该界面,用户在此界面中输入帐号密码后发送给服务器端。(3)进入邮箱界面:打印邮件列表,提供“读邮件”,“发邮件”,“删除邮件”,“退出邮箱”,“注销用户”等命令选择。(4)邮件编辑界面:当用户选择“发邮件”进入该界面。用户开始编辑新邮件。根据命令行提示语句依次输入收信者,主题,正文,按Ctrl+D结束编辑,邮件自动发送。(5)读邮件界面:当用户选择“读邮件”命令,服务器将要读的邮件传送到客户端,在标准终端显示。6、客户端通信模块在客户端主程序启动时,即调用SOCKET函数族与服务器建立连接。并在需要的场合与服务器交换数据。需要传送或接收数据的场合同服务器通信模块中所述。此模块与服务器不同的是需要知道服务器工作在哪个端口,这里存在一些技术问题,这个问题将在后面进行讨论。第三节服务器程序和客户端程序的详细设计与分析一、详细设计代码组织:如前所述,服务器程序分为主程序和通信模块及文件处理模块。主程序和通信模块放在一个文件中,共同组成服务器程序sevprog.c(1~372行)。文件处理模块由include语句引用。客户端程序放在一个文件cliprog.c中(375~758行)。虽然这两个程序在附录中是统一编行号(1~758),这只是为了解释方便,实际上它们是运行在不同机器上的两个不同的C文件。文件处理模块由合作者唐志军实现,限于篇幅,没有附源代码,只对接口函数作了一下解释。数据格式:自定义抽象数据结构邮件结构类型。一个邮件结构包括以下几项:收信者,发信者,主题,邮件正文,发信时间,邮件编号等等。有件结构定义见18~25行。同步机制:同步是通信模块实现正常收发信息的关键。类似与操作系统原理中的生产者消费者问题,当生产者还没有往缓冲中写数据,消费者就不能从缓冲区中读数据,否则取得的数据不是有效的数据,或者导致读文件失败。所以在客户端与服务器通信的过程中,必须有严格的同步控制。在本程序中,同步采用一个简单有效的办法。人为设计信息收发协议,根据协议规定的步骤,发信息方在开始发信息前先发送一个同步字符,然后再发有用信息。收信息方在发信息方发同步字符前采用循环读取的方式等待同步字符的到来,用WHILE循环实现,当读到同步字符,才开始读下面的有用信息。用于同步的函数有以下一对:intSendSockChar(intsockfd,charch):发送一个同步字符。参数为套接字号和一个待发送的字符。该函数将一个字符写入套接字中,即发送给对方。发送成功函数返回OK。函数实现见175~179行。charGetSockChar(intsockfd):接收一个同步字符,接收对方用SendSockChar()发来的同步字符,读取过程中用WHILE循环,直到读到有效的同步字符为止,函数返回值为读到的同步字符。函数实现见166~174行。5、读写函数:合理使用读写函数也是通信模块实现正常通信的重要点。在利用系统函数read(),write()的基础上,还另外使用了几个自编的函数:readline(),sockendl(),CutEndl(),分别说明如下:intreadline(int,void*,int):从套接字中读取一行内容。一行指的是一换行符'\n'为结束的一段字符串,包括换行符本身。此函数引自参考书[1],P79。实现见73~99行。intsockendline(int):发送换行符。由于从套接字中读内容采用readline函数,要读到换行符才标志一行结束,才能读出一行字符串,所以发送方发出一个字符串若不是一换行符结尾,则要补发一个换行符,才能使接受方正常读取。函数SOCKENDLINE实现补发换行符的功能。函数实现见100~106行。voidCutEndl(char*str):去换行符。接受方用READLINE函数接受一行字符串包括了结尾的换行符,这样会在一些地方造成错误,如字符串形式的用户名和密码若加了换行符就变成了另一个字符串了,虽然使用者感觉不到,但回在服务器程序验证密码时返回错误结果。CutEndl函数在必要的时候去掉刚刚接收到的字符串的末尾的换行符。函数实现见107~113行。6、头文件和常量定义:1~9行是需用到的系统头文件,分别用于SOCKET通信,I/O操作,字符串操作,时间读取等等。10~11行是对文件处理模块的包括。13~16行定义了一些常量,MAXSIZE为缓冲区的最大容量。MYPORT为服务器端建立SOCKET连接使用的端口号。BACKLOG为倾听队列最大长度。OK为函数成功返回标志。7、主程序服务器主程序:主程序用于初始化服务器的地址信息,创建套接字开始倾听,等待连接,当服务器进程接收到一个连接,用系统调用FORK创建一个子进程为客户服务,父进程继续在端口上等待连接。这样可以实现服务器同时为多个客户服务,这种工作方式成为并发服务器。本程序采用这种并发服务的方式。Main()函数代码见46~70行。客户端主程序:客户端的main()函数比服务器的更加简单。只是用到命令行参数,传入服务器程序运行的IP地址和端口号。命令行参数个数(3个)得到验证后建立与服务器的连接,然后调用客户端的服务主函数,服务完成即退出。客户端main()函数代码见422~438行。8、“连接函数”为了使主程序简洁,用套接字建立连接的过程都写进一个函数,用主程序来调用。服务器和客户端的该函数分别为StartListening()和ConnectToServer(),其中StartListening()通过调用socket(),bind(),listen()建立倾听套接字,开始等待客户的连接;ConnectToServer()通过调用socket(),connect()建立与服务器的连接,其参数为服务器的IP地址和端口号,调用时传入命令行参数argv[1]和argv[2]。函数实现分别见117~126行和440~451行。9、服务“主函数”当主程序建立连接后,服务器和客户双方都进入一个“主函数”,通过层层调用各子程序完成邮件服务功能。a、服务器主函数intServerMain(intsockfd):当一个用户与服务器建立连接,服务器程序产生一个子进程为之服务,该子进程调用ServerMain函数为客户服务,此函数返回,服务即结束。函数主要内容为接受用户第一次传来的服务请求,决定提供注册服务或登录服务,然后进入相应服务函数。此函数实现见150~165行。调用见第64行。

b、客户端主函数intClientMain(intsockfd):客户端主程序建立好与服务器的连接后,调用此函数,实现对用户的邮件服务。该函数的内容为提供用户进邮件系统的初始交互界面,让用户输入初始命令,选择注册或者登录,然后调用相应子程序进行服务,当子程序退出,该函数也结束,客户端的主程序也结束。此函数实现见453~468行,调用见433行。9、邮件服务函数:邮件系统功能的实现由以下一些服务函数实现,它们的作用为提供用户界面接收用户命令,在服务器与客户机间传输数据和命令,并调用文件操作模块的接口函数来实现对邮件的处理。这些函数在服务器和客户端成对出现,从而能实现成功的通信功能。a、服务器服务函数:intRegisterSev(intsockfd);/*注册功能服务器端函数181~190行*/intRecvUsrPswd(intsockfd,char*Usr,char*Pswd);/*接收用户密码214~224行*/intLoginSev(intsockfd);/*登录功能服务器端函数,192~213行*/intMailService(intsockfd,char*Usr);/*进入信箱后邮件服务服务器端函数,226~250行*/intSendMaillist(intsockfd,char*Usr);/*发送邮件列表服务器端函数,252~274行*/intReadMailSev(intsockfd);/*阅读邮件功能服务器端函数,276~297行*/intDelMailSev(intsockfd,char*Usr);/*删除邮件功能服务器端函数,299~313行*/intSendMailSev(intsockfd,char*Usr);/*发送邮件功能服务器端函数,315~372行*/b、客户端服务函数:intRegisterCli(intsockfd);/*注册功能客户端函数,503~512行*/intSendUsrPswd(intsockfd,char*Usr,char*Pswd);/*发送用户密码,554~568行*/intLoginCli(intsockfd);/*登录功能客户端函数,513~527行*/intMailClient(intsockfd);/*登陆成功邮件服务客户端函数,570~605行*/intReadMailCli(intsockfd);/*读邮件功能客户端函数,662~697行*/intDelMailCli(intsockfd);/*删邮件功能客户端函数,699~713行*/intSendMailCli(intsockfd);/*发邮件功能客户端函数,714~758行*/10、流程图以下是服务器程序和客户端程序的流程图,从图中可以看到本地各函数的调用关系,和两端对应函数的对应关系。其中,服务器程序根据收到的命令转移,客户端程序根据用户输入的命令转移。图中字母表示输入或收到的命令字符。开始开始创建套接字开始倾听创建套接字开始倾听StartListening()收到连接请求父进程子进程服务器主函数ServerMain()创建子进程服务器主函数ServerMain()创建子进程fork()LR登录程序LoginSev()登录程序LoginSev()注册程序RegisterSev()邮件服务函数MailService()邮件服务函数MailService()结束结束RDSE读邮件ReadMailSev()删邮件DelMailSev()读邮件ReadMailSev()删邮件DelMailSev()结束发邮件SendMailSev()图1服务器程序流程图开始开始建立连接建立连接ConnectToServer()客户端主函数客户端主函数ClientMain()RL注册子程序RegisterCli()注册子程序RegisterCli()登录子程序LoginCli()注册成功登录成功结束结束邮件服务客户端函数邮件服务客户端函数MailClient()REDS读邮件ReadMailCli()删邮件DelMailCli()读邮件ReadMailCli()删邮件DelMailCli()发邮件SendMailCli()结束图2客户端程序流程图11、文件处理模块接口函数第10行和11行的两个头文件"mail.h"和"usrpass.h"包括了服务器邮件处理模块和用户信息管理模块的函数,在服务器程序中调用了以下六个接口函数,分别说明如下:intUsrStore(char*Usr,char*Pswd);保存用户密码对。参数为字符串形式的用户名和密码,当服务器收到注册客户传来的密码,调用此函数,用户名和密码将写入用户信息文件PASSWD中去。函数调用见188行。intTestPasd(char*Usr,char*Pswd);验证密码。参数为字符串形式的用户名和密码,当服务器收到登录用户传来的用户名和密码,调用此函数,查找PASSWD文件,若找到匹配的用户密码对,返回TRUE(1),否则返回FALSE(0)。函数调用见203行。intgetmsgfrmdb(char*Usr);形成邮件列表临时文件。参数为字符串形式的用户名。当用户登录成功,服务器程序调用此函数,查找该用户的邮件信息文件,形成一个邮件列表的临时文件"MASSAGE",以待通信模块将邮件信息发送至客户端。函数调用见257行。intStoreMail(MailStruct*Mail);保存邮件。参数为邮件结构类型的邮件(邮件结构类型是自己定义的抽象数据类型,将在后面解释)。当发送邮件的客户将邮件发送至服务器,服务器根据收信者的用户名将邮件保存到特定文件中,即收信者的邮件文件中,以待收信者登录时可以读取。函数调用见366行。intGetMail(intmail_no,char*buf);取邮件。参数为整形邮件代号,和字符串形式缓冲区。当服务器收到欲读邮件的客户发过来的邮件代号,根据该代号在相应文件中查找到欲读的邮件,把内容写到一个缓冲里,用BUF返回。以待通信模块将它发送到客户端。函数调用见287行。intDelMail(intmail_no,char*Usr);删邮件。参数为整形邮件代号和字符串形式的用户名。当服务器根据客户发过来的邮件代号删除相应邮件,即修改邮件文件,并修改邮件信息文件。函数调用见307行。以上六个函数由合作者唐志军实现,限于篇幅,不列出具体实现的源代码。二、补充说明程序的编译和启动:服务器程序sevprog.c和文件处理模块的C文件和H文件放在同一个目录下,用SHELL提示符下,输入语句:gcc*.c-osevprog,既得到可执行文件sevprog,在SHELL提示符下,输入./sevprog即启动了服务器程序。客户端程序cliprog.c放在另外一个目录,在SHELL提示符下输入语句:gcccliprog.c-ocliprog即得到可执行文件cliprog,在另一个虚拟终端输入./cliprog5500,即启动了客户程序,为服务器的IP地址,5500为服务器程序中绑定的端口号。运行状况:由于条件限制,没有LINUX联网环境,该邮件系统在同一台电脑上模拟实现。实际运行环境为REDHATLINUX7.0。在字符界面用虚拟终端方式(用Alt+F*切换),用两个虚拟终端分别模拟服务器和客户机,分别运行服务器程序和客户机程序,运行成功。在GNOME图形界面,打开两个终端窗口,分别运行服务器程序和客户端程序也得到成功运行。下面是GNOME图形界面客程序运行图。LINUX截图程序得到。图一展示了在GNOME图形界面服务器程序和客户端程序同时运行于两个虚拟终端的情形。图二展示了服务器程序启动后的情形,服务器界面反映服务器的状态。图三为客户端程序启动后的界面。用户选择注册功能,注册了一个新用户tangweii.图四展示用户tangzj的登录状况。登陆成功后得到邮件信息列表,并被提示输入服务请求。图五为登陆成功后的界面,用户tangzj选择读邮件命令,并读了一封来自ducktang的信。图六展示发邮件界面。tangzj选择发邮件命令,给ducktang编辑了一封邮件并发出。图一图二图三图四图五图六已实现的功能:事先定的功能目标,邮件系统的基本功能都已实现。注册用户,在客户端校验密码,服务器保存资料;用户登录,服务器校验密码;客户登录成功时,服务器将该用户邮件列表传至客户机,用户选择读邮件可以读相应邮件,选择删邮件可以删相应邮件,选择发邮件可以进行邮件输入,将邮件内容发给服务器,让服务器保存在文件系统中,待收信者登录将邮件信息发给他,这样就完成了一次邮件发送过程。待完善的功能以及实现设想:由于时间有限,还有许多邮件系统本应具备的功能没有实现,下面作一分析,并提出相应的实现方案或设想。(1)发附件:本邮件系统没有发附件功能。此功能并不难实现。由于LINUX文件都采用二进制流形式,附件可以向文本文件一样打开后通过套接字发送。只是接收时应能区别它是附件文件,而非一般的文本文件。也就是附件的文件类型应该用文本形式另外发送,这样才能让接受者知道福附件到底是什么文件。(2)邮件编辑功能:本邮件系统在用户发邮件时,输入收信者地址、主题以及邮件正文采用在命令行直接输入的形式,这样不利于邮件的编辑。这是本邮件系统有待完善的重要地方。至今没有很好的解决方案。设想可以调用VI等编辑程序,将邮件内容编辑好了,再采用发送文件的方式发邮件。(3)邮件回复功能和抄送功能:这两个功能不难实现。邮件回复只需在客户端进入邮箱选择命令时增加一个选项,然后再增加对应的服务函数,由于实现与发邮件有所类似所以为简便起见,编程时忽略了该功能。抄送也只需在客户发收信者地址时增加一个数据结构,服务器保存邮件时就不仅保存在一个收信者的信件文件中了。(4)服务器端口号问题:在上一节介绍客户端通信模块时提到一个问题,即客户程序必须知道服务器程序的IP地址和端口号,IP地址是固定的,但端口号却不一定固定。本系统在服务器主程序DEFINE语句定义了一个端口号(5500,见第14行),在用BIND函数绑定端口时就绑定的这个端口。客户程序在启动程序时用命令行参数传入服务器的IP地址和端口号,所以服务器的端口号必须为客户机所知,但在实际情况中这并不容易做到。实际的邮件系统采用公用的协议,用的是小于1024的保留端口号,如SMTP协议端口号为25。我们实现的邮件系统不能使用保留端口号,所以服务器程序中绑定的端口号有可能已被其他程序使用。在模拟情况下,系统运行程序较少,发生端口号冲突的概率是很小的,但用到实际中这是一个不可避免的问题,目前还没有好的解决方案,只有当这个邮件系统非常完善,能申请一个保留的固定的端口号,才能解决这个问题。设计体会与致谢这次毕业设计是我第一次编写大型的程序,在指导老师沈明玉老师的指点下和合作者唐志军的通力合作下,这次设计的任务顺利完成。应该说一接到这个任务感到有些无从下手,难度很大,但经过几个月来慢慢的斟酌,学习,以及动手编程,在不断发现问题,解决问题的过程中,任务渐渐化整为零,化难为易,最终得到实现。在这一过程中,实现老师给出这一任务的初衷,不要求做出来的东西有多完美,只要求在动手的过程中能学到书本上学不到的东西。实际上,通过几个月的思考、学习和动手,在完成任务的同时,自己收获颇多,分别总结如下:1、熟悉了LINUX操作系统编程环境,熟悉LINUX网络编程的基本思想,积累了一些C语言编程经验,自己的动手能力得到提高。本次设计使用LINUX编程环境,没有先进的调试工具,这在编程中用到一些技巧,积累了一些编程经验。同时在编程过程中感到C语言的灵活功能强大,以及LINUX操作系统的发展前景。2、锻炼了自己现学现用的能力,许多东西以前都没有接触过,通过查阅参考书能够快速的使用。(如BSD套接字函数的使用,以及许多C函数的使用)3、锻炼了自己分析问题解决问题的能力,在编程过程中,经常遇到这样那样的问题而使进度有所停顿,经过许多思考和尝试,往往能在解决问题时感到山重水复疑无路,柳暗花明又一村的感觉。当一个个的问题得到解决,系统的雏形就逐渐形成。这让知道,在以后做更大项目的时候,遇到暂时的困难一定不要气馁,只要用心思考与探索,利用一切有用的因素,一定能找到解决问题的方案。4、锻炼了团队合作能力。本次设计顺利完成很大程度上得益于分工的合理和合作的愉快。在互相不知道对方怎样实现的情况下,能够顺利对接成功,这得意于一开始有明确的分工,在编程过程中不断有交流,特别是需要接口的地方有比较严格的商定,如一些需要互相调用的函数名,函数参数等等。另外,经过本次设计,提高了自己对程序设计开发系统的兴趣,提高了对LINUX操作系统的兴趣,为今后的学习方向,并行分布式处理软件,提供了一个良好的开端。再次感谢沈明玉老师的指导和唐志军的合作。参考文献1、UnixNetworkProgrammingWRichardSteventsPrenticeHall19982、TheCProgrammingLanguageDennisRitchie&KenThompson,PrenticeHall19883、操作系统原理(第三版)庞丽萍华中科技大学出版社2001.24、Linux网络编程林宇郭凌云人民邮电出版社2000.105、Linux网络编程李卓桓瞿华机械工业出版社2000.16、LinuxC函数库参考手册徐千洋中国青年出版社2002.17、GNU/LINUX编程指南K.Wall等著,王勇等译清华大学出版社2000.68、数据通信与计算机网络高传善等高等教育出版社2001.7附录:服务器程序和客户端程序的源代码0/*sevprog.c*/1#include<stdio.h>2#include<stdlib.h>3#include<errno.h>4#include<string.h>5#include<sys/types.h>6#include<netinet/in.h>7#include<sys/socket.h>8#include<sys/wait.h>910#include"mail.h"11#include"usrpass.h"1213#defineMAXSIZE50014#defineMYPORT550015#defineBACKLOG1016defineOK11718typedefstruct{19intmail_num;20charto[MAXSIZE];21charfrom[MAXSIZE];22charsubject[MAXSIZE];23charcontent[MAXSIZE];24time_trecvtime;25}MailStruct;2627intServerMain(int);2829charGetSockChar(int);30intSendSockChar(int,char);31intreadline(int,void*,int);32intsockendline(int);33voidCutEndl(char*str);3435intRegisterSev(int);36intRecvUsrPswd(int,char*,char*);37intLoginSev(int);3839intMailService(int,char*);40intSendMailList(int,char*);4142intReadMailSev(intsockfd);43intDelMailSev(intsockfd,char*Usr);44intSendMailSev(intsockfd,char*Usr);4546intmain()47{48intsockfd,new_fd;49structsockaddr_intheir_addr;50intsin_size;5152sockfd=StartListening();5354while(1)55{56sin_size=sizeof(structsockaddr_in);57new_fd=accept(sockfd,(structsockaddr*)&their_addr,&sin_size);58if(new_fd==-1){59perror("accept");60continue;61}62printf("server:gotconnectionfrom%s",inet_ntoa(their_addr.sin_addr));63if(!fork()){64ServerMain(new_fd);65close(new_fd);66}67close(new_fd);68}69while(waitpid(-1,NULL,WNOHANG)>0);70}717273int/*readatextlinefromadescriptor*/74readline(intfd,void*vptr,intmaxlen)75{76intn,rc;77charc,*ptr;7879ptr=vptr;80for(n=1;n<maxlen;n++){81again:82if((rc=read(fd,&c,1))==1){83*ptr++=c;84if(c=='\n')85break;/*newlineisstored,likefgets()*/86}elseif(rc==0){87if(n==1)88return(0);/*EOF,nodataread*/89else90break;91}else{92if(errno==EINTR)93gotoagain;94return(-1);/*error,errnosetbyread()*/95}96}97*ptr=0;/*nullterminatelikefgets()*/98return(n);99}100intsockendline(intsockfd)/*ok*/101{102constcharch='\n';103if(write(sockfd,&ch,1)==1)104return0;105else-1;106}107voidCutEndl(char*str)108{109intlen;110len=strlen(str);111if(str[len-1]=='\n')112str[len-1]='\0';113}114115116117intStartListening()/*ok*/118{119intsockfd;120structsockaddr_inmy_addr;121intsin_size;122123if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){124perror("socket");125exit(1);126}127

温馨提示

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

最新文档

评论

0/150

提交评论