linux非阻塞的socket发送数据出现EAGAIN错误的处理方法
一、非阻塞socket
??????? 非阻塞套接字是指執(zhí)行此套接字的網(wǎng)絡(luò)調(diào)用時(shí),不管是否執(zhí)行成功,都立即返回。比如調(diào)用recv()函數(shù)讀取網(wǎng)絡(luò)緩沖區(qū)中數(shù)據(jù),不管是否讀到數(shù)據(jù)都立即返回,而不會(huì)一直掛在此函數(shù)調(diào)用上。在實(shí)際Windows網(wǎng)絡(luò)通信軟件開發(fā)中,異步非阻塞套接字是用的最多的。平常所說的C/S(客戶端/服務(wù)器)結(jié)構(gòu)的軟件就是異步非阻塞模式的。
??? int32_t flags = fcntl(socket_fd, F_GETFL, 0);
??? fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK);
二、EAGAIN錯(cuò)誤
?????? 當(dāng)應(yīng)用程序在socket中設(shè)置O_NONBLOCK屬性后,如果發(fā)送緩存被占滿,send就會(huì)返回EAGAIN或EWOULDBLOCK的錯(cuò)誤。在將socket設(shè)置O_NONBLOCK屬性后,通過socket發(fā)送一個(gè)100K大小的數(shù)據(jù),第一次成功發(fā)送了13140數(shù)據(jù),之后繼續(xù)發(fā)送并未成功,errno數(shù)值為EAGAIN錯(cuò)誤。
三、EPOLL模式下EAGAIN錯(cuò)誤處理方式
??????? 方法:需要封裝socket_send()的函數(shù)用來處理這種情況,該函數(shù)會(huì)盡量將數(shù)據(jù)寫完再返回,返回發(fā)送的字節(jié)數(shù)。在socket_send()內(nèi)部,當(dāng)寫緩沖已滿(send()返回-1,且errno為EAGAIN),那么會(huì)等待后再重試。
??? 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);
??? }
?????? 這種方式并不很完美,當(dāng)發(fā)送大數(shù)據(jù)的時(shí)候,如果客戶端一直不調(diào)用recv函數(shù)接受數(shù)據(jù),那么服務(wù)器就會(huì)卡死在while循環(huán)中(持續(xù)調(diào)用send函數(shù)返回EAGAIN錯(cuò)誤)。對(duì)服務(wù)器來說,出現(xiàn)這種情況是致命的,屆時(shí)服務(wù)器的所有功能都不能正常運(yùn)轉(zhuǎn)。
?????? 如果當(dāng)send函數(shù)出現(xiàn)EAGAIN錯(cuò)誤的時(shí)候,直到當(dāng)前socket狀態(tài)變成可寫之前,不應(yīng)該繼續(xù)調(diào)用send函數(shù)發(fā)送數(shù)據(jù)。在發(fā)送數(shù)據(jù)之前,將socket的監(jiān)聽的事件增加EPOLLOUT,在數(shù)據(jù)全部發(fā)送之后,再取消EPOLLOUT的監(jiān)聽。
?????? socket監(jiān)聽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緩存結(jié)構(gòu)體代碼:
??? struct stSocketBuffer
??? {
??????? int32_t m_iHead;
??????? int32_t m_iTail;
??????? char???? m_szBuffer[max_socket_buffer_size];
??? };
?????? socket待發(fā)送數(shù)據(jù)放入緩存結(jié)構(gòu)代碼:
??? 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;
??? }
將緩存區(qū)數(shù)據(jù)發(fā)送出去代碼:
??? 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);
??? }
?????? 總結(jié),當(dāng)需要向socket發(fā)送數(shù)據(jù)時(shí),現(xiàn)將數(shù)據(jù)壓入發(fā)送緩存區(qū)(stSocketBuffer結(jié)構(gòu)體中),并且將socket加入可寫事件監(jiān)聽。當(dāng)socket觸發(fā)可寫事件(EPOLLOUT)時(shí),調(diào)用socket_send函數(shù)發(fā)送數(shù)據(jù),所有數(shù)據(jù)發(fā)送完畢,再清除EPOLLOUT事件。
?
總結(jié)
以上是生活随笔為你收集整理的linux非阻塞的socket发送数据出现EAGAIN错误的处理方法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 白领公寓剧情介绍
- 下一篇: Linux下的I/O复用与epoll详解