网络编程 发送what is time 返回时间.docx
《网络编程 发送what is time 返回时间.docx》由会员分享,可在线阅读,更多相关《网络编程 发送what is time 返回时间.docx(21页珍藏版)》请在冰豆网上搜索。
![网络编程 发送what is time 返回时间.docx](https://file1.bdocx.com/fileroot1/2023-4/17/5ec2f1b4-2033-49e7-aa12-b0b2777bfa69/5ec2f1b4-2033-49e7-aa12-b0b2777bfa691.gif)
网络编程发送whatistime返回时间
网络程序设计
课程设计报告
目录
第一章实验目的和要求1
1.1实验目的1
1.2实验要求1
1.3实验内容1
第二章实验内容2
2.1对题目的分析2
2.2守护进程的原理3
第三章设计实现4
3.1关键技术分析4
3.1.1创建守护进程4
3.1.2I/O复用的select函数6
3.1.3网络套接字函数7
3.2关键程序代码分析8
3.2.1创建守护进程8
3.2.2套接字编程9
3.2.3I/O复用的select函数10
3.2.4处理客户连接10
心得体会11
参考文献12
附录1程序界面截图13
附录2源代码14
第一章实验目的和要求
1.1实验目的
(1)熟练掌握所学到的网络套接字函数;
(2)掌握UDP和TCP编程关键函数;
(3)掌握多进程或多线程编程;
(4)掌握使用select实现I/O复用;
(5)掌握守护进程的编写。
1.2实验要求
(1)认真阅读和掌握本实验的相关的知识点。
(2)上机编写并运行程序。
(3)保存和打印出程序的运行结果。
1.3实验内容
本次课程设计题目如下:
(1)实现一个并发、IO复用的守护进程时间服务器,要求当客户端向服务器发送“what’stime?
”字符串时,服务器回应当时的系统时间字符串。
(2)实现一个类似于“飞鸽传书”的文件传输软件,要求其具有并发并发性、IO复用性。
当用户使用软件时要求指明“目的ip地址”和“要传输的文件的路径”。
第二章实验内容
2.1对题目的分析
我们选择实验的题目1即实现一个并发、IO复用的守护进程时间服务器,要求当客户端向服务器发送“what’stime?
”字符串时,服务器回应当时的系统时间字符串。
现在要用守护进程实现一个时间服务器,呈现的功能是:
服务器运行后自动成为守护进程,返回shell;客户端运行后收到服务器发来的当前时间。
从而达到利用网络把时间资讯传递给用户的目的。
要使服务器可以同时处理多个客户端的请求需要用到并发服务器,我们采用I/O多路复用。
详细过程如图2-1服务器端工作流程图所示。
图2-1服务端工作流程图
2.2守护进程的原理
在Client/Server模式下,服务器监听(Listen)在一个特定的端口上等待客户连接。
连接成功后服务器和客户端通过端口进行数据通信。
守护进程的工作就是打开一个端口,并且等待(Listen)进入连接。
如果客户端产生一个连接请求,守护进程就创建(Fork)一个子服务器响应这个连接,而主服务器继续监听其他的服务请求。
独立运行的守护进程由init脚本负责管理,所有独立运行的守护进程的脚本在/etc/rc.d/init.d/目录下。
系统服务都是独立运行的守护进程包括:
syslogd和cron等。
运行独立的守护进程工作方式称作:
stand-alone。
它Unix传统的C/S模式的访问模式。
服务器监听(Listen)在一个特点的端口上等待客户端的联机。
如果客户端产生一个连接请求,守护进程就创建(Fork)一个子服务器响应这个连接,而主服务器继续监听。
以保持多个子服务器池等待下一个客户端请求。
工作在stand-alone模式下的网络服务有route、gated。
另外是大家最熟悉是Web服务器:
Apache和邮件服务器Sendmail、域名服务器Bind。
因为这些负载很大服务器上,预先创子服务器,可以通过客户的服务速度。
在Linux系统中通过stand-alone工作模式启动的服务由/etc/rc.d/下面对应的运行级别当中的符号链接启动。
第三章设计实现
3.1关键技术分析
3.1.1创建守护进程
在linux操作系统中在系统的引导的时候会开启很多服务,这些服务就叫做守护进程。
守护进程是脱离于终端并且在后台运行的进程。
守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。
守护进程,也就是通常说的Daemon进程,是Linux中的后台服务进程。
它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
守护进程常常在系统引导装入时启动,在系统关闭时终止。
由于在Linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。
但是守护进程却能够突破这种限制,它从被执行开始运转,直到整个系统关闭时才退出。
如果想让某个进程不因为用户或终端或其他地变化而受到影响,那么就必须把这个进程变成一个守护进程。
创建一个守护进程如下图2-2守护进程:
图2-2守护进程服务器流程图
●创建守护进程,要先创建子进程,然后再退出父进程。
●使用的是系统函数setsid在子进程中创建新会话,从而使进程完全独立出来,从而摆脱其他进程的控制。
●改变当前目录为根目录,利用chdir(“/”)更改。
●重设文件权限掩码,使新文件的权限位不受原文件模式创建掩码的权限位的影响,使用函数umask()。
●close()关闭文件描述符。
同文件权限码一样,用fork函数新建的子进程会从父进程那里继承一些已经打开了的文件。
这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下,所以应该被关闭。
3.1.2I/O复用的select函数
我们需要服务器具有这样的能力:
如果一个或多个I/O条件满足(如输入已准备好被读,或描述字可以承接更多的输出)时,就被通知到。
这个能力被称为I/O复用,详细过程如图2-3I/O复用的过程。
图2-3I/O复用的过程
如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般要用到I/O复用。
select的功能可以用一句话来描述:
实现基于I/O多路复用的异步并发编程。
当server要对外提供大量的client请求服务时,假如使用阻塞方式,在单线程中,由于accept和recev都是阻塞式的,那么当一个client被服务器accept后,它可能在send发送消息时阻塞,因此服务器就会阻塞在recev调用。
即时此时有其他的client进行connect,也无法进行响应。
而如果使用select,在服务器端先注册由socket创建的文件描述符,然后进入select调用。
只有当由socket创建的文件描述符的状态发生改变时,才执行accept操作,并把得到的client的文件描述符进行注册,再次进入select调用。
当select检查到有文件描述符的状态改变时,如果是server的socket创建的文件描述符,则执行accept操作,否则执行recev操作。
当请求的client数目比较多时, select明显能够提高并发性。
3.1.3网络套接字函数
面向连接的客户机/服务器程序工作模式,服务器进程首先用socket()建立流套接字,在用bind()套接字与本地地址绑定,在用listen()侦听,即准备好接受连接,在用accept()接收连接,然后等待客户进程连接请求的到来,在用recv()接收数据,send()发送数据,最后用close()关闭套接字;客户端进程,首先用socket()建立套接字,在用connect()将套接字与远程主机连接,在用send()发送数据,recv()接收数据,最后用close()关闭套接字。
如下图3-3面向连接的客户/服务器程序工作模式描绘了网络套接字编程的过程。
图3-3面向连接的客户/服务器程序工作模式
3.2关键程序代码分析
3.2.1创建守护进程
●第一次调用fork的目的是保证调用setsid的调用进程不是进程组长。
(而setsid函数是实现与控制终端脱离的唯一方法)。
●setsid函数使进程成为新会话的会话头和进程组长,并与控制终端断开连接。
●第二次调用fork的目的是:
即使守护进程将来打开一个终端设备,也不会自动获得控制终端。
这样可以保证这次生成的进程不再是一个会话头。
●忽略SIGHUP信号的原因是,当第一次生成的子进程(会话头)终止时,该会话中的所有进程(第二次生成的子进程)都会收到该信号。
3.2.2套接字编程
●bind()函数就是绑定函数,其作用是调用socket()函数产生的套接字分配一个本地协议地址,建立地址与套接字的对应关系。
进程如果绑定了一个特定的本地IP地址到它的套接字上,对于TCP客户端,这就是此套接字上发送的IP数据包分配了源IP地址;对于TCP服务器端,这就限制了该套接字只接受目的地址为IP的客户连接。
●socket()创建TCP套接字,作为TCP通信的传输端点,函数中的family参数指明协议族,type参数指明产生套接字的类型,protocol参数是协议标志,一般在调用socket()函数时将其置为0。
●listen()函数,在调用之后服务器的状态从CLOSED转换到了LISTEN状态。
参数sokfd是要设置的描述符;backlog参数规定了请求队列中的最大连接数,对队列中等待服务请求的数目进行了限制。
如果一个服务请求到来时,输入队列已满,该套接字将拒绝连接请求。
●accept()函数使服务器接受客户端的连接请求,它将完成队列中的对头条目返回给进程,并产生一个新的套接字描述符,这个新生成的描述符称为“已连接套接字”当已完成对列为空时,进程睡眠,直到有已完成的连接到达时。
●TCP客户端使用connect()函数来配置套接字,建立一个与TCP服务器的连接,Connect()函数用于激发TCP的三次握手过程,建立与远程服务的连接,对于TCP连接的状态,conect()导致客户端从CLOSED状态转到了SYN_SENT状态。
若建立成功,也就是connect()调用成功,状态会再变到ESTABLISHED状态;若函数connect()调用失败,则套接字不能再使用,必须关闭。
如果想重新调用socket()函数,生成新的套接字。
●bzero()中server要置零的数据的起始地址。
从&servaddr指针所指的地址位置开始,将sizeof(sevaddr)字节置为0,bzero无返回值,并且使用string.h头文件sizeof的结果等于对象或者类型所占的内存字节数。
●sockaddr_in为IPv4套接字地址结构通常也称为“网际套接字地址结构”,名字为sockaddr_in。
●htons将一个无符号短整型(16位)的主机数值转换为网络字节顺序。
●SOCK_STREAM提供面向连接的稳定数据传输,即TCP协议。
OCK_STREAM应用在C语言socket编程中,在进行网络连接前,需要用socket函数向系统申请一个通信端口。
3.2.3I/O复用的select函数
●select()它允许进程指示内核等多个事件中的任意一个发生,并仅在一个或多个事件发生或经过指定时间时唤醒进程。
●listenfd参数是由socket()函数产生的套接字描述符,在调用accept()函数前,已经调用listen()函数将此套接字变成了监听套接字。
●cliaddr和len用来返回对方的套接字地址结构和对应的结构长度。
3.2.4处理客户连接
●connfd是套接字描述符,对于服务器是accept()函数返回的已连接套接字描述符,对客户端是调用socket()函数返回的套接字描述符。
●buff是指向发送信息的数据缓冲区。
●strlen(buff)指明传送数据的长度。
●write()发送成功后返回发送的字节数,失败返回-1。
心得体会
在本次网络程序设计课程设计中,我们小组选中的题目是“服务器端客户端通信”,即用守护进程创建一个时间服务器,实现客户端与服务器端的连接。
当然,这个题目最重要的地方并不在于服务器端与客户端实现了通信,最重要的是以守护进程的方式来实现并且要运用I/O复用。
守护进程,能够突破传统的进程创建与退出方式,不再是随着控制终端的打开和关闭而创建和退出,而是自己成为一个独立的部分,随着整个系统实行运转,不因用户或终端而受到影响,这是以守护进程创建的服务器的最大的优点,而I/O复用要解决TCP服务器既要处理监听端口又要处理以监听端口的问题。
在刚开始确定了这个题目的时候,我们先调试了书上的代码,发现书上给出的程序宏观上符合本次课程设计的概况要求,我们把代码分成若干块,以书中的程序为基础删除和添加功能,在服务器端的程序中,在定义完一系列的头文件后,先用一个函数创建守护进程,其次,编写带有select()函数的I/O复用的并发TCP服务器实现接收客户端消息;在客户端程序中,首先,实现输入字符串并且比较该字符串是否同定义的“what’stime”相同,其次,实现同数据端的连接收发数据。
这样,整个代码看起来功能层次分明,也便于学习。
因此,要完成一项任务,我们要从自己的实际出发,既不能局限于书本上的知识,我们一定要站在自身的角度上,刻苦学习,悉心钻研,这样才能圆满的完成任务。
参考文献
[1]甘刚著.Linux/UNIX网络编程.北京:
中国水利水电出版社,2008
附录1程序界面截图
我们在Linux环境下使用C语言完成了“客户端和服务器端的通信”实现了一个并发I/O复用的守护进程时间服务器。
实现结果如下图:
图附录1-1程序运行效果截图
附录2源代码
服务器:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#defineIPADDRESS"127.0.0.1"
#definePORT1234
#defineBUFFMAX1024
#defineBACKLOG5
#include
#include
#include
#defineMAXFD64
/*定义守护进程函数*/
voiddaemon_init(constchar*pname,intfacility)
{
inti;
pid_tpid;
/*为避免挂起控制终端将Daemon放入后台执行*/
if((pid=fork())!
=0)
exit(0);//是父进程,结束父进程,子进程继续
/*脱离控制终端,登录会话和进程组*/
setsid();//使进程成为会话组长
//由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
/*处理SIGCHLD信号*/
signal(SIGHUP,SIG_IGN);
//如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。
//如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。
/*禁止进程重新打开控制终端*/
if((pid=fork())!
=0)
exit(0);/*第一个子进程终止*/
/*将工作目录改变到根目录*/
chdir("/");
/*清除文件模式创建掩码*/
umask(0);
/*关闭所有打开的文件描述字*/
for(i=0;iclose(i);
/*用syslogd处理错误*/
openlog(pname,LOG_PID,facility);
}//enddaemon_init
//声明I/O多路复用select函数
staticvoidselect_(intlistenfd);
//声明处理客户端函数
staticvoiddeal_date(int*connfds,intnum,fd_set*prset,fd_set*pallset);
//主函数
intmain(intargc,char*argv[])
{
//structsockaddr_incliaddr;//IPv4套接字地址结构通常也称为“网际套接字地址结构”,名字为sockaddr_in。
//socklen_tcliaddrlen;//unsignedint
/*调用守护进程函数*/
daemon_init(argv[0],0);
/*生成TCP套接字*/
intsockfd;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1);//IPv4协议,字节流套接口
/*对服务器的结构初始化并赋值*/
structsockaddr_inserver;
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
//inet_pton(AF_INET,IPADDRESS,&server.sin_addr);
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(PORT);
/*将描述符sockfd与server套接字地址结构中的协议地址绑定*/
if(bind(sockfd,(structsockaddr*)&server,sizeof(server))==-1);
/*转换为被动套接字*/
listen(sockfd,BACKLOG);
/*I/O复用*/
select_(sockfd);
return0;
}//endmain
//I/O复用
staticvoidselect_(intlistenfd)
{
intconnfd,sockfd;
structsockaddr_incliaddr;
socklen_tcliaddrlen;
fd_setrset,allset;
intmaxfd,maxi;
intnready;//定义描述符个数
/*初始化客户连接描述符*/
inti;
intclients[FD_SETSIZE];
for(i=0;iclients[i]=-1;
/*初始化allset*/
FD_ZERO(&allset);
/*设置listenfd为1*/
FD_SET(listenfd,&allset);
maxfd=listenfd;
maxi=-1;
while
(1)//死循环
{
rset=allset;
/*获取可用描述符的个数*/
nready=select(maxfd+1,&rset,NULL,NULL,NULL);//描述字个数,读
/*测试监听描述符是否准备好*/
if(FD_ISSET(listenfd,&rset))
{
cliaddrlen=sizeof(cliaddr);
/*接受客户端连接请求*/
if((connfd=accept(listenfd,(structsockaddr*)&cliaddr,&cliaddrlen))==-1);
/*将新的连接描述符添加到客户端数组中*/
for(i=0;i{
if(clients[i]<0)
{
clients[i]=connfd;
break;
}
}
if(i==FD_SETSIZE)
printf("客户端太多啦!
!
");
/*将新的描述符添加到读描述符集合中*/
FD_SET(connfd,&allset);
maxfd=(connfd>maxfd?
connfd:
maxfd);//取最大值
maxi=(i>maxi?
i:
maxi);//取最大值
if(--nready<=0)
continue;//没有可读描述符
}
/*调用处理客户连接函数*/
deal_date(clients,maxi,&rset,&allset);
}//endwhile
}//endselect_
//处理客户连接
staticvoiddeal_date(int*connfds,intnum,fd_set*prset,fd_set*pallset)
{
inti,n;
charbuff[BUFFMAX];//数据缓存区
memset(buff,0,BUFFMAX);//将buff数组都赋值为0
time_tticks;
/*检查所有客户端数据*/
for(i=0;i<=num;i++)
{
if(connfds[i]<0)
continue;
/*测试客户描述符是否准备好*/
if(FD_ISSET(connfds[i],prset))
{
/*接收客户端发送的信息*/
if((n=read(connfds[i],buff,BUFFMAX))==0)
{
close(connfds[i]);//关闭连接
FD_CLR(connfds[i],pallset);
connfds[i]=-1;
continue;
}
/*发送当前系统时间*/
ticks=time(NULL);//获取当前系统时间
snprintf(buff,sizeof(buff),"%.24s\r\n",ctime(&ticks));//将buff数组中的字符格式化为字符串存在buff上。
/*发送时间*/
write(connfds[i],buff,strlen(buff));
}//endif
}//endfor
}//enddeal_date
客户端:
#include
#include
#include
#include
#include
#include
#include
#defineBUFFMAX1024
#defineIPADDRESS"127.0.0.1"
#defineSERV_PORT1234
//主函数
intmain(intargc,char*argv[])
{
intsockfd;
structsockaddr_inserver;
/*生成TCP套接字*/
sockfd=socket(AF_INET,SOCK_STR