C++ 网络编程实践(第一篇)

以下内容来源于B站视频https://www.bilibili.com/video/BV1j4411S7jg?p=18学习资源
仅将学习笔记记录如下供以后查阅及大家分享,欢迎讨论与批评指正

网络通信基础

1. tcp通信的客户端和服务端

建立客户端的步骤

1. 建立通信套接字socket   -- 类似读写文件中的File*
2. 连接服务端        -- fopen 打开文件
3. 向服务端发送数据   --  fwrite
4. 接收服务端数据    -- fread
5. 关闭socket       -- fclose

建立服务端的步骤

1. 创建socket
2. 绑定端口
3. 监听网络端口
4. 等待客户端连接(则塞)
5. 接收客户端数据
6. 向客户端发送数据
7. 关闭socket
2. winsocket创建客户端和服务端
  • 头文件 <WinSock2.h> 放在<windows.h>之前

  • 宏重定义,winsock第一版和第二版的宏重定义了 #define WIN32_LEAN_AND_MEAN

  • 引入库文件 ws2_32.lib

  • 服务端代码(循环接收客户端连接,一旦连接上,向客户端发送一条消息)

    // 启动windows socket网络环境
    WORD ver = MAKEWORD(2, 2);
    WSADATA dat;
    WSAStartup(ver, &dat);
    // 创建socket
    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock == INVALID_SOCKET)
    {
    std::cout << "create sock failed" << std::endl;
    }
    // 绑定端口
    sockaddr_in sin = {};
    sin.sin_family = AF_INET; // IPV4
    sin.sin_port = htons(10086);
    sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// INADDR_ANY
    auto res = bind(sock, (sockaddr*)&sin, sizeof(sin));
    if (res == SOCKET_ERROR)
    {
    std::cout << "bind port error!" << std::endl;
    }
    // 监听端口
    res = listen(sock, 5);
    if (res == SOCKET_ERROR)
    {
    std::cout << "listen port error!" << std::endl;
    }
    std::cout << "wait for client in" << std::endl;
    // 等待客户端连接
    sockaddr_in clientAddr = {};
    int nAddrLen = sizeof(clientAddr);
    SOCKET clientSock = INVALID_SOCKET;
    char msgBuf[] = "hello";
    // 循环等待客户端连接
    while (1)
    {
    clientSock = accept(sock, (sockaddr*)&clientAddr, &nAddrLen);
    if (clientSock == INVALID_SOCKET)
    {
    std::cout << "receive client socket failed!" << std::endl;
    }
    std::cout << "client ip: " << inet_ntoa(clientAddr.sin_addr) << std::endl;
    // 向服务端发送数据
    send(clientSock, msgBuf, strlen(msgBuf) + 1, 0);
    }
    
    // 关闭套接字
    closesocket(sock);
    
    // 清除windows socket环境
    WSACleanup();
    
  • 客户端代码(连接服务器并接收服务端发送的一条消息)

    // 启动windows socket网络环境
    WORD ver = MAKEWORD(2, 2);
    WSADATA dat;
    WSAStartup(ver, &dat);
    
    // 创建socket
    SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET)
    {
    std::cout << "create socket failed" << std::endl;
    }
    // 连接服务器
    sockaddr_in sin = {};
    sin.sin_family = AF_INET;
    sin.sin_port = htons(10086);
    sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    auto res = connect(sock, (sockaddr*)&sin, sizeof(sockaddr_in));
    if (res == SOCKET_ERROR)
    {
    std::cout << "connect server failed" << std::endl;
    }
    // 接收服务器发送的消息
    char buf[1024] = { 0 };
    int len = recv(sock, buf, 1024, 0);
    if (len > 0)
    {
    std::cout << "data from server is" << buf << std::endl;
    }
    
    // 关闭套接字
    closesocket(sock);
    WSACleanup();
    std::cin.get();
    
  • 服务端改造1(只等待一个客户端接入,循环等待客户端发送数据,处理数据并响应数据给客户端)

    	// 启动windows socket网络环境
    	WORD ver = MAKEWORD(2, 2);
    	WSADATA dat;
    	WSAStartup(ver, &dat);
    	// 创建socket
    	SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    	if (sock == INVALID_SOCKET)
    	{
    		std::cout << "create sock failed" << std::endl;
    	}
    	// 绑定端口
    	sockaddr_in sin = {};
    	sin.sin_family = AF_INET; // IPV4
    	sin.sin_port = htons(10086);
    	sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// INADDR_ANY
    	auto res = bind(sock, (sockaddr*)&sin, sizeof(sin));
    	if (res == SOCKET_ERROR)
    	{
    		std::cout << "bind port error!" << std::endl;
    	}
    	// 监听端口
    	res = listen(sock, 5);
    	if (res == SOCKET_ERROR)
    	{
    		std::cout << "listen port error!" << std::endl;
    	}
    	std::cout << "wait for client in" << std::endl;
    	// 等待客户端连接
    	sockaddr_in clientAddr = {};
    	int nAddrLen = sizeof(clientAddr);
    	SOCKET clientSock = INVALID_SOCKET;
    	char msgBuf[] = "hello";
    	// 等待客户端连接
    	clientSock = accept(sock, (sockaddr*)&clientAddr, &nAddrLen);
    	if (clientSock == INVALID_SOCKET)
    	{
    		std::cout << "receive client socket failed!" << std::endl;
    	} 
    	std::cout << "client ip: " << inet_ntoa(clientAddr.sin_addr) << std::endl;
    	while (1)
    	{
    		int len = recv(clientSock, msgBuf, 1024, 0);
    		if (len <= 0)
    		{
    			std::cout << "receive from client failed" << std::endl;
    			break;
    		}
    		std::cout << "receive msg is : " << msgBuf << std::endl;
    		std::string resStr = "";
    		Handle(msgBuf, resStr);
    		// 向服务端发送数据
    		send(clientSock, resStr.c_str(), resStr.length() + 1, 0);
    	}
    
    	// 关闭套接字
    	closesocket(sock);
    
    	// 清除windows socket环境
    	WSACleanup();
    	std::cout << "session end" << std::endl;
    	std::cin.get();
    
  • 客户端改造1(连接成功后,循环输入命令发送给服务端,并接收服务端返回的消息并处理,即打印)

    	// 启动windows socket网络环境
    	WORD ver = MAKEWORD(2, 2);
    	WSADATA dat;
    	WSAStartup(ver, &dat);
    
    	// 创建socket
    	SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
    	if (sock == INVALID_SOCKET)
    	{
    		std::cout << "create socket failed" << std::endl;
    	}
    	// 连接服务器
    	sockaddr_in sin = {};
    	sin.sin_family = AF_INET;
    	sin.sin_port = htons(10086);
    	sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    	auto res = connect(sock, (sockaddr*)&sin, sizeof(sockaddr_in));
    	if (res == SOCKET_ERROR)
    	{
    		std::cout << "connect server failed" << std::endl;
    	}
    	std::cout << "connect server succeed" << std::endl;
    
    	// 输入命令发送给服务器并接收返回的消息
    	char buf[1024] = { 0 };
    	while (true)
    	{
    		std::cout << "请输入命令:" << std::endl;
    		std::cin >> buf;
    		if (strcmp(buf, "exit") == 0)
    		{
    			std::cout << "receive end command" << std::endl;
    			break;
    		}
    		send(sock, buf, strlen(buf) + 1, 0);
    
    		// 接收响应
    		int len = recv(sock, buf, 1024, 0);
    		if (len > 0)
    		{
    			std::cout << "data from server is " << buf << std::endl;
    		}
    
    	}
    	// 关闭套接字
    	closesocket(sock);
    	WSACleanup();
    	std::cout << "session end" << std::endl;
    	std::cin.get();
    

    结构化消息数据

  • 结构体 struct(一次性将多个信息返回给客户端)

    typedef struct DataPackage_STRU
    {
    	int age;
    	char name[32];
    } DataPackage;
    
    // 单纯的结构作为报文时,客户端发送异常数据,服务器也会进行处理,未进行过滤
    
  • 网络数据报文

    1. 一个报文包含包头和包体
    2. 包头: 描述消息包的大小及数据的作用以及发送者,接收者等
    3. 包体: 实际使用的数据buf
    
    // 服务端接收两次消息,先接收消息头,判断消息是否有效,有效的话继续接收消息体
    // 服务端返回消息时,也是两次发送,先发送消息头,再发送消息体
    
    // 定义两种有效命令,每种命令对应相应的消息体
    enum CmdIndex
    {
    	LOGIN_IN,
    	LOGIN_OUT,
    	LOGIN_ERROR
    };
    
    // LOGIN_IN命令对应的消息体结构
    typedef struct LoginIn_STRU
    {
    	char userName[32];
    	char pwd[32];
    } LoginIn;
    
    typedef struct LoginInRes_STRU
    {
    	int res;
    } LoginInRes;
    
    // LOGIN_OUT命令对应的消息体结构
    typedef struct LoginOut_STRU
    {
    	char userName[32];
    } LoginOut;
    
    typedef struct LoginOutRes_STRU
    {
    	int res;
    } LoginOutRes;
    
    // 消息头
    typedef struct DataHeader_STRU
    {
    	unsigned short dataLen;
    	unsigned short cmdId;
    } DataHeader;
    
    • 服务端报文改造代码

      	// 启动windows socket网络环境
      	WORD ver = MAKEWORD(2, 2);
      	WSADATA dat;
      	WSAStartup(ver, &dat);
      	// 创建socket
      	SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
      	if (sock == INVALID_SOCKET)
      	{
      		std::cout << "create sock failed" << std::endl;
      	}
      	// 绑定端口
      	sockaddr_in sin = {};
      	sin.sin_family = AF_INET; // IPV4
      	sin.sin_port = htons(10086);
      	sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// INADDR_ANY
      	auto res = bind(sock, (sockaddr*)&sin, sizeof(sin));
      	if (res == SOCKET_ERROR)
      	{
      		std::cout << "bind port error!" << std::endl;
      	}
      	// 监听端口
      	res = listen(sock, 5);
      	if (res == SOCKET_ERROR)
      	{
      		std::cout << "listen port error!" << std::endl;
      	}
      	std::cout << "wait for client in" << std::endl;
      	// 等待客户端连接
      	sockaddr_in clientAddr = {};
      	int nAddrLen = sizeof(clientAddr);
      	SOCKET clientSock = INVALID_SOCKET;
      	char msgBuf[] = "hello";
      	// 等待客户端连接
      	clientSock = accept(sock, (sockaddr*)&clientAddr, &nAddrLen);
      	if (clientSock == INVALID_SOCKET)
      	{
      		std::cout << "receive client socket failed!" << std::endl;
      	} 
      	std::cout << "client ip: " << inet_ntoa(clientAddr.sin_addr) << std::endl;
      	DataPackage dataObj;
      	while (1)
      	{
      		// 先接收消息头
      		DataHeader header;
      		int len = recv(clientSock, (char*)&header, sizeof(header), 0);
      		if (len <= 0)
      		{
      			std::cout << "receive from client failed" << std::endl;
      			break;
      		}
      		std::cout << "receive msg header is : " << header.cmdId << std::endl;
      		switch (header.cmdId)
      		{
      		case LOGIN_IN:
      		{
      			// 接收消息体
      			LoginIn data;
      			recv(clientSock, (char*)&data, sizeof(data), 0);
      
      			// 返回消息头
      			send(clientSock, (char*)&header, sizeof(header), 0);
      			// 返回消息体
      			LoginInRes resData;
      			resData.res = 1;
      			send(clientSock, (char*)&resData, sizeof(resData), 0);
      		}
      			break;
      		case LOGIN_OUT:
      		{
      			// 接收消息体
      			LoginOut data;
      			recv(clientSock, (char*)&data, sizeof(data), 0);
      			// ... handle
      			// 返回消息头
      			send(clientSock, (char*)&header, sizeof(header), 0);
      			// 返回消息体
      			LoginOutRes resData;
      			resData.res = 1;
      			send(clientSock, (char*)&resData, sizeof(resData), 0);
      		}
      		default:
      			// 返回消息头
      			header.cmdId = LOGIN_ERROR;
      			header.dataLen = 0;
      			send(clientSock, (char*)&header, sizeof(header), 0);
      			break;
      		}
      	}
      
      	// 关闭套接字
      	closesocket(sock);
      
      	// 清除windows socket环境
      	WSACleanup();
      	std::cout << "session end" << std::endl;
      	std::cin.get();
      
    • 客户端报文改造代码(使用同一套报文定义)

      	// 启动windows socket网络环境
      	WORD ver = MAKEWORD(2, 2);
      	WSADATA dat;
      	WSAStartup(ver, &dat);
      
      	// 创建socket
      	SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
      	if (sock == INVALID_SOCKET)
      	{
      		std::cout << "create socket failed" << std::endl;
      	}
      	// 连接服务器
      	sockaddr_in sin = {};
      	sin.sin_family = AF_INET;
      	sin.sin_port = htons(10086);
      	sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
      	auto res = connect(sock, (sockaddr*)&sin, sizeof(sockaddr_in));
      	if (res == SOCKET_ERROR)
      	{
      		std::cout << "connect server failed" << std::endl;
      	}
      	std::cout << "connect server succeed" << std::endl;
      
      	// 输入命令发送给服务器并接收返回的消息
      	char buf[1024] = { 0 };
      	DataPackage data;
      	while (true)
      	{
      		std::cout << "请输入命令:" << std::endl;
      		std::cin >> buf;
      		if (strcmp(buf, "exit") == 0)
      		{
      			std::cout << "receive end command" << std::endl;
      			break;
      		}
      		else if(strcmp(buf, "loginin") == 0)
      		{
      			LoginIn data = { "zhanghu", "mima" };
      			DataHeader header;
      			header.cmdId = LOGIN_IN;
      			header.dataLen = sizeof(LoginIn);
      			send(sock, (char*)&header, sizeof(DataHeader), 0);
      			send(sock, (char*)&data, sizeof(LoginIn), 0);
      
      			// 接收数据
      			DataHeader resHeader = {};
      			LoginInRes resData = {};
      			recv(sock, (char*)&resHeader, sizeof(DataHeader), 0);
      			recv(sock, (char*)&resData, sizeof(LoginInRes), 0);
      			std::cout << resData.res << std::endl;
      		}
      		else if(strcmp(buf, "loginout") == 0)
      		{
      			LoginOut data = { "zhanghu"};
      			DataHeader header;
      			header.cmdId = LOGIN_OUT;
      			header.dataLen = sizeof(LoginOut);
      			send(sock, (char*)&header, sizeof(DataHeader), 0);
      			send(sock, (char*)&data, sizeof(LoginOut), 0);
      
      			// 接收数据
      			DataHeader resHeader = {};
      			LoginOutRes resData = {};
      			recv(sock, (char*)&resHeader, sizeof(DataHeader), 0);
      			recv(sock, (char*)&resData, sizeof(LoginOutRes), 0);
      			std::cout << resData.res << std::endl;
      		}
      		else
      		{
      			std::cout << "not support cmd, please reentry:" << std::endl;
      		}
      	}
      	// 关闭套接字
      	closesocket(sock);
      	WSACleanup();
      	std::cout << "session end" << std::endl;
      	std::cin.get();
      
  • 合并包头和包体,一次发送和接收

    // 使用继承包头的方法合并
    // 客户端只发送一次,服务端需要分两次接收,先接收包头,再做偏移地址接收包体
    
    // 服务端
    	// 启动windows socket网络环境
    	WORD ver = MAKEWORD(2, 2);
    	WSADATA dat;
    	WSAStartup(ver, &dat);
    	// 创建socket
    	SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    	if (sock == INVALID_SOCKET)
    	{
    		std::cout << "create sock failed" << std::endl;
    	}
    	// 绑定端口
    	sockaddr_in sin = {};
    	sin.sin_family = AF_INET; // IPV4
    	sin.sin_port = htons(10086);
    	sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// INADDR_ANY
    	auto res = bind(sock, (sockaddr*)&sin, sizeof(sin));
    	if (res == SOCKET_ERROR)
    	{
    		std::cout << "bind port error!" << std::endl;
    	}
    	// 监听端口
    	res = listen(sock, 5);
    	if (res == SOCKET_ERROR)
    	{
    		std::cout << "listen port error!" << std::endl;
    	}
    	std::cout << "wait for client in" << std::endl;
    	// 等待客户端连接
    	sockaddr_in clientAddr = {};
    	int nAddrLen = sizeof(clientAddr);
    	SOCKET clientSock = INVALID_SOCKET;
    	char msgBuf[] = "hello";
    	// 等待客户端连接
    	clientSock = accept(sock, (sockaddr*)&clientAddr, &nAddrLen);
    	if (clientSock == INVALID_SOCKET)
    	{
    		std::cout << "receive client socket failed!" << std::endl;
    	} 
    	std::cout << "client ip: " << inet_ntoa(clientAddr.sin_addr) << std::endl;
    	while (1)
    	{
    		// 先接收消息头
    		DataHeader header;
    		int len = recv(clientSock, (char*)&header, sizeof(header), 0);
    		if (len <= 0)
    		{
    			std::cout << "receive from client failed" << std::endl;
    			break;
    		}
    		std::cout << "receive msg header is : " << header.cmdId << std::endl;
    		switch (header.cmdId)
    		{
    		case LOGIN_IN:
    		{
    			// 接收消息体
    			LoginIn data;
    			recv(clientSock, (char*)&data + sizeof(DataHeader), sizeof(data) - sizeof(DataHeader), 0);
    
    			// 返回消息
    			LoginInRes resData;
    			send(clientSock, (char*)&resData, sizeof(resData), 0);
    		}
    			break;
    		case LOGIN_OUT:
    		{
    			// 接收消息体
    			LoginOut data;
    			recv(clientSock, (char*)&data + sizeof(DataHeader), sizeof(data) - sizeof(DataHeader), 0);
    			// ... handle
    			// 返回消息
    			LoginOutRes resData;
    			send(clientSock, (char*)&resData, sizeof(resData), 0);
    		}
    		default:
    			// 返回消息头
    			header.cmdId = LOGIN_ERROR;
    			header.dataLen = 0;
    			send(clientSock, (char*)&header, sizeof(header), 0);
    			break;
    		}
    	}
    
    	// 关闭套接字
    	closesocket(sock);
    
    	// 清除windows socket环境
    	WSACleanup();
    	std::cout << "session end" << std::endl;
    	std::cin.get();
    
    // 客户端
    	// 启动windows socket网络环境
    	WORD ver = MAKEWORD(2, 2);
    	WSADATA dat;
    	WSAStartup(ver, &dat);
    
    	// 创建socket
    	SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
    	if (sock == INVALID_SOCKET)
    	{
    		std::cout << "create socket failed" << std::endl;
    	}
    	// 连接服务器
    	sockaddr_in sin = {};
    	sin.sin_family = AF_INET;
    	sin.sin_port = htons(10086);
    	sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    	auto res = connect(sock, (sockaddr*)&sin, sizeof(sockaddr_in));
    	if (res == SOCKET_ERROR)
    	{
    		std::cout << "connect server failed" << std::endl;
    	}
    	std::cout << "connect server succeed" << std::endl;
    
    	// 输入命令发送给服务器并接收返回的消息
    	char buf[1024] = { 0 };
    	DataPackage data;
    	while (true)
    	{
    		std::cout << "请输入命令:" << std::endl;
    		std::cin >> buf;
    		if (strcmp(buf, "exit") == 0)
    		{
    			std::cout << "receive end command" << std::endl;
    			break;
    		}
    		else if(strcmp(buf, "loginin") == 0)
    		{
    			LoginIn data;
    			std::string userName = "zhangsan";
    			memcpy(data.userName, userName.c_str(), userName.length() + 1);
    			std::string pwd = "123456";
    			memcpy(data.pwd, pwd.c_str(), pwd.length() + 1);
    			send(sock, (char*)&data, sizeof(LoginIn), 0);
    
    			// 接收数据
    			LoginInRes resData = {};
    			recv(sock, (char*)&resData, sizeof(LoginInRes), 0);
    			std::cout << "res cmd is "<< resData.cmdId << " res is " << resData.res << std::endl;
    		}
    		else if(strcmp(buf, "loginout") == 0)
    		{
    			LoginOut data;
    			std::string userName = "zhangsan";
    			memcpy(data.userName, userName.c_str(), userName.length() + 1);
    			send(sock, (char*)&data, sizeof(LoginOut), 0);
    
    			// 接收数据
    			LoginOutRes resData = {};
    			recv(sock, (char*)&resData, sizeof(LoginOutRes), 0);
    			std::cout << "res cmd is " << resData.cmdId << " res is " << resData.res << std::endl;
    		}
    		else
    		{
    			std::cout << "not support cmd, please reentry:" << std::endl;
    		}
    	}
    	// 关闭套接字
    	closesocket(sock);
    	WSACleanup();
    	std::cout << "session end" << std::endl;
    	std::cin.get();
    	return 0;
    
  • 网络消息长度的问题

    1. 固长数据/变长数据
    2. 粘包/拆包
    3. 分包/组包
    
3. socket的select网络模型(处理多客户端场景)
// select的原理: 将多个套接字放在一个集合里,统一检查集合中套接字的状态(可读,可写,异常),调用、select后,会更新集合中套接字的状态,如果可读,即可执行fdRead中的socket,依次避免多个客户端时阻塞,当没有数据写入或连接时,select会阻塞,可设置等待时间timeout(s)

// 伯克利 socket
// 参数1 nfds: 在windows下无作用,整数值,fd_set中所有socket描述符的范围,而不是数量
// 即描述符最大值 + 1
// 参数2 readfds: 需要查询的套接字集合
// 参数3 writefds:
// 参数4 exceptfds: 
// 参数5 timeout:
fd_set fdRead;
fd_set fdWrite;
fd_set fdExp;
FD_ZERO(&fdRead); // 清空集合
FD_ZERO(&fdWrite); // 清空集合
FD_ZERO(&fdExp); // 清空集合
FD_SET(sock, &fdRead);
FD_SET(sock, &fdWrite);
FD_SET(sock, &fdExp);
select(sock+1, &fdRead, &fdWrite, &fdExp, NULL)

报文结构定义

// 定义两种有效命令,每种命令对应相应的消息体
enum CmdIndex
{
	LOGIN_IN,
	LOGIN_OUT,
	LOGIN_IN_RES,
	LOGIN_OUT_RES,
	NEW_JOIN,
	LOGIN_ERROR
};

// 消息头
struct DataHeader
{
	unsigned short dataLen;
	unsigned short cmdId;
};

struct LoginNew : public DataHeader
{
	LoginNew()
	{
		dataLen = sizeof(LoginNew);
		cmdId = NEW_JOIN;
	}
	int socket;
};

// LOGIN_IN命令对应的消息体结构
struct LoginIn : public DataHeader
{
	LoginIn()
	{
		dataLen = sizeof(LoginIn);
		cmdId = LOGIN_IN;
	}
	char userName[32];
	char pwd[32];
};

struct LoginInRes : public DataHeader
{
	LoginInRes()
	{
		dataLen = sizeof(LoginInRes);
		cmdId = LOGIN_IN_RES;
	}
	int res{ 1 };
};

// LOGIN_OUT命令对应的消息体结构
struct LoginOut : public DataHeader
{
	LoginOut()
	{
		dataLen = sizeof(LoginOut);
		cmdId = LOGIN_OUT;
	}
	char userName[32];
};

struct LoginOutRes : public DataHeader
{
	LoginOutRes()
	{
		dataLen = sizeof(LoginOutRes);
		cmdId = LOGIN_OUT_RES;
	}
	int res{ 1 };
};

支持多客户端连接服务端改造代码(利用select模型实现多客户端连接,设置time_val,使select不阻塞,当有新客户端接入时,向已存在的客户端广播消息,消息处理过程:现获取消息头,再解析消息体,并返回消息)

	// 启动windows socket网络环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
	// 创建socket
	SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sock == INVALID_SOCKET)
	{
		std::cout << "create sock failed" << std::endl;
	}
	// 绑定端口
	sockaddr_in sin = {};
	sin.sin_family = AF_INET; // IPV4
	sin.sin_port = htons(10086);
	sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// INADDR_ANY
	auto res = bind(sock, (sockaddr*)&sin, sizeof(sin));
	if (res == SOCKET_ERROR)
	{
		std::cout << "bind port error!" << std::endl;
	}
	// 监听端口
	res = listen(sock, 5);
	if (res == SOCKET_ERROR)
	{
		std::cout << "listen port error!" << std::endl;
	}


	while (1)
	{
		fd_set fdRead;
		fd_set fdWrite;
		fd_set fdExp;
		FD_ZERO(&fdRead); // 清空集合
		FD_ZERO(&fdWrite); // 清空集合
		FD_ZERO(&fdExp); // 清空集合
		FD_SET(sock, &fdRead);
		FD_SET(sock, &fdWrite);
		FD_SET(sock, &fdExp);

		// 把新加入的客户端加入可读的集合
		for (int i = 0; i < vecSockets.size(); ++i)
		{
			FD_SET(vecSockets[i], &fdRead);
		}

		time_val 
		int ret = select(sock + 1, &fdRead, &fdWrite, &fdExp, NULL);
		if(ret < 0)
		{ 
			break;
		}

		// 为了accept不发生阻塞, 现在fd_set中判断sock的状态是否可读
		if (FD_ISSET(sock, &fdRead))
		{
			FD_CLR(sock, &fdRead);
			std::cout << "wait for client in" << std::endl;
			// 等待客户端连接
			sockaddr_in clientAddr = {};
			int nAddrLen = sizeof(clientAddr);
			SOCKET clientSock = INVALID_SOCKET;
			char msgBuf[] = "hello";
			// 等待客户端连接
			clientSock = accept(sock, (sockaddr*)&clientAddr, &nAddrLen);
			if (clientSock == INVALID_SOCKET)
			{
				std::cout << "receive client socket failed!" << std::endl;
			}
			std::cout << "client ip: " << inet_ntoa(clientAddr.sin_addr) << std::endl;
			vecSockets.push_back(clientSock);
		}

		for (int i = 0; i < fdRead.fd_count; i++)
		{
			if (-1 == HandleMessage(fdRead.fd_array[i]))
			{
				auto itr = std::find(vecSockets.begin(), vecSockets.end(), fdRead.fd_array[i]);
				if (itr != vecSockets.end())
				{
					vecSockets.erase(itr);
				}
			}
		}

	}

	// 关闭套接字
	for (int i = 0; i < vecSockets.size(); ++i)
	{
		closesocket(vecSockets[i]);
	}

	closesocket(sock);

	// 清除windows socket环境
	WSACleanup();
	std::cout << "session end" << std::endl;
	std::cin.get();

目前的客户端只能输入命令后,发送一条消息,并接收一条响应消息,无法接收服务器主动推送的消息,

利用select模型实现客户端接收服务器推送消息

	// 启动windows socket网络环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);

	// 创建socket
	SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock == INVALID_SOCKET)
	{
		std::cout << "create socket failed" << std::endl;
	}
	// 连接服务器
	sockaddr_in sin = {};
	sin.sin_family = AF_INET;
	sin.sin_port = htons(10086);
	sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	auto res = connect(sock, (sockaddr*)&sin, sizeof(sockaddr_in));
	if (res == SOCKET_ERROR)
	{
		std::cout << "connect server failed" << std::endl;
	}
	std::cout << "connect server succeed" << std::endl;

	while (true)
	{
		fd_set fdRead;
		FD_ZERO(&fdRead);
		FD_SET(sock, &fdRead);
		timeval t = { 1, 0 };
		int ret = select(sock + 1, &fdRead, 0, 0, &t);
		if (ret < 0)
		{
			std::cout << "select 任务结束";
			break;
		}
		if (FD_ISSET(sock, &fdRead))
		{
			FD_CLR(sock, &fdRead);
			if (-1 == HandleMessage(sock))
			{
				std::cout << "ending" << std::endl;
				break; 
			}
		}
		std::cout << "spare time,you can do something" << std::endl;
	}
	// 关闭套接字
	closesocket(sock);
	WSACleanup();
	std::cout << "session end" << std::endl;
	std::cin.get();

客户端封装

将客户端封装成库

message

#ifndef COMMON_MESSAGE_DEF_
#define COMMON_MESSAGE_DEF_

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


// 定义两种有效命令,每种命令对应相应的消息体
enum CmdIndex
{
   
	LOGIN_IN,
	LOGIN_OUT
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值