Bugs

MMORPG game develop.

网络编程 心得2

如果客户端必须使用绑定端口,
那么在关闭的时候,会经历TIME_WAIT的过程,一般windows下是2分钟,这段时间,客户端connect的时候,会出错(WSAEADDRINUSE:10048),
怎么不经历这个状态呢?
使用下面代码:

  // 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历TIME_WAIT的过程:
  BOOL bDontLinger = FALSE;
  if (setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL))< 0) {
   wsaperror("setsockopt");
   break;
  }

posted on 2008-04-24 18:33 Bugs 阅读(4493) 评论(7)  编辑 收藏 引用

评论

# re: 网络编程 心得2[未登录] 2008-04-25 08:57 christanxw

SO_DONTLINGER的设置将会使得未发送的系统缓冲区数据丢失。更好的还是设置为重用端口。  回复  更多评论   

# re: 网络编程 心得2[未登录] 2008-04-25 10:47 Bugs

@christanxw 重用端口,设置过了,但在windows(只做了win测试)平台下,是没有作用的。

// 下面是测试代码,在window平台下测试过了。
// 没有linux环境,有环境的朋友可以试试。

#ifdef WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#pragma comment(lib, "WS2_32.lib")
#pragma warning( disable : 4996 )

#endif

#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef WIN32
#include <winsock2.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#endif

#ifndef WIN32
#define wsaperror perror
#else
void wsaperror(char *);
#endif

int main(int argc, char *argv[])
{
#ifdef WIN32
WSADATA wsaData;
#endif
int succeeded = 0;
struct sockaddr_in addr = {0};
int i;

// 远端地址 随便链接一个处于listen状态的
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("192.168.170.13");
addr.sin_port = htons(135);

#ifdef WIN32
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
wsaperror("WSAStartup");
exit(1);
}
#endif
while (1) {
int s = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in local_addr = {0};


#ifdef WIN32
int name_len = sizeof(local_addr);
#else
socklen_t name_len = sizeof(local_addr);
#endif

if (s < 0) {
wsaperror("socket");
break;
}

// 下面的代码,有linux环境的朋友,注释掉,不设置DontLinger试试。
//
// 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历TIME_WAIT的过程:
BOOL bDontLinger = FALSE;
if (setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL))< 0) {
wsaperror("setsockopt");
break;
}

i = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &i, sizeof(i)) < 0) {
wsaperror("setsockopt");
break;
}


// 本地地址
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = inet_addr("192.168.170.13");
local_addr.sin_port = htons(10000);

if (bind(s, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0) {
wsaperror("bind");
break;
}

if (getsockname(s, (struct sockaddr *) &local_addr, &name_len) < 0) {
break;
}

if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
//fprintf(stderr, "Local port failed: %s : %d\n", inet_ntoa(local_addr.sin_addr), ntohs(local_addr.sin_port));
wsaperror("connect");
break;
}

succeeded++;

/* shutdown(s, 2); */
#ifdef WIN32
closesocket(s);
#else
close(s);
#endif

fprintf(stderr, "%d\n", ntohs(local_addr.sin_port));
fflush(stdout);
}

fprintf(stderr, "%d connections succeeded\n", succeeded);

#ifdef WIN32
WSACleanup();
#endif
exit(1);
}

#ifdef WIN32
void wsaperror(char *str)
{
int err = WSAGetLastError();
char *errstr;

switch (err) {
case WSAEADDRINUSE:
errstr = "WSAEADDRINUSE";
break;
case WSAENOBUFS:
errstr = "WSAENOBUFS";
break;
default:
errstr = "Unknown error";
break;
}

fprintf(stderr, "%s: Error %d (%s)\n", str, err, errstr);
}
#endif
  回复  更多评论   

# re: 网络编程 心得2 2008-04-25 10:59 bugs_killer

这个方法太猥琐了...

SO_DONTLINGER 这个选项会导致 数据的接收端会丢失数据.  回复  更多评论   

# re: 网络编程 心得2 2008-04-25 11:03 Bugs

这里在说说 REUSERADDR,
ACE网络编程里说过,windows 在 2000以后, REUSERADDR 标识默认为1的。

但我在2003下测试的时候:
1.如果客户端使用了绑定端口,没有设置REUSERADDR为1时,bind的时候,会返回失败,反之成功。
但成功失败都不会影响客户端调用connect,只是成功后,则使用你指定的ADDR进行connect,失败则是随机分配的PORT进行连接。

2.但设置了REUSERADDR的作用也仅仅是上面的功能。
当在客户端主动断开连接后,处于TIME_WAIT状态时,在2MSL时间里,你绑定(bind)PORT的时候,虽然不会失败,但在connect的时候,会返回WSAEADDRINUSE(10048)错误。

10048的描述是:通常每个套接字地址(协议/网络地址/端口)只允许使用一次。

这里就跟《网络编程 心得1》里说的,处于主动关闭后的Time_wait状态下,
如果设置了REUSERADDR为1时,是可以复用prot进行连接的。
网上找了很多资料,说windows平台下这里是一个bug。
在linux平台下,还没有环境做测试,有时间找个环境测试下,应该是可以复用PORT,进行连接的。

那么现在windows平台下怎么避免2MSL时间内不能连接的问题呢。
BOOL bDontLinger = FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
这样做,使主动关闭方在close后,不经历TIME_WAIT状态,也就是说这样可能会丢失数据,比如关闭四次握手最后一次ACK如果在路由中迷途,就会影响到他的重发。
但不这样,还有其他办法吗?  回复  更多评论   

# re: 网络编程 心得2 2008-05-07 09:28 abettor

@Bugs
据我所知,貌似没有。
如果真的那么恨TIME_WAIT,我觉得可以在应用层上配合一下。也就是说,socket上仍然设置SO_DONTLINGER这个选项,但是,在制定应用层协议的时候,把主动closesocket的人定为最后一个接收数据包的人,不接收到最后一个包,就不closesocket。
但是,话说回来,这样所消耗的资源貌似比TIME_WAIT还大,呵呵。恕我直言,TCP/IP协议问题再多,也是N多大牛设计并完善出来的,不是耍一些小聪明就能将人家“改造”的更优秀的。  回复  更多评论   

# re: 网络编程 心得2[未登录] 2008-05-08 17:15 Bugs

@abettor
谢谢你的建议,我又对这个部分有了一个重新的了解了。  回复  更多评论   

# re: 网络编程 心得2 2008-05-09 18:20 hellfire

这样会导致缓冲区中的数据发送不出去的。

交给应用层来处理关闭,应用层的开发负担太大了。

按照msdn 上的bug描述,应该是由于你在服务器失败之后,没有关闭对应客户端的socket. 才导致的这个问题
  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理