在这几个月中阅读和编写了大量的套接字程序,所以对套接字 程序的编写也有了新的认识 。现在这里分享出来与大家交流一下,不足之处还望大家指正。
这里的示例程序是修改自 Winsocket入门教程一:多线程阻塞式服务器和阻塞式客户端程序(TCP) 中的客户端程序。相对于原来的程序,主要进行了以下几个方面的处理:
1.添加了更好的错误处理代码。使用GOTO语句将错误处理放到一起,可以使错误处理的代码更利于维护,并且减少代码的重复(Code duplicate)
2.使用微软推荐的函数getaddrinfo函数获取连接地址,不过冒似使用这种方法要写的代码更多。
3.发送和接收时使用循环发送和接收,以处理一次发送或者接收不完的情况。
示例代码如下
// 此示例程序参考MSDN Winsocket Client Demo // 示例和自己处理Winsocket程序的经验而成 // 不足和错误之处望大家指正 // 原始DEMO地址:http://msdn.microsoft.com/zh-cn/library/ms737591%28v=VS.85%29.aspx // 如果在引用WinSock2.h的同时需要引用Windows.h // 必须定义下列宏。因为Windows.h中包含了WinSock.h // 如果不定义下列宏,会引起数据结构和函数重定义的问题 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include <cstdio> #include <cstdlib> #include <Windows.h> #include <WinSock2.h> #include <WS2tcpip.h> #include <IPHlpApi.h> // Winsocket程序需要链接ws2_32.lib #pragma comment(lib, "ws2_32.lib") int main() { int iRet = 0; WSADATA wd; // Winsocket程序必须要调用WSAStartup对 // ws2_32.dll的初始化,否则下面的函数调用 // 均会失败 iRet = WSAStartup(MAKEWORD(2, 2), &wd); if (SOCKET_ERROR == iRet) { printf("Call WSAStartup failed, errno = %d./r/n", WSAGetLastError()); goto error_handle1; } // 使用getaddrinfo创建进行连接(connect)所需要的地址 addrinfo aiInst; addrinfo *paiResult = NULL; addrinfo *paiThis = NULL; ZeroMemory(&aiInst, sizeof(aiInst)); aiInst.ai_flags = AI_NUMERICHOST; // 使用IP地址,不使用名称解析 aiInst.ai_family = AF_INET; // 协议族:IPV4 aiInst.ai_socktype = SOCK_STREAM; // 流式套接字 aiInst.ai_protocol = IPPROTO_TCP; // TCP传输 const char *pszHost = "127.0.0.1"; const char *pszService = "10001"; // getaddrinfo根据填入的参数 // 可能会生成一个带有多个地址的链表 // 注意必须释放paiResult iRet = getaddrinfo(pszHost, pszService, &aiInst, &paiResult); if (SOCKET_ERROR == iRet) { printf("call getaddrinfo failed, errno = %d./r/n", WSAGetLastError()); goto error_handle2; } // 由协议族、套接字类型以及协议类型创建所需要的套接字 // 以上三个参数决定了创建套接字的特性。该特性决定了连 // 接的属性。例如AF_INET、SOCK_STREAM、IPPROTO_TCP // 创建的套接字拥有TCP连接属性。即由该套接字创建的连接 // 为TCP连接 SOCKET sktConn = INVALID_SOCKET; for (paiThis = paiResult; NULL != paiThis; paiThis = paiThis->ai_next) { sktConn = socket(paiResult->ai_family, paiResult->ai_socktype, paiResult->ai_protocol); if (INVALID_SOCKET == sktConn) { printf("call socket failed, errno = %d./r/n", WSAGetLastError()); goto error_handle3; } // 由所创建的套接字和地址连接服务器 iRet = connect(sktConn, paiResult->ai_addr, (int)paiResult->ai_addrlen); if (SOCKET_ERROR == iRet) { printf("call connect failed, errno = %d./r/n", WSAGetLastError()); closesocket(sktConn); sktConn = INVALID_SOCKET; continue; } break; } if (INVALID_SOCKET == sktConn) { goto error_handle3; } // 也可以使用下面的方法创建连接地址 // sockaddr_in adrServ; // ZeroMemory(&adrServ, sizeof(sockaddr_in)); // adrServ.sin_family = AF_INET; // adrServ.sin_port = htons(10001); // adrServ.sin_addr.s_addr = inet_addr("127.0.0.1"); // connect(sktConn, (sockaddr*)&adrServ, sizeof(sockaddr_in)); printf("Connect to %s:%s succeed./r/n", pszHost, pszService); // 如果发送的消息过长 // 可能需要发送多次才能发送完成 // 这里展示一种简单的发送方法 const char *pszSend = "Hello world!!!"; int iSndLen = (int)strlen(pszSend); int iHadSnd = 0; do { iRet = send(sktConn, pszSend + iHadSnd, iSndLen, 0); if (0 < iRet) { printf("%d bytes send./r/n", iRet); } else if (0 == iRet) { printf("All data send./r/n"); } else { printf("call send failed, errno = %d./r/n", WSAGetLastError()); goto error_handle4; } iHadSnd += iRet; iSndLen -= iRet; } while (0 < iRet); // 如果不再需要发送数据,则应该使用shutdown配合 // SD_SEND关闭该套接字的功能,以减轻服务器端的 // 资源占用 iRet = shutdown(sktConn, SD_SEND); if (SOCKET_ERROR == iRet) { printf("call shutdown failed, errno = %d./r/n", WSAGetLastError()); goto error_handle4; } // 接收从服务器发来的数据 // 如果消息过长,则需要多次接收 // 这里展示一种简单的接收方法 const int iRcvLen = 511; char szRcvBuf[iRcvLen + 1]; // 0结束符 do { iRet = recv(sktConn, szRcvBuf, iRcvLen, 0); szRcvBuf[iRet] = 0; if (0 < iRet) { printf("%d bytes received: %s./r/n", iRet, szRcvBuf); } else if (0 == iRet) { printf("All data received./r/n"); } else { printf("call recv failed, errno = %d./r/n", WSAGetLastError()); } } while (0 < iRet); error_handle4: // 确定已经收完数据后在关闭套接字(优雅关闭(Grace closed)) // while(0 < recv(sktConn, szRcvBuf, iRcvLen, 0)); closesocket(sktConn); sktConn = INVALID_SOCKET; error_handle3: freeaddrinfo(paiResult); // 释放连接地址 paiResult = NULL; error_handle2: WSACleanup(); // 清理ws2_32.dll相关资源 error_handle1: system("pause"); return iRet; }