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

网络编程实验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
#endif
int 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.o
CLIENTOBJ=client.o sockutil.o

server:${SERVEROBJ}
    gcc ${SERVEROBJ} -o server
client:${CLIENTOBJ}
    gcc ${CLIENTOBJ} -o client
clean:
    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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值