调试
服务端在执行命令接口,新增了一个测试接口函数
1 服务端测试代码:
int TestConnect() { CPacket packet(2000, NULL, 0); CServerSocket::getInstance()->Send(packet); return 0; }
2 客户端调试代码:
void CRemoteClientDlg::OnBnClickedBtnTest() { CClientSocket* pClient = CClientSocket::getInstance(); bool ret = pClient->InitSocket("127.0.0.1"); if (!ret) { AfxMessageBox("网络初始化失败"); return; } CPacket packet(2000, NULL, 0); pClient->Send(packet); int cmd = pClient->DealCommand(); TRACE("ack:%d\r\n", cmd); pClient->CloseSocket(); }
完善:
3 存在的问题
3.1 客户端初始化套接字存在问题
问题:第一次测试连接正常,后面就无法连接
原因:服务器套接字是在构造函数里面初始话的,当第一次连接后对方关闭了套接字,我们这边的套接字就是个无效套接字了,再在上面进行connect就会失败
解决方案:在connect之间就重新初始化套接字
// 初始化套接字,true成功,false失败 bool InitSocket(const std::string& ipAddress) { if (m_socket_server != INVALID_SOCKET)CloseSocket(); m_socket_server = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addr_server; addr_server.sin_family = AF_INET; // htons 主机字节序转为网络字节序 addr_server.sin_port = htons(60000); // inet_addr 函数将包含 IPv4 点十进制地址的字符串转换为IN_ADDR结构的正确地址。 addr_server.sin_addr.S_un.S_addr = inet_addr(ipAddress.c_str()); // 服务器地址是否成功设置 if (addr_server.sin_addr.S_un.S_addr == NULL) { AfxMessageBox("指定ip地址无效\r\n"); TRACE("连接失败,:%d %s", GetLastError(), GetErrorInfo(GetLastError()).c_str()); return false; } // 发起三次握手,连接成功后服务器就阻塞在recv等待对方发送数据 int ret = connect(m_socket_server, (sockaddr*)&addr_server, sizeof(SOCKADDR_IN)); if (ret == -1) { AfxMessageBox("连接失败\r\n"); TRACE("连接失败,:%d %s", GetLastError(), GetErrorInfo(GetLastError()).c_str()); return false; } return true; }
3.2 服务器接收缓冲区内存泄漏
由于buffer是new出来的,再加上我们可知我们这是个短连接,客户端一次只发送一个命令
原因:buffer是new出来的,没有delete
解决方案:在结束函数之前delete掉buffer
// 循环接收处理数据,存放在m_packet里面 int DealCommand() { if (m_socket_client == INVALID_SOCKET) { return false; } // 存在内存泄漏bug char* buffer = new char[BUFFER_SIZE]; if (buffer == NULL) { TRACE("内存不足\r\n"); return -2; } memset(buffer, 0, BUFFER_SIZE); // 缓冲区当前所有数据长度 static int index = 0; while (true) { size_t len = recv(m_socket_client, buffer + index, BUFFER_SIZE - index, 0); if (len <= 0) { // 短连接每次处理完数据需要delete掉buffer delete[] buffer; return -1; } // 每次接收数据后,index加上这个长度 index += len; // 让每次解析都是全部的数据长度,如果直接传index进去解析,会导致index值被修改 len = index; // len是个引用 本次解析的数据长度 m_packet = CPacket((BYTE*)buffer, len); if (len > 0) { // 解析完了后,把buffer往前移 memmove(buffer, buffer + len, BUFFER_SIZE - len); // index 减去用掉的长度 index -= len; delete[] buffer; return m_packet.sCmd; } } delete[] buffer; }
4 代码优化
4.1 客户端接收缓冲区代码优化
因为客户端一次可能接收多个数据包,所以这个缓冲区需要一直能存在
所以定义一个成员变量来保存
std::vector<char> m_buffer;
在构造函数里面初始化大小
CClientSocket() { if (InitSocketEnv() == FALSE) { MessageBox(NULL, _T("无法初始化套接字环境,请检查网络环境"), _T("初始化错误"), MB_OK | MB_ICONERROR); exit(0); } // buffer大小初始化 m_buffer.resize(BUFFER_SIZE); }