Socket層的核心是兩個(gè)函數(shù):sosend()soreceive().這兩個(gè)函數(shù)負(fù)責(zé)處理所有Socket層和協(xié)議層之間的I/O操作。

select()系統(tǒng)調(diào)用的作用是監(jiān)控文件描述符的狀態(tài)。一般用于Socket I/O操作,也可以用于其它文件I/O操作。

Socket緩存

我們知道,每一個(gè)Socket都有一個(gè)發(fā)送緩存和一個(gè)接收緩存。緩存的類型為sockbuf。

    struct sockbuf{        struct mbuf * sb_mb;//mbuf鏈,用于存儲(chǔ)用戶數(shù)據(jù)
        u_long sb_mbcnt;//mbuf鏈的長度        
        u_long sb_mbmax;//mbuf鏈的最大長度。
        u_long sb_cc;//緩存中的實(shí)際字節(jié)數(shù)
        
        u_long sb_hiwat;        long sb_lowat;        struct selinfo sb_sel;        short sb_flags;        short sb_timeo;//read/write超時(shí)時(shí)間
    }
  • sb_mb指向存儲(chǔ)數(shù)據(jù)的mbuf鏈的指針,指向的是mbuf鏈的第一個(gè)mbuf

  • sb_cc的值等于存儲(chǔ)在mbuf鏈中的數(shù)據(jù)字節(jié)數(shù),即mbuf鏈中存儲(chǔ)的有效數(shù)據(jù)的字節(jié)數(shù)

  • sb_hiwatsb_lowat用來調(diào)整Socket的流控算法(不是TCP的流量控制)

  • sb_mbcnt為分配給緩存中的所有mbuf的存儲(chǔ)器數(shù)量,即mbuf鏈的長度

  • sb_mbmax為分配給該Socket mbuf緩存的存儲(chǔ)器數(shù)量的上限,即mbuf鏈的最大長度。
    默認(rèn)的上限在socket系統(tǒng)調(diào)用中發(fā)送PRU_ATTACH請(qǐng)求時(shí)由協(xié)議設(shè)置。只要內(nèi)核要求的每個(gè)socket緩存的大小不超過262 144個(gè)字節(jié)的限制(sb_max,這是一個(gè)全局變量),進(jìn)程就可以修改緩存的上限和下限。

Internet協(xié)議的默認(rèn)的Socket緩存限制

萬碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

萬碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

因?yàn)槊恳粋€(gè)收到的UDP數(shù)據(jù)報(bào)的源地址和其攜帶的數(shù)據(jù)一起排隊(duì),所以UDP協(xié)議的sb_hiwat的默認(rèn)值設(shè)置為能容納40個(gè)1K字節(jié)長的數(shù)據(jù)報(bào)和相應(yīng)的sockaddr_in結(jié)構(gòu)(每個(gè)16個(gè)字節(jié))。

  • sb_sel是用來實(shí)現(xiàn)select()系統(tǒng)調(diào)用的selinfo結(jié)構(gòu)

  • sb_timeo用來限制一個(gè)進(jìn)程在讀寫調(diào)用中被阻塞的時(shí)間。

下表列出了sb_flags的所有可能的值

sb-flags說明
SB_LOCK一個(gè)進(jìn)程已經(jīng)鎖定了socket緩存
SB_WANT一個(gè)進(jìn)程正在等待給socket緩存加鎖
SB_WAIT一個(gè)進(jìn)程正在等待接收數(shù)據(jù)或發(fā)送數(shù)據(jù)所需的緩存
SB_SEL一個(gè)或多個(gè)進(jìn)程正在選擇這個(gè)緩存
SB_ASYNC為這個(gè)緩存產(chǎn)生異步I/O信號(hào)
SB_NOINTR信號(hào)不取消加鎖請(qǐng)求
SB_NOTIFY(SB_WAIT|SB_SEL|SB_ASYNC) 一個(gè)進(jìn)程正在等待緩存的變化,如果緩存發(fā)送任何變化,用wakeup通知該進(jìn)程

write、writev、sendto和sendmsg系統(tǒng)調(diào)用

我們將write()writevsendto()sendmsg()四個(gè)系統(tǒng)調(diào)用統(tǒng)稱為"寫系統(tǒng)調(diào)用",它們的作用是往網(wǎng)絡(luò)連接上發(fā)送數(shù)據(jù)。相對(duì)于最一般的調(diào)用sendmsg()而言,前三個(gè)系統(tǒng)調(diào)用是比較簡單的接口。

所有的寫系統(tǒng)調(diào)用都要直接或間接地調(diào)用sosend。sosend的功能是將進(jìn)程來的數(shù)據(jù)復(fù)制到內(nèi)核,并將數(shù)據(jù)傳遞給與socket相關(guān)的協(xié)議。

函數(shù)描述符類型緩存數(shù)量是否指明目的地址標(biāo)志?控制信息?
write任何類型1


writev任何類型[1..UIO_MAXIOV]


sendsocket1
.
sendtosocket1..
sendmsgsocket[1..UIO_MAXIOV]...

write()writev()系統(tǒng)調(diào)用適用于任何描述符,而其它的系統(tǒng)調(diào)用只適用于socket描述符。

writev()sendmsg()系統(tǒng)調(diào)用可以接收從多個(gè)(應(yīng)用層)緩存中來的數(shù)據(jù)。從多個(gè)緩存中寫數(shù)據(jù)稱為"收集(gathering)",同它相對(duì)應(yīng)的讀操作稱為"分散(scattering)"。執(zhí)行收集操作時(shí),內(nèi)核按序接收類型為iovec的數(shù)據(jù)中指定的緩存中的數(shù)據(jù)。數(shù)組最多有UIO_MAXIOV個(gè)單元。

struct iovec
{    char * iov_base;//基線地址,指向長度為iov_len個(gè)字節(jié)的緩存的開始
    size_t iov_len;//長度};

如果沒有這種接口(writev),一個(gè)進(jìn)程將不得不將多個(gè)緩存復(fù)制到一個(gè)大的緩存中,或調(diào)用多個(gè)寫系統(tǒng)調(diào)用來發(fā)送多個(gè)緩存中的數(shù)據(jù)。對(duì)于數(shù)據(jù)報(bào)協(xié)議而言,調(diào)用一次write()就是發(fā)送一個(gè)數(shù)據(jù)報(bào),數(shù)據(jù)報(bào)的發(fā)送不能用多個(gè)寫動(dòng)作來實(shí)現(xiàn)。

數(shù)據(jù)報(bào)協(xié)議要求每一個(gè)寫調(diào)用必須指定一個(gè)目的地址。因?yàn)?code style="margin: 1px 5px; padding: 0px 5px !important; line-height: 1.8; vertical-align: middle; display: inline-block; font-family: "Courier New", sans-serif !important; font-size: 12px !important; background-color: rgb(245, 245, 245) !important; border: 1px solid rgb(204, 204, 204) !important; border-radius: 3px !important;">write()、writev()send()調(diào)用接口不支持對(duì)目的地址的指定,因此這些調(diào)用只能在調(diào)用connect()將目的地址同一個(gè)無連接的socket聯(lián)系起來后才能被調(diào)用。調(diào)用sendto()sendmsg()時(shí)必須提供目的地址,或在調(diào)用它們之前調(diào)用connect()來指定目的地址。

sendmsg()系統(tǒng)調(diào)用

只有通過sendmsg()系統(tǒng)調(diào)用才能訪問到與socket API的輸出有關(guān)的所有功能。sendmsg()sendit()函數(shù)準(zhǔn)備sosend()系統(tǒng)調(diào)用所需的數(shù)據(jù)結(jié)構(gòu),然后由sosend()系統(tǒng)調(diào)用將報(bào)文發(fā)送給相應(yīng)的協(xié)議。

對(duì)于SOCK_DGRAM協(xié)議而言,報(bào)文就是數(shù)據(jù)報(bào)。對(duì)于SOCK_STREAM協(xié)議而言,報(bào)文是一串字節(jié)流。

sendmsg()有三個(gè)參數(shù):socket描述符、指向msghdr結(jié)構(gòu)的指針、幾個(gè)控制標(biāo)志。函數(shù)copyin()msghdr結(jié)構(gòu)從用戶空間復(fù)制到內(nèi)核空間

struct msghdr
{    caddr_t msg_name;//可選的目的地址
    u_int msg_namelen;//msg_name的長度
    struct iovec * msg_iov;//分散/收集數(shù)組
    u_int msg_iovlen;//msg_iov數(shù)組長度
    caddr_t msg_control;//控制信息
    u_int msg_controllen;//控制信息長度
    int msg_flags;//接收標(biāo)志};

控制信息(msg_control字段)的類型為cmsghdr結(jié)構(gòu):

struct cmsghdr
{
    u_int cmsg_len;    int cmsg_level;    int cmsg_type;
};
struct sendmsg_args
{    int s;    caddr_t msg;    int flags;
};

sendmsg(struct proc * p,struct sendmsg_args * uap,int * retval)
{    struct msghdr msg;    struct iovec aiov[UIO_SMALLOV],*iov;    int error;    
    /**     *一個(gè)有8個(gè)元素(UIO_SMALLIOV)的iovec數(shù)組從棧中自動(dòng)分配。     *如果分配的數(shù)組不夠大,sendmsg將調(diào)用MALLOC分配更大的數(shù)組。如果進(jìn)程指定的數(shù)組單元大于1024(UIO_MAXIOV),則返回EMSGSIZE。     *copyin將iovec數(shù)組從用戶空間復(fù)制到棧中的數(shù)組或一個(gè)更大的動(dòng)態(tài)分配的數(shù)組中。     *這種技術(shù)避免了調(diào)用malloc帶來的高代價(jià),因?yàn)榇蠖鄶?shù)情況下,數(shù)組的單元數(shù)小于等于8     */
    
    //將msg數(shù)據(jù)從用戶空間復(fù)制到內(nèi)核空間
    if(error = copyin(uap->msg,(caddr_t)&msg,sizeof(msg))){        return (error);
    }    
    if((u_int)msg.msg_iovlen >= UIO_SMALLIOV){        if((u_int)msg.msg_iovlen >= UIO_MAXIOV){            return (EMSGSIZE);
        }
        
        MALLOC(iov,struct iovec *,sizeof(struct iovec)*(u_int)msg.msg_iovlen,M_IOV,M_WAITOK);
    }else{
        iov = aiov;
    }    
    if(msg.msg_iovlen && (error = copyin((caddr_t)msg.msg_iov,(caddr_t)iov,(unsigned)(msg.msg_iovlen * sizeof(struct iovec))))){        goto done;
    }
    msg.msg_iov = iov;    
    //如果sendit返回,則表明數(shù)據(jù)已經(jīng)發(fā)送給相應(yīng)的協(xié)議或出現(xiàn)差錯(cuò)
    error = sendit(p,uap->s,&msg,uap->flags,retval);
done:    if(iov != aiov){
        FREE(iov,M_IOV);
    }    
    return (error);
}

sendit系統(tǒng)調(diào)用

sendit(struct proc * p,int s,struct msghdr * mp,int flags,int * retsize)
{    struct file * fp;    struct uio auio;    struct iovec * iov;    int i;    struct mbuf * to, * control;    int len,error;    
    if(error = getsock(p->p_fd,s,&fp)){        return error;
    }    
    /**     *初始化uio結(jié)構(gòu),并將應(yīng)用層的輸出緩存中的數(shù)據(jù)收集到內(nèi)核緩存中     */
    auio.uio_iov = mp->msg_iov;
    auio.uio_iovcnt = mp->msg_iovlen;
    auio.uio_segflg = UIO_USERSPACE;
    auio.uio_rw = UIO_WRITE;
    auio.uio_procp = p;
    auio.uio_offset = 0;
    auio.uio_resid = 0;
    
    iov = mp->msg_iov;    
    /**     *將傳送的數(shù)據(jù)的長度通過一個(gè)for循環(huán)來計(jì)算     */
    for(i = 0;i < mp->msg_iovlen;i++,iov++){        /**         *保證緩存的長度非負(fù)         */
        if(iov->iov_len < 0){            return (EINVAL);
        }        /**         *保證uio_resid不溢出,因?yàn)閡io_resid是一個(gè)**有符號(hào)的**整數(shù),且iov_len要求非負(fù)         */    
        if((auio.uio_resid += iov->iov_len) < 0){            return (EINVAL);
        }
    }    
    /**     *如果進(jìn)程提供了地址和控制信息,則sockargs將地址和控制信息復(fù)制到內(nèi)核緩存中     */
    if(mp->msg_name){//如果進(jìn)程提供了地址
            //將地址復(fù)制到內(nèi)核中
        if(error = sockargs(&to,mp->msg_name,mp->msg_namelen,MT_SONAME)){            return (error);
        }
    }else{
        top = 0;
    }    
    if(mp->msg_control){//如果進(jìn)程提供了控制信息
        if(mp->msg_controllen < sizeof(struct cmsghdr)){
            error = EINVAL;            goto bad;;
        }            //將控制信息復(fù)制到內(nèi)核中
        if(error = sockargs(&control,mp->msg_control,mp->msg_controllen,MT_CONTROL)){            goto bad;
        }
    }else{
        control = 0;
    }    
    /**     *發(fā)送數(shù)據(jù)和清除緩存     */

    len = auio.uio_resid;//為了防止sosend不接受所有數(shù)據(jù)而無法計(jì)算傳送的字節(jié)數(shù),將uio_resid的值保存在len中
    
    //將socket、目的地址、uio結(jié)構(gòu)(包含了要發(fā)送的數(shù)據(jù))、控制信息和標(biāo)志全部傳給函數(shù)sosend
    if(error = sosend((struct socket *)fp->f_data,to,&auio,(struct mbuf*)0,control,fkags)){        if(auio.uio_resid != len && (error == ERESTART || error == EINTR || error == EWOULDBLOCK)){
            error = 0;
        }        
        if(error == EPIPE){
            psignal(p,SIGPIPE);
        }
    }    
    if(0 == error){    /**     *如果沒有差錯(cuò)出現(xiàn)(或差錯(cuò)被丟棄),則計(jì)算傳送的字節(jié)數(shù),并將其保存在*retsize中。     *如果sendit返回0,syscall返回*retsize給進(jìn)程而不是返回差錯(cuò)代碼     */
        *retsize = len - auio.uio_resid;
    }
    
bad://釋放包含目的地址的緩存。
    if(to){
        m_freem(to);
    }    
    return (error);
}

sosend系統(tǒng)調(diào)用

sosend()是socket層中最復(fù)雜的函數(shù)之一。前面提到的所有五個(gè)寫系統(tǒng)調(diào)用最終都要調(diào)用sosend()。sosend()的功能就是:根據(jù)socket指明的協(xié)議支持的語義和緩存的限制,將數(shù)據(jù)和控制信息傳遞給socket指明的協(xié)議的pr_usrreq函數(shù).sosend從不將數(shù)據(jù)放在發(fā)送緩存(輸出緩存)中,存儲(chǔ)和移走數(shù)據(jù)應(yīng)由協(xié)議來完成。

sosend()對(duì)發(fā)送緩存的sb_hiwatsb_lowat值的解釋,取決于對(duì)應(yīng)的協(xié)議是否實(shí)現(xiàn)可靠或不可靠的數(shù)據(jù)傳送功能。

可靠的協(xié)議緩存

對(duì)于提供可靠的數(shù)據(jù)傳輸協(xié)議,例如TCP,發(fā)送緩存保存了還沒有發(fā)送的數(shù)據(jù)和已經(jīng)發(fā)送但還沒有被確認(rèn)的數(shù)據(jù)。sb_cc等于發(fā)送緩存的數(shù)據(jù)的字節(jié)數(shù),且0 <= sb_cc <= sb_hiwat

如果有帶外數(shù)據(jù)發(fā)送,則sb_cc有可能暫時(shí)超過sb_hiwat

so_send應(yīng)該確保在通過pr_usrreq()函數(shù)將數(shù)據(jù)傳遞給協(xié)議層之前有足夠的發(fā)送緩存。

協(xié)議層會(huì)將數(shù)據(jù)放到發(fā)送緩存中。sosend通過下面兩種方式之一將數(shù)據(jù)傳送給協(xié)議層:

TCP應(yīng)用程序?qū)ν獬龅腡CP報(bào)文段的大小沒有控制。例如,在TCP Socket上發(fā)送一個(gè)長度為4096字節(jié)的報(bào)文,假定發(fā)送緩存中有足夠的緩存,則Socket層將該報(bào)文分成兩部分,每一部分長度為2048個(gè)字節(jié),分別存放在一個(gè)帶外部簇的mbuf中。然后,在協(xié)議處理時(shí),TCP將根據(jù)連接上的MSS將數(shù)據(jù)分段,通常情況下,MSS為2048個(gè)字節(jié)。

當(dāng)一個(gè)報(bào)文因?yàn)樘蠖鴽]有足夠的緩存時(shí),協(xié)議允許報(bào)文被分成多段。但sosend仍然不將數(shù)據(jù)傳送給協(xié)議層知道發(fā)送緩存中的空閑空間大小大于sb_lowat。對(duì)于TCP而言,sb_lowat的默認(rèn)值是2048,從而阻止Socket層在發(fā)送緩存快滿時(shí)用小塊數(shù)據(jù)干擾TCP.

不可靠的協(xié)議緩存

對(duì)于提供不可靠的數(shù)據(jù)傳輸?shù)膮f(xié)議而言,例如UDP,發(fā)送緩存不需要保存任何數(shù)據(jù),也不等待任何確認(rèn)。

每一個(gè)報(bào)文一旦被排隊(duì)等待發(fā)送到相應(yīng)的網(wǎng)絡(luò)設(shè)備,Socket層立即將它傳送到協(xié)議。在這種情況下,sb_cc總是等于0,sb_hiwat指定每一次寫的最大長度,間接指明數(shù)據(jù)報(bào)的最大長度。

UDP協(xié)議的sb_hiwat的默認(rèn)值為9216(9 x 1024)。如果進(jìn)程沒有通過SO_SNDBUFSocket選項(xiàng)改變sb_hiwat的值,則發(fā)送長度大于9216個(gè)字節(jié)的數(shù)據(jù)報(bào)將導(dǎo)致差錯(cuò)。不僅如此,其它的協(xié)議限制也可能不允許一個(gè)進(jìn)程發(fā)送大的數(shù)據(jù)報(bào)。

對(duì)于NFS寫而言,9216已足夠大,NFS寫的數(shù)據(jù)加上協(xié)議首部的長度一般默認(rèn)為8192個(gè)字節(jié)。

實(shí)現(xiàn)

sosend(struct socket * so,struct mbuf * addr,struct uio * uio,struct mbuf * top,struct mbuf * control,int flags)
{    /**     *初始化     */
     
     struct proc * p = curproc;     struct mbuf **mp;     struct mbuf * m;     long space,len,resid;     int clen = 0,error,s,dontroute,mlen;     
     /**      * 如果sosendallatonce等于true(任何設(shè)置了PR_ATOMIC的協(xié)議)或數(shù)據(jù)已經(jīng)通過top中的mbuf鏈傳送給sosend,則將設(shè)置atomic。這個(gè)標(biāo)志[控制數(shù)據(jù)是作為一個(gè)mbuf鏈還是作為獨(dú)立的mbuf傳送給協(xié)議].      */
     
     int automic = sosendallatonce(so) || top;     
     
     /**      *resid等于iovec緩存中的數(shù)據(jù)字節(jié)數(shù)或top中的mbuf鏈中的數(shù)據(jù)字節(jié)數(shù)      */
     if(uio){
         resid = uio->uio_resid;
     }else{
         resid = top->m_pkthdr.len;
     }     
     
     if(resid < 0){         return (EINVAL);
     }     
     /**      *如果僅僅要求對(duì)這個(gè)報(bào)文不通過路由表進(jìn)行路由選擇,則設(shè)置dontroute      */
     
     dontroute = (flags & MSG_DONTROUTE) && (so->so_options & SO_DONTROUTE) == 0 && (so->so_proto->pr_flags & PR_ATOMIC);
     p->p_stats->p_ru.ru_msgsnd++;     
     if(control){        /**         * clen等于在可選的控制緩存中的字節(jié)數(shù)         */
         clen = control->m_len;
     }     
#define snderr(errno) {error = errno;splx(s);goto release;}
     
     /**      *sosend的主循環(huán)從restart開始,在循環(huán)的開始調(diào)用sblock()給發(fā)送緩存加鎖。通過加鎖確保多個(gè)進(jìn)程按序互斥訪問socket緩存      */restart:    if(error = sblock(&so->so_snd,SBLOCKWAIT(flags)))
    {        goto out;
    }    
    /**     *主循環(huán)直到[將所有數(shù)據(jù)都傳送給協(xié)議層](即resid = 0)時(shí)才會(huì)退出     */
    do{    
        /**         * 等待發(fā)送緩存有空閑空間         */
    
        s = splnet();        
        
        /**         * 如果Socket輸出被禁止,即TCP連接的寫通道已經(jīng)關(guān)閉,則返回EPIPE         */
        if(so->so_state & SS_CANTSENDMORE){
            snderr(EPIPE);
        }    
        /**         * 如果Socket正處于差錯(cuò)狀態(tài)(例如,前一個(gè)數(shù)據(jù)報(bào)可能已經(jīng)產(chǎn)生了一個(gè)ICMP不可達(dá)的差錯(cuò)),則返回so->so_error         */
        if(so->so_error){
            snderr(so->so_error);
        }    
        /**         * 如果協(xié)議請(qǐng)求連接且連接還沒有建立或連接請(qǐng)求還沒有啟動(dòng),則返回EMOTCONN.         * sosend允許只有控制信息但沒有數(shù)據(jù)的寫操作         */
        if((so->so_state & SS_ISCONNECTED) == 0){            if(so->so_proto->pr_flags & PR_CONNREQUIRED){                if((so->so_state & SS_ISCONFIRMING) == 0 && !(resid == 0 && clen != 0)){
                    snderr(ENOTCONN);
                }
            }else if(addr == 0){
                snderr(EDESTADDRREQ);
            }

http://www.cnblogs.com/kakawater/p/7112817.html