管理器之socket

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wellwang1993/article/details/83276511

上一篇主要介绍了任务管理器,这篇开始介绍socket管理器,我们知道服务器在支持并发的时候会有几种不同的方式,在讲解下面的内容之前,先回顾一下传统的tcp和udp是怎么进行通信的,这里由简单到复杂

1.第一种是最原始的tcp调用,服务端调用socket创建套接字进行通信,调用bind绑定端口,调用listen来监听端口是否有客户端请求过来,该函数用一个队列来保存未处理的连接请求,然后阻塞到accept系统调用,accept如果发现有连接请求过来了,他就会获取到该连接请求,这个时候accept就会返回一个新的fd,用于客户端和服务端的通信,否则服务端就继续阻塞的等待客户端的连接。比如有三个客户端连接服务端,那么这个时候总共有四个fd,三个是accept返回的用于传输数据的新的fd,一个是socket创建的用于监听的fd。

最原始的udp调用,传统的udp没有tcp复杂,因为它在通信之前不需要三次握手,因此它的调用socket以及bind以后,服务端和客户端就可以进行通信了

2.为了提高并发量,提出了多进程多线程的方式,对于tcp服务器来说,它其实是有多个文件描述符,包括监听描述符和已连接描述符,而对于udp来说只有一个描述符就够了,因为它本身是没有连接的概念的。因此针对tcp服务器来说,它可以在accept以后把新的fd交给新的进程或者线程,然后主进程继续阻塞到accept上,实现并发;而对于udp来说,并发的常见思路是使用多线程。服务器在读取一个新请求之后,可以交由一个线程处理,该线程在处理之后直接将响应内容发给客户端。另一方面,udp服务器和多个客户端交互,但是却没有多个socket。典型的解决方案是,服务器为每个客户端创建一个新的socket,并绑定一个新的端口。客户端以后就通过这个新的socket与服务器通信,获得响应。总结来说,udp并发服务器,针对多个客户端,可以创建多个socket;针对多个请求,可以使用多线程(线程池)进行处理。

3.还有一种比较好的方式是多路复用,介于后面用到了select,因此这里主要分析一下select,程序在读取数据之前,需要等待数据就绪。在select机制下,数据就绪之后,程序会被通知有数据就绪,但是并不知道是哪个文件描述符的数据就绪,需要遍历所有文件描述符,因此操作的时间复杂性是线性的;在epoll机制下,数据就绪后,程序不仅知道有数据就绪,而且可以知道是哪个文件描述符的数据就绪,因此操作时间是常数的。由于select的操作是线性复杂的,在使用select时,当文件描述符的数量较大后(一般以1024为界限),就需要再起额外的线程。而epoll的操作是常数的,所以无论有多少个文件描述符,都不影响其性能。不过,一般epoll仍然需要和多线程配合。常见的做法是,主进程通过epoll机制观察有无新的请求,当有新的请求,就由一个工作线程进行处理。比如说针对tcp来说,它会监听listenfd和普通的accepdfd,后者是前者调用accept以后返回的,listenfd就是监听来自客户端的新的连接请求

以上为背景,我们开始分析bind是如何做的,因为bind同样即支持tcp协议,支持tcp的原因在于udp协议会有长度的限制,在数据包的大小超过一定的范围以后就会采用tcp协议;又支持udp协议,但是介于俩者会有重复的地方,所以会以udp为主,tcp为辅

socket的这一部分要和server那一篇中的client_start结合起来看,我这里先做一个概要,如果看不下去了,可以看一下这个概要,你可以把它理解为一条主线:

首先要搞清楚,无论是tcp协议还是udp协议,最终处理的都是dns的查询请求,只不过一个是通过udp协议进行的,一个是通过tcp协议进行的,那么tcp协议最终在监听到数据以后,还是会转为和对udp请求一样的处理方式,也就是最终都是通过client_request来处理请求的

server在启动的时候,会让一个工作线程去加载配置文件,就是run_server这个函数,它扫描出来要监听的端口,对于udp来说,他在socket,bind以后就开始监听该端口了,这里开了cpu个client,他们等待watcher线程分配连接,但是那个时候还没有把它放入到select中去监听,加载完成以后调用clinet_start,这个时候它会直接调用recvmsg,如果有请求的话直接发送client_request事件给工作线程让它去处理udp请求,如果没有数据的话则把它放入到sock->recv_list中,然后把该socket放入到select中监听(也就是交给watcher线程),watcher线程在监听到该socket有数据的时候,调用dispatch_recv函数,把sock封装到internal_recv事件中,然后把事件发送给工作线程,工作线程执行internal_recv,它的内部跑的还是doio_recv,如果返回结果为success,那么他就调用sendevent发送task给客户端,同时把本次事件从sock的recv_list事件中清除,其余的交给client_request处理,然后再次监听该socket,如果返回soft,那么继续监听就可以

对于tcp来说,它扫描端口,然后在run_server中socket,bind,listen,然后在client_start中把该监听socket放入到sock->accept队列中,同时通知watcher线程去监听该socket,当有连接请求过来的时候,他就调用dispatch_accept,在这个函数中注册internal_accept事件,然后通知工作线程去处理该请求,工作线程会调用Internal_accept,该工作线程会调用accept获取新的请求,设置该事件对应的newsocket的信息,把该事件从accept_list中删除,然后用新的socket更新旧的manger的fds,maxfd等信息,注意这里还没有把该新的fd添加到watcher线程中监控,然后触发工作线程去调用client_newconn函数,这里获取到client信息,将client更新为新的socket,然后调用client_read

而udp在启动的时候,先自己监听udp的主端口,然后如果那会没有请求过来的话才把他交给watcher线程去处理,当有新的数据来的时候,他就驱动工作线程去调用internal_recv,工作线程直接调用recvmsg,如果调用成功的话,则直接驱动工作线程去调用client_request,同时再次监听该socket的可读性

tcp则是在启动的时候,就把自己的监听socket交给watcher线程去监听,当有新的连接过来的时候,它就驱动工作线程去调用internal_accept,然后工作线程调用它,其实就是调用accept获取到一个新的连接,然后更新事件中的socket,注意此时并没有把该新的socket交给watcher线程去处理,然后驱动工作线程去调用client_newconn,然后工作线程获取到该事件中的client,然后调用client_read,而client_read处理的其实就是把它的事件转为socketevent事件,同样最终的目的还是驱动client_request事件

也就是说他相当于有一组连接池,一组线程池,读写分开,watcher线程监听socket,可读的时候就把一个连接分给线程池去做,线程池的某个线程可以拿到一个客户连接,然后就可以去处理业务了,线程处理业务,并不阻塞当前监听状况,而watcher线程此时仍然可以再次监听该socket,监听它的可读,当然这个时候也可以监听它的可写性了,因为如果业务请求处理完了,就可以驱动线程去回写请求了。就是这种模式下,无论是udp还是tcp都可以实现高并发的操作。

//以该函数作为入口函数
result = isc_socketmgr_create(ns_g_mctx, &ns_g_socketmgr);
{
    isc_socketmgr_t *manager = isc_mem_get(mctx, sizeof(*manager))
    //同样初始化manger的一些信息
    //把所有的fd设置为0
    memset(manager->fds, 0, sizeof(manager->fds));
    //初始sock列表
    ISC_LIST_INIT(manager->socklist);
    //条件变量
    isc_condition_init(&manager->shutdown_ok) != ISC_R_SUCCESS
    //创建一对pipe用于线程之间的通信
    pipe(manager->pipe_fds) != 0
    //初始化select用到的fd集合
    FD_ZERO(&manager->read_fds);
	FD_ZERO(&manager->write_fds);
    //将pipe[0]加入到读中,同时设置maxfd
    FD_SET(manager->pipe_fds[0], &manager->read_fds);
    manager->maxfd = manager->pipe_fds[0];
    //启动watcher线程,可以看到这里只启动了一个
    isc_thread_create(watcher, manager, &manager->watcher)
}
它是一个线程,工作线程和该线程通过pipe进行通信,该线程负责fd的增删以及初始化相应的action
watcher
{
    //获取到父线程用于读的那个fd
    ctlfd = manager->pipe_fds[0]
    //开始进入循环
    while (!done)
    {
        //获取select用到的三个参数
        readfds = manager->read_fds;
		writefds = manager->write_fds;
		maxfd = manager->maxfd + 1;
        //调用select
        cc = select(maxfd, &readfds, &writefds, NULL, NULL);
        
        //说明工作线程发来对线程的修改信息,注意这个是特定的只处理ctrl_fd的
        if (FD_ISSET(ctlfd, &readfds))
        {
            //注意这是个死循环,处理内部的对一些fd的处理,注意该fd的状态一定是MANAGED
            for (;;)
            {
                //读取待操作fd到fd,以及要监听该fd的什么状态到msg
                select_readmsg(manager, &fd, &msg)
                if (msg == SELECT_POKE_NOTHING)
                {
                    break;
                }
                //如果是shutdown的话,则直接跳出最外层的循环退出该watcher线程
                if (msg == SELECT_POKE_SHUTDOWN) {
					done = ISC_TRUE;

					break;
				}
                //对于内部来的信息进行一些处理
                wakeup_socket(manager, fd, msg)
                {    
                    //如果他是快关闭的,那么直接从fd_set中清除掉
                    if (manager->fdstate[fd] == CLOSE_PENDING)
                    {
                        manager->fdstate[fd] = CLOSED;
	                	FD_CLR(fd, &manager->read_fds);
	                	FD_CLR(fd, &manager->write_fds);
		                (void)close(fd);
                    }
                    //注意通过pipe[0]传来的fd肯定是这个状态的
                    if (manager->fdstate[fd] != MANAGED)
		                return;
                    //通过它获取到对应的socket,依据msg的信息要么监听该fd的读要么是写
                    sock = manager->fds[fd]
                    if (msg == SELECT_POKE_READ)
		                FD_SET(sock->fd, &manager->read_fds);
	                if (msg == SELECT_POKE_WRITE)
		                FD_SET(sock->fd, &manager->write_fds)
                }
            }
        }
        //开始处理别的fd
        process_fds(manager, maxfd, &readfds, &writefds)
        {
            //注意他处理的所有fd是有限的
            REQUIRE(maxfd <= (int)FD_SETSIZE);
            for (i = 0; i < maxfd; i++)
            {
                //排除过pipe的俩个fd
                if (i == manager->pipe_fds[0] || i == manager->pipe_fds[1])
			        continue;
                //对于这个状态的fd采用和上面一样的处理方式
                if (manager->fdstate[i] == CLOSE_PENDING)
                {}
                //获取到对于的sock
                sock = manager->fds[i];
                //描述符可读的情况,针对同一个socket,它的task是从sock->accept_list或者scok->recv_list中获取到的,因此针对同一个socket的请求,它的连接请求被看做是一个task,它的数据发送也被看做是一个task,
                read:
                if (!SOCK_DEAD(sock))//如果socket的引用计数不为0才去进行一系列的操作
                {
                    //如果该sock正处于listen的状态,当有数据的时候,它需要调用accept返回一个已连接套接字,注意这里listen的永远是同一个端口
                    if (sock->listener)
                    {
                        dispatch_accept(sock)
                        {
                            //声明事件和连接
                            intev_t *iev
                            isc_socket_newconnev_t *ev
                            //获取到该sock对应的accept_list的一个accept事件,这个的赋值操作是在server刚启动的时候有一个client_start进行赋值的
                            ev = ISC_LIST_HEAD(sock->accept_list);
                            if (ev == NULL)
		                        return;
                            sock->pending_accept = 1;
                            //这是注册该socket在可读的时候应该要调用的函数,就是下面的internal_accept
	                        iev = &sock->readable_ev
                            sock->references++;

                            iev->ev_sender = sock;
                            
	                        iev->ev_action = internal_accept;
	                        iev->ev_arg = sock;

                            isc_task_send(ev->ev_sender, (isc_event_t **)&iev)
                            {
                                如果task是idle的,那么就代表该sock是第一次被投入使用,因此在把event添加到task的events集合中以后,修改该task的状态为ready,同时通过task获取到manger,isc_taskmgr_t *manager = task->manager;把刚刚的task再添加到ready_task中,SIGNAL(&manager->work_available)唤醒某个工作线程去处理该任务;如果task本身已经是ready的了,那么直接把task添加到ready_task中就可以了,因为该task本身也处于ready状态,所以也不需要被唤醒了
                            }
                        }
                    }
                    else//这就是对一个已连接socket进行正常的数据传输,是调用accept以后返回的sockfd
                    {
                        //对于tcp来说他就是调用accept以后返回的那个fd,对于udp来说就是套接字
                        dispatch_recv(sock)
                        {
                            isc_socketevent_t *ev = ISC_LIST_HEAD(sock->recv_list);
                            sock->pending_recv = 1;
                            //前面处于listen的时候,给sock->readable_ev赋值的是internal_accept,意思是listen到数据了,然后调用accept;这里处于acceptd状态,给sock->readable_ev赋值的是internal_recv,意思是该fd有数据可读了,应该调用internal_recv
	                        iev = &sock->readable_ev;
                            sock->references++;
	                        iev->ev_sender = sock;
	                        iev->ev_action = internal_recv;//注册接收数据的函数
	                        iev->ev_arg = sock;

                            isc_task_send(ev->ev_sender, (isc_event_t **)&iev)
                        }
                        
                    }
                    FD_CLR(i, &manager->read_fds);
                }
                //如果是描述符可写的话
                if (!SOCK_DEAD(sock))
                {
                    //可写的时候,作为客户端需要主动连接对方了
                    if (sock->connecting)
                    {
                        dispatch_connect(sock);
                        {
                            isc_socket_connev_t *ev = ev = sock->connect_ev;
                            sock->references++; 
	                        iev->ev_sender = sock;
	                        iev->ev_action = internal_connect;
	                        iev->ev_arg = sock;

                            isc_task_send(ev->ev_sender, (isc_event_t **)&iev)
                        }
                    }
                    else//这是真的要发送数据了
                    {
                        dispatch_send(sock);
                        {
                            isc_socketevent_t *ev = ISC_LIST_HEAD(sock->send_list);
                            sock->pending_send = 1;
	                        iev = &sock->writable_ev;
                            sock->references++;
	                        iev->ev_sender = sock;
	                        iev->ev_action = internal_send;
	                        iev->ev_arg = sock;

                            isc_task_send(ev->ev_sender, (isc_event_t **)&iev);
                        }
                    }
                    //处理完该fd的情况,则清除掉该fd
                    FD_CLR(i, &manager->write_fds);
                }
            }
        }
    }
}

小结:

可以看出来bind用了一个单独的线程去做fd的处理,通过pipe与主进程和各个线程进行通信,每一个fd对应一个isc_socket_t的结构,该fd的连接,该fd的数据的接收和发送都通过该socket里面对应的不同的task进行,可以理解为一个fd会有四个task,包括accept_list,recv_list,connetc_list,send_list中获取到的task,而事件则包括Internal_accept,internal_recv,internal_connect,internal_send四个事件,最终工作线程要处理的就是这些task对应的事件,我们的udp对查询的响应就是通过internal_recv和internal_send来实现的

在server刚启动的时候会执行一个client_start函数,该函数会把要监听的socket加入到select模型中,它在执行过程中会注册一个client_newconn事件,因为下面的accept事件会用到它,所以在这里也一并做分析,这样就可以把tcp事件的建立连接到数据数据传输以及udp的数据传输整体的串联起来了

下面可以具体分析一下这五个事件是做了一些什么,首先是accept事件

该函数主要做的事情就是:
1.当客户端有连接请求过来的时候,它会触发调用inetrnal_accept,这个函数做的就是调用accept初始化一个新的fd,更新manger的一些manfd信息,这个时候并不会把它添加到watcher线程的select中进行监控,因为还没有添加到manger的readfds中去;
2.如果需要增加新的监听socket则通过pipe告诉watcher线程,但是往往不会这样做,因为该监听socket已经在server启动的时候在client_start中做了;
3.它会把该事件发送到task中,该task通过sock->accept_list->ev_sender获取到,事件就是sock->accept_list的,只不过它会把新的fd复制到它内部,而该事件对应的action就是client_newconn
internal_accept(isc_task_t *me,isc_event_t *ev)
{
    //这个通过上面的赋值就可以看出来
    isc_socket_t *sock = ev->ev_sender
    isc_socketmgr_t *manager = sock->manger
    代表处理了
    sock->pending_accept = 0
    sock->references--
    if (sock->references == 0)
    {
        return
    }
    isc_socket_newconnev_t *dev = ISC_LIST_HEAD(sock->accept_list)
    //通过accept_list获取到task,进而获取到address信息,下面调用accept转变socket
    addrlen = sizeof(dev->newsocket->address.type);
    fd = accept(sock->fd, &dev->newsocket->address.type.sa, (void *)&addrlen)
    //把新的fd赋值到dev中
    if (fd != -1)
    {
        dev->newsocket->address.length = addrlen;
		dev->newsocket->pf = sock->pf;
    }
    //把事件从accept_list中删除
    ISC_LIST_UNLINK(sock->accept_list, dev, ev_link);

    //因为每次listen的都是同一个端口,所以if一般不会进去,因为那个listen的端口一直在被监听,server刚启动的时候会有一个client_start,它在client_start会执行一个isc_socket_accept,这个函数做了很多重要的事情,包括声明isc_socket_newconnev_t *dev事件,为他的action注册client_newconn事件,同时把该dev事件enqueue到sock->accept_list中,然后他会把这个监听scoket通过Pipe[1]告诉watcher线程进行监听,注意这是唯一一次enqueue监听socket的地方,这里该if条件一般不会进去
    if (!ISC_LIST_EMPTY(sock->accept_list))
    {
        //这个是告诉他需要监听一个新的端口
        select_poke(sock->manager, sock->fd, SELECT_POKE_ACCEPT);
    }

    if (fd != -1) 
    {
        //dev->newsocket在上面被赋值了,这里把这个已连接套接字添加到socklist中
        ISC_LIST_APPEND(manager->socklist, dev->newsocket, link);
        //上面赋值协议类型以及长度,这里赋值关键的fd
        dev->newsocket->fd = fd;
		dev->newsocket->bound = 1;
		dev->newsocket->connected = 1;
        //赋值本地的地址
        dev->address = dev->newsocket->address;

        //这里把新的socket信息赋值到全局的ns_g_socket中,并且设置状态为MANAGED,还记得上面状态为MANAGED的才会处理,注意这里只是把该新的fd添加到fds,更新到maxfd,并没有添加到resdfds和writefds中,因此在执行完这个函数以后select并不会对他进行监控
        manager->fds[fd] = dev->newsocket;
		manager->fdstate[fd] = MANAGED;
        
        manager->maxfd = fd;
    }
    它的task没变,通过dev->ev_sender获取到(这个的赋值操作是在client_start的时候有一个isc_socket_accept函数中执行的),而它的事件的ev_sender同样还是sock,这次把该dev事件发送到task中,而它要执行的事件就是client_newconn事件
    dev->result = result;
	task = dev->ev_sender;
	dev->ev_sender = sock;

    isc_task_sendanddetach(&task, ISC_EVENT_PTR(&dev))
		   
}

下面就分析一下上面的client_newconn事件,该事件其实是在server启动的时候注册的,注册的流程等到讲解client_start的时候再讲解,总之这里就可以理解为它是server一开始启动的时候必做的事情。这里重点分析一下client_newconn做了什么,因为他是上面inetrnal_accept函数做完以后就要做得事情,这个函数的重点部分放到udp接收数据的时候处理,毕竟对于dns协议来说,udp才是正统夫人

client_newconn(isc_task_t *task, isc_event_t *event)
{
    //获取到client
    ns_client_t *client = event->ev_arg;
    isc_socket_newconnev_t *nevent = (isc_socket_newconnev_t *)event;
    该client对应的连接数减减
    client->naccepts--;
    client->interface->ntcpcurrent--;
    
    //就是internal_accept里面的那个result,就是反应调用accept以后的一些情况
    if (nevent->result == ISC_R_SUCCESS)
    {
        //这样该client就拿到新的tcp连接
        client->tcpsocket = nevent->newsocket;
        client->state = NS_CLIENTSTATE_READING;
        //把连接信息放到client中
        (void)isc_socket_getpeername(client->tcpsocket,&client->peeraddr);

         //转换地址
         isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);   
         dns_tcpmsg_init(client->mctx, client->tcpsocket,&client->tcpmsg);
         //这里如果是tcpmsg,也就是紧急数据的话,那么bind就优先处理改数据了
         client_read(client);                   
         {
            //同样是注册client_request事件,它是通过tcpmsg消息注册的
            result = dns_tcpmsg_readmessage(&client->tcpmsg, client->task,client_request, client);
            {
                //注册新的事件
                ISC_EVENT_INIT(&tcpmsg->event, sizeof(isc_event_t), 0, 0,DNS_EVENT_TCPMSG, action, arg, tcpmsg,NULL, NULL);
		       result = isc_socket_recv(tcpmsg->sock, &region, 0,tcpmsg->task, recv_length, tcpmsg);
                {
                    //这里再次转化为socketevent事件,最终的处理和udp是一样的
                    isc_socketevent_t *dev = allocate_socketevent(sock, ISC_SOCKEVENT_RECVDONE, action, arg);    
                    //这个函数就和udp的过来的一样了
                    return (isc_socket_recv2(sock, region, minimum, task, dev, 0));
                    {
                        
                    }
                }
				 
		       
            }
			
         }                         

    }

}

下面分析internal_recv这个事件,在udp来数据的时候就会调用这个函数,具体的分析如下

在介绍下面这个函数之前需要有俩个前提强调一下,这些都是client_start中针对udp数据要做的,这里只是简单的说明一下:
1.在server启动的时候会调用client_start,对于udp来说调用client_udprecv,进而调用isc_socket_recv2(client->udpsocket, &r, 1,client->task, client->recvevent, 0)进而把client_request事件作为action注册到isc_socketevent_t *类型的dev事件中,这个事件要做的就是处理udp的请求,这么做的目的是在调用下面这个internal_recv函数的时候会有task_send的调用,它意味着此时有udp的数据来了,也就是有udp的请求来了,那么通知watcher工作线程去调用client_request去处理该请求
2.同样还是在server启动的时候函数在调用recvmsg的那一刻未必刚好有udp请求过来,因此会把它放到sock->recv_list中,与此同时如果它的socket还没有被监控的话,就把该fd加到watcher线程中进行监控,注意整个server监听的只有这一个端口
3.对于udp来说,他会有多个client同时调用client_start,也就意味着在sock->recv_list中可能会有多个请求,所以才有后面的循环处理

                        
下面的函数是,针对udp做的,当发现socket有数据的时候就说明有udp请求了,这个时候调用该函数进行数据的接收工作
internal_recv(isc_task_t *me, isc_event_t *ev)
{
    isc_socket_t *sock = ev->ev_sender;
    sock->pending_recv = 0;
    sock->references--
    if (sock->references == 0) 
    {
        return
    }
    //上面在client_start的时候没有udp请求过来的数据被放到这个列表里,因此这里把它取出来进行处理,因此这个列表里可能会有多个请求
    isc_socketevent_t *dev = ISC_LIST_HEAD(sock->recv_list);
    //这里开始调用recv接收数据了,bind是使用recvmsg这个系统调用来接收数据的,该函数的分析放到下面
    while(dev!=NULL)
    {
        switch (doio_recv(sock, dev))
        {
            //取第一个请求出来进行处理,然后发生错误,这个时候不进行该请求的处理,因为watcher线程每次处理完sock以后都会把sock移除掉,但是介于这里可能还有别的请求在,因此需要再次监控该socket
            case DOIO_SOFT:
			    goto poke;
            //读到success的处理方式和DOIO_HARD的方式是一样的,直接让工作线程调用clieng_request进行请求的处理,它会认为本次请求结束,然后继续下一个请求的处理,处理完所有请求以后,watcher线程对fd的监控是有client_request来做的
            case DOIO_SUCCESS:
            //表明io发生了一个错误,这个时候还是会把一些信息返回给发送者,因为是io错误,同时发生了错误就break,而且可以看到如果发生错误,则要把该事件从sock->recv_list中移除
            case DOIO_HARD:
                send_recvdone_event(sock, &dev);
                {
                    isc_task_t *task = (*dev)->ev_sender;
                    (*dev)->ev_sender = sock;
                    //移除该事件
                    if (ISC_LINK_LINKED(*dev, ev_link))
		                ISC_LIST_DEQUEUE(sock->recv_list, *dev, ev_link);
                    //下面发送任务,然后执行lient_request
                    if (((*dev)->attributes & ISC_SOCKEVENTATTR_ATTACHED)== ISC_SOCKEVENTATTR_ATTACHED)
                    {
                        //注意这个分支的在发送完事件以后会把task赋值为NULL,意思就是这次上次调用recvmsg但是没有收到信息的那个socket,这次可以清除掉了
                        isc_task_sendanddetach(&task, (isc_event_t **)dev);
                    }
                    else
                    {
                        isc_task_send(task, (isc_event_t **)dev);
                    }
	    
                }
			    break;
            //读到这个说明从对端读到的数据字节数为0,这个时候可以直接分发掉dev事件,但是把它的result设置为ISC_R_EOF,就是不处理了,然后继续监听该socket
           
            case DOIO_EOF:
                do {
				    dev->result = ISC_R_EOF;
				    send_recvdone_event(sock, &dev);
				    dev = ISC_LIST_HEAD(sock->recv_list);
			    } while (dev != NULL);
			    goto poke;
             
 		}
        dev = ISC_LIST_HEAD(sock->recv_list);
        
    }
    
    if (!ISC_LIST_EMPTY(sock->recv_list))
		select_poke(sock->manager, sock->fd, SELECT_POKE_READ);

}


在分析下面这个函数之前有必要看一下recvmsg函数,因为它的一些特性会衍生出一些不同的写法
1.一个重要的数据结构:
struct msghdr {
    void *msg_name; /* 消息的协议地址 */
    socklen_t msg_namelen; /* 地址的长度 */
    struct iovec *msg_iov; /* 多io缓冲区的地址 */
    int msg_iovlen; /* 缓冲区的个数 */
    void *msg_control; /* 辅助数据的地址 */
    socklen_t msg_controllen; /* 辅助数据的长度 */
    int msg_flags; /* 接收消息的标识 */
};
struct iovec {
    void    *iov_base;      /* starting address of buffer */
    size_t  iov_len;        /* size of buffer */
}
前面俩个存储的是协议的地址以及长度,主要是未连接的udp套接字,对于tcp来说可以设置为null和0,而recvmsg是一个值结果参数,它在调用完成以后会保存着对端的地址,msg_iov和msg_iovlen两个成员用于指定数据缓冲区数组,iov_base是数据的起始地址,iov_len是数组的长度,在调用函数之前应该事先分配好这些内存;msg_control和msg_controllen是用来设置辅助数据的位置和大小的,辅助数据(ancillary data)也叫作控制信息


doio_recv函数的分析,因为是调用recvmsg,所以就会有一系列相关的数据结构声明
doio_recv()
{
    struct msghdr msghdr;
    //在调用recvmsg之前要分配好一些内存以及内存的大小
    build_msghdr_recv(sock, dev, &msghdr, iov, &read_count);
    {
        memset(msghdr, 0, sizeof(struct msghdr));
        if (sock->type == isc_sockettype_udp)
        {
            依据sock->pf的是ipv4,ipv6,unix赋值msg的信息
        }
          
        //获取buffer
        buffer = ISC_LIST_HEAD(dev->bufferlist);
        iovcount = 0
        
        //这里有单io buffer和多io buffer,就是iov数组的大小,这里写的是多io buffer

        while(buffer!=NULL)
        {
            isc_buffer_availableregion(buffer, &available);
            iov[iovcount].iov_base = (void *)(available.base);
		    iov[iovcount].iov_len = available.length;
		    read_count += available.length;
		    iovcount++;
            buffer = ISC_LIST_NEXT(buffer, link);
        }
        
        //存储到msg中
        msg->msg_iov = iov;
	    msg->msg_iovlen = iovcount;
    }

    //系统调用接收数据
    cc = recvmsg(sock->fd, &msghdr, 0);
    
    //中间有一系列的对返回出错的处理

    //这是对控制数据的处理
    if (sock->type == isc_sockettype_udp)
		process_cmsg(sock, &msghdr, dev);

    更新dev的buffer
    dev->n += cc;
    actual_count = cc;
    buffer = ISC_LIST_HEAD(dev->bufferlist);
    while (buffer != NULL && actual_count > 0U)
    {
        //这里意味着它有多个io_buffer接收了数据
        if (isc_buffer_availablelength(buffer) <= actual_count) 
        {
            actual_count -= isc_buffer_availablelength(buffer);
			isc_buffer_add(buffer,isc_buffer_availablelength(buffer));
				       
        }
        else//这里就意味着只有一个io_buffer
        {
            isc_buffer_add(buffer, actual_count);
			actual_count = 0;
			break
        }
        buffer = ISC_LIST_NEXT(buffer, link);
    }

    //同样是对返回结果的处理
    if (((size_t)cc != read_count) && (dev->n < dev->minimum))
		return (DOIO_SOFT);
    dev->result = ISC_R_SUCCESS;
	return (DOIO_SUCCESS);
}

上面在执行完task_send以后,就要开始处理请求了,对于udp来说就是client_request,那么下面重点分析一下这个函数,他其实就是处理dns请求的

client_request

猜你喜欢

转载自blog.csdn.net/wellwang1993/article/details/83276511