socket结构化传输网络数据(简易版)

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

Client

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<WinSock2.h>
#include<Windows.h>
#include<iostream>

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

//传输的数据结构,最简单的数据包
//每个函数的类型必须一样,而且在客户端和服务端传输和接收顺序一致,也就是内存对齐
//long类型的在64位编译器下就是64位,而32位编译器下就是32位,
//所以需要考虑平台和系统,关注是否内存对齐

struct DataPackage {
    
    
	int age;
	char name[32];
};

int main()
{
    
    
	//启动Windows socket 2.x环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
	//1.建立一个socket
	SOCKET _sock= socket(AF_INET,SOCK_STREAM,0);
	//多做判断才能创建一个健壮的代码
	if (INVALID_SOCKET == _sock)
	{
    
    
		printf("ERROR,建立socket失败\n");
	}
	else
	{
    
    
		printf("建立socket成功\n");
	}
	//2.链接服务器connet
	//这样写快速初始化结构体,要不这样就是乱的
	sockaddr_in _sin = {
    
    };
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	//error C4996: 'inet_addr': Use inet_pton() or InetPton() instead or define _WINSOCK_DEPRECATED_NO_WARNINGS to disable deprecated API warnings
	_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	//sizeof()传入类型更安全,不应该是变量,防止传入的是一个指针
	int ret=connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in));
	if (SOCKET_ERROR == ret)
	{
    
    
		printf("ERROR,链接服务器失败\n");

	}
	else
	{
    
    
		printf("连接服务器成功\n");
	}

	
	while (true)
	{
    
    
		//3.输入请求命令
		//要跟服务器端字符串缓存匹配,否则容易溢出
		char cmdBuf[128] = {
    
    };
		//4.处理请求
		//scanf会抛出异常c++下建议使用cin
		//scanf_s("%s", cmdBuf);
		std::cin >> cmdBuf;
		if (0 == strcmp(cmdBuf, "exit"))
		{
    
    
			printf("收到exit,任务结束.\n");
			break;
		}
		else
		{
    
    
			//5.向服务器发送请求命令
			send(_sock, cmdBuf, strlen(cmdBuf) + 1, 0);
		}

		//6.接收服务器信息
		char recvBuf[128] = {
    
    };
		//接收类型,数据缓冲区(char型)
		//recv接收到数据的时候会返回一个数据长度
		int nlen = recv(_sock, recvBuf, 128, 0);
		if (nlen > 0)
		{
    
    
			//强行转换,这样数据不安全,先简单示范接收功能
			DataPackage* dp = (DataPackage*)recvBuf;
			printf("接收到数据:年龄=%d,姓名=%s \n", dp->age,dp->name);
		}

	}

	
	//4.关闭套件字closesocket
	closesocket(_sock);
	//清除Windows socket环境
	WSACleanup();
	getchar();
	return 0;
}

server

// 加这个宏定义也可以规避sock2和window.h的重编译问题
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS

//winsock2一定要在windows前,否则会有宏定义重编译问题。sock2是新库,windows是老库。
#include<WinSock2.h>
#include<Windows.h>
#include<iostream>

//加入静态链接库,否则会出现问题:无法解析的外部符号 __imp__WSAStartup@8
//也可以在属性页、连接器、输入、附加依赖项中输入ws_32.lib
#pragma comment(lib,"ws2_32.lib")

//注意数据顺序
struct DataPackage {
    
    
	int age;
	char name[32];
};

int main()
{
    
    

	//版本号
	WORD ver = MAKEWORD(2, 2);
	//数据指针
	WSADATA dat;
	//windows下socket的启动函数,启动socket环境
	WSAStartup(ver, &dat);

	//开始编写网络环境
	//1.建立一个socket。套接字inet6为最新,数据类型:数据流,网络类型tcp,udp
	//定义返回类型,用来储存socket数据(uint型)
	SOCKET _sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

	//2 bind 绑定用于接受客户端连接的网络端口
	//sock地址的结构体
	sockaddr_in _sin = {
    
    };
	//网络类型
	_sin.sin_family = AF_INET;
	//端口号htons(host to net unsigned short)主机到网络的字节序的转换.用来寻找服务程序
	_sin.sin_port = htons(4567);
	//定义IP地址,主机不只一个IP地址。cmd中ipconfig可以查看。
	//如果纯内网127就可以。还可以屏蔽外网的访问。
	//S_un是一个联合体。
	//INADDR_ANY不限定网路地址。
	//_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	_sin.sin_addr.S_un.S_addr = INADDR_ANY;
	//sock名,地址,地址长度。强制转换地址类型sockaddr结构体里的数据类型不利于我们直接填写,
	//addr_in的结构体与普通addr结构体相同,而且有常见的数据类型方便填写。
	if (SOCKET_ERROR==bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))//判断是否绑定成功
	{
    
    
		//如果端口号被别的程序占用,就是绑定失败
		printf("ERROR,绑定用于接受客户端连接的网络端口失败\n");
	}
	else
	{
    
    
		printf("绑定网络端口成功\n");
	}

	//3.listen监听网络端口
	//最大等待5人来连接
	if (SOCKET_ERROR == listen(_sock, 5))
	{
    
    
		printf("ERROR,监听失败\n");
	}
	else
	{
    
    
		printf("监听成功\n");
	}

	//4 accept 等待客户端连接
	sockaddr_in clientAddr = {
    
    };
	//定义client返回的socket数据的长度
	int nAddrlen = sizeof(sockaddr_in);
	//定义一个无效的sock地址用来进行判断是否接收到客服端
	SOCKET _clientSock = INVALID_ATOM;
	
	//如果固定长读就在在循环外计算出发送数据的长度,节省计算时间
	//int bufSize = sizeof(msgBuf);
	//循环重复执行

	//新用户加入
	//返回一个客户端的socket网络地址
	_clientSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrlen);
	//判断
	if (INVALID_SOCKET == _clientSock)
	{
    
    
		printf("错误,接收到无效客户端SOCKET\n");
	}
	else {
    
    
		//inet_ntoa 把客户端转换成可读的ip地址
		//inet_ntoa 过时了,需要定义一个宏define _WINSOCK_DEPRECATED_NO_WARNINGS
		printf("新客户端加入:IP=%s \n", inet_ntoa(clientAddr.sin_addr));
	}

	//接收缓冲区
	char _recvBuf[128] = {
    
    };
	while (true)
	{
    
    
		//5.接收客户端数据
		int nLen = recv(_clientSock, _recvBuf, 128, 0);
		if (nLen <= 0)
		{
    
    
			printf("客户端已经退出,任务结束。");
			break;
		}

		//6.处理请求
		if (0 == strcmp(_recvBuf, "getInfo"))
		{
    
    
			//定义接收的结构体对象
			DataPackage dp = {
    
    80,"名叫李逵"};
			//传输的内容类型必须是const char* 所有强制转换,而char其实就是一个取常量字符的指针所以&dp
			//sizeof datapackage = int 4+ char[32] =36
			send(_clientSock,(const char*)&dp, sizeof(DataPackage), 0);
		}
		else if (0 == strcmp(_recvBuf, "getAge"))
		{
    
    
			//
			char msgBuf[] = "芳龄十八";
			send(_clientSock, msgBuf, strlen(msgBuf) + 1, 0);
		}
		else 
		{
    
    
			//定义发送数据,
			char msgBuf[] = "There is Server!";
			//7.send 向客户端发送数据
			//+1把字符串结尾\0参数也加入长度,把\0页发送出去,0是默认flag参数
			send(_clientSock, msgBuf, strlen(msgBuf) + 1, 0);
		}
	}
	//8. 关闭自身的套节字
	closesocket(_sock);
	//结束编写网路环境

	//结束socket
	WSACleanup();
	printf("任务结束。");
	getchar();
	return 0;
}

启动双端,输入getInfo,发现获得信息准确,但是输入不同字符命名,却得到乱码信息。
因为我们在发送一个不支持的命令的时候,依旧使用的是字符串,而不是发送DataPackage,
而在服务端我们却全部使用DATAPackage来处理的数据。
所以当(DataPackage*)强制转化的时候,发送的来的是一个字符转的内存结构,并不是我们想要的Datapackage的数据结构,所以会转化错误。
下面我们需要进行创建完整的网络数据报文(包头+包体),来避免这个问题。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/guanxunmeng8928/article/details/109390369