http://badqiu./blog/1149965
linux下使用write\send發(fā)送數(shù)據(jù)報(bào) EAGAIN : Resource temporarily unavailable 錯(cuò)
首先是我把套接字設(shè)置為異步的了,然后在使用write發(fā)送數(shù)據(jù)時(shí)采取的方式是循環(huán)發(fā)送大量的數(shù)據(jù);由于是異步的,write\send將要發(fā)送的數(shù)據(jù)提交到發(fā)送緩沖區(qū)后是立即返回的,并不需要對(duì)端確認(rèn)數(shù)據(jù)已接收。在這種情況下是很有可能出現(xiàn)發(fā)送緩沖區(qū)被填滿,導(dǎo)致write\send無法再向緩沖區(qū)提交要發(fā)送的數(shù)據(jù)。因此就產(chǎn)生了Resource temporarily unavailable的錯(cuò)誤,EAGAIN 的意思也很明顯,就是要你再次嘗試。
把發(fā)送部分修改如下
- int SeanSend(int fd, void *buffer, int length)
- {
- int bytes_left;
- int written_bytes;
- char *ptr;
- ptr=(char *)buffer;
- bytes_left=length;
- while(bytes_left>0)
- {
- /* 開始寫*/
- written_bytes=write(fd, ptr, bytes_left);
- if(written_bytes<=0) /* 出錯(cuò)了*/
- {
- if(errno==EINTR) /* 中斷錯(cuò)誤 我們繼續(xù)寫*/
- {
- continue;
- printf("[SeanSend]error errno==EINTR continue\n");
- }
- else if(errno==EAGAIN) /* EAGAIN : Resource temporarily unavailable*/
- {
- sleep(1);//等待一秒,希望發(fā)送緩沖區(qū)能得到釋放
- continue;
- printf("[SeanSend]error errno==EAGAIN continue\n");
- }
- else /* 其他錯(cuò)誤 沒有辦法,只好退了*/
- {
- printf("[SeanSend]ERROR: errno = %d, strerror = %s \n"
- , errno, strerror(errno));
- return(-1);
- }
- }
- bytes_left-=written_bytes;
- ptr+=written_bytes;/* 從剩下的地方繼續(xù)寫?? */
- }
- return length;
- }
注意的問題有:
1.connect返回值判定
之前的程序
if(connect(tcp_client_sock,(structsockaddr*)&server, server_length) <0)
//向服務(wù)器發(fā)起連接,連接成功后client_socket代表了客戶機(jī)和服務(wù)器的一個(gè)socket連接
{
printf("Can Not Connect To %s!\n",SERVE_IP);
close(tcp_client_sock);
return-1;
//exit(1);
}
else
{
TCP_CONNET_FLAG = 1;//建立連接
...
}
但是在網(wǎng)上查詢發(fā)現(xiàn):當(dāng)我們以非阻塞的方式來進(jìn)行連接的時(shí)候,返回的結(jié)果如果是-1,這并不代表這次連接發(fā)生了錯(cuò)誤,如果它的返回結(jié)果是EINPROGRESS,那么就代表連接還在進(jìn)行中。后面可以通過poll或者select來判斷socket是否可寫,如果可以寫,說明連接完成了。
更改如下:
//先定位為非阻塞模式,立即返回狀態(tài);如有錯(cuò)誤存為SO_ERROR值
if((flags =fcntl(tcp_client_sock,F_GETFL, 0 )) < 0)
{
perror("fcntl");
return -1;
}
flags |= O_NONBLOCK;
if(fcntl(tcp_client_sock, F_SETFL, flags) < 0)//設(shè)置socket為非阻塞模式
{
perror("fcntl");
return -1;
}
if(connect(tcp_client_sock,(structsockaddr*)&server, server_length) <0) //向服務(wù)器發(fā)起連接
{
if(errno != EINPROGRESS)//非等待狀態(tài)
{
perror("connect error");
close(tcp_client_sock);//下一步重連
//return -1;
}
else//EINPROGRESS:正常處理連接
{
perror("connect");//查詢ERROR值
printf("check delay connected\n");
goto done;//進(jìn)一步檢查是否握手完成
}
}
else
{
done:
...//下一步的select判定connect是否完成及tcp_client_sock可寫性
}
這里,perror("connect")語(yǔ)句查詢ERROR值為“Operation now inprogress”,表明非阻塞connect立即返回的狀態(tài)為正在建立三次握手;如果不想出現(xiàn)這種情況,可以將以上關(guān)于非阻塞設(shè)置socket的語(yǔ)句放到connect之后,就會(huì)在阻塞方式下等待connect完成,但仍需要作進(jìn)一步檢查。對(duì)于非阻塞方式,下一步就可以通過select自定義超時(shí)時(shí)間(通常比阻塞方式下connect超時(shí)時(shí)間短),并進(jìn)一步檢查是否連接錯(cuò)誤和規(guī)定時(shí)間內(nèi)套接口可讀寫性。
2.select超時(shí)設(shè)置問題
如果設(shè)置connect為非阻塞函數(shù)后,進(jìn)行select時(shí)只關(guān)注writefds,忽略readfds,exceptfds,可能出現(xiàn)一個(gè)問題:本來不想由于connect阻塞等太久,結(jié)果用select后反而傻等了。
如果在connect后開始select,只關(guān)注writefds,設(shè)置的超時(shí)是10秒,在connect發(fā)出[SYN]后:假定目標(biāo)IP的主機(jī)不存在或者是目標(biāo)端口給防火墻過濾了,那么你等再久也不會(huì)有任何回復(fù),這時(shí)候如果是阻塞connect可能要15秒才返回,那么你10秒就返回了,這種情況就賺了5秒。
然而假定connect的目標(biāo)IP主機(jī)是存在的也沒防火墻,只是端口是沒打開的,在connect發(fā)出[SYN]后的1秒系統(tǒng)已經(jīng)收到目標(biāo)主機(jī)回復(fù)的[RST,ACK],也就是說系統(tǒng)此時(shí)已經(jīng)知道這個(gè)端口是連接不上的了,但是應(yīng)用程序只關(guān)注writefds,后面的9秒鐘select就會(huì)傻傻的等待下去……原本以為用select來減少不必要的等待時(shí)間,如果不設(shè)置參數(shù)exceptfds,這時(shí)候反而浪費(fèi)時(shí)間。
3.send/recv 返回值
由于send、recv函數(shù)用于已連接的數(shù)據(jù)報(bào)或流式套接口s進(jìn)行數(shù)據(jù)的接收。所以在非阻塞socket的客戶端程序中recv、send函數(shù)成功返回并不代表對(duì)端一定收到了發(fā)送的消息。tcp協(xié)議本身是可靠的,并不等于應(yīng)用程序用tcp發(fā)送數(shù)據(jù)就一定是可靠的。不管是否阻塞,send發(fā)送的大小,并不代表對(duì)端recv到多少的數(shù)據(jù).
關(guān)于recv返回值,百度的解釋:
1).若無錯(cuò)誤發(fā)生,recv()返回讀入的字節(jié)數(shù)。
2).如果連接已中止,返回0。
3).否則的話,返回SOCKET_ERROR錯(cuò)誤。
如果套接口為SOCK_STREAM類型,并且遠(yuǎn)端“優(yōu)雅”地中止了連接,那么recv()一個(gè)數(shù)據(jù)也不讀取,立即返回。如果立即被強(qiáng)制中止,那么recv()將以WSAECONNRESET錯(cuò)誤失敗返回。
接收數(shù)據(jù)時(shí)perror時(shí)常遇到"Resource temporarilyunavailable"的提示,errno代碼為11(EAGAIN)。這表明你在非阻塞模式下調(diào)用了阻塞操作,在該操作沒有完成就返回這個(gè)錯(cuò)誤,這個(gè)錯(cuò)誤不會(huì)破壞socket的同步,不用管它,下次循環(huán)接著recv就可以。對(duì)非阻塞socket而言,EAGAIN不是一種錯(cuò)誤。在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。其實(shí)這算不上錯(cuò)誤,只是一種異常而已。
另外,如果出現(xiàn)EINTR即errno為4,錯(cuò)誤描述Interrupted systemcall,即由于信號(hào)中斷導(dǎo)致操作失敗,也應(yīng)該繼續(xù)。
val= recv(client_sock,info,length,MSG_NOSIGNAL);
if(val< 0)//判斷是否網(wǎng)絡(luò)無數(shù)據(jù)或接收緩沖區(qū)是否準(zhǔn)備好
{
if(val==EAGAIN||EWOULDBLOCK||EINTR)
{
printf("Recvdata Timeout.Waiting...\n");
return1;
}
else//網(wǎng)絡(luò)異常斷開或阻塞;需重新連接
{
perror("recv");
return-1;
}
}
elseif(val == 0)//server端正常關(guān)閉,需重新連接
{
TCP_CONNET_FLAG=0;//關(guān)閉TCP連接flag
printf("socketclose nomally\n");
return0;
}
else//返回接收字節(jié)大于零,正常接收數(shù)據(jù)
{
...//對(duì)接收緩沖區(qū)info的每個(gè)字節(jié)作讀寫操作處理
return 1;
}
關(guān)于send函數(shù)在阻塞模式和非阻塞模式下的區(qū)別:
在阻塞模式下,send函數(shù)的過程是將應(yīng)用程序請(qǐng)求發(fā)送的數(shù)據(jù)拷貝到發(fā)送緩存中發(fā)送并得到確認(rèn)后再返回.但由于發(fā)送緩存的存在,表現(xiàn)為:如果發(fā)送緩存大小比請(qǐng)求發(fā)送的大小要大,那么send函數(shù)立即返回,同時(shí)向網(wǎng)絡(luò)中發(fā)送數(shù)據(jù);否則,send向網(wǎng)絡(luò)發(fā)送緩存中不能容納的那部分?jǐn)?shù)據(jù),并等待對(duì)端確認(rèn)后再返回(接收端只要將數(shù)據(jù)收到接收緩存中,就會(huì)確認(rèn),并不一定要等待應(yīng)用程序調(diào)用recv);
在非阻塞模式下,send函數(shù)的過程僅僅是將數(shù)據(jù)拷貝到協(xié)議棧的緩存區(qū)而已,如果緩存區(qū)可用空間不夠,則盡能力的拷貝,返回成功拷貝的大小;如緩存區(qū)可用空間為0,則返回-1,同時(shí)設(shè)置errno為EAGAIN.
當(dāng)客戶通過Socket提供的send函數(shù)發(fā)送大的數(shù)據(jù)包時(shí),就可能返回一個(gè)EGGAIN的錯(cuò)誤。該錯(cuò)誤產(chǎn)生的原因是由于send函數(shù)中的size變量大小超過了tcp_sendspace的值。tcp_sendspace定義了應(yīng)用在調(diào)用send之前能夠在kernel中緩存的數(shù)據(jù)量。當(dāng)應(yīng)用程序在socket中設(shè)置了O_NDELAY或者O_NONBLOCK屬性后,如果發(fā)送緩存被占滿,send就會(huì)返回EAGAIN的錯(cuò)誤。
為了消除該錯(cuò)誤,有三種方法可以選擇:
1).調(diào)大tcp_sendspace,使之大于send中的size參數(shù)
---no -p -otcp_sendspace=65536
2).在調(diào)用send前,在setsockopt函數(shù)中為SNDBUF設(shè)置更大的值
intopt=SO_REUSEADDR;
setsockopt(tcp_client_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
opt = 256*1024;//512k
int optlen = sizeof(int);
setsockopt(tcp_client_sock,SOL_SOCKET,SO_SNDBUF,&opt,sizeof(int));
getsockopt(tcp_client_sock,SOL_SOCKET,SO_SNDBUF,&opt,&optlen);
3).使用write替代send,因?yàn)閣rite沒有設(shè)置O_NDELAY或者O_NONBLOCK
在CSDN中看到有這樣一種情況:
假如發(fā)送端流量大于接收端的流量(意思是epoll所在的程序讀比轉(zhuǎn)發(fā)的socket要快),由于是非阻塞的socket,那么send()函數(shù)雖然返回,但實(shí)際緩沖區(qū)的數(shù)據(jù)并未真正發(fā)給接收端,這樣不斷的讀和發(fā),當(dāng)緩沖區(qū)滿后會(huì)產(chǎn)生EAGAIN錯(cuò)誤,同時(shí),不理會(huì)這次請(qǐng)求發(fā)送的數(shù)據(jù).
所以,需要根據(jù)send()函數(shù)返回值及errno值作進(jìn)一步處理。遇到該情況,函數(shù)要求盡量將數(shù)據(jù)寫完再返回,或通過更改發(fā)送緩沖區(qū)大小。當(dāng)寫緩沖已滿(send()返回-1,且errno為EAGAIN),那么會(huì)等待后再重試send().這種方式并不很完美,在理論上可能會(huì)長(zhǎng)時(shí)間的阻塞在socket的send()中,但暫沒有更好的辦法.
SendFlag&=TCP_CONNET_FLAG;//測(cè)試tcp_client_sock描述符可寫且TCP連接存在
if(SendFlag==1)//嘗試發(fā)送
{
res=send_client_info(tcp_client_sock,buf,45);
if(res<0)
{
if(errno == EINTR)//當(dāng)socket是非阻塞時(shí),如返回此錯(cuò)誤,表示寫緩沖隊(duì)列已滿,返回后判斷網(wǎng)絡(luò)狀態(tài)再重試.
return -1;
if(errno == EAGAIN)//發(fā)送緩沖區(qū)剩余0字節(jié),延時(shí)等待發(fā)送;
{
usleep(10000);
res=send_client_info(tcp_client_sock,buf,45);
}
}
}
|