非阻塞socket程式設計

2021-08-14 13:47:09 字數 4137 閱讀 8225

**:

阻塞:阻塞呼叫是指呼叫結果返回之前,當前執行緒會被掛起。該程序被標記為睡眠狀態並被排程出去。函式只有在得到結果之後才會返回。當socket工作在阻塞模式的時候, 如果沒有資料的情況下呼叫該函式,則當前執行緒就會被掛起,直到有資料為止。 

非阻塞:非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函式不會阻塞當前執行緒,而會立刻返回。epoll工作在非阻塞模式時,才會發揮作用。

正常情況下,socket工作在阻塞模式下,在呼叫accept,connect,read,write等函式時,都是阻塞方式,直到讀到資料才會返回。但是,如果將socket設定為非阻塞狀態,那麼這麼些函式就會立即返回,不會阻塞當前執行緒。 

設定非阻塞socket的方法是:

int setnonblock(int isock)

tcp的socket一旦通過listen()設定為server後,就只能通過accept()函式,被動地接受來自客戶端的connect請求。程序對accept()的呼叫是阻塞的,就是說如果沒有連線請求就會進入睡眠等待,直到有請求連線,接受了請求(或者超過了預定的等待時間)才會返回。 

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

返回值是乙個新的套接字描述符,它代表的是和客戶端的新的連線,可以把它理解成是乙個客戶端的socket,這個socket包含的是客戶端的ip和port資訊 。失敗返回-1, 錯誤原因存於errno 中。 

之後的read和write函式中的fd都是指這個 new_fd。

阻塞模式下呼叫accept()函式,而且沒有新連線時,程序會進入睡眠狀態。 

非阻塞模式下呼叫accept()函式,而且沒有新連線時,將返回ewouldblock(11)錯誤

可以用以下**來測試:

int setnonblock(int isock)

int main(int argc, char* argv)

return

0;}

如果設定為非阻塞,accept會立即返回,列印出錯誤資訊,errno=11,有新的連線時,連線會成功; 

如果不設定非阻塞,程序就阻塞在accept那裡,直到有新的連線到來。

非阻塞模式下,accept函式可以與epoll結合,實現等待。具體可以見另一篇博文: 

在阻塞模式下,客戶端呼叫connect()函式將激發tcp的三路握手過程,但僅在連線建立成功或出錯時才返回。 

非阻塞工作模式,呼叫connect()函式會立刻返回einprocess錯誤,但tcp通訊的三路握手過程正在進行,所以可以使用select函式來檢查這個連線是否建立成功。

源自berkeley的實現有兩條與select函式和非阻塞相關的規則: 

1>.當連線成功建立時,描述字變成可寫。 

2>.當連線建立出錯時,描述字變成即可讀又可寫。getsockopt()函式的errno == 0表示只可寫。

處理非阻塞 connect 的步驟: 

(1) 建立socket,並利用fcntl將其設定為非阻塞 

(2) 呼叫connect函式,如果返回0,則連線建立;如果返回-1,檢查errno ,如果值為 einprogress,則連線正在建立。 

(3) 為了控制連線建立時間,將該socket描述符加入到select的可寫集合中,採用select函式設定超時。 

(4) 如果規定時間內成功建立,則描述符變為可寫;否則,採用getsockopt函式捕獲錯誤資訊。當errno == 0表示只可寫。

例項: 

redis客戶端cli (command line inte***ce),位於源**的src/deps/hiredis下面。 

實際上,不僅是redis客戶端,其他類似的client/server架構中,client均可採用非阻塞式connect實現。 

當然,也可以用poll或epoll來代替select。 

非阻塞模式 connect() + select()**:

int routernode::connect()

; servaddr.sin_family = af_inet;

inet_pton(af_inet, ip_.c_str(), &servaddr.sin_addr);

servaddr.sin_port = htons(port_);

int ret = ::connect(fd_, (struct sockaddr *)&servaddr, sizeof(servaddr));

if(ret == 0)

int error = 0;

socklen_t len = sizeof (error);

if(errno != einprogress)

fd_set wset;//寫集合

fd_zero(&wset);

fd_set(fd_, &wset);

struct timeval tval;

tval.tv_sec = 3;//3s

tval.tv_usec = 0;

if (select(fd_ + 1, null, &wset, null, &tval) == -1) //出錯、超時,連線失敗

if(!fd_isset(fd_, &wset))//不可寫

if (getsockopt(fd_, sol_socket, so_error, &error, &len) == -1)

if(error)

is_connected_ = true;

return

0;__fail:

close(fd_);

return -1;

}

對於寫操作write,非阻塞socket在傳送緩衝區沒有空間時會直接返回-1,錯誤號ewouldblock或eagain,表示沒有空間可寫資料,如果錯誤號是別的值,則表明傳送失敗。 

如果傳送緩衝區中有足夠空間或者是不足以拷貝所有待傳送資料的空間的話,則拷貝前面n個能夠容納的資料,返回實際拷貝的位元組數。 

而對於阻塞socket而言,如果傳送緩衝區沒有空間或者空間不足的話,write操作會直接阻塞住,如果有足夠空間,則拷貝所有資料到傳送緩衝區,然後返回。 

實現**:

/**

* 返回-1:失敗

* 返回》0: 成功

*/int writenonblock(int fd, const

char* send_buf, size_t send_len)

else

//遇到eagain直接退出

}sentlen += ret;

}return sentlen;

}

對於阻塞的socket,當socket的接收緩衝區中沒有資料時,read呼叫會一直阻塞住,直到有資料到來才返回。 

當socket緩衝區中的資料量小於期望讀取的資料量時,返回實際讀取的位元組數。 

當sockt的接收緩衝區中的資料大於期望讀取的位元組數時,讀取期望讀取的位元組數,返回實際讀取的長度。

對於非阻塞socket而言,socket的接收緩衝區中有沒有資料,read呼叫都會立刻返回。 

接收緩衝區中有資料時,與阻塞socket有資料的情況是一樣的,如果接收緩衝區中沒有資料,則返回-1, 

錯誤號為ewouldblock或eagain,表示該操作本來應該阻塞的,但是由於本socket為非阻塞的socket, 

因此立刻返回,遇到這樣的情況,可以在下次接著去嘗試讀取。如果返回值是其它負值,則表明讀取錯誤。 

實現**:

/**

* 返回-1:失敗

* 返回》0: 成功

*/int readnonblock(int fd, char* recv_buf, size_t recv_len)

else

if(ret > 0)

else

if(errno == eintr)

else

//遇到eagain直接退出

}return readlen;

}

recvfrom,sendto等函式也是同樣類似的方法。

非阻塞socket程式設計

socket程式設計中可能出現阻塞的呼叫有4個 1.write send sendto sendmsg sendv等,如果某個程序呼叫乙個阻塞的tcp套接字 預設設定 如果傳送緩衝區沒有空間,呼叫程序將會睡眠,直到有空間為止。如果tcp套接字是非阻塞的,且沒有空間可寫,則會返回乙個ewouleblo...

非阻塞socket程式設計

阻塞 阻塞呼叫是指呼叫結果返回之前,當前執行緒會被掛起。該程序被標記為睡眠狀態並被排程出去。函式只有在得到結果之後才會返回。當socket工作在阻塞模式的時候,如果沒有資料的情況下呼叫該函式,則當前執行緒就會被掛起,直到有資料為止。非阻塞 非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函式不...

socket程式設計 阻塞和非阻塞

阻塞方式下,connect首先傳送syn請求道伺服器,當客戶端收到伺服器返回的syn的確認時,則connect返回.否則的話一直阻塞.非阻塞方式,connect將啟用tcp協議的三次握手,但是connect函式並不等待連線建立好才返回,而是立即返回。返回的錯誤碼為einprogress,表示正在進行...