博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
windows网络编程 | 基于控制台的简单服务器/客户端程序(5.4小节随书案例)
阅读量:283 次
发布时间:2019-03-01

本文共 7066 字,大约阅读时间需要 23 分钟。

文章目录

注:本例参考自一书。


5.4 编程举例

本节通过一个Windows控制台应用程序实现基于流式套接字的回射功能。所谓回射是指服务器接收客户发来的字符,并将接收到的内容再次发送回客户端,以此作为检测网络和主机运行状态的一种途径。客户发送的字符可以是用户输入的字符串,也可以是程序生成的序号、随机数等。本节设计客户和服务器两个独立的网络应用程序,结合网络操作的基本步骤讲述流式套接字编程中各函数的使用方法。

5.4.1 基于流式套接字的回射客户端编程操作

说明:此客户端程序接收命令行参数 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;}

5.4.2 基于流式套接字的回射服务器端编程操作

服务器端完整代码:

#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 专业版 。

1. 创建解决方案

新建工程,并创建两个项目,分别用于编写服务器端和客户端的程序。将上述代码写入各自的 .cpp 文件中 。

在这里插入图片描述

2. 客户端设置

设置客户端命令行参数:鼠标右键cli项目——》属性

在这里插入图片描述
选择调试,在命令参数中输入服务器ip地址。由于我们是本地测试,这里使用本地环回地址 127.0.0.1
在这里插入图片描述

3. 设置多项目启动

由于我们需要同时运行服务器和客户端两个程序,这里我们设置编译器为多项目启动方式。

3.1 设置多项目启动

右键解决方案——》属性

在这里插入图片描述
选择通用属性——》启动项目——》多个启动项目——》把sercli都设置为启动。
在这里插入图片描述

3.2 设置启动顺序

设置启动顺序,在项目依赖项中选择 cli 项目 依赖于 ser

在这里插入图片描述

查看项目生成顺序是否为(ser、cli),如果不是,请重新设置项目依赖项。

在这里插入图片描述
在这里插入图片描述

4. 启动运行

点击运行,运行结果如下图所示:

在这里插入图片描述

转载地址:http://deio.baihongyu.com/

你可能感兴趣的文章