C++实现Windows下服务端与客户端Socket通信(一)_c++ socket通讯考虑客户端版本版本问题-程序员宅基地

技术标签: c++  websocket  mfc  visualstudio  网络编程  

个人简介

  • 作者简介:大家好,我是abcdefzzzz,2021年7月正式加入新生代农民工大军。
  • 个人主页:abcdefzzzz
  • 支持我:码字不易 欢迎关注点赞收藏️留言
  • 格言:日有所学,不见其增,必有所长!

源码链接:基于MFC可视化界面的Socket双向通信+protobuf传输源码-C++文档类资源-CSDN下载

一、开发配置介绍

1.开发软件:VS2017

2.开发语言:C++

3.操作系统:Windows10

3.使用技术:Socket、MFC

二、演示效果

(一)Windows服务端:

(二)Windows客户端:

基于MFC的客户端效果

控制台客户端效果

①客户端与服务端建立连接

②客户端向服务端发送注册、登录的数据信息。

③服务端验证客户端的信息,向客户端发送是否验证成功的消息。

三、C++程序实现

(一)Socket传输字符串数据

I.服务端:

1.创建控制台应用程序:ServerSocket_CSDN

2.服务端程序代码&注释

#define _WINSOCK_DEPRECATED_NO_WARNINGS			//防止报错
#include <iostream>
#include <WinSock2.h>
using namespace std;

#pragma comment(lib,"ws2_32.lib")

int main()
{
	/*初始化启动信息*/
	WORD sockVersion = MAKEWORD(2, 2);//调用2.2版本的socket
	WSADATA wsaData;				  //WSA(Windows Sockets Asynchronous)异步套接字

	//将指定版本的socket与该应用程序绑定
	if (WSAStartup(sockVersion, &wsaData) != 0)	//返回为0则表示初始化成功
		return 0;

	/*创建服务器Socket*/
	//AF_INET,使用IPV4通信;SOCK_STREAM,使用流式套接字;IPPROTO_TCP,采用TCP协议

	SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (serverSocket == INVALID_SOCKET)			//如果创建失败,则输出错误
	{
		cout << "socket error:" << WSAGetLastError() << endl;
		WSACleanup();			//中止Windows Sockets DLL的使用;与上面WSAStartup()配套使用
		return 0;
	}
	//创建服务器Socket套接字成功后

	/*sockaddr_in是一个结构体,需要指定 协议族 + IP地址 + 端口号*/
	sockaddr_in serverAddr;
	serverAddr.sin_family = AF_INET;				//指定协议族,AF_INET<->IPV4;AF_INET6<->IPV6
	serverAddr.sin_addr.S_un.S_addr = INADDR_ANY;	//指定IP地址
	serverAddr.sin_port = htons(8888);				//指定端口号

	//Socket绑定地址,第二个参数注意需要使用强制转换
	if (bind(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
	{
		cout << "Bind Error!" << endl;
		closesocket(serverSocket);
		WSACleanup();
		return 0;
	}

	//建立监听客户端请求的信号,设置最多有5个客户端
	if (listen(serverSocket, 5) == SOCKET_ERROR)//如果建立失败
	{
		cout << "Listen Error !" << endl;
		closesocket(serverSocket);				//关闭Socket套接字
		WSACleanup();
		return 0;
	}
	cout << "正在监听..." << endl;

	//开始不断处理各个客户端请求
	while (true)
	{
		SOCKET clientSocket = INVALID_SOCKET;	//初始化一个客户端socket
		sockaddr_in clientAddr;					//客户端的地址结构
		int iAddrLength = sizeof(clientAddr);	//求出该结构的长度
		cout << "等待登录..." << endl;
		//接收客户端的连接请求。第一个参数为服务端Socket,第二个参数用来接收从客户端传来的地址,第三个参数为地址结构的长度
		clientSocket = accept(serverSocket, (SOCKADDR*)&clientAddr, &iAddrLength);//accept为阻塞函数
		
		if (clientSocket == INVALID_SOCKET)		//如果接收失败
		{
			cout << "Accept Error !" << WSAGetLastError() << endl;
			closesocket(serverSocket);
			WSACleanup();
			return 0;
		}
		//inet_ntoa将网络地址转换为字符输出
		cout << "客户端地址:" << inet_ntoa(clientAddr.sin_addr) << endl;

		//开始不断接收该客户端数据
		char buffFromClient[1024];			//用于接收客户端传来的数据
		while(true)
		{
			memset(buffFromClient, 0, sizeof(buffFromClient));
			//recv也为阻塞函数,只有客户端发送数据过来后,程序才会往下继续走
			int iLenOfRecvData = recv(clientSocket, buffFromClient, sizeof(buffFromClient), 0);
			
			if (iLenOfRecvData > 0)		//如果接收的数据不为空
			{
				cout << buffFromClient << endl;
			}
			else
			{
				cout << "服务器断开,无接收..." << endl;
				break;
			}
			char sendToClientBuff[1024] = "服务端收到啦!";
			send(clientSocket, sendToClientBuff, sizeof(sendToClientBuff), 0);
		}
		closesocket(clientSocket);//关闭与该客户端的套接字
	}
	closesocket(serverSocket);//关闭服务器套接字
	WSACleanup();
}

II.客户端:

1.新建MFC对话框程序:ClientSocket_CSDN

2.UI设计,添加Button控件

3.实现点击Button控件向服务端发送消息的函数

        ①在.h文件中添加需要的头文件、库,定义一个全局变量:m_clientSocket。

        ②修改项目属性,将SDL检查设置为:否,用于防止报错。

        ③编写OnBnClickedButton1()函数

void CClientSocketCSDNDlg::OnBnClickedButton1()
{
	WORD sockVersion = MAKEWORD(2, 2);
	WSADATA wsaData;//WSA(Windows Sockets Asynchronous)异步套接字
	if (WSAStartup(sockVersion, &wsaData) != 0)
		return;

	m_clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (m_clientSocket == INVALID_SOCKET)
	{
		MessageBox("Invalid Socket!", "错误", MB_ICONERROR);
		return;
	}
	//建立一个客户端地址
	sockaddr_in serAddr;
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(8888);
	serAddr.sin_addr.S_un.S_addr = inet_addr("127.1.0.1");
	//客户端向服务端请求连接
	if (connect(m_clientSocket, (sockaddr*)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
	{
		MessageBox("Connect Error!!", "错误", MB_ICONERROR);
		closesocket(m_clientSocket);
		return;
	}
	//与服务端连接成功,开发发送消息
	char sendToServerBuff[1024] = "服务端,我来啦~";
	send(m_clientSocket, *(&sendToServerBuff), sizeof(sendToServerBuff), 0);

	//接收服务端返回的消息
	char buffFromServer[1024];
	int recvDataLen = recv(m_clientSocket, buffFromServer, sizeof(buffFromServer), 0);
	if (recvDataLen > 0)
	{
		MessageBox(buffFromServer);
	}

	closesocket(m_clientSocket);
	WSACleanup();
}

(二)Socket传输结构体数据

见我另一篇博客:(详细源码)C++ socket 传输不同类型数据的四种方式_abcdefzzzz的博客-程序员宅基地

(三)Socket传输类对象数据

I.服务端

1.添加StudentInfo类

2.服务端目录结构如下:

 3.类对象的.h程序代码

//StudentInfo.h文件如下,.cpp文件自行实现

#pragma once
#include <iostream>
using namespace std;
class StudentInfo
{
private:
	int m_iId;
	string m_strName;
	bool m_bSex;

public:
	StudentInfo();
	~StudentInfo();

	int GetId();
	string GetName();
	bool GetSex();

	void SetId(int iId);
	void SetName(string strName);
	void SetSex(bool bSex);
};

4.实现传输类对象的程序代码

#define _WINSOCK_DEPRECATED_NO_WARNINGS			//防止报错
#include <iostream>
#include <string>
#include <WinSock2.h>
#include "StudentInfo.h"
using namespace std;

#pragma comment(lib,"ws2_32.lib")
struct Student
{
	int iId;
	string strName;
	bool bSex;
};
int main()
{
	/*初始化启动信息*/
	WORD sockVersion = MAKEWORD(2, 2);//调用2.2版本的socket
	WSADATA wsaData;				  //WSA(Windows Sockets Asynchronous)异步套接字

	//将指定版本的socket与该应用程序绑定
	if (WSAStartup(sockVersion, &wsaData) != 0)	//返回为0则表示初始化成功
		return 0;

	/*创建服务器Socket*/
	//AF_INET,使用IPV4通信;SOCK_STREAM,使用流式套接字;IPPROTO_TCP,采用TCP协议

	SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (serverSocket == INVALID_SOCKET)			//如果创建失败,则输出错误
	{
		cout << "socket error:" << WSAGetLastError() << endl;
		WSACleanup();			//中止Windows Sockets DLL的使用;与上面WSAStartup()配套使用
		return 0;
	}
	//创建服务器Socket套接字成功后

	/*sockaddr_in是一个结构体,需要指定 协议族 + IP地址 + 端口号*/
	sockaddr_in serverAddr;
	serverAddr.sin_family = AF_INET;				//指定协议族,AF_INET<->IPV4;AF_INET6<->IPV6
	serverAddr.sin_addr.S_un.S_addr = INADDR_ANY;	//指定IP地址
	serverAddr.sin_port = htons(8888);				//指定端口号

	//Socket绑定地址,第二个参数注意需要使用强制转换
	if (bind(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
	{
		cout << "Bind Error!" << endl;
		closesocket(serverSocket);
		WSACleanup();
		return 0;
	}

	//建立监听客户端请求的信号,设置最多有5个客户端
	if (listen(serverSocket, 5) == SOCKET_ERROR)//如果建立失败
	{
		cout << "Listen Error !" << endl;
		closesocket(serverSocket);				//关闭Socket套接字
		WSACleanup();
		return 0;
	}
	cout << "正在监听..." << endl;

	//开始不断处理各个客户端请求
	StudentInfo stuInfo;
	while (true)
	{
		SOCKET clientSocket = INVALID_SOCKET;	//初始化一个客户端socket
		sockaddr_in clientAddr;					//客户端的地址结构
		int iAddrLength = sizeof(clientAddr);	//求出该结构的长度
		cout << "等待登录..." << endl;
		//接收客户端的连接请求。第一个参数为服务端Socket,第二个参数用来接收从客户端传来的地址,第三个参数为地址结构的长度
		clientSocket = accept(serverSocket, (SOCKADDR*)&clientAddr, &iAddrLength);//accept为阻塞函数
		
		if (clientSocket == INVALID_SOCKET)		//如果接收失败
		{
			cout << "Accept Error !" << WSAGetLastError() << endl;
			closesocket(serverSocket);
			WSACleanup();
			return 0;
		}
		//inet_ntoa将网络地址转换为字符输出
		cout << "客户端地址:" << inet_ntoa(clientAddr.sin_addr) << endl;

		//开始不断接收该客户端数据
		char buffFromClient[1024];			//用于接收客户端传来的数据
		while(true)
		{
			memset(buffFromClient, 0, sizeof(buffFromClient));
			//recv也为阻塞函数,只有客户端发送数据过来后,程序才会往下继续走
			int iLenOfRecvData = -1;
			//传输类对象数据
			iLenOfRecvData = recv(clientSocket, (char*)&stuInfo, sizeof(StudentInfo), 0);
			if (iLenOfRecvData > 0)		//如果接收的数据不为空
			{
				cout << stuInfo.GetId() << endl;
				cout << stuInfo.GetName() << endl;
				cout << stuInfo.GetSex() << endl;
			}
			else
			{
				cout << "服务器断开,无接收..." << endl;
				break;
			}
			char sendToClientBuff[1024] = "服务端收到啦!";
			send(clientSocket, sendToClientBuff, sizeof(sendToClientBuff), 0);
		}
		closesocket(clientSocket);//关闭与该客户端的套接字
	}
	closesocket(serverSocket);//关闭服务器套接字
	WSACleanup();
}

II.客户端

1.添加StudentInfo类(同服务端)

2.客户端目录结构如下:

 3.类对象的程序代码同服务端。

 4.编写OnBnClickedButton1函数,实现传输类对象的程序代码


void CClientSocketCSDNDlg::OnBnClickedButton1()
{
	WORD sockVersion = MAKEWORD(2, 2);
	WSADATA wsaData;//WSA(Windows Sockets Asynchronous)异步套接字
	if (WSAStartup(sockVersion, &wsaData) != 0)
		return;

	m_clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (m_clientSocket == INVALID_SOCKET)
	{
		MessageBox("Invalid Socket!", "错误", MB_ICONERROR);
		return;
	}
	//建立一个客户端地址
	sockaddr_in serAddr;
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(8888);
	serAddr.sin_addr.S_un.S_addr = inet_addr("127.1.0.1");
	//客户端向服务端请求连接
	if (connect(m_clientSocket, (sockaddr*)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
	{
		MessageBox("Connect Error!!", "错误", MB_ICONERROR);
		closesocket(m_clientSocket);
		return;
	}
	//与服务端连接成功,开始发送消息
	//char sendToServerBuff[1024] = "服务端,我来啦~";
	char sendToServerBuff[1024];
	//初始化stuInfo类对象
	StudentInfo stuInfo;
	stuInfo.SetId(111);
	stuInfo.SetName("abcdefzzzzz");
	stuInfo.SetSex(true);
	send(m_clientSocket, (char*)&stuInfo, sizeof(StudentInfo), 0);
	//send(m_clientSocket, *(&sendToServerBuff), sizeof(sendToServerBuff), 0);
	

	//接收服务端返回的消息
	char buffFromServer[1024];
	int recvDataLen = recv(m_clientSocket, buffFromServer, sizeof(buffFromServer), 0);
	if (recvDataLen > 0)
	{
		MessageBox(buffFromServer);
	}

	closesocket(m_clientSocket);
	WSACleanup();
}

 以上于2022.4.6更新。

(四)Socket+Protobuf数据序列化

(未完待续...)

有任何问题和改进建议可私信或评论留言!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39675394/article/details/123736232

智能推荐

linux devkmem 源码,linux dev/mem dev/kmem实现访问物理/虚拟内存-程序员宅基地

文章浏览阅读451次。dev/mem: 物理内存的全镜像。可以用来访问物理内存。/dev/kmem: kernel看到的虚拟内存的全镜像。可以用来访问kernel的内容。调试嵌入式Linux内核时,可能需要查看某个内核变量的值。/dev/kmem正好提供了访问内核虚拟内存的途径。现在的内核大都默认禁用了/dev/kmem,打开的方法是在 make menuconfig中选中 device drivers --> ..._dev/mem 源码实现

vxe-table 小众但功能齐全的vue表格组件-程序员宅基地

文章浏览阅读7.1k次,点赞2次,收藏19次。vxe-table,一个小众但功能齐全并支持excel操作的vue表格组件_vxe-table

(开发)bable - es6转码-程序员宅基地

文章浏览阅读62次。参考:http://www.ruanyifeng.com/blog/2016/01/babel.htmlBabelBabel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行// 转码前input.map(item => item + 1);// 转码后input.map(function (item) { return item..._让开发环境支持bable

FPGA 视频处理 FIFO 的典型应用_fpga 频分复用 视频-程序员宅基地

文章浏览阅读2.8k次,点赞6次,收藏29次。摘要:FPGA视频处理FIFO的典型应用,视频输入FIFO的作用,视频输出FIFO的作用,视频数据跨时钟域FIFO,视频缩放FIFO的作用_fpga 频分复用 视频

R语言:设置工作路径为当前文件存储路径_r语言设置工作目录到目标文件夹-程序员宅基地

文章浏览阅读575次。【代码】R语言:设置工作路径为当前文件存储路径。_r语言设置工作目录到目标文件夹

background 线性渐变-程序员宅基地

文章浏览阅读452次。格式:background: linear-gradient(direction, color-stop1, color-stop2, ...);<linear-gradient> = linear-gradient([ [ <angle> | to <side-or-corner>] ,]? &l..._background线性渐变

随便推点

【蓝桥杯省赛真题39】python输出最大的数 中小学青少年组蓝桥杯比赛 算法思维python编程省赛真题解析-程序员宅基地

文章浏览阅读1k次,点赞26次,收藏8次。第十三届蓝桥杯青少年组python编程省赛真题一、题目要求(注:input()输入函数的括号中不允许添加任何信息)1、编程实现给定一个正整数N,输出正整数N中各数位最大的那个数字。例如:N=132,则输出3。2、输入输出输入描述:只有一行,输入一个正整数N输出描述:只有一行,输出正整数N中各数位最大的那个数字输入样例:

网络协议的三要素-程序员宅基地

文章浏览阅读2.2k次。一个网络协议主要由以下三个要素组成:1.语法数据与控制信息的结构或格式,包括数据的组织方式、编码方式、信号电平的表示方式等。2.语义即需要发出何种控制信息,完成何种动作,以及做出何种应答,以实现数据交换的协调和差错处理。3.时序即事件实现顺序的详细说明,以实现速率匹配和排序。不完整理解:语法表示长什么样,语义表示能干什么,时序表示排序。转载于:https://blog.51cto.com/98..._网络协议三要素csdn

The Log: What every software engineer should know about real-time data's unifying abstraction-程序员宅基地

文章浏览阅读153次。主要的思想,将所有的系统都可以看作两部分,真正的数据log系统和各种各样的query engine所有的一致性由log系统来保证,其他各种query engine不需要考虑一致性,安全性,只需要不停的从log系统来同步数据,如果数据丢失或crash可以从log系统replay来恢复可以看出kafka系统在linkedin中的重要地位,不光是d..._the log: what every software engineer should know about real-time data's uni

《伟大是熬出来的》冯仑与年轻人闲话人生之一-程序员宅基地

文章浏览阅读746次。伟大是熬出来的  目录  前言  引言 时间熬成伟大:领导者要像狼一样坚忍   第一章 内圣外王——领导者的心态修炼  1. 天纵英才的自信心  2. 上天揽月的企图心  3. 誓不回头的决心  4. 宠辱不惊的平常心  5. 换位思考的同理心  6. 激情四射的热心  第二章 日清日高——领导者的高效能修炼  7. 积极主动,想到做到  8. 合理掌控自己的时间和生命  9. 制定目标,马..._当狼拖着受伤的右腿逃生时,右腿会成为前进的阻碍,它会毫不犹豫撕咬断自己的腿, 以

有源光缆AOC知识百科汇总-程序员宅基地

文章浏览阅读285次。在当今的大数据时代,人们对高速度和高带宽的需求越来越大,迫切希望有一种新型产品来作为高性能计算和数据中心的主要传输媒质,所以有源光缆(AOC)在这种环境下诞生了。有源光缆究竟是什么呢?应用在哪些领域,有什么优势呢?易天将为您解答!有源光缆(Active Optical Cables,简称AOC)是两端装有光收发器件的光纤线缆,主要构成部件分为光路和电路两部分。作为一种高性能计..._aoc 光缆

浏览器代理服务器自动配置脚本设置方法-程序员宅基地

文章浏览阅读2.2k次。在“桌面”上按快捷键“Ctrl+R”,调出“运行”窗口。接着,在“打开”后的输入框中输入“Gpedit.msc”。并按“确定”按钮。如下图 找到“用户配置”下的“Windows设置”下的“Internet Explorer 维护”的“连接”,双击选择“自动浏览器配置”。如下图 选择“自动启动配置”,并在下面的“自动代理URL”中填写相应的PAC文件地址。如下..._設置proxy腳本