淺析:setsockopt()改善socket網路程式的健壯性

LiuYinChina發表於2013-02-06

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  nLe  
---------------------------------------------------------------  
 
其實我覺得第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.

相關文章