1. 程式人生 > >管理器之socket

管理器之socket

 

上一篇主要介紹了工作管理員,這篇開始介紹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