在以前编写SOCKET包装类的时候,碰到了一个不解的问题,在优快云论坛上也多亏了各位大虾的帮忙,从而得到了解决。今天重新翻看到,觉得这是一个有价值的问题,所以整理发布在这里。
问题是这样的:
请问,对socket进行包装,其中一个方法是接受客户端的连接。以下的两个函数,一个有问题,一个没问题。请帮忙看看!
1、这个函数是没问题的,把对象用引用传进去
void CDGSocket::AcceptClient(CDGSocket &s)
{
struct sockaddr_in cliAddrTmp;
int cliAddrSize = sizeof(struct sockaddr_in);
SOCKET sClient = accept(m_hSocket, (struct sockaddr *)&cliAddrTmp, &cliAddrSize);
if(sClient == INVALID_SOCKET)
{
ThrowException("accept() failed!");
}
s.m_hSocket = sClient;
}
客户程序的调用如下:
CDGSocket sClient;
sServer.AcceptClient(sClient);
2、这个函数是有问题的
CDGSocket CDGSocket::AcceptClient()
{
CDGSocket sClientObj;
struct sockaddr_in cliAddrTmp;
int cliAddrSize = sizeof(struct sockaddr_in);
sClientObj.m_hSocket = accept(m_hSocket, (struct sockaddr *)&cliAddrTmp, &cliAddrSize);
if(sClientObj.m_hSocket == INVALID_SOCKET)
{
ThrowException("accept() failed!");
}
return sClientObj;
}
客户程序是这样调用的:
CDGSocket sClient;
sClient = sServer.AcceptClient();
但是就在返回的时候程序出现了异常,我非常迷惑这里是什么原因造成的。
我的拷贝构造函数是这样的,里面没有其他的成员变量,也没有动态开辟的空间。
按道理来说应该不需要拷贝构造函数。
CDGSocket::CDGSocket(const CDGSocket &s)
{
m_hSocket = s.m_hSocket;
}
我自己尝试解决所做的试验:
1、跟踪出错函数的时候程序是这样跑的:
return sClientObj;--> 拷贝构造函数--〉析构函数--〉返回--〉出错
开始猜想可能是拷贝构造函数的问题,但是试了一下其他的拷贝内存的方法,也没什么用。
2、查看了MSDN中SOCKET的解释,发现,SOCKET其实就是unsigned int,是一个由Windows分配的句柄值,有点类似于HWND,这种的变量的赋值是否有点特别。
问题分析:
从上面两个方法可以看出来,一个是把接受到的连接放在引用中返回,一个是在返回值中返回。在返回值中返回的对象会以这样一种方式执行,调用拷贝构造函数把值拷贝到外部--〉调用析构函数把原来的值析构掉--〉返回。
大虾们的帮助:
healer_kx(甘草(楼主揭贴吧,我们这些上班灌水的也不容易))回复于 2006-07-21 16:50:46 得分 30
你不是Socket方面的问题,你是C++问题上的错误。
Socket是不应该具有拷贝构造语意的对象,
也就是说,不要写出
CDGSocket sClientObj;
return sClientObj;
这样的代码。
这能意味着什么呢?
就是说,一个Socket对象,它本身具有了某种功能后,你把它拷贝给了别的对象?
这个是不行的。
2 楼healer_kx(甘草(楼主揭贴吧,我们这些上班灌水的也不容易))回复于 2006-07-21 16:54:21 得分 0
有些类型的对象,是不应该具有这样的语意的。某位大侠教我的,切记切记。
3 楼sinall()回复于 2006-07-21 17:16:16 得分 30
把你的CDGSocket声明和CDGSocket构造、析构函数拿出来看看。
怀疑你的~CDGSocket()里有关闭socket的操作。
所以你的CDGSocket不具备拷贝构造函数也是很正常的。boost还专门有个noncopyable类,从该类派生的类都不具备拷贝构造和赋值操作!
4 楼dylgsy(一雨田)回复于 2006-07-22 01:02:04 得分 0
两位大虾,都说得不错,谢谢先。
to 甘草:
Socket是不应该具有拷贝构造语意的对象。
这句话有点不明,那是否说不可以使用这个方法返回?只能用第一种方法呢?
to sinall:
我的析构函数里确实有关闭socket的操作,但是为什么不可以在这里关闭socket呢?socket不是被拷贝了吗?关闭了原来的socket,有什么关系呢?不太明白,请说清楚点。谢谢!!
下面是我的构造和析构函数的定义:
CDGSocket::CDGSocket()
{
m_hSocket = INVALID_SOCKET;
m_hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(m_hSocket == INVALID_SOCKET)
{
ThrowException("socket() failed!");
}
}
CDGSocket::~CDGSocket()
{
if(m_hSocket != INVALID_SOCKET)
Close();
}
// 这里的close函数如下:
void CDGSocket::Close()
{
if(m_hSocket != INVALID_SOCKET)
{
if(SOCKET_ERROR == closesocket(m_hSocket))
ThrowException("CloseHandle(m_hSocket) failed!");
m_hSocket = INVALID_SOCKET;
}
}
请再帮忙看看,十分感谢!!
5 楼UPCC(杂食动物)回复于 2006-07-22 01:20:10 得分 40
我的析构函数里确实有关闭socket的操作,但是为什么不可以在这里关闭socket呢?socket不是被拷贝了吗?关闭了原来的socket,有什么关系呢?
--------------------------------------------------------------
其实我给你说个简单的,Socket是什么?Socket其实什么都不是,只是对一类技术操作的一个概念而已,真实的,他是一个象文件指针一类的东西。系统接受到一个连接请求就在内存打开一个新的文件,文件的指针也就是新的Socket了。这个文件存放了所有连接内容有关的一切东西,这一点你可以你查看Linux的源代码,或者别的技术文档。
你在你的类的析构函数里摧毁了Socket,那么文件被关闭,返回一个被关闭的文件的指针有作用吗?(备注:这一段是将事论事的,你程序具体什么问题,我不能确认)
6 楼dylgsy(一雨田)回复于 2006-07-22 01:32:37 得分 0
感谢UPCC,我对你说的话是这样理解的:
SOCKET是一个WINDOWS的分配的句柄,WINDOWS把这个当成一个标示,凡是要对SOCKET做IO操作,那就会调用这个句柄,然后这个句柄如果被关闭了,就相当于WINDOWS不再认这个句柄了,也就是对它进行的任何操作都会失效,这里的拷贝构造函数,也只是拷贝了它的数值而已,WINDOWS再也不认这个标示了,所以,我们不能对它做任何操作了。也就是甘草所说的,这是一类不具备拷贝构造函数语义的对象。对吗?
那按照这种结论,就只能使用第一种的方法去获取SOCKET了。
这是根据你们三位所说总结出来的,不知道理解对不对。请再指教,谢谢!
7 楼UPCC(杂食动物)回复于 2006-07-22 01:45:45 得分 0
对的。
8 楼UPCC(杂食动物)回复于 2006-07-22 01:47:38 得分 0
但对关技术细节的东西要分开来处理,毕竟实际的东西比理论的东西复杂
9 楼dylgsy(一雨田)回复于 2006-07-23 13:07:40 得分 0
自己再做了个试验:
SOCKET s1 = accept(...);
SOCKET s2 = s1;
closesocket(s1);
send(s2, ...);
recv(s2, ...);
发现s1被关闭之后,s2也不可用了,再证明了上述的结论是正确的,好,谢谢各位,结贴。
总结:
在现在回看起来,SOCKET只是一个数值(WINDOWS管理的一个数值),而对象是一个有一定内存空间的东西,例如方法、成员变量等都需要占用一定的内存空间。从这一点来看,SOCKET不能称之为一个对象,就像文件句柄、线程句柄等。所以针对这些类型的变量,我们一定要小心,它们是不具有拷贝构造的语义的。