setsockopt()改善程式的健壯性

工程師WWW發表於2013-12-24

1. 如果在已經處於 ESTABLISHED狀態下的socket(一般由埠號和標誌符區分)呼叫
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));
Note:1.在設定了逗留延時,用於一個非阻塞的socket是作用不大的,最好不用;2.如果想要程式不經歷SO_LINGER需要設定SO_DONTLINGER,或者設定l_onoff=0;

10.還一個用的比較少的是在SDI或者是Dialog的程式中,可以記錄socket的除錯資訊:
(前不久做過這個函式的測試,調式資訊可以儲存,包括socket建立時候的引數,採用的
具體協議,以及出錯的程式碼都可以記錄下來)
BOOL bDebug=TRUE;
setsockopt(s,SOL_SOCKET,SO_DEBUG,(const char*)&bDebug,sizeof(BOOL));

11.附加:往往通過setsockopt()設定了緩衝區大小,但還不能滿足資料的傳輸需求,
我的習慣是自己寫個處理網路緩衝的類,動態分配記憶體; 一般的習慣是自己寫個處理網路緩衝的類,動態分配記憶體;下面我將這個類寫出,希望對大家有所幫助:  
//仿照String改寫而成
//==============================================================================
//二進位制資料,主要用於收發網路緩衝區的資料
//CNetIOBuffer以MFC類CString的原始碼作為藍本改寫而成,用法與CString類似
//但是CNetIOBuffer中存放的是純粹的二進位制資料,'\0'並不作為它的結束標誌。
//其資料長度可以通過GetLength()獲得,緩衝區地址可以通過運算子LPBYTE獲得

//==============================================================================
//Copyright (c) All-Vision Corporation. All rights reserved.
//Module: NetObject
//File:    SimpleIOBuffer.h
//Author: gdy119
//Email:  8751webmaster@126.com
//Date:    2004.11.26
//==============================================================================
//NetIOBuffer.h
#ifndef _NETIOBUFFER_H
#define _NETIOBUFFER_H
//=============================================================================
#define MAX_BUFFER_LENGTH 1024*1024
//=============================================================================
//主要用來處理網路緩衝的資料
class CNetIOBuffer
{
protected:
   LPBYTE             m_pbinData;
   int                m_nLength;
   int                m_nTotalLength;
   CRITICAL_SECTION   m_cs;
   void               Initvalibers();
   public:
    CNetIOBuffer();
    CNetIOBuffer(const LPBYTE lbbyte, int nLength);
    CNetIOBuffer(const CNetIOBuffer &binarySrc);
    virtual ~CNetIOBuffer();
    //=============================================================================
    bool CopyData(const LPBYTE lbbyte, int nLength);
    bool ConcatData(const LPBYTE lbbyte, int nLength);
    void ResetIoBuffer();
    int GetLength() const;
    bool SetLength(int nLen);
    LPBYTE GetCurPos();
    int GetRemainLen();
    bool IsEmpty() const;
    operator LPBYTE() const;
    static GetMaxLength() {return MAX_BUFFER_LENGTH;}
    const CNetIOBuffer& ōperator=(const CNetIOBuffer& buffSrc);
};
#endif//


//NetOBuffer.cpp:implementation of the CNetIOBuffer class.
//======================================================================
#include "stdafx.h"
#include "NetIOBuffer.h"
//======================================================================
//=======================================================================
//Construction/Destruction
CNetIOBuffer::CNetIOBuffer()
{
Initvalibers();
}

CNetIOBuffer::CNetIOBuffer(const LPBYTE lbbyte, int nLength)
{
Initvalibers();
CopyData(lbbyte, nLength);
}

CNetIOBuffer::~CNetIOBuffer()
{
delete []m_pbinData;
m_pbinData=NULL;
DeleteCriticalSection(&m_cs);
}

CNetIOBuffer::CNetIOBuffer(const CNetIOBuffer& binarySrc)
{
Initvalibers();
CopyData(binarySrc,binarySrc.GetLength());
}

void CNetIOBuffer::Initvalibers()
{
m_pbinData = NULL;
m_nLength = 0;
m_nTotalLength = MAX_BUFFER_LENGTH;
if(m_pbinData==NULL)
{
   m_pbinData=new BYTE[m_nTotalLength];
   ASSERT(m_pbinData!=NULL);
}

InitializeCriticalSection(&m_cs);
}

void CNetIOBuffer::ResetIoBuffer()
{
EnterCriticalSection(&m_cs);
m_nLength = 0;
memset(m_pbinData,0,m_nTotalLength);
LeaveCriticalSection(&m_cs);
}

bool CNetIOBuffer::CopyData(const LPBYTE lbbyte, int nLength)
{
if(nLength > MAX_BUFFER_LENGTH)
   return FALSE;
ResetIoBuffer();
EnterCriticalSection(&m_cs);
memcpy(m_pbinData, lbbyte, nLength);
m_nLength = nLength;
LeaveCriticalSection(&m_cs);

return TRUE;
}

bool CNetIOBuffer::ConcatData(const LPBYTE lbbyte, int nLength)
{
if(m_nLength + nLength > MAX_BUFFER_LENGTH)
   return FALSE;

EnterCriticalSection(&m_cs);
memcpy(m_pbinData+m_nLength, lbbyte, nLength);
m_nLength += nLength;
LeaveCriticalSection(&m_cs);
return TRUE;
}

int CNetIOBuffer::GetLength() const
{
return m_nLength;
}

bool CNetIOBuffer::SetLength(int nLen)
{
if(nLen > MAX_BUFFER_LENGTH)
   return FALSE;
  
EnterCriticalSection(&m_cs);
m_nLength = nLen;
LeaveCriticalSection(&m_cs);
return TRUE;
}

LPBYTE CNetIOBuffer::GetCurPos()
{
if(m_nLength < MAX_BUFFER_LENGTH)
   return (m_pbinData+m_nLength);
else
   return NULL;
}

CNetIOBuffer::operator LPBYTE() const
{
return m_pbinData;
}

int CNetIOBuffer::GetRemainLen()
{
return MAX_BUFFER_LENGTH - m_nLength;
}

bool CNetIOBuffer::IsEmpty() const
{
return m_nLength == 0;
}

const CNetIOBuffer& CNetIOBuffer::operator=(const CNetIOBuffer& buffSrc)
{
if(&buffSrc!=this)
{
   CopyData(buffSrc, buffSrc.GetLength());
}

return *this;

---------------------------------------------------------------  
其實我覺得第5條很應該值得注意  
int   nZero=0;  
setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char   *)&nZero,sizeof(nZero));  
記得以前有些朋友討論過,socket雖然send成功了,但是其實只是傳送到資料緩衝區裡面了,而並沒有真正的在物理裝置上傳送出去;而通過這條語句,將傳送緩衝區設定為0,即遮蔽掉髮送緩衝以後,一旦send返回(當然是就阻塞套結字來說),就可以肯定資料已經在傳送的途中了^_^,但是這樣做也許會影響系統的效能  
---------------------------------------------------------------  

setoptsock()這個函式   設定成埠複用的時候,很容易對一些沒有進行單獨bind模式的程式造成危害。  
比如old的   ping   icmp   door,簡單的sniffer後,收到包,然後設定setoptsock   bind   web服務,然後建立個cmd程式   bind再80埠。
======================================
Example Code
The following example demonstrates the setsockopt function.

#include <stdio.h>
#include "winsock2.h"
void main() {
//---------------------------------------
// Declare variables
WSADATA wsaData;
SOCKET ListenSocket;
sockaddr_in service;
//---------------------------------------
// Initialize Winsock
int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
if( iResult != NO_ERROR )
printf("Error at WSAStartup\n");
//---------------------------------------
// Create a listening socket
ListenSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if (ListenSocket == INVALID_SOCKET) {
printf("Error at socket()\n");
WSACleanup();
return;
}
//---------------------------------------
// Bind the socket to the local IP address
// and port 27015
hostent* thisHost;
char* ip;
u_short port;
port = 27015;
thisHost = gethostbyname("");
ip = inet_ntoa (*(struct in_addr *)*thisHost->h_addr_list);
service.sin_family = AF_INET;
service.sin_addr.s_addr = inet_addr(ip);
service.sin_port = htons(port);
if ( bind( ListenSocket,(SOCKADDR*) &service, sizeof(service) ) == SOCKET_ERROR ) {
printf("bind failed\n");
closesocket(ListenSocket);
return;
}
//---------------------------------------
// Initialize variables and call setsockopt. 
// The SO_KEEPALIVE parameter is a socket option 
// that makes the socket send keepalive messages
// on the session. The SO_KEEPALIVE socket option
// requires a boolean value to be passed to the
// setsockopt function. If TRUE, the socket is
// configured to send keepalive messages, if FALSE
// the socket configured to NOT send keepalive messages.
// This section of code tests the setsockopt function
// by checking the status of SO_KEEPALIVE on the socket
// using the getsockopt function.
BOOL bOptVal = TRUE;
int bOptLen = sizeof(BOOL);
int iOptVal;
int iOptLen = sizeof(int);

if (getsockopt(ListenSocket, SOL_SOCKET, SO_KEEPALIVE, (char*)&iOptVal, &iOptLen) != SOCKET_ERROR) {
printf("SO_KEEPALIVE Value: %ld\n", iOptVal);
}

if (setsockopt(ListenSocket, SOL_SOCKET, SO_KEEPALIVE, (char*)&bOptVal, bOptLen) != SOCKET_ERROR) {
printf("Set SO_KEEPALIVE: ON\n");
}

if (getsockopt(ListenSocket, SOL_SOCKET, SO_KEEPALIVE, (char*)&iOptVal, &iOptLen) != SOCKET_ERROR) {
printf("SO_KEEPALIVE Value: %ld\n", iOptVal);
}
WSACleanup();
return;
}

Notes for IrDA Sockets
Keep in mind the following:
The Af_irda.h header file must be explicitly included. 
IrDA provides the following settable socket option: 
Value Type Meaning 
IRLMP_IAS_SET *IAS_SET Sets IAS attributes 
The IRLMP_IAS_SET socket option enables the application to set a single attribute of a single class in the local IAS. The application specifies the class to set, the attribute, and attribute type. The application is expected to allocate a buffer of the necessary size for the passed parameters.
IrDA provides an IAS Database that stores IrDA-based information. Limited access to the IAS Database is available through the windows Sockets 2 interface, but such access is not normally used by applications, and exists primarily to support connections to non-windows devices that are not compliant with the windows Sockets 2 IrDA conventions.
The following structure, IAS_SET, is used with the IRLMP_IAS_SET setsockopt option to manage the local IAS Database:
typedef struct _IAS_SET {
char irdaClassName[IAS_MAX_CLASSNAME];
char irdaAttribName[IAS_MAX_ATTRIBNAME];
u_long irdaAttribType;
union
{
LONG irdaAttribInt;
struct
{
u_short Len;
u_char OctetSeq[IAS_MAX_OCTET_STRING];
} irdaAttribOctetSeq;
struct
{
u_char Len;
u_char CharSet;
u_char UsrStr[IAS_MAX_USER_STRING];
} irdaAttribUsrStr;
} irdaAttribute;
} IAS_SET, *PIAS_SET, FAR *LPIAS_SET;

The following structure, IAS_QUERY, is used with the IRLMP_IAS_QUERY setsockopt option to query a peer's IAS Database:
typedef struct _windows_IAS_QUERY {
u_char irdaDeviceID[4];
char irdaClassName[IAS_MAX_CLASSNAME];
char irdaAttribName[IAS_MAX_ATTRIBNAME];
u_long irdaAttribType;
union
{
LONG irdaAttribInt;
struct
{
u_long Len;
u_char OctetSeq[IAS_MAX_OCTET_STRING];
} irdaAttribOctetSeq;
struct
{
u_long Len;
u_long CharSet;
u_char UsrStr[IAS_MAX_USER_STRING];
} irdaAttribUsrStr;
} irdaAttribute;
} IAS_QUERY, *PIAS_QUERY, FAR *LPIAS_QUERY;

Many SO_ level socket options are not meaningful to IrDA. Only SO_LINGER is specifically supported.
Note setsockopt must be called before bind on windows NT 4.0, windows 95, and windows 98 platforms.

Requirements
Client Requires windows XP, windows 2000 Professional, windows NT Workstation, windows Me, windows 98, or windows 95. 
Server Requires windows Server 2003, windows 2000 Server, or windows NT Server. 
Header Declared in Winsock2.h.
Library Link to Ws2_32.lib.
DLL Requires Ws2_32.dll. 

當你close一個socket的時候,   
這個埠並不是立即釋放的,它會等夠一定的時間才會釋放,具體的原因請參考Unix網路程式設計。   
這樣的話,當你從新建立使用同一個埠的socket的時候,   
就會出現地址已經在使用的錯誤,所以一般都會在建立了socket後,設定socket的SO_REUSEADDR的選項,   
unix下是   
s    =    socket(PF_INET,SOCK_STREAM,0);   
if(    !isvalidsock(s)    )   
{
error...   
}   
const    int    on    =    1;   
if(    setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))     
{
error..   
}   
然後再執行bind的操作

相關文章