select版回射服务器
1.代码:
/*************************************************************************
> File Name: server.c
> Author: zhuziyu
> Mail: 1157817544@qq.com
> Created Time: 2017年04月08日 星期六 16时44分00秒
> Description:
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <strings.h>
#include <netinet/in.h>
#define MAXLINE 1024
#define SERV_PORT 6000
#define LISTENQ 100
#define OPEN_MAX 90
int main(int argc, char *argv[])
{
int i,maxi,maxfd,listenfd,connfd,sockfd;
int nready = 1,client[FD_SETSIZE];
size_t n;
fd_set rset,allset;//实际上这是两个数组
char buf[MAXLINE];
socklen_t clilen;
struct sockaddr_in cliaddr,servaddr;
listenfd = socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr));
listen(listenfd,LISTENQ);
/*对client数组的和rset数组初始化,想原图*/
maxfd = listenfd;
maxi = -1;
for(i = 0;i < FD_SETSIZE;i++){
client[i] = -1;
}
FD_ZERO(&allset);
FD_SET(listenfd,&allset);
for(;;){
rset = allset;
nready = select(maxfd+1,&rset,NULL,NULL,NULL);//就绪状态描述字的个数得到了
printf("select之后:nready = %d\n",nready);
if(FD_ISSET(listenfd,&rset) ) {
//一旦新客户的连接,这个监听套接字就变成了可读的
clilen = sizeof(cliaddr);
connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&clilen);
printf("有新客户连接了\n");
for(i = 0;i < FD_SETSIZE;i++ ){
if(client[i] < 0) {
client[i] = connfd;
break;
}
}
if(i == FD_SETSIZE) {
printf("已经满了,这个客户来晚了\n");
exit(0);
}
FD_SET(connfd,&allset);//想图
if(connfd > maxfd) maxfd = connfd;//update
if(i > maxi) maxi = i;
if((--nready) <= 0) continue;//没有其他任务了,继续回到select去阻塞等待任务
}
for(i = 0;i <= maxi;i++){
if((sockfd = client[i]) < 0) {
//这个曾经有套接字来过但是现在没有了或者这个地方没有套接字来过
continue;
}
if(FD_ISSET(sockfd,&rset)) {
//证明当前有连接
memset(buf,0,MAXLINE);
if((n = read(sockfd,buf,MAXLINE)) == 0) {
//但是这个链接这次没有任何反应,那么我把它关了,下次重新打开
close(sockfd);
FD_CLR(sockfd,&allset);
client[i] = -1;
}
else {
printf("server recv the buf = %s\n",buf);
write(sockfd,buf,n);
}
if((--nready) == 0){
//所有任务都处理完了,不用再循环检测了
break;
}
}
}
}
return 0;
}
2.学习和解释
有关client[FD_SETSIZE]:
这个数组维护当前和服务器建立连接的所有的套客户接字。从下标0到N - 1表示当前和服务器建立连接的客户一共有N个。其中,从下标N-1到FD_SETSIZE的值为-1。表示这个位置的资源没有客户在用。
有关fd_set allset:
这个fd_set是一个数组类型,allset大小是int*(系统规定的,服务器所维护的最大读/写/异常条件的描述符)。用法是这样的:以客户程序为例,如果这个客户程序的套接字描述符是connfd,那么allset上会有allset[connfd] = 1,反之则是0。
讲道理,针对回射服务器,一个服务器只要维护一个读的描述集合就可以了,为什么要维护两个?
原因:其次:其实我觉得这一点最重要。由于select本质是返回活跃的,有事件发生的个数,但是单单凭借一个nready的值不足以判断是哪个事件发生了返回,因此select用rset一次性记录了在本次select之前发生事件的所有套接字名单。可以看到,select中,rset以值-结果参数的形式进行传递,因此,select完全可以修改rset值。如果select修改了rset,变得只记录了有事件发生的套接字,那么没事件发生,但是和服务器相连的那些用户套接字怎么办呢?如果这次的select更改了rset,那么下次select就没办法监视上一次select之后被抹掉的其余用户套接字了。因此,有一个allset,在每次select之前,给rset重新赋值,表示这让select去监控所有的与服务器相连的套接字。给select传递参数时候,也是传递的所有的与服务器相连的套接字。在select执行的过程中,select更改rset,使得rset只保留本次监测到的有事件发生的套接字,以便接下来监测,rset中套接字发生的是什么事件。其实,select把每次不活跃的套接字在rset上队赢得位置全部变成负数或者0了。呼呼呼,好啰嗦,希望我表达清楚了。
对于read的理解:
read(sockfd,buf ,1024) == 0的发生,只有一种情况,那就客户端发送了一个FIN使得服务器上这个在这个合乎的文件描述符变成了可读,当服务器读到了这个已经连接的套接字,read将返回0。于是关闭套接字并更新湘阴的数据结构,把client[]数组对应的位置置为-1。
poll版回射服务器
1.代码/*************************************************************************
> File Name: server.c
> Author: zhuziyu
> Mail: 1157817544@qq.com
> Created Time: 2017年04月09日 星期日 13时38分31秒
> Description:
************************************************************************/
/*
*
*POLLIN和POLLRDNORM的坑被我整了一下午才明白,POLLIN比较保险,书上给的是POLLRDNORM
*回头我查查这两个除了汉字解释以外的区别。再补充吧。
*
* */
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/poll.h>
#include <poll.h>
#include <string.h>
#include <strings.h>
#define MAXSIZE 1024
#define OPEN_MAX 1024
#define SERV_PORT 6000
#define LISTENQ 1024
int main(int argc, char *argv[])
{
int i ,nready;
int maxi;
int listenfd,sockfd,connfd;
size_t n;
char buf[MAXSIZE];
socklen_t clilen;
struct pollfd client[OPEN_MAX];
struct sockaddr_in cliaddr,sockaddr;
listenfd = socket(AF_INET,SOCK_STREAM,0);
bzero(&sockaddr,sizeof(sockaddr));
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(SERV_PORT);
sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(listenfd,(struct sockaddr *)&sockaddr,sizeof(struct sockaddr_in));
listen(listenfd,LISTENQ);
client[0].fd= listenfd;
client[0].events = POLLIN |POLLERR;
client[0].revents = 0;
for(i = 1;i < OPEN_MAX;i++){
client[i].fd = -1;
}
maxi = 0;
for(;;){
nready = poll(client,maxi +1,-1);
if(client[0].revents & POLLIN){
printf("有要创建客户的需求!\n");
clilen = sizeof(cliaddr);
connfd = accept(listenfd,(struct sockaddr *)&cliaddr,& clilen);
for(i = 1;i < OPEN_MAX;i++){
if(client[i].fd < 0) {
client[i].fd = connfd;
client[i].events = POLLIN;
break;
}
}
if(i == OPEN_MAX) {
printf("这个客户来晚了!\n");
close(connfd);
client[i].fd = -1;
continue;
}
if(i > maxi) maxi= i;
if((--nready) <= 0) continue;
}
printf("开始进行回射部分!\n");
for(i =1;i <= maxi;i++) {
if(client[i].fd < 0) continue;
if(client[i].revents & POLLIN){
sockfd = client[i].fd;
memset(buf,0,1024);
n = read(sockfd,buf,MAXSIZE);
if(n > 0){
write(sockfd,buf,MAXSIZE);
}
if(n <= 0){
close(sockfd);
client[i].fd = -1;
}
if((--nready) <= 0) break;
}
}
}
return 0;
}
2.学习解释
对struct poll client[MAX_SIZE]的解释:
struct pollfd{
int fd; //文件描述符
short events; //请求的事件
short revents; //返回的事件
};
event和revent:
event反应的是要求服务器监测的事件,revent反应的是这个套接字发生的事件。
关于这些常量,我没有清楚普通或者优先级带数据可读的区别。一开始我按照书上的用普通数据可读,但是不给力,改成POLLIN就啥时没有。POLLIN = POLLRDNORM | POLLRNBAND。POLLIN保险。
常量 | 说明 |
POLLIN | 普通或优先级带数据可读 |
POLLRDNORM | 普通数据可读 |
POLLRDBAND | 优先级带数据可读 |
POLLPRI | 高优先级数据可读 |
POLLOUT | 普通数据可写 |
POLLWRNORM | 普通数据可写 |
POLLWRBAND | 优先级带数据可写 |
POLLERR | 发生错误 |
POLLHUP | 发生挂起 |
POLLNVAL | 描述字不是一个打开的文件 |