一、非阻塞socket
??????? 非阻塞套接字是指執行此套接字的網絡調用時,不管是否執行成功,都立即返回。比如調用recv()函數讀取網絡緩沖區中數據,不管是否讀到數據都立即返回,而不會一直掛在此函數調用上。在實際Windows網絡通信軟件開發中,異步非阻塞套接字是用的最多的。平常所說的C/S(客戶端/服務器)結構的軟件就是異步非阻塞模式的。
??? int32_t flags = fcntl(socket_fd, F_GETFL, 0);
??? fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK);
二、EAGAIN錯誤
?????? 當應用程序在socket中設置O_NONBLOCK屬性后,如果發送緩存被占滿,send就會返回EAGAIN或EWOULDBLOCK的錯誤。在將socket設置O_NONBLOCK屬性后,通過socket發送一個100K大小的數據,第一次成功發送了13140數據,之后繼續發送并未成功,errno數值為EAGAIN錯誤。
三、EPOLL模式下EAGAIN錯誤處理方式
??????? 方法:需要封裝socket_send()的函數用來處理這種情況,該函數會盡量將數據寫完再返回,返回發送的字節數。在socket_send()內部,當寫緩沖已滿(send()返回-1,且errno為EAGAIN),那么會等待后再重試。
??? int32_t socket_send(int fd, char* data, int32_t size)
??? {
??????? if (NULL == data || size <= 0)
??????? {
??????????? return -1;
??????? }
??????? int32_t remainded = size;
??????? int32_t sended = 0;
??????? char* pszTmp = data;
??????? while(remainded > 0)
??????? {
??????????? sended = send(fd, pszTmp, (size_t)remainded, 0);
??????????? if (sended > 0)
??????????? {
??????????????? pszTmp += sended;
??????????????? remainded -= sended;
??????????? }
??????????? else if (errno == EAGAIN)
??????????? {
??????????????? continue;
??????????? }
??????????? else
??????????? {
?????????????? break;
??????????? }
??????? }
??????? return (size - remainded);
??? }
?????? 這種方式并不很完美,當發送大數據的時候,如果客戶端一直不調用recv函數接受數據,那么服務器就會卡死在while循環中(持續調用send函數返回EAGAIN錯誤)。對服務器來說,出現這種情況是致命的,屆時服務器的所有功能都不能正常運轉。
?????? 如果當send函數出現EAGAIN錯誤的時候,直到當前socket狀態變成可寫之前,不應該繼續調用send函數發送數據。在發送數據之前,將socket的監聽的事件增加EPOLLOUT,在數據全部發送之后,再取消EPOLLOUT的監聽。
?????? socket監聽EPOLLOUT代碼:
??? void epoll_event_mod(int epoll_socket_fd, int fd)
??? {
??????? struct epoll_event epollEvent;
??????? memset(&epollEvent, 0x0, sizeo(epollEvent));
??????? epollEvent.data.fd = fd;
??????? epollEvent.events = EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLOUT;
??????? epollEvent.data.ptr = NULL;
??????? epoll_ctl(epoll_socket_fd, EPOLL_CTL_MOD, fd, &m_epoll_event);
??? }
?????? socket緩存結構體代碼:
??? struct stSocketBuffer
??? {
??????? int32_t m_iHead;
??????? int32_t m_iTail;
??????? char???? m_szBuffer[max_socket_buffer_size];
??? };
?????? socket待發送數據放入緩存結構代碼:
??? int32_t push_socket_data(int fd, char* data, int32_t size)
??? {
??????? if (NULL == data || size <= 0)
??????? {
??????????? return -1;
??????? }
??????? stSocketBuffer* pstBuffer = get_socket_buffer(fd);
??????? if (NULL == pstBuffer)
??????? {
??????????? return -2;
??????? }
??????? if ( size > max_socket_buffer_size + m_iHead - m_iTail)
??????? {
??????????? return -3;
??????? }
??????? if (size + m_iTail > max_socket_buffer_size)
??????? {
??????????? memcopy(&pstBuffer->m_szBuffer[0], &pstBuffer->m_szBuffer[pstBuffer->m_iHead], pstBuffer->m_iTail? - pstBuffer->m_iHead);
??????????? pstBuffer->m_iTail -= pstBuffer->m_iHead;
??????????? pstBuffer->m_iHead = 0;
??????? }
??????? memcpy(&pstBuffer->m_szBuffer[pstBuffer->m_iTail], data, size);
??????? pstBuffer->m_iTail += size;
??????? return 0;
??? }
將緩存區數據發送出去代碼:
??? int32_t socket_send(int fd)
??? {
??????? stSocketBuffer* pstBuffer = get_socket_buffer(fd);
??????? if (NULL == pstBuffer)
??????? {
??????????? return -1;
??????? }
??? ?
??????? int32_t remainded = pstBuffer->m_iTail - pstBuffer->m_iHead;
??????? int32_t sended = 0;
??????? char* pszTmp = &pstBuffer->m_szBuffer[pstBuffer->m_iHead];
??????? int32_t again_count = 0;
??????? while(remainded > 0 && again_count < 2)
??????? {
??????????? sended = send(fd, pszTmp, (size_t)remainded, 0);
??????????? if (sended > 0)
??????????? {
??????????????? pstBuffer->m_iHead += sended;
??????????????? pszTmp += sended;
??????????????? remainded -= sended;
??????????? }
??????????? else if (errno == EAGAIN)
??????????? {
??????????????? ++ again_count;
??????????????? continue;
??????????? }
??????????? else
??????????? {
??????????????? break;
??????????? }
??????? }
??????? return (size - remainded);
??? }
?????? 總結,當需要向socket發送數據時,現將數據壓入發送緩存區(stSocketBuffer結構體中),并且將socket加入可寫事件監聽。當socket觸發可寫事件(EPOLLOUT)時,調用socket_send函數發送數據,所有數據發送完畢,再清除EPOLLOUT事件。
?