网络编程实验2-循环服务器设计与select多路转换

来源:互联网 发布:pp助手 mac 备份app 编辑:程序博客网 时间:2024/05/20 06:07

网络编程实验2-循环服务器设计与select多路转换


实验目的

本次实验的主要目的是学习设计循环服务器,并学习使用select实现单线程并发。

实验内容

1、设计基于UDP的循环服务器;
2、使用select设计客户端程序。

实验基础知识

1、UDP循环服务器
单个执行线程使用一个套接字与多个客户通信。
2、循环的、无连接的服务器的算法
创建套接字并将其绑定到所提供服务的熟知端口上;
重复地读取来自客户的请求,构造响应,按照应用协议向客户发回响应。
3、创建被动套接字

int  passivesock(const char *service, const char *transport,int qlen );int  passiveUDP ( const *service );{   return passivesock( service,” udp” , 0 );}

4、服务器的并发
服务器只使用单个控制线程,就能为若干个客户提供表面上的并发性。
5、服务器中的数据驱动处理
若并发服务器处理每个请求仅需很少的时间,通常它就按顺序方式执行,而执行由数据的到达驱动。只有在工作量太大,以致CPU不能顺序执行时,分时机制才取而代之。
6、用单线程进行数据驱动处理
编写一个单线程并发服务器的关键是通过在操作系统原语select中使用异步I/0。一个服务器为它必须管理的每一个连接创建一个套接字,然后调用select等待任意连接上数据的到达。
实际上,由于select可在所有的套接字上等待I/O,它也能同时等待新的的连接。
7、select 函数

#include <sys/select.h>#include <sys/time.h> int select(int nfds, fd_set  *readfds, fd_set  *writefdsfd_set  *errorfds, struct timeval  *timeout);struct timeval  {    time_t  tv_sec;    /* 秒seconds  */    long     tv_usec;   /* 微秒microseconds  */}

返回值:>0, 状态发生变化的描述符的总数;
=0, 超时;
=-1,出错。
其中:nfds: maxfds+1;
readfds: 读数据测试的fds;
wirtefds: 写数据测试的fds;
errorfds: 异常数据测试的fds;
timeout: 超时指针,NULL: 阻塞;
tv_sec = 0;
tv_usec = 0 : 非阻塞。
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 500000;
8、使用select多路转接
使用select多路转接实现IO多路转接,先构造一张有关描述符的列表,然后调用select,直到这些描述符中的一个已准备好进行IO时,该函数才返回。返回时,它告诉进程哪些描述符已准备好可进行IO。
使用多路转接所用API:

#include<sys/select.h>int select(int maxfdp1,fd_set* rfds,fd_set* wfds,fd_set* efds,struct timeval* tvptr);int FD_ISSET(int fd,fd_set* fdset);void FD_CLR(int fd,fd_set* fdset);void FD_SET(int fd,fd_set* fdset);void FD_ZERO(fd_set* fdset);

实验步骤

1.编写头文件sockutil.h

#ifndef SOCKUTIL_H#define SOCKUTIL_H#include<sys/types.h>#include<sys/socket.h>#include<arpa/inet.h>#include<netinet/in.h>#include<netdb.h>#ifndef INADDR_NONE#define INADDR_NONE 0xFFFFFFFF#endifint connectSock(char* host,char* service,char* protocol);int passiveSock(char* service,char* protocol,int qlen);void errexit(char* fmt,...);#endif

2.创建sockutil.c文件,编写通用过程connectSock及passiveSock代码

#include "sockutil.h"#include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdarg.h>#include <unistd.h>#include <errno.h>int connectSock(char* host,char* service,char* protocol){        struct hostent* pHost;        struct servent* pServ;        struct protoent* pProto;        struct sockaddr_in addr;        int s,type;        memset(&addr,0,sizeof(addr));        addr.sin_family=AF_INET;        if((pHost=gethostbyname(host))!=NULL)                memcpy(&addr.sin_addr,pHost->h_addr,pHost->h_length);        else if((addr.sin_addr.s_addr=inet_addr(host))==INADDR_NONE)                errexit("can't get \"%s\" host entry",host);        if((pServ=getservbyname(service,protocol))!=NULL)                addr.sin_port=pServ->s_port;        else if((addr.sin_port=htons((unsigned short)atoi(service)))==0)                errexit("can't get \"%s\" service entry",service);        if((pProto=getprotobyname(protocol))==0)                errexit("can't get \"%s\" protocol entry",protocol);        if(strcmp(protocol,"tcp")==0)                type=SOCK_STREAM;        else                type=SOCK_DGRAM;        s=socket(PF_INET,type,pProto->p_proto);        if(s<0)                errexit("can't create socket");        if(connect(s,(struct sockaddr*)&addr,sizeof(addr))<0)                errexit("can't connect to %s:%s",host,service);        return s;}int passiveSock(char* service,char* protocol,int qlen){        struct servent* pServ;        struct protoent* pProto;        struct sockaddr_in addr;        int s,type,reuse=1;        memset(&addr,0,sizeof(addr));        addr.sin_family=AF_INET;        addr.sin_addr.s_addr=INADDR_ANY;        if((pServ=getservbyname(service,protocol))!=NULL)                addr.sin_port=pServ->s_port;        else if((addr.sin_port=htons((unsigned short)atoi(service)))==0)                errexit("can't get \"%s\" service entry",service);        if((pProto=getprotobyname(protocol))==0)                errexit("can't get \"%s\" protocol entry",protocol);        if(strcmp(protocol,"tcp")==0)                type=SOCK_STREAM;        else                type=SOCK_DGRAM;        s=socket(PF_INET,type,pProto->p_proto);        if(s<0)                errexit("can't create socket");        if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(int))<0)                errexit("setsockopt err");        if(bind(s,(struct sockaddr*)&addr,sizeof(addr))<0)                errexit("can't bind to %s port",service);        if(type==SOCK_STREAM&&listen(s,qlen)<0)                errexit("can't to listen on %s port",service);        return s;}void errexit(char* fmt,...){        va_list arg_ptr;        va_start(arg_ptr,fmt);        vfprintf(stderr,fmt,arg_ptr);        fprintf(stderr,":%s.\n",strerror(errno));        va_end(arg_ptr);        exit(errno);}

3.编写头文件server.h

#ifndef SERVER_H#define SERVER_H//SERVER_CONFIG#define SERV_PORT "8888"//地址链表项目结构体struct addrItem{    struct sockaddr_in addr;     struct addrItem* next;};#endif

4.编写server.c文件,实现UDP单线程循环式聊天服务器

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<pthread.h>#include<unistd.h>#include<sys/select.h>#include"sockutil.h"#include"server.h"#include"msg.h"//UDP服务器端套接字##int    sockFd;//地址链表struct addrItem* addrHead;struct addrItem* addrTail;//函数声明//声明函数1,添加需要广播的地址struct addrItem* createNewAddrItem(struct sockaddr_in* addr);//声明函数2,广播消息的函数int broadcastMsg(struct msg* msg);//主线程int main(int argc,char* argv[]){    //计数    int n;    //IP字符串转换缓冲区    char addrBuf[16];    //套接字用    socklen_t slen;    //消息及账户格式    struct msg* pMsg;    if(argc!=2)        errexit("USAGE:%s port.\n",argv[0]);    sockFd=passiveSock(argv[1],"udp",0);    slen=sizeof(struct sockaddr_in);    while(1)    {               pMsg=buildMsg("");        if((n=recvfrom(sockFd,pMsg->content,MSG_LEN,0,                (struct sockaddr*)&pMsg->addr,&slen))<0)            errexit("recvfrom error");        inet_ntop(AF_INET, (void *)&pMsg->addr.sin_addr, addrBuf, 16);        printf("来自%s(%d)的消息:%s",                addrBuf,ntohs(pMsg->addr.sin_port),pMsg->content);        createNewAddrItem(&pMsg->addr);        broadcastMsg(pMsg);         memset(&pMsg,0,sizeof(pMsg));       }}//保存地址 struct addrItem* createNewAddrItem(struct sockaddr_in* addr){    //为地址链表分配内存##    struct addrItem* ai=(struct addrItem*)malloc(sizeof(struct addrItem));    struct addrItem* p;     //if判断,如果传入的地址已经存在于地址链表中,直接返回地址    //否则需要将地址添加到地址链表中##    if(addr==NULL)          return NULL;     for(p=addrHead;p!=NULL;p=p->next){          if(p->addr.sin_addr.s_addr==addr->sin_addr.s_addr &&                                 p->addr.sin_port==addr->sin_port) {           //已保存该地址,直接返回           return p;            }       }       //未保存该地址,加入地址列表中       ai=(struct addrItem*)malloc(sizeof(struct addrItem));     ai->addr=*addr;     ai->next=NULL;     if(addrHead==NULL)          addrHead=addrTail=ai;      else {          addrTail->next=ai;           addrTail=ai;       }      return ai; }//群发消息int broadcastMsg(struct msg* msg){    //循环地址链表,执行sendto向相应地址发送消息##    struct addrItem* p,*ptmp;     if(msg==NULL)          return -1;      for(p=addrHead;p!=NULL;p=p->next){         sendto(sockFd,msg->content,sizeof(struct msg),0,(struct sockaddr*)&p->addr,sizeof(struct sockaddr));      }      return 0;}

5.编写头文件client.h

#ifndef CLIENT_H#define CLIENT_H#define BUFSIZE 100#endif

6.编写client.c文件,设计select单线程客户端

#include<stdio.h>#include<string.h>#include<unistd.h>#include<sys/select.h>#include"client.h"#include"sockutil.h"int main(int argc,char* argv[]){    int ret,n;    int sockFd;    struct sockaddr_in servAddr;    fd_set oset,nset;    char strBuf[BUFSIZE];    //UDP地址绑定到服务器    if(argc!=3)           errexit("USAGE:%s hostname port",argv[0]);     sockFd=connectSock(argv[1],argv[2],"udp");    //初始化fd_set对象    FD_ZERO(&oset);       FD_SET(STDIN_FILENO,&oset);      FD_SET(sockFd,&oset);      nset=oset;     while(1)    {        //根据select的返回,执行相关的消息发送和接收函数        ret=select(4,&nset,NULL,NULL,0);           if(FD_ISSET(sockFd,&nset))        {               memset(strBuf,0,BUFSIZE);               recv(sockFd,strBuf,BUFSIZE,0);              printf("%s",strBuf);           }           if(FD_ISSET(STDIN_FILENO,&nset))           {               fgets(strBuf,BUFSIZE,stdin);              send(sockFd,strBuf,sizeof(strBuf),0);               memset(strBuf,0,BUFSIZE);           }              nset=oset;    }}

7.编写头文件msg.h

#ifndef MSG_H #define MSG_H #include"sockutil.h"  #define MSG_LEN       100//最大消息长度,包含'\0' //struct msg and configurations struct msg{      char content[MSG_LEN];//Msg Content     struct sockaddr_in addr;       struct msg* next;//next one pointer };  //struct msgQue struct msgQ{      struct msg* head;     struct msg* tail; };   //control function  //函数详细说明见msg.c  struct msg* buildMsg(char* cont); int freeMsg(struct msg* pMsg); struct msgQ* buildMsgQ(void);  int freeMsgQ(struct msgQ* pMsgQ);  struct msg* getFromMsgQ(struct msgQ* pMsgQ);  int appToMsgQ(struct msgQ* pMsgQ,struct msg* pMsg);   #endif//MSG_H

8.编写msg.c文件,用来存储发送的消息内容

#include<stdio.h> #include<string.h> #include<stdlib.h> #include"msg.h" //构建消息  //输入:消息内容指针//输出:指向struct msg类型对象的指针,失败时返回NULL struct msg* buildMsg(char* cont) {     struct msg* pMsg;       pMsg=(struct msg*)malloc(sizeof(struct msg));      if(pMsg==NULL)           return NULL;      strcpy(pMsg->content,cont);       memset(&pMsg->addr,0,sizeof(struct sockaddr_in));    pMsg->next=NULL;  return pMsg;   }   //卸载消息   //输入:指向struct msg类型对象的指针  //输出:正确卸载,返回0;否则,返回-1  int freeMsg(struct msg* pMsg) {     if(pMsg!=NULL){          free(pMsg);          pMsg=NULL;          return 0;      }      return -1;   } //构建消息队列  //输入:无   //输出:消息队列对象指针,失败时返回NULL struct msgQ* buildMsgQ(void) {      struct msgQ* pMsgQ=   (struct msgQ*)malloc(sizeof(struct msgQ));     if(pMsgQ==NULL)           return NULL;     pMsgQ->head=NULL;      pMsgQ->tail=NULL;      return pMsgQ;  }  //卸载消息队列   //输入:指向消息队列对象的指针 //输出:成功返回0,失败返回-1  int freeMsgQ(struct msgQ* pMsgQ) {      struct msg* pMsg,*tmp;    if(pMsgQ!=NULL){          pMsg=pMsgQ->head;          while(pMsg!=NULL){              tmp=pMsg->next;               free(pMsgQ);                pMsg=tmp;            }           free(pMsgQ);         pMsgQ=NULL;          return 0;       }       return -1;  }   //获取消息队列头部消息  //输入:消息队列指针  //输出:头部消息的指针,如果队列为空或发生错误,则返回NULL  struct msg* getFromMsgQ(struct msgQ* pMsgQ) {     struct msg* pHead;      if(pMsgQ==NULL)          return NULL;     if(pMsgQ->head==NULL)         return NULL;      pHead=pMsgQ->head;      if(pMsgQ->head==pMsgQ->tail)         pMsgQ->head=pMsgQ->tail=NULL;     else            pMsgQ->head=pMsgQ->head->next;     return pHead;   }   //在消息队列尾部加入消息队列 //输入:消息队列指针,消息对象指针  //输出:操作成功返回0,操作失败返回-1  int appToMsgQ(struct msgQ* pMsgQ,struct msg* pMsg) {      if(pMsgQ==NULL || pMsg==NULL)          return -1;      if(pMsgQ->head==NULL)          pMsgQ->head=pMsgQ->tail=pMsg;      pMsgQ->tail->next=pMsg;     pMsgQ->tail=pMsg;  }

9.编写makefile文件,对已编写完成的代码进行编译运行

SERVEROBJ=msg.o server.o sockutil.oCLIENTOBJ=client.o sockutil.oserver:${SERVEROBJ}    gcc ${SERVEROBJ} -o serverclient:${CLIENTOBJ}    gcc ${CLIENTOBJ} -o clientclean:    rm -rf ${SERVEROBJ} server client

简要说明:Makefile 文件描述了整个工程的编译、连接等规则。为工程编写Makefile 的好处是能够使用一行命令来完成“自动化编译”。SERVEROBJ、CLIENTOBJ为定义的一个变量,gcc命令为编译变量中的文件,-o用来设置编译后的输出文件名称。
rm命令用来删除指定的文件或目录 -r表明同时删除目录下的所有子目录,-f表明强行删除文件或目录,不提示任何信息。

10.显示实验结果

执行命令:make clean server client 对文件进行编译。
执行命令:./server 7777运行服务器端。
再开启一个新窗口,执行命令:./client localhost 7777运行客户端。
在客户端输入任意数据,服务器端能显示接收到的数据则试验成功。

阅读全文
0 0