1.用到的库
①libev
使用它的对于select,epoll等等的封装来实现IO多路复用
②csapp
使用的是csapp书中的socket函数,以及包装函数来实现一些socket的函数
2.功能
server关注的事件有两个。
①listenfd有连接请求到来的时候,accept,并且将建立的新的socket连接建立为监听事件,加入loop之中。
②在connfd上面有信息过来的时候,进行echo,每次echo一句话。当客户端那边断开的时候,进行一些清理工作。
3.代码
①客户端代码
#include "csapp.h"
#include "csapp.c"
int main(int argc, char**argv)
{
int clientfd, port;
char *host, buf_send[MAXLINE], buf_recv[MAXLINE];
rio_t rio;
if (argc != 3)
{
fprintf(stderr, "usage: %s <host> <port>\n", argv[0]);
exit(0);
}
host = argv[1];
port = atoi(argv[2]);
clientfd = Open_clientfd(host, port);
Rio_readinitb(&rio, clientfd);
while(Fgets(buf_send, MAXLINE, stdin) != NULL)
{
rio_writen(clientfd, buf_send, 1);
rio_writen(clientfd, buf_send+1 , strlen(buf_send)-1 );
Rio_readlineb(&rio, buf_recv, MAXLINE);
Fputs(buf_recv, stdout);
}
Close(clientfd);
printf("我退出,我让座,假洒脱。\n");
exit(0);
}
②服务器代码
#include <ev.h>
#include "csapp.h"
#include "csapp.c"
#define MAXCONN 2
int conn_cnt = 0;
static void listenfd_cb(struct ev_loop *loop, ev_io *w, int revents);
static void connfd_cb(struct ev_loop *loop, ev_io *w, int revents);
ev_io listenfd_watcher;
ev_io connfd_watcher[MAXCONN];
static void
listenfd_cb(struct ev_loop *loop, ev_io *w, int revents)
{
conn_cnt++;
if(conn_cnt > MAXCONN)
app_error("listenfd_cb error: too many clients.");
//accept for the w->fd, int connfd = accept()
socklen_t clientlen = sizeof(struct sockaddr_in);
struct sockaddr_in clientaddr;
int connfd = Accept(w->fd, (SA *)&clientaddr, &clientlen);
//find the watcher position for new connfd
int i;
for(i = 0; i < MAXCONN; i++)
{
if(connfd_watcher[i].fd == -1)
break;
}
//bind connfd with a new watcher
ev_init(&connfd_watcher[i], connfd_cb);
ev_io_set(&connfd_watcher[i], connfd, EV_READ);
ev_io_start(loop, &connfd_watcher[i]);
}
static void
connfd_cb(struct ev_loop *loop, ev_io *w, int revents)
{
//read one line from w->fd
//then echo it
int bytes_cnt;
char buf[MAXLINE];
rio_t rio;
Rio_readinitb(&rio, w->fd);
if ((bytes_cnt = Rio_readlineb(&rio, buf, MAXLINE)) != 0)
{
printf("Server received %d bytes on fd %d\n",
bytes_cnt, w->fd);
Rio_writen(w->fd, buf, bytes_cnt);
}
else //if EOF from client, the do some cleaning.
{
conn_cnt--;
Close(w->fd);
ev_io_stop(loop, w);
w->fd = -1;
}
}
int
main(int argc, char **argv)
{
int listenfd, port;
socklen_t clientlen = sizeof(struct sockaddr_in);
struct sockaddr_in clientaddr;
if (argc != 2) {
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(0);
}
port = atoi(argv[1]);
listenfd = Open_listenfd(port);
int i;
for(i = 0; i < MAXCONN; i++)
{
connfd_watcher[i].fd = -1;
}
struct ev_loop *loop = ev_default_loop(0);
ev_init(&listenfd_watcher, listenfd_cb);
ev_io_set(&listenfd_watcher, listenfd, EV_READ );
ev_io_start(loop, &listenfd_watcher);
ev_run(loop, 0);
return 0;
}
4.注意事项
①编译
由于要采用csapp的一些函数,所以有最开始的两行文件包含的宏命令,csapp.h, csapp.c在网上很容易找到。
在编译的时候也要注意,我的这个服务器文件保存为select.c,所以编译命令为
$gcc select.c -o select -lev -pthread
②解释
服务端程序主要分为四部分
a。宏命令与全局变量
MAXCONN用来表示最多接受多少个连接
conn_cnt用来表示目前已经接受了多少个连接,特别注意:这里没有考虑并发的问题,如果客户端过多,同时退出,每个对conn_cnt减一,可能出错。
然后是两个回调函数前向声明。
然后就是watcher,由于连接请求每次处理一个,所以声明一个即可。
而建立的连接需要同时关注,所以用一个数组。
b。listenfd_watcher回调函数
首先判断,有没有超过最大连接数,有则弹出信息并终止,否则继续
然后接受连接请求、
接着从connfd_watcher数组中,挑出还没有被使用的,判断的标准是,看其fd是否是-1,(我会在main中,先将其置为-1,一旦使用就不是-1)
然后进行一些列的init,set,start
c。connfd_watcher回调函数
这个回调函数的任务就是接受客户端的信息并echo。
所以先定义用于接受信息的变量,然后判断,如果还有信息,则echo一句,并重新等待信息到来。
如果收到EOF,那么进行扫尾工作,
首先,当前连接数减1 然后,关闭fd, 然后将此监听器从loop中注销,然后将监听器的fd置-1,表示此监听器可以被重新使用了。
特别注意:将监听器fd置1,一定要在将监听器从loop中注销之后,因为监听器如果还在loop中其信息是不能更改的。
d。main函数
先进行socket相关操作。
然后将connfd_watcher数组的每个监听器的fd设为1,表示未被使用。
然后就是对于loop和listenfd_watcher的操作。