服务器socketstreamtcpc
原贴地址:http://topic.csdn.net/u/20090103/16/a0414edb-b289-4c72-84da-39e155e8f4be.html
如下演示程序,程序目的是:先准备好一个ServerSocket,监听端口8880,
然后建一个ClientSocket(受限于业务需要,必须在ServerSocket准备好后再建Client),也必须绑定同一端口8880,问题是:为什么对ClientSocket bind(port 8880)时,会报错EADDRINUSE?我已经启用了SO_REUSEADDR。
为了方便于大家试运行,我把代码简化的没有其他库依赖,只需g++ -o demo demo.cpp即可运行。
C/C++ code
//demo.cpp
#include <stdio.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <linux/tcp.h>int SockDemo()
{
sockaddr_in in;
memset(&in,‘\0‘,sizeof(in));
in.sin_family=AF_INET;
in.sin_port=htons(8880);
in.sin_addr.s_addr=INADDR_ANY;int reuse0=1;
int serv=socket(AF_INET, SOCK_STREAM, 0);
if (setsockopt(serv, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse0, sizeof(reuse0))==-1) return errno;
if (bind(serv, (sockaddr*)&in, sizeof(sockaddr)) == -1) return errno;
if (listen(serv, SOMAXCONN)==-1) return errno;int reuse1=1;
int client=socket(AF_INET, SOCK_STREAM, 0);
if (setsockopt(client, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse1, sizeof(reuse1))==-1) return errno;if (bind(client, (sockaddr*)&in, sizeof(sockaddr)) == -1) return errno;
//****** 此处为报错 *****:errno=98, Address already in use.sleep(1);
close(client);
close(serv);
return 0;}
int main(int argc, char *argv[])
{
int errcode=SockDemo();
printf("errno=%d, %s.\n", errcode, strerror(errcode));
return 0;
}如果真如各位所说,下面的现象该做何解释??
示例代码中,只要去掉listen(serv, SOMAXCONN)一句即可正常运行。
使用 SO_REUSEPORT 这个吧。
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地址捆绑。#define SO_REUSEPORT 15
使用这两个套接口选项的建议:
在所有TCP服务器中,在调用bind之前设置SO_REUSEADDR套接口选项;
当编写一个同一时刻在同一主机上可运行多次的多播应用程序时,设置SO_REUSEADDR选项,并将本组的多播地址作为本地IP地址捆绑。
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
(const void *)&nOptval , sizeof(int)) < 0) ...
附
Q:编写 TCP/SOCK_STREAM 服务程序时,SO_REUSEADDR到底什么意思?
A:这个套接字选项通知内核,如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息,指明"地址已经使用中"。如果你的服务程序停止后想立即重启,而新套接字依旧使用同一端口,此时SO_REUSEADDR 选项非常有用。必须意识到,此时任何非期望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不可能。
一个套接字由相关五元组构成,协议、本地地址、本地端口、远程地址、远程端口。SO_REUSEADDR 仅仅表示可以重用本地本地地址、本地端口,整个相关五元组还是唯一确定的。所以,重启后的服务程序有可能收到非期望数据。必须慎重使用 SO_REUSEADDR 选项。【2】
【1】 http://topic.csdn.net/u/20090103/16/a0414edb-b289-4c72-84da-39e155e8f4be.html
【2】
以下博客对这个问题进行了对答式的解答:
http://blog.sina.com.cn/s/blog_53a2ecbf010095db.html
【3】 http://www.sudu.cn/info/html/edu/20050101/296180.html但是这就引出了另外的一个问题,就是设置这个属性后允许一个套接字上同时有两个应用程序进行监听,那系统究竟会将数据发送给哪一个程序呢?系统会将数据首先交给监听IP最确定的应用程序。例如应用程序A在调用监听函数时设置的属性是addr.sin_addr.S_addr := INADDR_ANY;而另外的一个应用程序B则监听的地址为addr.sin_addr.S_addr := inet_addr(PChar(sMainIP));同时这两个应用程序监听的端口都是相同的,这是系统接收到数据后会首先交给B然后再交给A。因此一般为了程序安全我们会禁止这种情况的发生。因此需要设置下面的SO_EXCLUSIVEADDRUSE属性。
SO_REUSEADDR