• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            牽著老婆滿(mǎn)街逛

            嚴(yán)以律己,寬以待人. 三思而后行.
            GMail/GTalk: yanglinbo#google.com;
            MSN/Email: tx7do#yahoo.com.cn;
            QQ: 3 0 3 3 9 6 9 2 0 .

            UDP_CORK,TCP_CORK以及TCP_NODELAY

            轉(zhuǎn)載自:http://soft-app.iteye.com/blog/919784

            這三個(gè)選項(xiàng)十分有意思,并且困擾了很多人。特別是cork選項(xiàng),它到底和nodelay有什么區(qū)別,到底怎樣影響了Nagle算法。在tcp的實(shí)現(xiàn)中(特指linux內(nèi)核的協(xié)議棧實(shí)現(xiàn)),cork和nodelay非常讓人看不出區(qū)別,這一塊的實(shí)現(xiàn)非常復(fù)雜,看內(nèi)核實(shí)現(xiàn)之前最好先明白它們大概在說(shuō)什么,否則很容易迷失的。
            所謂的cork就是塞子的意思,形象地理解就是用cork將連接塞住,使得數(shù)據(jù)先不發(fā)出去,等到拔去塞子后再發(fā)出去,而nodelay事實(shí)上是為了禁用Nagle算法,Nagle算法為了增加了網(wǎng)絡(luò)的吞吐量而犧牲了響應(yīng)時(shí)間體驗(yàn),這在有些應(yīng)用中是不合適的,比如交互式應(yīng)用(終端登錄或者遠(yuǎn)程X應(yīng)用 etc.),因此有必要提供一個(gè)選項(xiàng)將它禁用掉,Nagle算法在RFC1122中有提及,它的實(shí)現(xiàn)實(shí)際上很簡(jiǎn)單,利用了tcp本身的一些特性,在算法描述中,關(guān)鍵的一點(diǎn)是“什么時(shí)候真實(shí)的發(fā)送數(shù)據(jù)”,這個(gè)問(wèn)題的解答也是很簡(jiǎn)單,原則上只要發(fā)出的包都被對(duì)端ack了就可以發(fā)送了,這實(shí)際上也是一種權(quán)衡,Nagle算法最初的目的在于解決大量小包存在于網(wǎng)絡(luò)從而造成網(wǎng)絡(luò)擁塞的問(wèn)題(一個(gè)小包可能只有幾個(gè)字節(jié),比如ls,cat等等,然而為每個(gè)小包封裝幾個(gè)協(xié)議頭,其大小就不可忽視了,大量此類(lèi)小包存在于網(wǎng)絡(luò)勢(shì)必會(huì)使得網(wǎng)絡(luò)帶寬的利用率大大下降),如果包被ack了,說(shuō)明包已經(jīng)離開(kāi)了網(wǎng)絡(luò)進(jìn)入了對(duì)端主機(jī),這樣就可以發(fā)送數(shù)據(jù)了,此時(shí)無(wú)需再等,有多少數(shù)據(jù)發(fā)送多少(當(dāng)然要考慮窗口大小和MTU),如果很極端地等待更多的數(shù)據(jù),那么響應(yīng)度會(huì)更低,換句話(huà)簡(jiǎn)單的說(shuō)Nagle算法只允許一個(gè)未被ack的包存在于網(wǎng)絡(luò),它并不管包的大小,因此它事實(shí)上就是一個(gè)擴(kuò)展的停-等協(xié)議,只不過(guò)它是基于包停-等的,而不是基于字節(jié)停-等的。
            可以看出,Nagle算法完全由tcp協(xié)議的ack機(jī)制決定,這會(huì)帶來(lái)一些問(wèn)題,比如如果對(duì)端ack回復(fù)很快的話(huà),Nagle事實(shí)上不會(huì)拼接太多的數(shù)據(jù)包,雖然避免了網(wǎng)絡(luò)擁塞,網(wǎng)絡(luò)總體的利用率依然很低,Nagle真的做到了“只做好一件事”的原則,然而有沒(méi)有另外一種算法,可以提高整體網(wǎng)絡(luò)利用率呢?也就是說(shuō)盡量以不能再多的數(shù)據(jù)發(fā)送,這里之所以說(shuō)是盡量還是權(quán)衡導(dǎo)致的,某時(shí)可以發(fā)送數(shù)據(jù)的時(shí)候?qū)?huì)發(fā)送數(shù)據(jù),即使當(dāng)前數(shù)據(jù)再小也不再等待后續(xù)的可能拼接成更大包的數(shù)據(jù)的到來(lái)。
            實(shí)際上,這樣的需求可以用TCP_CORK來(lái)實(shí)現(xiàn),但是實(shí)現(xiàn)得可能并不像你想象的那么完美,cork并不會(huì)將連接完全塞住。內(nèi)核其實(shí)并不知道應(yīng)用層到底什么時(shí)候會(huì)發(fā)送第二批數(shù)據(jù)用于和第一批數(shù)據(jù)拼接以達(dá)到MTU的大小,因此內(nèi)核會(huì)給出一個(gè)時(shí)間限制,在該時(shí)間內(nèi)沒(méi)有拼接成一個(gè)大包(努力接近MTU)的話(huà),內(nèi)核就會(huì)無(wú)條件發(fā)送,這里給出的只是一個(gè)大致的思想,真實(shí)的情況還要受到窗口大小以及擁塞情況的影響,因此tcp“何時(shí)發(fā)送數(shù)據(jù)”這個(gè)問(wèn)題非常復(fù)雜。
            Nagle算法和CORK算法非常類(lèi)似,但是它們的著眼點(diǎn)不一樣,Nagle算法主要避免網(wǎng)絡(luò)因?yàn)樘嗟男“?協(xié)議頭的比例非常之大)而擁塞,而CORK算法則是為了提高網(wǎng)絡(luò)的利用率,使得總體上協(xié)議頭占用的比例盡可能的小。如此看來(lái)這二者在避免發(fā)送小包上是一致的,在用戶(hù)控制的層面上,Nagle算法完全不受用戶(hù)socket的控制,你只能簡(jiǎn)單的設(shè)置TCP_NODELAY而禁用它,CORK算法同樣也是通過(guò)設(shè)置或者清除TCP_cork使能或者禁用之,然而Nagle算法關(guān)心的是網(wǎng)絡(luò)擁塞問(wèn)題,只要所有的ack回來(lái)則發(fā)包,而CORK算法卻可以關(guān)心內(nèi)容,在前后數(shù)據(jù)包發(fā)送間隔很短的前提下(很重要,否則內(nèi)核會(huì)幫你將分散的包發(fā)出),即使你是分散發(fā)送多個(gè)小數(shù)據(jù)包,你也可以通過(guò)使能CORK算法將這些內(nèi)容拼接在一個(gè)包內(nèi),如果此時(shí)用Nagle算法的話(huà),則可能做不到這一點(diǎn)。
            接下來(lái)看一下內(nèi)核代碼,然后給出一個(gè)測(cè)試程序來(lái)感性感受這些選項(xiàng)。tcp的發(fā)送函數(shù)是tcp_sendmsg,這個(gè)函數(shù)中存在一個(gè)大循環(huán),用于將用戶(hù)數(shù)據(jù)置入skb中,它的形式如下:
            int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
            size_t size)
            {

            while (--iovlen >= 0) {
            0.更新數(shù)據(jù)結(jié)構(gòu)元數(shù)據(jù);
            while (seglen > 0) {
            int copy;
            skb = sk->sk_write_queue.prev;
            1.如果既有skb的長(zhǎng)度過(guò)大或者根本還沒(méi)有一個(gè)skb則分配一個(gè)skb;
            2.將數(shù)據(jù)拷貝到既有的skb或者新的skb中;
            3.更新skb和用戶(hù)數(shù)據(jù)的元數(shù)據(jù);
            //如果數(shù)據(jù)還沒(méi)有達(dá)到mss,則繼續(xù),換句話(huà)就是如果數(shù)據(jù)已經(jīng)達(dá)到mss了就接著往下走來(lái)權(quán)衡是否馬上發(fā)送。
            if (skb->len != mss_now || (flags & MSG_OOB))
            continue;
            4.權(quán)衡發(fā)送與否
            continue;
            }
            }
            out:
            //如果循環(huán)完成,所有數(shù)據(jù)都進(jìn)入了skb,調(diào)用tcp_push來(lái)權(quán)衡是否發(fā)送
            tcp_push(sk, tp, flags, mss_now, tp->nonagle);
            }
            tcp_push很短但是很復(fù)雜,
            static inline void tcp_push(struct sock *sk, struct tcp_opt *tp, int flags,
            int mss_now, int nonagle)
            {
            if (sk->sk_send_head) {
            struct sk_buff *skb = sk->sk_write_queue.prev;
            ...
            //如果有MSG_MORE,則當(dāng)作cork來(lái)處理
            __tcp_push_pending_frames(sk, tp, mss_now,
            (flags & MSG_MORE) ? TCP_NAGLE_CORK : nonagle);
            }
            }
            static __inline__ void __tcp_push_pending_frames(struct sock *sk,
            struct tcp_opt *tp,
            unsigned cur_mss,
            int nonagle)
            {
            struct sk_buff *skb = sk->sk_send_head;
            if (skb) {
            if (!tcp_skb_is_last(sk, skb)) //如果已經(jīng)有了很多的skb,則盡量馬上發(fā)送
            nonagle = TCP_NAGLE_PUSH;
            //只有tcp_snd_test返回1才會(huì)發(fā)送數(shù)據(jù),該函數(shù)很復(fù)雜
            if (!tcp_snd_test(tp, skb, cur_mss, nonagle) || 
            tcp_write_xmit(sk, nonagle))
            tcp_check_probe_timer(sk, tp); 
            }
            tcp_cwnd_validate(sk, tp);
            }
            static __inline__ int tcp_snd_test(struct tcp_opt *tp, struct sk_buff *skb,
            unsigned cur_mss, int nonagle)
            {
            //如果有TCP_NAGLE_PUSH標(biāo)志(或者tcp_nagle_check同意發(fā)送)且未ack的數(shù)據(jù)夠少且...則可以發(fā)送
            return (((nonagle&TCP_NAGLE_PUSH) || tp->urg_mode
            || !tcp_nagle_check(tp, skb, cur_mss, nonagle)) &&
            ((tcp_packets_in_flight(tp) < tp->snd_cwnd) ||
            (TCP_SKB_CB(skb)->flags & TCPCB_FLAG_FIN)) &&
            !after(TCP_SKB_CB(skb)->end_seq, tp->snd_una + tp->snd_wnd));
            }
            tcp_nagle_check函數(shù)是一個(gè)很重要的函數(shù),它基本決定了數(shù)據(jù)是否可以發(fā)送的80%,內(nèi)核源碼中對(duì)該函數(shù)有一條注釋?zhuān)?br /> -3. Or TCP_NODELAY was set.
            -4. Or TCP_CORK is not set, and all sent packets are ACKed.
            就是說(shuō)如果TCP_NODELAY值為1就可以直接發(fā)送,或者cork被禁用的情況下所有發(fā)出的包都被ack了也可以發(fā)送數(shù)據(jù),這里體現(xiàn)的就是Nagle算法和CORK算法的區(qū)別了,Nagle算法只要求所有的出發(fā)包都ack就可以發(fā)送,而不管當(dāng)前包是否足夠大(雖然它通過(guò)tcp_minshall_check保證了包不太小),而如果啟用cork的話(huà),可能僅僅數(shù)據(jù)被ack就不夠了,這就是為何在代碼注釋中說(shuō)cork要比Nagle更stronger的原因,同時(shí)這段代碼也說(shuō)明了為何TCP_CORK和TCP_NODELAY不能一起使用的原因,它們有共同的東西,卻在做著不同的事情。看看tcp_nagle_check:
            static __inline__ int
            tcp_nagle_check(struct tcp_opt *tp, struct sk_buff *skb, unsigned mss_now, int nonagle)
            {
            return (skb->len < mss_now &&
            !(TCP_SKB_CB(skb)->flags & TCPCB_FLAG_FIN) &&
            ((nonagle&TCP_NAGLE_CORK) ||
            (!nonagle &&
            tp->packets_out &&
            tcp_minshall_check(tp))));
            }
            看看__tcp_push_pending_frames的最后,有一個(gè)tcp_check_probe_timer調(diào)用,就是說(shuō)在沒(méi)有數(shù)據(jù)被發(fā)送的時(shí)候會(huì)調(diào)用這個(gè)函數(shù)。這個(gè)函數(shù)有兩個(gè)作用,第一個(gè)是防止0窗口導(dǎo)致的死鎖,另一個(gè)作用就是定時(shí)發(fā)送由于使能了CORK算法或者Nagle算法一直等待新數(shù)據(jù)拼接而沒(méi)有機(jī)會(huì)發(fā)送的數(shù)據(jù)包。這個(gè)timer內(nèi)置在重傳timer之中,其時(shí)間間隔和rtt有關(guān),一旦觸發(fā)則會(huì)發(fā)送數(shù)據(jù)包或者窗口探測(cè)包。反過(guò)來(lái)可以理解,如果沒(méi)有這個(gè)timer的話(huà),啟用cork的連接將幾乎(可能根據(jù)實(shí)現(xiàn)的不同還會(huì)受別的因素影響,太復(fù)雜了)每次都發(fā)送mtu大小的數(shù)據(jù)包。該timer調(diào)用tcp_probe_timer函數(shù):
            static void tcp_probe_timer(struct sock *sk)
            {
            struct tcp_opt *tp = tcp_sk(sk);
            int max_probes;
            //1.如果有數(shù)據(jù)在網(wǎng)絡(luò)上,則期望馬上回來(lái)ack,ack中會(huì)通告對(duì)端窗口
            //2.如果沒(méi)有數(shù)據(jù)要發(fā)送,則無(wú)需關(guān)注對(duì)端窗口,即使為0也無(wú)所謂
            if (tp->packets_out || !sk->sk_send_head) {
            tp->probes_out = 0;
            return;
            }
            //這個(gè)sysctl_tcp_retries2是可以調(diào)整的
            max_probes = sysctl_tcp_retries2;
            if (tp->probes_out > max_probes) {
            tcp_write_err(sk);
            } else {
            tcp_send_probe0(sk);
            }
            }
            tcp_send_probe0會(huì)調(diào)用tcp_write_wakeup函數(shù),該函數(shù)會(huì)要么發(fā)送可以發(fā)送的數(shù)據(jù),如果由于發(fā)送隊(duì)列越過(guò)了發(fā)送窗口導(dǎo)致不能發(fā)送,則發(fā)送一個(gè)窗口探測(cè)包:
            int tcp_write_wakeup(struct sock *sk)
            {
            if (sk->sk_state != TCP_CLOSE) {
            struct tcp_opt *tp = tcp_sk(sk);
            struct sk_buff *skb;
            if ((skb = sk->sk_send_head) != NULL &&
            before(TCP_SKB_CB(skb)->seq, tp->snd_una+tp->snd_wnd)) {
            ...//在sk_send_head隊(duì)列上取出一個(gè)發(fā)送出去,其ack會(huì)帶回對(duì)端通告窗口的大小
            err = tcp_transmit_skb(sk, skb_clone(skb, GFP_ATOMIC));
            ...
            return err;
            } else {
            ...
            return tcp_xmit_probe_skb(sk, 0);
            }
            }
            return -1;
            }
            這個(gè)probe timer雖然一定程度阻礙了cork的滿(mǎn)載發(fā)送,然而它卻是必要的,這是由于tcp并不為純的ack包(不帶數(shù)據(jù)的ack包)提供確認(rèn),因此一旦這種ack包丟失,那么就有可能死鎖,發(fā)送端的窗口無(wú)法更新,接收端由于已經(jīng)發(fā)送了ack而等待接收數(shù)據(jù),兩端就這樣僵持起來(lái),因此需要一個(gè)timer,定期發(fā)送一個(gè)探測(cè)包,一個(gè)ack丟失,不能所有的ack都丟失吧,在timer到期時(shí),如果本來(lái)發(fā)送隊(duì)列上有數(shù)據(jù)要發(fā)送,則直接發(fā)送這些數(shù)據(jù)而不再發(fā)送探測(cè)包,因?yàn)榘l(fā)送了這些數(shù)據(jù),所以它“破壞”了cork的承諾,不過(guò)也因此增強(qiáng)了響應(yīng)度。
            在示出應(yīng)用程序之前,總結(jié)一下內(nèi)核在哪里會(huì)發(fā)送tcp包,在解釋在哪里會(huì)發(fā)送tcp包之前,首先說(shuō)明內(nèi)核協(xié)議棧為了高效和低耦合設(shè)計(jì),tcp_sendmsg并不一定真實(shí)發(fā)送數(shù)據(jù),真實(shí)發(fā)送數(shù)據(jù)的地點(diǎn)在:
            1.tcp_sendmsg內(nèi)部(廢話(huà)!),如果權(quán)衡的結(jié)果需要發(fā)送則發(fā)送;
            2.收到對(duì)端ack的時(shí)候會(huì)調(diào)用tcp_data_snd_check來(lái)發(fā)送,它同樣完全按照cork策略來(lái)的;
            3.probe timer到期后作為窗口探測(cè)包發(fā)送一些數(shù)據(jù),它“破壞”了cork,在塞子上捅破一個(gè)口子;
            4.連接斷開(kāi)或者進(jìn)程退出時(shí)可能會(huì)將所有數(shù)據(jù)刷到對(duì)端;
            5.當(dāng)禁用cork或者啟用nodelay的時(shí)候會(huì)將pending的數(shù)據(jù)刷入對(duì)端。
            下面看一下應(yīng)用層的測(cè)試程序:
            客戶(hù)端程序:client
            #define BUFF_SIZE 500 
            #define REMOTE_PORT 6800 
            signed int len = 0; 
            int main(int argc, char *argv[])
            {
            int sock;
            struct sockaddr_in remote_addr;
            int on = 1;
            unsigned char buff[BUFF_SIZE];
            int i;
            if (argc != 5) {
            printf("usage: client server_ip on|off cork|nodelay usec\n");
            exit(-1);
            }
            int msd = atoi(argv[4]);
            if (!strcmp(argv[2], "on"))
            on = 1;
            else if (!strcmp(argv[2], "off"))
            on = 0;
            for (i = 0; i < BUFF_SIZE; i++) {
            buff[i] = 'q'; 
            }
            sock = socket(AF_INET, SOCK_STREAM, 0);
            if (!strcmp(argv[3], "nodelay")) {
            setsockopt(sock, SOL_TCP, TCP_NODELAY, &dontroute, sizeof(dontroute));
            } else if (!strcmp(argv[3], "cork")) {
            setsockopt(sock, SOL_TCP, TCP_CORK, &dontroute, sizeof(dontroute));
            }
            struct sockaddr_in sa;
            memset (&sa, '\0', sizeof(sa));
            sa.sin_family = AF_INET;
            sa.sin_addr.s_addr = inet_addr (argv[1]); 
            sa.sin_port = htons(REMOTE_PORT); 
            connect(sock, (struct sockaddr*) &sa, sizeof(sa));
            while(1) {
            len = send(sock, buff, BUFF_SIZE, MSG_MORE);
            if (len < 0) 
            exit(-1);
            usleep(msd);
            }
            return (0);
            }
            服務(wù)器程序:server
            int main (int argc, char **argv)
            {
            int err;
            int listen_sd;
            int sd;
            struct sockaddr_in sa_serv;
            struct sockaddr_in sa_cli;
            size_t client_len;
            char* str;
            char buf [500];
            listen_sd = socket (AF_INET, SOCK_STREAM, 0); 
            memset (&sa_serv, '\0', sizeof(sa_serv));
            sa_serv.sin_family = AF_INET;
            sa_serv.sin_addr.s_addr = INADDR_ANY;
            sa_serv.sin_port = htons (6800); 
            err = bind(listen_sd, (struct sockaddr*) &sa_serv, sizeof (sa_serv)); 
            err = listen (listen_sd, 5); 
            client_len = sizeof(sa_cli);
            while (1) {
            sd = accept (listen_sd, (struct sockaddr*) &sa_cli, &client_len);
            while (1) {
            err = read(sd, buf, sizeof(buf)); 
            if (err <= 0)
            break;
            }
            }
            close (sd);
            }
            運(yùn)行之:
            client 192.168.x.y on cork 66000
            在我的機(jī)器上,第四個(gè)參數(shù)最大到66000時(shí)cork會(huì)滿(mǎn)載發(fā)送,如果usleep的時(shí)間再長(zhǎng)一些,probe timer就是“幫忙”發(fā)送數(shù)據(jù)了,給你的感覺(jué)是,啟用了cork為何看起來(lái)沒(méi)有什么用。這個(gè)時(shí)間在不同環(huán)境在有所不同,因?yàn)閜robe timer導(dǎo)致了cork的破壞,而probe timer和rtt有關(guān),rtt又和網(wǎng)絡(luò)環(huán)境有關(guān)...再進(jìn)行一個(gè)測(cè)試,執(zhí)行下列命令:sysctl -w net.ipv4.tcp_retries2=-1
            然后以比較高的時(shí)間間隔以及比較小的BUFF_SIZE在開(kāi)啟cork情況下運(yùn)行client程序,我們發(fā)現(xiàn)第一個(gè)包還沒(méi)發(fā)完進(jìn)程就會(huì)退出,這是由于cork盡力在組包,間隔過(guò)大導(dǎo)致probe timer過(guò)期,然后tp->probes_out > max_probes判斷通過(guò),導(dǎo)致超時(shí)退出,這個(gè)可以從/proc/net/netstat中的超時(shí)計(jì)數(shù)器中看出來(lái),如果間隔比較短,每次新的數(shù)據(jù)pending到既有的skb上而不發(fā)送,重置probe timer,使得timer總是不過(guò)期,終于pending的數(shù)據(jù)到達(dá)了mtu的大小,cork的滿(mǎn)載發(fā)送起作用進(jìn)而發(fā)送之。
            還有一個(gè)概念是“糊涂窗口”,那就是接收端接收緩慢并不斷確認(rèn),導(dǎo)致窗口一直很小,而發(fā)送端收到ack就再次發(fā)送小包,這樣導(dǎo)致一直發(fā)送-確認(rèn)很小的包...這個(gè)是可以通過(guò)應(yīng)用層編程來(lái)避免的,另外也可以通過(guò)cork算法或者Nagle算法來(lái)減輕,但是無(wú)論怎樣都逃不過(guò)一些timer自動(dòng)幫你發(fā)送數(shù)據(jù)。
            最后,好像遺漏了UDP_CORK,很簡(jiǎn)單,udp沒(méi)有連接,沒(méi)有確認(rèn),因此也就不需要什么timer之類(lèi)的復(fù)雜機(jī)制,也因此,它是真正承諾的cork,除非你在應(yīng)用層手工拔掉塞子,否則數(shù)據(jù)將不會(huì)發(fā)出。

            posted on 2012-09-25 02:59 楊粼波 閱讀(1810) 評(píng)論(0)  編輯 收藏 引用 所屬分類(lèi): 網(wǎng)絡(luò)編程C++

            中文字幕久久精品无码| 亚洲精品成人久久久| 66精品综合久久久久久久| 91久久精品电影| 亚洲成av人片不卡无码久久| 蜜桃麻豆WWW久久囤产精品| 99国产欧美久久久精品蜜芽| 久久久久久狠狠丁香| 久久精品国产精品亚洲艾草网美妙| 久久午夜羞羞影院免费观看| 久久伊人色| 99久久国产综合精品五月天喷水 | 久久精品9988| 久久久久人妻精品一区三寸蜜桃| 精品久久久久香蕉网| 国产精品成人无码久久久久久| 香蕉久久永久视频| 久久人人爽人人爽人人AV| 欧美伊香蕉久久综合类网站| 久久最新免费视频| 国产精品99久久99久久久| 色综合久久无码五十路人妻| 热99re久久国超精品首页| 久久综合综合久久综合| 久久久久av无码免费网| 99久久国产综合精品网成人影院 | 色偷偷88888欧美精品久久久| 久久国产V一级毛多内射| 77777亚洲午夜久久多人| 欧洲国产伦久久久久久久| 狠狠色婷婷综合天天久久丁香| 2021国内久久精品| 国内精品久久久久久久coent| 久久久久人妻一区精品性色av| 亚洲精品tv久久久久久久久久| 亚洲国产成人久久综合一| 国产精品毛片久久久久久久| 久久精品卫校国产小美女| 久久久无码精品午夜| 国产日韩欧美久久| 亚洲欧美国产精品专区久久|