网络编程实验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运行客户端。
在客户端输入任意数据,服务器端能显示接收到的数据则试验成功。
- 网络编程实验2-循环服务器设计与select多路转换
- I/O多路转换之select与select服务器
- 网络编程实验3-并发多线程服务器设计
- Linux下网络socket编程——实现服务器(select)与多个客户端通信
- Linux c==网络编程、循环服务器、并发服务器、I/O多路转接 (23)
- Linux c==网络编程、循环服务器、并发服务器、I/O多路转接
- linux网络编程2-服务器设计
- Linux【网络编程】——I/O多路转接之Select服务器
- C++网络编程服务器select模型(参考)
- Linux网络编程之select服务器
- Verilog 编程实验(2)-4位2选1多路选择器的设计与实现
- 使用select.select编写聊天室服务器 《Python网络编程攻略》
- 网络编程中设计并发服务器,使用多进程与多线程有什么区别?
- Linux网络编程之循环服务器
- Linux网络编程之循环服务器
- Linux网络编程之循环服务器
- [Linux网络编程] 循环服务器的实现
- 网络编程-实验2-C与C通信
- http://www.cnblogs.com/Dreamer-1/p/5530221.html
- git使用一
- 快慢链表和快慢指针
- Tomcat+JSP+Oracle信息查询系统开发笔记(2)
- 本地存储(openFileInput,openFileOutput)
- 网络编程实验2-循环服务器设计与select多路转换
- java之socket的OOBInline和UrgentData和发送心跳包研究
- 指针数组与数组指针
- 7. Nginx 预定义变量
- 记录一些学习迷茫时要经常看看的网址
- WATCHDOG驱动框架
- 找到最长子串位置
- Far Manager的简单使用
- POI操作Excel读取与导出