linux 才socket设置端口复用,[转]linux 下Socket端口重用

2009-09-04 10:58

重用UDP端口

要重用UDP端口,需要在绑定套接字之前将套接字设置为支持SO_REUSEADDR。

当两个或多个UDP套接字绑定了同一个IP和端口后,每一个套接字都可以独立进行数据的发送,但是接收的情况却比较复杂:

1、如果数据报的目的地址为常规的单播地址时,谁及时地进行了recvfrom,谁将得到该数据报,其他的套接字将无法收到该数据报。但如果在recvfrom时使用了MSG_PEEK参数,则数据报仍会留在缓冲中供其他套接字使用(具体可参见MSDN中关于recvfrom函数的介绍)。MSG_PEEK有助于多个套接字共享接收到的数据报,但仍需小心处理。

2、如果该数据报的目的地址是广播或者多播,则每个套接口都会收到该数据报的拷贝。

解决 性参考:Currently, Linux does not allow reuse of

same local port for both TCP server and TCP

client.而且小测试程序能够成功,和我的库测试程序的差别也就在这一点了,因此接受连接和发起连接的端口分开即可。

其他资料:

这个是捣乱最大的:http://www.ajaxstu.com/Linuxbiancheng/294968.html

请问:linux难道没有端口重用吗?我用SO_REUSEPORT报找不到

已经搞定,需要改/usr/include/asm/socket.h:// To add :#define SO_REUSEPORT

15 // http://hi.baidu.com/qiaoyong/blo ...

b1f4190ff47707.html

freebsd与linux下bind系统调用小结

只考虑AF_INET的情况(同一端口指ip地址与端口号都相同)

1.freebsd支持SO_REUSEPORT和SO_REUSEADDR选项,而linux只支持SO_REUSEADDR选项。

2.freebsd下,使用SO_REUSEPORT选项,两个tcp的socket可以绑定同一个端口;同样,使用SO_REUSEPORT选项,两个udp的socket可以绑定同一个端口。

3.linux下,两个tcp的socket不能绑定同一个端口;而如果使用SO_REUSEADDR选项,两个udp的socket可以绑定同一个端口。

4.freebsd下,两个tcp的socket绑定同一端口,只有第一个socket获得数据。

5.freebsd下,两个udp的socket绑定同一端口,如果数据包的目的地址是单播地址,则只有第一个socket获得数据,而如果数据包的目的地址是多播地址,则两个socket同时获得相同的数据。

6.linux下,两个udp的socket绑定同一端口,如果数据包的目的地址是单播地址,则只有最后一个socket获得数据,而如果数据包的目的地址是多播地址,则两个socket同时获得相同的数据。

Unix网络API

SO_REUSEADDR和SO_REUSEPORT

SO_REUSEADDR提供如下四个功能:

SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在。这通常是重启监听服务器时出现,若不设置此选项,则bind时将出错。

SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。对于TCP,我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。

SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。

SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言(TCP不支持多播)。

SO_REUSEPORT选项有如下语义:

此选项允许完全重复捆绑,但仅在想捆绑相同IP地址和端口的套接口都指定了此套接口选项才性。

如果被捆绑的IP地址是一个多播地址,则SO_REUSEADDR和SO_REUSEPORT等效。

使用这两个套接口选项的建议:

在所有TCP服务器中,在调用bind之前设置SO_REUSEADDR套接口选项;

当编写一个同一时刻在同一主机上可运行多次的多播应用程序时,设置SO_REUSEADDR选项,并将本组的多播地址作为本地IP地址捆绑。

http://www.cnblogs.com/showna/articles/757768.html

setsockopt()改善程序的健壮性 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获得

//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

#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的操作

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值