在编写一个使用C++ socket实现的TCP服务端与客户端小软件时接连碰上2个小陷阱,
终归是实践不足,基本功不扎实。
第1个问题: 服务端的accept函数没有阻塞
程序运行到accept这里时直接就跳了过去,根本没停下来。
怀疑过socket的配置是否有错误,经过各种调试,当把socket部分的代码从工程中截取出,单独放到一个空白工程中运行时,一切又都正常了。
证实了socket的配置没有问题,只能是原工程中的哪个头文件包含后把socket的函数覆盖了。
又经过了几次试验,最终定位到了罪魁祸首竟然是using namespace std
网上查了一下才知道原来这也是个常见问题。因为std的命名空间里包含了太多常用的函数名,当全局使用std命名空间时,很容易发生函数运行没有得到预期的效果。
在socket的这个问题中,重要的bind函数被std中的同名函数覆盖,使得服务端的socket没有绑定ip地址和商品,自然之后的accept函数也就不能正常阻塞。
对策有2种,
1是把bind函数改为全局调用方式 ::bind(), 摆脱std命名空间的影响。
2是放弃std命名空间在全局范围内的声明,改用指定使用函数的方式。 如 using std::cout 或using std::endl
第2个问题:客户端socket断开后重连失败
这是在测试的时候发现的问题。这次的小软件设计是客户端在与服务端保持TCP连接的过程中可以随意向服务端发送命令。操作结束后可以暂时断开TCP连接,当有需求时再次连接。
因此客户端的处理框架是循环等待用户输入,再根据内容来判断需要向服务端发起什么样的操作请求。
WORD wVersionRequested;
WSADATA wsaData;
int err;
SOCKET sockClient;
wVersionRequested = MAKEWORD(1, 1);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
return;
}
if (LOBYTE(wsaData.wVersion) != 1 ||
HIBYTE(wsaData.wVersion) != 1) {
WSACleanup();
return;
}
sockClient = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
inet_pton(AF_INET, "127.0.0.1", &addrSrv.sin_addr.s_addr);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
while (1)
{
char in_buf[40];
string input_command;
cin >> in_buf;
input_command = in_buf;
if (input_command.compare("TCP connect") == 0)
{
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
char recvBuf[500]; recv(sockClient, recvBuf, 500, 0);
printf("%s\n", recvBuf);
}
else if (input_command.compare("operation") == 0)
{
char requst_buf[40] = "operation";
send(sockClient, requst_buf, strlen(requst_buf) + 1, 0);
char recvBuf[500]; recv(sockClient, recvBuf, 500, 0);
printf("%s\n", recvBuf);
}
else if (input_command.compare("disconnect") == 0)
{
char requst_buf[40] = "disconnect";
send(sockClient, requst_buf, strlen(requst_buf) + 1, 0);
char recvBuf[500]; recv(sockClient, recvBuf, 500, 0);
printf("%s\n", recvBuf);
closesocket(sockClient);
}
}
这样的客户端代码在启动后第一次连接服务端时一切都很正常,但在断开后做再次连接时发生了异常,服务端并没有收到再连接请求,一直停留在accept阻塞的位置。而客户端却能收到socket的信息反馈,但进一步确认后发现也只能收到返回消息,需要服务端处理的操作就无法得到服务端的响应。
这个现象就像是客户端在做二次连接时,连接请求并没有送到服务端,而只是把客户端可能会收到的返回消息作为缓存放在socket中,给客户端造成一种已经再次连接上的假象。
又经过一阵网络搜索,原来这也是一种常见现象。已经连接过的socket资源不能直接用于再次连接,要么对它做一次初始化,要么再次生成一个新的socket资源。作为修改,我选择了后者。
if (input_command.compare("TCP connect") == 0)
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(1, 1);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
return;
}
if (LOBYTE(wsaData.wVersion) != 1 ||
HIBYTE(wsaData.wVersion) != 1) {
WSACleanup();
return;
}
SOCKET sockClient;
sockClient = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
inet_pton(AF_INET, "127.0.0.1", &addrSrv.sin_addr.s_addr);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
char recvBuf[500]; recv(sockClient, recvBuf, 500, 0);
printf("%s\n", recvBuf);
}
把socket的建立代码移到发起TCP连接时才运行,保证每次发起连接请求用的都是新的socket,问题得到解决。