博客
关于我
windows网络编程 | 基于控制台的简单服务器/客户端程序(5.4小节随书案例)
阅读量:286 次
发布时间:2019-03-01

本文共 7042 字,大约阅读时间需要 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/

你可能感兴趣的文章
MySQL 是如何加锁的?
查看>>
MySQL 是怎样运行的 - InnoDB数据页结构
查看>>
mysql 更新子表_mysql 在update中实现子查询的方式
查看>>
MySQL 有什么优点?
查看>>
mysql 权限整理记录
查看>>
mysql 权限登录问题:ERROR 1045 (28000): Access denied for user ‘root‘@‘localhost‘ (using password: YES)
查看>>
MYSQL 查看最大连接数和修改最大连接数
查看>>
MySQL 查看有哪些表
查看>>
mysql 查看锁_阿里/美团/字节面试官必问的Mysql锁机制,你真的明白吗
查看>>
MySql 查询以逗号分隔的字符串的方法(正则)
查看>>
MySQL 查询优化:提速查询效率的13大秘籍(避免使用SELECT 、分页查询的优化、合理使用连接、子查询的优化)(上)
查看>>
mysql 查询数据库所有表的字段信息
查看>>
【Java基础】什么是面向对象?
查看>>
mysql 查询,正数降序排序,负数升序排序
查看>>
MySQL 树形结构 根据指定节点 获取其下属的所有子节点(包含路径上的枝干节点和叶子节点)...
查看>>
mysql 死锁 Deadlock found when trying to get lock; try restarting transaction
查看>>
mysql 死锁(先delete 后insert)日志分析
查看>>
MySQL 死锁了,怎么办?
查看>>
MySQL 深度分页性能急剧下降,该如何优化?
查看>>
MySQL 深度分页性能急剧下降,该如何优化?
查看>>