Socket API之Windows+base版

278 阅读2分钟

捕获.PNG

  • Server
#include <WinSock2.h>
#include <windows.h>
#include <iostream>
#define _WINSOCK_DEPRECATED_NO_WARNINGS

using namespace std;

// #pragma comment(lib,"ws2_32.lib") 或者 链接器》》输入》》添加附加依赖项
int main()
{
	WORD version = MAKEWORD(2, 2);
	WSADATA data;
	WSAStartup(version, &data);

	// 创建套接字
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	// 绑定监听的网络端口
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	int ret = bind(_sock, (sockaddr *)&_sin, sizeof(sockaddr_in));
	if (SOCKET_ERROR == ret)
	{
		cout << "绑定端口失败" << endl;
		return -1;
	}
	cout << "绑定端口成功" << endl;
	if (SOCKET_ERROR == listen(_sock, 5))
	{
		cout << "监听失败" << endl;
		return -1;
	}
	cout << "监听成功" << endl;

	// 等待接收客户端的连接
	sockaddr_in client_addr = {};
	int nRecvLen = sizeof(sockaddr_in);
	SOCKET _client = INVALID_SOCKET;
	char buf[] = "hello,client";
	cout << strlen(buf) << endl;
	while (true)
	{
		_client = accept(_sock, (sockaddr *)&client_addr, &nRecvLen);
		if (_client == INVALID_SOCKET)
		{
			cout << "无效SOCKET" << endl;
		}
		// 获取客户端的地址
		cout << "新客户端加入,IP:" << inet_ntoa(client_addr.sin_addr) << endl;
		send(_client, buf, strlen(buf) + 1, 0); //带上结束符号
	}
	// 关闭socket
	closesocket(_sock);
	WSACleanup();
	return 0;
}
  • client
#include <WinSock2.h>
#include <windows.h>
#include <iostream>

//#define _WINSOCK_DEPRECATED_NO_WARNINGS
using namespace std;
// #pragma comment(lib,"ws2_32.lib") 或者 链接器》》输入》》添加附加依赖项
int main()
{
	WORD version = MAKEWORD(2, 2);
	WSADATA data;
	WSAStartup(version, &data);
	SOCKET _sock = socket(AF_INET,SOCK_STREAM,0);
	if (SOCKET_ERROR == _sock) 
	{
		cout << "绑定端口成功" << endl;
		return -1;
	}
	sockaddr_in _sin = {}; // 需要连接的服务器地址
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	if (SOCKET_ERROR == connect(_sock, (sockaddr *)&_sin, sizeof(sockaddr_in)))
	{
		cout << "连接失败" << endl;
		return -1;
	}
	cout << "连接服务器成功" << endl;
	
	while (true)
	{
		char cmdBuf[1024] = {};
		scanf("%s", cmdBuf);
		if (0 == strcmp(cmdBuf, "exit"))
		{
			break;
		}
		else
		{
			send(_sock, cmdBuf, strlen(cmdBuf)+1 ,0);
		}
		char recv_buf[1024] = {};
		int nLen = recv(_sock, recv_buf, 1024, 0);
		cout << "nLen = " << nLen << endl;
		if (nLen > 0)
		{
			cout << "接收到数据:" << recv_buf << endl;
		}
	}
	// 关闭socket
	closesocket(_sock);
	WSACleanup();
	return 0;
}

结构体强转二进制流

struct Data{
    int code;
    char  name[256];
}

Data data = {1,"wang"};
send(socket,(const char *)&data,strlen(data)+1,0);

当存在多个业务时,服务端接收到数据,将无法知道将二进制流强转成哪种结构体对象。在网络通信中,一个报文通常是包含包头和包体的,我们可以构造出来一个结构体,它的包头部分对包体进行描述。

eg:

emun CMD
{
    CMD_LOGIN,
    CMD_LOGIN_RESULT,
    CMD_LOGOUT,
    CMD_LOGOUT_RESULT,
    ERROR
}
struct DataHeader
{
    int dataLength;
    int cmd;
}
struct Login : public DataHeader
{
    Login()
    {
    	dataLength = sizeof(Login);
    	cmd = CMD_LOGIN;
    }
    char username[32];
    char password[32];
}

server端recv的时候,分两次读取。第一次读请求头,第二次头部信息读取指定长度请求体。对上面的代码进行简单改造:

  • server
#include <WinSock2.h>
#include <windows.h>
#include <iostream>
#define _WINSOCK_DEPRECATED_NO_WARNINGS

using namespace std;

// #pragma comment(lib,"ws2_32.lib") 或者 链接器》》输入》》添加附加依赖项

enum CMD
{
	CMD_LOGIN,
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,
	CMD_LOGOUT_RESULT,
	CMD_ERROR
};
struct DataHeader
{
	int dataLength;
	int cmd;
};
struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char username[32];
	char password[32];
};

struct LoginResult : public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		result = 0;
	}
	int result;
};

struct Logout : public DataHeader
{
	Logout()
	{
		dataLength = sizeof(Logout);
		cmd = CMD_LOGOUT;
	}
	char username[32];
};

struct LogoutResult : public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_LOGOUT;
		result = 0;
	}
	int result;
};

int main()
{
	WORD version = MAKEWORD(2, 2);
	WSADATA data;
	WSAStartup(version, &data);

	// 创建套接字
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	// 绑定监听的网络端口
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	_sin.sin_addr.S_un.S_addr = ADDR_ANY;

	if (SOCKET_ERROR == bind(_sock, (sockaddr *)&_sin, sizeof(sockaddr_in)))
	{
		cout << "绑定端口失败" << endl;
		return -1;	
	}
	cout << "绑定端口成功" << endl;

	if (SOCKET_ERROR == listen(_sock, 5))
	{
		cout << "监听失败" << endl;
		return -1;
	}
	cout << "监听成功" << endl;
		

	// 等待接收客户端的连接
	sockaddr_in client_addr = {};
	int nRecvLen = sizeof(sockaddr_in);
	cout << "nRecvLen = " << nRecvLen << endl;
	SOCKET _client = -1;
	char recvBuf[1024] = {};

	_client = accept(_sock, (sockaddr *)&client_addr, &nRecvLen);
   if (_client == INVALID_SOCKET)
	{
    	cout << "无效SOCKET" << endl;
	}
	// 获取客户端的地址
	cout << "新客户端加入,IP:" << inet_ntoa(client_addr.sin_addr) << endl;

	while (true) 
	{
		// 用一个buf先接收请求头部的数据
		char szRecv[1024] = {};
		int nLen = recv(_client, (char*)&szRecv, sizeof(DataHeader),0);
		DataHeader* header = (DataHeader*)szRecv;
		
		if (nLen <= 0)
		{
			cout << "客户端已经退出" << endl;
			break;
		}
		// cout << "收到命令" << recvBuf << endl;

		switch (header->cmd)
		{
			case CMD_LOGIN:
			{
				recv(_client, szRecv +sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
				Login* login = (Login*)szRecv;
				cout << "收到数据:CMD_LOGIN,长度:" << login->dataLength << ",username:" << login->username << ",password" << login->password << endl;
				LoginResult loginRes = {};
				send(_client,(const char*)&loginRes,sizeof(loginRes),0);
				break;
			}
			case CMD_LOGOUT:
			{
				
				recv(_client, szRecv +sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
				Logout* logout =  (Logout*)szRecv;
				cout << "收到数据:CMD_LOGOUT,长度:" << logout->dataLength << ",username:" << logout->username  << endl;
				LogoutResult logoutRes = {};
				send(_client, (const char*)&logoutRes, sizeof(logoutRes), 0);
				break;
			}
			default:
				DataHeader header = { 0,CMD_ERROR };
				/*header->cmd = CMD_ERROR;
				header->dataLength = 0;*/
				send(_client, (const char*)&header, sizeof(header), 0);
				break;
		}
	}
	// 关闭socket
	closesocket(_sock);
	WSACleanup();
	return 0;
}
  • client
#include <WinSock2.h>
#include <windows.h>
#include <iostream>

//#define _WINSOCK_DEPRECATED_NO_WARNINGS
using namespace std;
// #pragma comment(lib,"ws2_32.lib") 或者 链接器》》输入》》添加附加依赖项

enum CMD
{
	CMD_LOGIN,
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,
	CMD_LOGOUT_RESULT,
	CMD_ERROR
};
struct DataHeader
{
	int dataLength;
	int cmd;
};
struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char username[32];
	char password[32];
};

struct LoginResult : public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		result = 0;
	}
	int result;
};

struct Logout : public DataHeader
{
	Logout()
	{
		dataLength = sizeof(Logout);
		cmd = CMD_LOGOUT;
	}
	char username[32];
};

struct LogoutResult : public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_LOGOUT;
		result = 0;
	}
	int result;
};

int main()
{
	WORD version = MAKEWORD(2, 2);
	WSADATA data;
	WSAStartup(version, &data);
	SOCKET _sock = socket(AF_INET,SOCK_STREAM,0);
	if (SOCKET_ERROR == _sock) 
	{
		cout << "绑定端口成功" << endl;
		return -1;
	}
	sockaddr_in _sin = {}; // 需要连接的服务器地址
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	if (SOCKET_ERROR == connect(_sock, (sockaddr *)&_sin, sizeof(sockaddr_in)))
	{
		cout << "连接失败" << endl;
		return -1;
	}
	cout << "连接服务器成功" << endl;
	while (true)
	{
		char cmdBuf[1024] = {};
		scanf("%s", cmdBuf);
		if (0 == strcmp(cmdBuf, "login"))
		{
			Login login;
			strcpy(login.username,"scott");
			strcpy(login.password,"123456");
			send(_sock, (const char*)&login, sizeof(login), 0);

			// 接收返回值
			LoginResult loginRes = {};
			recv(_sock,(char*)&loginRes,sizeof(loginRes),0);
			cout << "返回值:" << loginRes.result << endl;
		}
		else if (0 == strcmp(cmdBuf, "logout"))
		{
			Logout logout = {};
			strcpy(logout.username, "scott");
			send(_sock, (const char*)&logout, sizeof(logout), 0);

			// 接收返回值
			LogoutResult logoutRes = {};
			recv(_sock, (char*)&logoutRes, sizeof(logoutRes), 0);
			cout << "返回值:" << logoutRes.result << endl;
		}
		else if (0 == strcmp(cmdBuf, "exit"))
		{
			cout << "停止连接" << endl;
			break;
		}
		else 
		{
			cout << "输入错误指令" << endl;
			break;
		}
	}
	// 关闭socket
	closesocket(_sock);
	WSACleanup();
	
	return 0;
}

base版本的就是这些,但是是阻塞式单收发的,这在实际的开发中肯定不行,我们需要一种不用一直阻塞的方法来收发消息。