getsockopt/setsockopt函數說明(一)

功能描述:
獲取或者設置與某個套接字關聯的選項。選項可能存在於多層協議中,它們總會出現在最上面的套接字層。當操作套接字選項時,選項位於的層和選項的名稱必須給出。爲了操作套接字層的選項,應該將層的值指定爲SOL_SOCKET。爲了操作其它層的選項,控制選項的合適協議號必須給出。例如,爲了表示一個選項由TCP協議解析,層應該設定爲協議號TCP。

用法:
int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);

int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);

參數:
sock:將要被設置或者獲取選項的套接字。
level:選項所在的協議層。
optname:需要訪問的選項名。
optval:對於getsockopt(),指向返回選項值的緩衝。對於setsockopt(),指向包含新選項值的緩衝。
optlen:對於getsockopt(),作爲入口參數時,選項值的最大長度。作爲出口參數時,選項值的實際長度。對於setsockopt(),現選項的長度。

返回說明:

成功執行時,返回0。失敗返回-1,errno被設爲以下的某個值
EBADF:sock不是有效的文件描述詞
EFAULT:optval指向的內存並非有效的進程空間
EINVAL:在調用setsockopt()時,optlen無效
ENOPROTOOPT:指定的協議層不能識別選項
ENOTSOCK:sock描述的不是套接字

參數詳細說明:

level指定控制套接字的層次.可以取三種值:
1)SOL_SOCKET:通用套接字選項.
2)IPPROTO_IP:IP選項.
3)IPPROTO_TCP:TCP選項.
optname指定控制的方式(選項的名稱),我們下面詳細解釋

optval獲得或者是設置套接字選項.根據選項名稱的數據類型進行轉換

選項名稱 說明 數據類型
========================================================================
SOL_SOCKET
------------------------------------------------------------------------
SO_BROADCAST 允許發送廣播數據 int
SO_DEBUG 允許調試 int
SO_DONTROUTE 不查找路由 int
SO_ERROR 獲得套接字錯誤 int
SO_KEEPALIVE 保持連接 int
SO_LINGER 延遲關閉連接 struct linger
SO_OOBINLINE 帶外數據放入正常數據流 int
SO_RCVBUF 接收緩衝區大小 int
SO_SNDBUF 發送緩衝區大小 int
SO_RCVLOWAT 接收緩衝區下限 int
SO_SNDLOWAT 發送緩衝區下限 int
SO_RCVTIMEO 接收超時 struct timeval
SO_SNDTIMEO 發送超時 struct timeval
SO_REUSERADDR 允許重用本地地址和端口 int

SO_REUSEPORT ?http://blog.chinaunix.net/uid-26851094-id-3318435.html int

SO_TYPE 獲得套接字類型 int
SO_BSDCOMPAT 與BSD系統兼容 int
========================================================================
IPPROTO_IP
------------------------------------------------------------------------
IP_HDRINCL 在數據包中包含IP首部 int
IP_OPTINOS IP首部選項 int
IP_TOS 服務類型
IP_TTL 生存時間 int
========================================================================
IPPRO_TCP
------------------------------------------------------------------------
TCP_MAXSEG TCP最大數據段的大小 int
TCP_NODELAY 不使用Nagle算法 int
========================================================================

返回說明:
成功執行時,返回0。失敗返回-1,errno被設爲以下的某個值
EBADF:sock不是有效的文件描述詞
EFAULT:optval指向的內存並非有效的進程空間
EINVAL:在調用setsockopt()時,optlen無效
ENOPROTOOPT:指定的協議層不能識別選項
ENOTSOCK:sock描述的不是套接字

SO_RCVBUF和SO_SNDBUF:每個套接口都有一個發送緩衝區和一個接收緩衝區,使用這兩個套接口選項可以改變缺省緩衝區大小。

//接收緩衝區
int nRecvBuf=32*1024; //設置爲32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

//發送緩衝區
int nSendBuf=32*1024;//設置爲32K

setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

注意:

當設置TCP套接口接收緩衝區的大小時,函數調用順序是很重要的,因爲TCP的窗口規模選項是在建立連接時用SYN與對方互換得到的。對於客戶,O_RCVBUF選項必須在connect之前設置;對於服務器,SO_RCVBUF選項必須在listen前設置。

結合原理說明:

1.每個套接口都有一個發送緩衝區和一個接收緩衝區。接收緩衝區被TCP和UDP用來將接收到的數據一直保存到由應用進程來讀。 TCP:TCP通告另一端的窗口大小。TCP套接口接收緩衝區不可能溢出,因爲對方不允許發出超過所通告窗口大小的數據。 這就是TCP的流量控制,如果對方無視窗口大小而發出了超過窗口大小的數據,則接收方TCP將丟棄它。 UDP:當接收到的數據報裝不進套接口接收緩衝區時,此數據報就被丟棄。UDP是沒有流量控制的;快的發送者可以很容易地就淹沒慢的接收者,導致接收方的 UDP丟棄數據報。
2.我們經常聽說tcp協議的三次握手,但三次握手到底是什麼,其細節是什麼,爲什麼要這麼做呢?
第一次:客戶端發送連接請求給服務器,服務器接收;
第二次:服務器返回給客戶端一個確認碼,附帶一個從服務器到客戶端的連接請求,客戶機接收,確認客戶端到服務器的連接.
第三次:客戶機返回服務器上次發送請求的確認碼,服務器接收,確認服務器到客戶端的連接.
我們可以看到:
1. tcp的每個連接都需要確認.
2. 客戶端到服務器和服務器到客戶端的連接是獨立的.
我們再想想tcp協議的特點:連接的,可靠的,全雙工的,實際上tcp的三次握手正是爲了保證這些特性的實現.

3.setsockopt的用法

1.closesocket(一般不會立即關閉而經歷TIME_WAIT的過程)後想繼續重用該socket:
BOOL bReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));

2. 如果要已經處於連接狀態的soket在調用closesocket後強制關閉,不經歷TIME_WAIT的過程:
BOOL bDontLinger = FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));

3.在send(),recv()過程中有時由於網絡狀況等原因,發收不能預期進行,而設置收發時限:
int nNetTimeout=1000;//1秒
//發送時限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
//接收時限
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));

4.在send()的時候,返回的是實際發送出去的字節(同步)或發送到socket緩衝區的字節(異步);系統默認的狀態發送和接收一次爲 8688字節(約爲8.5K);在實際的過程中發送數據和接收數據量比較大,可以設置socket緩衝區,而避免了send(),recv()不斷的循環收發:
// 接收緩衝區
int nRecvBuf=32*1024;//設置爲32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//發送緩衝區
int nSendBuf=32*1024;//設置爲32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

5. 如果在發送數據的時侯,希望不經歷由系統緩衝區到socket緩衝區的拷貝而影響程序的性能:
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));

6.同上在recv()完成上述功能(默認情況是將socket緩衝區的內容拷貝到系統緩衝區):
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));

7.一般在發送UDP數據報的時候,希望該socket發送的數據具有廣播特性:
BOOL bBroadcast=TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));

8.在client連接服務器過程中,如果處於非阻塞模式下的socket在connect()的過程中可以設置connect()延時,直到accpet()被呼叫(本函數設置只有在非阻塞的過程中有顯著的作用,在阻塞的函數調用中作用不大)
BOOL bConditionalAccept=TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));

9.如果在發送數據的過程中(send()沒有完成,還有數據沒發送)而調用了closesocket(),以前我們一般採取的措施是"從容關閉"shutdown(s,SD_BOTH),但是數據是肯定丟失了,如何設置讓程序滿足具體應用的要求(即讓沒發完的數據發送出去後在關閉socket)?
struct linger {
u_short l_onoff;
u_short l_linger;
};
linger m_sLinger;
m_sLinger.l_onoff=1;//(在closesocket()調用,但是還有數據沒發送完畢的時候容許逗留)
//如果m_sLinger.l_onoff=0;則功能和2.)作用相同;
m_sLinger.l_linger=5;//(容許逗留的時間爲5秒)
setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));

--------------------------------------------------------------------------------------------------------------------

轉載自:http://www.easewe.com/Article/document/674.htm

套接口選項

在前面的幾章中,我們討論了使用套接口的基礎內容。現在我們要來探討一些可用的其他的特徵。在我們掌握了這一章的概念之後,我們就爲後面的套接口的高級主題做好了準備。在這一章,我們將會專注於下列主題:
如何使用getsockopt(2)函數獲得套接口選項值
如何使用setsockopt(2)函數設置套接口選項值
如何使用這些常用的套接口選項

得到套接口選項

有時,一個程序需要確定爲當前爲一個套接口進行哪些選項設置。這對於一個子程序庫函數尤其如此,因爲這個庫函數並不知道爲這個套接口進行哪些設置,而這個套接口需要作爲一個參數進行傳遞。程序也許需要知道類似於流默認使用的緩衝區的大小。

允許我們得到套接口選項值的爲getsockopt函數。這個函數的概要如下:
#include <sys/types.h>
#include <sys/socket.h>
int getsockopt(int s,
int level,
int optname,
void *optval,
socklen_t *optlen);
函數參數描述如下:
1 要進行選項檢驗的套接口s
2 選項檢驗所在的協議層level
3 要檢驗的選項optname
4 指向接收選項值的緩衝區的指針optval
5 指針optlen同時指向輸入緩衝區的長度和返回的選項長度值
當函數成功時返回0。當發生錯誤時會返回-1,而錯誤原因會存放在外部變量errno中。

協議層參數指明瞭我們希望訪問一個選項所在的協議棧。通常我們需要使用下面中的一個:
SOL_SOCKET來訪問套接口層選項
SOL_TCP來訪問TCP層選項

我們在這一章的討論將會專注於SOL_SOCKET層選項的使用。

參數optname爲一個整數值。在這裏所使用的值首先是由所選用的level參數來確定的。在一個指定的協議層,optname參數將會確定我們希望訪問哪一個選項。下表列出了一些層與選項的組合值:

協議層 選項名字
SOL_SOCKET SO_REUSEADDR
SOL_SOCKET SO_KKEPALIVE
SOL_SOCKET SO_LINGER
SOL_SOCKET SO_BROADCAST
SOL_SOCKET SO_OOBINLINE
SOL_SOCKET SO_SNDBUF
SOL_SOCKET SO_RCVBUF
SOL_SOCKET SO_TYPE
SOL_SOCKET SO_ERROR
SOL_TCP SO_NODELAY

上表所列的大多數選項爲套接口選項,其中的層是由SOL_SOCKET指定的。爲了比較的目的包含了一個TCP層套接口選項,其中的層是由SOL_TCP指定的。

大多數套接口選項獲得後存放在int數據類型中。當查看手冊頁時,數據類型int通常會有一些假設,除非表明了其他東西。當使用一個布爾值時,當值爲非零時,int表示TRUE,而如果爲零,則表示FALSE。

應用getsockopt(2)

在這一部分,我們將會編譯並運行一個getsndrcv.c的程序,這個程序會獲得並報告一個套接口的發送以及接收緩衝區的大小尺寸。
/*getsndrc.v
*
* Get SO_SNDBUF & SO_RCVBUF Options:
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <assert.h>

/*
* This function report the error and
* exits back to the shell:
*/
static void bail(const char *on_what)
{
if(errno != 0)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
}
fputs(on_what,stderr);
fputc('\n',stderr);
exit(1);
}

int main(int argc,char **argv)
{
int z;
int s=-1; /* Socket */
int sndbuf=0; /* Send buffer size */
int rcvbuf=0; /* Receive buffer size */
socklen_t optlen; /* Option length */

/*
* Create a TCP/IP socket to use:
*/
s = socket(PF_INET,SOCK_STREAM,0);
if(s==-1)
bail("socket(2)");

/*
* Get socket option SO_SNDBUF:
*/
optlen = sizeof sndbuf;
z = getsockopt(s,SOL_SOCKET,SO_SNDBUF,&sndbuf,&optlen);

if(z)
bail("getsockopt(s,SOL_SOCKET,"
"SO_SNDBUF)");

assert(optlen == sizeof sndbuf);

/*
* Get socket option SON_RCVBUF:
*/

optlen = sizeof rcvbuf;
z = getsockopt(s,SOL_SOCKET,SO_RCVBUF,&rcvbuf,&optlen);
if(z)
bail("getsockopt(s,SOL_SOCKET,"
"SO_RCVBUF)");

assert(optlen == sizeof rcvbuf);

/*
* Report the buffer sizes:
*/
printf("Socket s: %d\n",s);
printf("Send buf: %d bytes\n",sndbuf);
printf("Recv buf: %d bytes\n",rcvbuf);

close(s);
return 0;
}
程序的運行結果如下:
$ ./getsndrcv
socket s : 3
Send buf: 65535 bytes
Recv buf: 65535 bytes

設置套接口選項

如果認爲套接口的默認發送以及接收緩衝區的尺寸太大時,作爲程序設計者的我們可以將其設計爲一個小的緩衝區。當我們程序一個程序的幾個實例同時運行在我們的系統上時,這顯得尤其重要。

可以通過setsockopt(2)函數來設計套接口選項。這個函數的概要如下:
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int s,
int level,
int optname,
const void *optval,
socklen_t optlen);

這個函數與我們在上面所討論的getsockopt函數類似,setsockopt函數的參數描述如下:
1 選項改變所要影響的套接口s
2 選項的套接口層次level
3 要設計的選項名optname
4 指向要爲新選項所設置的值的指針optval
5 選項值長度optlen

這個函數參數與上面的getsockopt函數的參數的區別就在於最後一個參數僅是傳遞參數值。在這種情況下只是一個輸入值。

應用setsockopt函數

下面的例子代碼爲一個套接口改變了發送以及接收緩衝區的尺寸。在設置完這些選項以後,程序會得到並報告實際的緩衝區尺寸。

/*setsndrcv.c
*
* Set SO_SNDBUF & SO_RCVBUF Options:
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <assert.h>

/*
* This function report the error and
* exits back to the shell:
*/
static void bail(const char *on_what)
{
if(errno!=0)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
}
fputs(on_what,stderr);
fputc('\n',stderr);
exit(1);
}

int main(int argc,char **argv)
{
int z;
int s=-1; /* Socket */
int sndbuf=0; /* Send buffer size */
int rcvbuf=0; /* Receive buffer size */
socklen_t optlen; /* Option length */

/*
* Create a TCP/IP socket to use:
*/
s = socket(PF_INET,SOCK_STREAM,0);
if(s==-1)
bail("socket(2)");

/*
* set the SO_SNDBUF size :
*/
sndbuf = 5000; /* Send buffer size */
z = setsockopt(s,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof sndbuf);
if(z)
bail("setsockopt(s,SOL_SOCKET,"
"SO_SNDBUF)");

/*
* Set the SO_RCVBUF size:
*/
rcvbuf = 8192; /* Receive buffer size */
z = setsockopt(s,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof rcvbuf);
if(z)
bail("setsockopt(s,SOL_SOCKET,"
"SO_RCVBUF)");

/*
* As a check on the above ....
* Get socket option SO_SNDBUF:
*/
optlen = sizeof sndbuf;
z = getsockopt(s,SOL_SOCKET,SO_SNDBUF,&sndbuf,&optlen);
if(z)
bail("getsockopt(s,SOL_SOCKET,"
"SO_SNDBUF)");

assert(optlen == sizeof sndbuf);

/*
* Get socket option SO_RCVBUF:
*/
optlen = sizeof rcvbuf;
z = getsockopt(s,SOL_SOCKET,SO_RCVBUF,&rcvbuf,&optlen);
if(z)
bail("getsockopt(s,SOL_SOCKET"
"SO_RCVBUF)");
assert(optlen == sizeof rcvbuf);

/*
* Report the buffer sizes:
*/
printf("Socket s: %d\n",s);
printf(" Send buf: %d bytes\n",sndbuf);
printf(" Recv buf: %d bytes\n",rcvbuf);

close(s);
return 0;
}

程序的運行結果如下:
$ ./setsndrcv
Socket s : 3
Send buf: 10000 bytes
Recv buf: 16384 bytes
$

在這裏我們要注意程序所報告的結果。他們看上去似乎是所指定的原始尺寸的兩倍。這個原因可以由Linux內核源碼模塊net/core/sock.c中查到。我們可以查看一下SO_SNDBUF以及SO_RCVBUF的case語句。下面一段是由內核模塊sock.c中摘錄的一段處理SO_SNDBUF的代碼:
398 case SO_SNDBUF:
399 /* Don't error on this BSD doesn't and if you think
400 about it this is right. Otherwise apps have to
401 play 'guess the biggest size' games. RCVBUF/SNDBUF
402 are treated in BSD as hints */
403
404 if (val > sysctl_wmem_max)
405 val = sysctl_wmem_max;
406 set_sndbuf:
407 sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
408 if ((val * 2) < SOCK_MIN_SNDBUF)
409 sk->sk_sndbuf = SOCK_MIN_SNDBUF;
410 else
411 sk->sk_sndbuf = val * 2;
412
413 /*
414 * Wake up sending tasks if we
415 * upped the value.
416 */
417 sk->sk_write_space(sk);
418 break;

由這段代碼我們可以看到實際發生在SO_SNDBUF上的事情:
1 檢測SO_SNDBUF選項值來確定他是否超過了緩衝區的最大值
2 如果步驟1中的SO_SNDBUF選項值沒有超過最大值,那麼就使用這個最大值,而不會向調用者返回錯誤代碼
3 如果SO_SNDBUF選項值的2倍小於套接口SO_SNDBUF的最小值,那麼實際的SO_SNDBUF則會設置爲SO_SNDBUF的最小值,否則則會SO_SNDBUF選項值則會設置爲SO_SNDBUF選項值的2倍

從這裏我們可以看出SO_SNDBUF的選項值只是所用的一個提示值。內核會最終確定爲SO_SNDBUF所用的最佳值。

查看更多的內核源碼,我們可以看到類似的情況也適用於SO_RCVBUF選項。如下面的一段摘錄的代碼:
427 case SO_RCVBUF:
428 /* Don't error on this BSD doesn't and if you think
429 about it this is right. Otherwise apps have to
430 play 'guess the biggest size' games. RCVBUF/SNDBUF
431 are treated in BSD as hints */
432
433 if (val > sysctl_rmem_max)
434 val = sysctl_rmem_max;
435 set_rcvbuf:
436 sk->sk_userlocks |= SOCK_RCVBUF_LOCK;
437 /*
438 * We double it on the way in to account for
439 * "struct sk_buff" etc. overhead. Applications
440 * assume that the SO_RCVBUF setting they make will
441 * allow that much actual data to be received on that
442 * socket.
443 *
444 * Applications are unaware that "struct sk_buff" and
445 * other overheads allocate from the receive buffer
446 * during socket buffer allocation.
447 *
448 * And after considering the possible alternatives,
449 * returning the value we actually used in getsockopt
450 * is the most desirable behavior.
451 */
452 if ((val * 2) < SOCK_MIN_RCVBUF)
453 sk->sk_rcvbuf = SOCK_MIN_RCVBUF;
454 else
455 sk->sk_rcvbuf = val * 2;
456 break;

取得套接口類型

實際上我們只可以得到一些套接口選項。SO_TYPE就是其中的一例。這個選項會允許傳遞套接口的一個子函數來確定正在處理的是哪一種套接口類型。

如下面是一段得到套接口s類型的示例代碼:
/*gettype.c
*
* Get SO_TYPE Option:
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <assert.h>

/*
* This function report the error and
* exits back to the shell:
*/
static void bail(const char *on_what)
{
if(errno!=0)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
}
fputs(on_what,stderr);
fputc('\n',stderr);
exit(1);
}

int main(int argc,char **argv)
{
int z;
int s = -1; /* Socket */
int so_type = -1; /* Socket type */
socklen_t optlen; /* Option length */

/*
* Create a TCT/IP socket to use:
*/
s = socket(PF_INET,SOCK_STREAM,0);
if(s==-1)
bail("socket(2)");

/*
* Get socket option SO_TYPE:
*/
optlen = sizeof so_type;
z = getsockopt(s,SOL_SOCKET,SO_TYPE,&so_type,&optlen);
if(z)
bail("getsockopt(s,SOL_SOCKET,"
"SO_TYPE)");
assert(optlen == sizeof so_type);
/*
* Report the result:
*/
printf("Socket s: %d\n",s);
printf(" SO_TYPE : %d\n",so_type);
printf(" SO_STREAM = %d\n",SOCK_STREAM);

close(s);
return 0;
}
程序的運行結果如下:
$./gettype
Socket s: 3
SO_TYPE : 1
SO_STREAM = 1

設置SO_REUSEADDR選項

在第11章,"併發客戶端服務器"的第一部分中,提供並測試了一個使用fork系統調用設計的服務器。圖12.1顯示了在一個telnet命令與服務器建立連接之後的三個步驟。
這些步驟如下:
1 啓動服務器進程(PID 926)。他監聽客戶端連接。
2 啓動客戶端進程(telnet命令),並且連接到服務器進程(PID 926)。
3 通過fork調用創建服務器子進程,這會保留的原始的父進程(PID 926)並且創建一個新的子進程(PID 927)。
4 連接的客戶端套接口由於服務器父進程(PID 926)關閉,僅在子進程(PID 927)中保持連接的客戶端套接口處理打開狀態。
5 telnet命令與服務器子進程(PID 927)隨意交互,而獨立於父進程(PID 926)。

在步驟5,有兩個套接口活動:
服務器(PID 926)監聽192.168.0.1:9099
客戶端由套接口192.168.0.1:9099進行服務(PID 927),他連接到客戶端地址192.168.0.2:1035

客戶端由進程ID 927進行服務。這意味着我們可以殺掉進程ID 926,而客戶端仍可以繼續被服務。然而,卻不會有新的連接連接到服務器,因爲並沒有服務器監聽新的連接(監聽服務器PID 926已被殺死)

現在如果我們重啓服務器來監聽新的連接,就會出現問題。當新的服務器進程試着綁定IP地址192.168.0.1:9099時,bind函數就會返回 EADDRINUSE的錯誤代碼。這個錯誤代碼表明IP已經在9099端口上使用。這是因爲進程PID 927仍然在忙於服務一個客戶端。地址192.168.0.1:9099仍爲這個進程所使用。

這個問題的解決辦法就是殺掉進程927,這個關閉套接口並且釋放IP地址和端口。然而,如果正在被服務的客戶是我們所在公司的CEO,這樣的做法似乎不是一個選擇。同時,其他的部門也會抱怨我們爲什麼要重新啓動服務器。

這個問題的一個好的解決辦法就是使用SO_REUSEADDR套接口選項。所有的服務器都應使用這個選項,除非有一個更好的理由不使用。爲了有效的使用這個選項,我們應在監聽連接的服務器中執行下面的操作:
1 使用通常的socket函數創建一個監聽套接口
2 調用setsockopt函數設置SO_REUSEADDR爲TRUE
3 調用bind函數

套接口現在被標記爲可重用。如果監聽服務器進程因爲任何原因終止,我們可以重新啓動這個服務器。當一個客戶正爲另一個服務器進程使用同一個IP和端口號進行服務時尤其如此。

爲了有效的使用SO_REUSEADDR選項,需要考慮下面的情況:
在監聽模式下並沒有同樣的IP地址和端口號的其他套接口
所有的同一個IP地址和端口號的套接口必須將SO_REUSEADDR選項設置爲TRUE

這就意味着一個指定的IP地址和端口號對上只可以用一個監聽器。如果這樣的套接口已經存在,那麼設置這樣的選項將不會達到我們的目的。

只有所有存在的同一個地址和端口號的套接口有這個選項設置,將SO_REUSEADDR設置爲TRUE纔會有效。如果存在的套接口沒有這個選項設置,那麼bind函數就會繼續並且會返回一個錯誤號。

下面的代碼顯示如何將這個選項設置爲TRUE:

#define TRUE 1
#define FALSE 0
int z; /* Status code */
int s; /* Socket number */
int so_reuseaddr = TRUE;
z = setsockopt(s,SOL_SOCKET,SO_REUSEADDR,
&so_reuseaddr,
sizeof so_reuseaddr);
如果需要SO_REUSEADDR選項可以由getsockopt函數進行查詢。

設置SO_LINGER選項

另一個常用的選項就是SO_LINGER選項。與SO_REUSEADDR選項所不同的是這個選項所用的數據類型並不是一個簡單的int類型。

SO_LINGER選項的目的是控制當調用close函數時套接口如何關閉。這個選項只適用於面向連接的協議,例如TCP。

內核的默認行爲是允許close函數立即返回給調用者。如果可能任何未發送的TCP/IP數據將會進行傳送,但是並不會保證這樣做。因爲close函數會立即向調用者返回控制權,程序並沒有辦法知道最後一位的數據是否進行了發送。

SO_LINGER選項可以作用在套接口上,來使得程序阻塞close函數調用,直到所有最後的數據傳送到遠程端。而且,這會保證兩端的調用知道套接口正常關閉。如果失敗,指定的選項超時,並且向調用程序返回一個錯誤。

通過使用不同的SO_LINGER選項值,可以應用一個最後場景。如果調用程序希望立即中止通信,可以在linger結構中設置合適的值。然後,一個到close的調用會初始化一個通信中止連接,而丟棄所有未發送的數據,並立即關閉套接口。


SO_LINGER的這種操作模式是由linger結構來控制的:
struct linger {
int l_onoff;
int l_linger;
};
成員l_onoff爲一個布爾值,非零值表示TRUE,而零則表示FALSE。這個選項的三個值描述如下:

1 設置l_onoff爲FALSE使得成員l_linger被忽略,而使用默認的close行爲。也就是說,close調用會立即返回給調用者,如果可能將會傳輸任何未發送的數據。
2 設置l_onoff爲TRUE將會使得成員l_linger的值變得重要。當l_linger非零時,這代表應用在close函數調用上的以秒計的超時時限。如果超時發生之前,有未發送的數據並且成功關閉,函數將會成功返回。否則,將會返回錯誤,而將變量errno的值設置爲EWOULDBLOCK。
3 將l_onoff設置爲TRUE而l_linger設置爲零時使得連接中止,在調用close時任何示發送的數據都會丟棄。

我們也許希望得到一些建議,在我們的程序中使用SO_LINGER選項,並且提供一個合理的超時時限。然後,可以檢測由close函數的返回值來確定連接是否成功關閉。如果返回了一個錯誤,這就告知我們的程序也許遠程程序並不能接收我們發送的全部數據。相對的,他也許僅意味着連接關閉時發生的問題。

然而,我們必須保持清醒,這樣的方法在一些服務器設計中會產生新的問題。當在close函數調用上將SO_LINGER選項配置爲超時(linger),當我們的服務器在close函數調用中執行超時時會阻止其他的客戶端進行服務。如果我們正在一個進程中服務多個客戶端進程時就會存在這個問題。使用默認的行爲也許更爲合適,因爲這允許close函數立即返回。而任何未發送的數據也會爲內核繼續發送。

最後,如果程序或是服務器知道連接應何時中止時可以使用中止行爲。這也許適用於當服務器認爲沒有訪問權限的用戶正試着進行訪問的情況。這種情況下的客戶並不會得到特別的關注。
SO_LINGER的這種操作模式是由linger結構來控制的:
struct linger {
int l_onoff;
int l_linger;
};
成員l_onoff爲一個布爾值,非零值表示TRUE,而零則表示FALSE。這個選項的三個值描述如下:

1 設置l_onoff爲FALSE使得成員l_linger被忽略,而使用默認的close行爲。也就是說,close調用會立即返回給調用者,如果可能將會傳輸任何未發送的數據。
2 設置l_onoff爲TRUE將會使得成員l_linger的值變得重要。當l_linger非零時,這代表應用在close函數調用上的以秒計的超時時限。如果超時發生之前,有未發送的數據並且成功關閉,函數將會成功返回。否則,將會返回錯誤,而將變量errno的值設置爲EWOULDBLOCK。
3 將l_onoff設置爲TRUE而l_linger設置爲零時使得連接中止,在調用close時任何示發送的數據都會丟棄。

我們也許希望得到一些建議,在我們的程序中使用SO_LINGER選項,並且提供一個合理的超時時限。然後,可以檢測由close函數的返回值來確定連接是否成功關閉。如果返回了一個錯誤,這就告知我們的程序也許遠程程序並不能接收我們發送的全部數據。相對的,他也許僅意味着連接關閉時發生的問題。

然而,我們必須保持清醒,這樣的方法在一些服務器設計中會產生新的問題。當在close函數調用上將SO_LINGER選項配置爲超時(linger),當我們的服務器在close函數調用中執行超時時會阻止其他的客戶端進行服務。如果我們正在一個進程中服務多個客戶端進程時就會存在這個問題。使用默認的行爲也許更爲合適,因爲這允許close函數立即返回。而任何未發送的數據也會爲內核繼續發送。

最後,如果程序或是服務器知道連接應何時中止時可以使用中止行爲。這也許適用於當服務器認爲沒有訪問權限的用戶正試着進行訪問的情況。這種情況下的客戶並不會得到特別的關注。
SO_LINGER的這種操作模式是由linger結構來控制的:
struct linger {
int l_onoff;
int l_linger;
};
成員l_onoff爲一個布爾值,非零值表示TRUE,而零則表示FALSE。這個選項的三個值描述如下:

1 設置l_onoff爲FALSE使得成員l_linger被忽略,而使用默認的close行爲。也就是說,close調用會立即返回給調用者,如果可能將會傳輸任何未發送的數據。
2 設置l_onoff爲TRUE將會使得成員l_linger的值變得重要。當l_linger非零時,這代表應用在close函數調用上的以秒計的超時時限。如果超時發生之前,有未發送的數據並且成功關閉,函數將會成功返回。否則,將會返回錯誤,而將變量errno的值設置爲EWOULDBLOCK。
3 將l_onoff設置爲TRUE而l_linger設置爲零時使得連接中止,在調用close時任何示發送的數據都會丟棄。

我們也許希望得到一些建議,在我們的程序中使用SO_LINGER選項,並且提供一個合理的超時時限。然後,可以檢測由close函數的返回值來確定連接是否成功關閉。如果返回了一個錯誤,這就告知我們的程序也許遠程程序並不能接收我們發送的全部數據。相對的,他也許僅意味着連接關閉時發生的問題。

然而,我們必須保持清醒,這樣的方法在一些服務器設計中會產生新的問題。當在close函數調用上將SO_LINGER選項配置爲超時(linger),當我們的服務器在close函數調用中執行超時時會阻止其他的客戶端進行服務。如果我們正在一個進程中服務多個客戶端進程時就會存在這個問題。使用默認的行爲也許更爲合適,因爲這允許close函數立即返回。而任何未發送的數據也會爲內核繼續發送。

最後,如果程序或是服務器知道連接應何時中止時可以使用中止行爲。這也許適用於當服務器認爲沒有訪問權限的用戶正試着進行訪問的情況。這種情況下的客戶並不會得到特別的關注。

 

 

 轉自:http://ffwmxr.blog.163.com/blog/static/66372722201192214651351/

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章