本文共 7066 字,大约阅读时间需要 23 分钟。
注:本例参考自一书。
本节通过一个Windows控制台应用程序实现基于流式套接字的回射功能。所谓回射是指服务器接收客户发来的字符,并将接收到的内容再次发送回客户端,以此作为检测网络和主机运行状态的一种途径。客户发送的字符可以是用户输入的字符串,也可以是程序生成的序号、随机数等。本节设计客户和服务器两个独立的网络应用程序,结合网络操作的基本步骤讲述流式套接字编程中各函数的使用方法。
说明:此客户端程序接收命令行参数 argv[1] 传入的服务器端地址,与服务器进行连接。
Visual Studio 编译器设置命令行参数方法:项目(右键)属性——》调试——》命令参数
客户端完整代码:
#define WIN32_LEAN_AND_MEAN#include#include #include #include #include // 连接到WinSock 2对应的lib文件:Ws2_32.lib, Mswsock.lib, Advapi32.lib#pragma comment (lib, "Ws2_32.lib")#pragma comment (lib, "Mswsock.lib")#pragma comment (lib, "AdvApi32.lib")// 定义默认的缓冲区长度和端口号#define DEFAULT_BUFLEN 512#define DEFAULT_PORT "27015"int __cdecl main(int argc, char **argv){ WSADATA wsaData; SOCKET ConnectSocket = INVALID_SOCKET; struct addrinfo *result = NULL, *ptr = NULL, hints; char sendbuf[] = "this is a test"; char recvbuf[DEFAULT_BUFLEN]; int iResult; int recvbuflen = DEFAULT_BUFLEN; // 验证参数的合法性 if (argc != 2) { printf("usage: %s server-name\n", argv[0]); return 1; } // 初始化套接字 iResult = WSAStartup(MAKEWORD(2,2), &wsaData); if (iResult != 0) { printf("WSAStartup failed with error: %d\n", iResult); return 1; } ZeroMemory( &hints, sizeof(hints) ); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; // 解析服务器地址和端口号 iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result); if ( iResult != 0 ) { printf("getaddrinfo failed with error: %d\n", iResult); WSACleanup(); return 1; } // 尝试连接服务器地址,直到成功 for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) { // 创建套接字 ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); if (ConnectSocket == INVALID_SOCKET) { printf("socket failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } // 向服务器请求连接 iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen); if (iResult == SOCKET_ERROR) { closesocket(ConnectSocket); ConnectSocket = INVALID_SOCKET; continue; } break; } freeaddrinfo(result); if (ConnectSocket == INVALID_SOCKET) { printf("Unable to connect to server!\n"); WSACleanup(); return 1; } // 发送缓冲区中的测试数据 iResult = send( ConnectSocket, sendbuf, (int)strlen(sendbuf), 0 ); if (iResult == SOCKET_ERROR) { printf("send failed with error: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } printf("Bytes Sent: %ld\n", iResult); // 数据发送结束,调用shutdown()函数声明不再发送数据,此时客户端仍可以接收数据 iResult = shutdown(ConnectSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("shutdown failed with error: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } // 持续接收数据,直到服务器关闭连接 do { iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0); if ( iResult > 0 ) printf("Bytes received: %d\n", iResult); else if ( iResult == 0 ) printf("Connection closed\n"); else printf("recv failed with error: %d\n", WSAGetLastError()); } while( iResult > 0 ); // 关闭套接字 closesocket(ConnectSocket); // 释放资源 WSACleanup(); return 0;}
服务器端完整代码:
#undef UNICODE#define WIN32_LEAN_AND_MEAN#include#include #include #include #include // 连接到WinSock 2对应的lib文件:Ws2_32.lib#pragma comment (lib, "Ws2_32.lib")// 定义默认的缓冲区长度和端口号#define DEFAULT_BUFLEN 512#define DEFAULT_PORT "27015"int __cdecl main(void){ WSADATA wsaData; int iResult; SOCKET ListenSocket = INVALID_SOCKET; SOCKET ClientSocket = INVALID_SOCKET; struct addrinfo* result = NULL; struct addrinfo hints; int iSendResult; char recvbuf[DEFAULT_BUFLEN]; int recvbuflen = DEFAULT_BUFLEN; // 初始化WinSock iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { printf("WSAStartup failed with error: %d\n", iResult); return 1; } ZeroMemory(&hints, sizeof(hints)); // 声明IPv4地址族,流式套接字,TCP协议 hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_PASSIVE; // 解析服务器地址和端口号 iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result); if (iResult != 0) { printf("getaddrinfo failed with error: %d\n", iResult); WSACleanup(); return 1; } // 为面向连接的服务器创建套接字 ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); if (ListenSocket == INVALID_SOCKET) { printf("socket failed with error: %ld\n", WSAGetLastError()); freeaddrinfo(result); WSACleanup(); return 1; } // 为套接字绑定地址和端口号 iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen); if (iResult == SOCKET_ERROR) { printf("bind failed with error: %d\n", WSAGetLastError()); freeaddrinfo(result); closesocket(ListenSocket); WSACleanup(); return 1; } freeaddrinfo(result); // 监听连接请求 iResult = listen(ListenSocket, SOMAXCONN); if (iResult == SOCKET_ERROR) { printf("listen failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } // 接受客户端的连接请求,返回连接套接字ClientSocket ClientSocket = accept(ListenSocket, NULL, NULL); if (ClientSocket == INVALID_SOCKET) { printf("accept failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } // 在必须要监听套接字的情况下释放该套接字 closesocket(ListenSocket); // 持续接收数据,直到对方关闭连接 do { iResult = recv(ClientSocket, recvbuf, recvbuflen, 0); if (iResult > 0) { // 情况1:成功接收到数据 printf("Bytes received: %d\n", iResult); // 将缓冲区的内容回送给客户端 iSendResult = send(ClientSocket, recvbuf, iResult, 0); if (iSendResult == SOCKET_ERROR) { printf("send failed with error: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return 1; } printf("Bytes sent: %d\n", iSendResult); } else if (iResult == 0) { // 情况2:连接关闭 printf("Connection closing...\n"); } else { // 情况3:接收发生错误 printf("recv failed with error: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return 1; } } while (iResult > 0); // 关闭连接 iResult = shutdown(ClientSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("shutdown failed with error: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return 1; } // 关闭套接字,释放资源 closesocket(ClientSocket); WSACleanup(); return 0;}
运行环境 Visual Studio 2019 。 windows 10 专业版 。
新建工程,并创建两个项目,分别用于编写服务器端和客户端的程序。将上述代码写入各自的 .cpp
文件中 。
设置客户端命令行参数:鼠标右键cli项目——》属性
127.0.0.1
由于我们需要同时运行服务器和客户端两个程序,这里我们设置编译器为多项目启动方式。
右键解决方案——》属性
设置启动顺序,在项目依赖项中选择 cli 项目 依赖于 ser。
查看项目生成顺序是否为(ser、cli),如果不是,请重新设置项目依赖项。
点击运行,运行结果如下图所示:
转载地址:http://deio.baihongyu.com/