zoukankan      html  css  js  c++  java
  • C/C++ 开发一款多人聊天室

    基于UDP协议实现

    服务端代码:

    // Test_Console.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    #include <iostream>
    #include <WinSock2.h>
    #include <WS2tcpip.h>
    #include <Windows.h>
    #include <thread>
    #include <cstdio>
    
    using namespace std;
    
    #pragma region 全局变量
    
    SOCKET server;					// 服务端套接字
    sockaddr_in sai_server;			// 服务端信息(ip、端口)
    
    // 消息格式
    struct umsg {
    	int type;				// 协议(1:加入 2:退出 3:发消息)
    	char name[64];			// 用户名字
    	char text[512];			// 文本信息
    };
    
    // 客户端链表
    typedef struct ucnode {
    	sockaddr_in addr;		// 客户端的地址和端口号
    	umsg msg;				// 客户端传来的消息
    	ucnode* next;
    } *ucnode_t;
    
    #pragma endregion
    
    
    #pragma region 依赖函数
    
    // 链表插入数据
    ucnode* insertNode(ucnode* head, sockaddr_in addr,umsg msg) {
    	ucnode* newNode = new ucnode();
    	newNode->addr = addr;
    	newNode->msg = msg;
    	ucnode* p = head;
    	if (p == nullptr) {
    		head = newNode;
    	}
    	else {
    		while (p->next != nullptr) {
    			p = p->next;
    		}
    		p->next = newNode;
    	}
    	return head;
    }
    
    // 链表删除数据
    ucnode* deleteNode(ucnode* head, umsg msg) {
    	ucnode* p = head;
    	if (p == nullptr) {
    		return head;
    	}
    	if (strcmp(p->msg.name, msg.name) == 0){
    		head = p->next;
    		delete p;
    		return head;
    	}
    	while (p->next != nullptr && strcmp(p->next->msg.name, msg.name) != 0) {
    		p = p->next;
    	}
    	if (p->next == nullptr) {
    		return head;
    	}
    	ucnode* deleteNode = p->next;
    	p->next = deleteNode->next;
    	delete deleteNode;
    	return head;
    }
    
    #pragma endregion
    
    int main()
    {
    	cout << "我是服务端" << endl;
    
    	// 初始化 WSA ,激活 socket
    	WSADATA wsaData;
    	if (WSAStartup(
    		MAKEWORD(2, 2), 		// 规定 socket 版本为 2.2
    		&wsaData				// 接收关于套接字的更多信息
    		)) {
    		cout << "WSAStartup failed : " << GetLastError() << endl;
    	}
    
    	// 初始化 socket、服务器信息
    	server = socket(
    		AF_INET, 		// IPV4
    		SOCK_DGRAM,		// UDP
    		0				// 不指定协议
    		);
    	sai_server.sin_addr.S_un.S_addr = 0;	// IP地址
    	sai_server.sin_family = AF_INET;		// IPV4
    	sai_server.sin_port = htons(8090);		// 传输协议端口
    
    	// 本地地址关联套接字
    	if (bind(
    		server, 					// 要与本地地址绑定的套接字
    		(sockaddr*)&sai_server, 	// 用来接收客户端消息的 sockaddr_in 结构体指针
    		sizeof(sai_server)			
    		)) {
    		cout << "bind failed : " << GetLastError() << endl;
    		WSACleanup();
    	}
    
    	// 初始化客户端链表
    	ucnode* listHead = new ucnode();
    	listHead->next = nullptr;
    	ucnode* lp = listHead;
    
    	// 监听消息
    	while (1) {
    		// 接收来自客户端的消息
    		umsg msg;
    		int len_client = sizeof(sockaddr);
    		recvfrom(
    			server, 					// 本地套接字
    			(char*)&msg, 				// 存放接收到的消息
    			sizeof(msg), 				
    			0, 							// 不修改函数调用行为
    			(sockaddr*)&sai_server, 	// 接收客户端的IP、端口
    			&len_client					// 接收消息的长度,必须初始化,否则默认为0 收不到消息				
    		);
    		
    		// sin_addr 转 char[](char[] 转 sin_addr 使用 inet_top)
    		char arr_ip[20];
    		inet_ntop(AF_INET, &sai_server.sin_addr, arr_ip, 16);
    		
    		// 处理消息(1:用户登录,2:用户退出,3:普通会话)
    		switch (msg.type) {
    		case 1: 
    			insertNode(listHead, sai_server, msg); 
    			cout << "[" << arr_ip << ":" << ntohs(sai_server.sin_port) << "] " << msg.name << ":" << "---登录---" << endl;
    			break;
    		case 2:
    			deleteNode(listHead, msg);
    			cout << "[" << arr_ip << ":" << ntohs(sai_server.sin_port) << "] " << msg.name << ":" << "---退出---" << endl;
    			break;
    		case 3:
    			cout << "[" << arr_ip << ":" << ntohs(sai_server.sin_port) << "] " << msg.name << ":" << msg.text << endl;
    			// 更新 msg.text
    			lp = listHead;
    			while (lp) {
    				if (strcmp(lp->msg.name, msg.name) == 0) {
    					strncpy(lp->msg.text, msg.text, sizeof(msg.text));
    					lp->msg.type = msg.type;
    					break;
    				}
    				lp = lp->next;
    			}
    			// 向其他客户端广播(除自己之外)
    			lp = listHead;
    			while (lp) {
    				if (strcmp(lp->msg.name,"") != 0 && strcmp(lp->msg.name, msg.name) != 0) {
    					sendto(
    						server, 					// 本地套接字
    						(char*)&msg, 				// 消息结构体
    						sizeof(msg), 				
    						0, 							// 不修改函数调用行为
    						(sockaddr*) & lp->addr, 	// 目标客户端地址
    						sizeof(lp->addr)
    					);
    				}
    				lp = lp->next;
    			}
    			break;
    		}
    	}
    
    	// 禁用 socket
    	WSACleanup();
    
    	getchar();
        return 0;
    }
    

    客户端代码:

    #include <iostream>
    #include <WinSock2.h>
    #include <WS2tcpip.h>
    #include <Windows.h>
    #include <thread>
    #include <cstdio>
    #include <string>
    
    #pragma comment(lib,"ws2_32.lib")
    
    using namespace std;
    
    #pragma region 全局变量
    
    SOCKET client;					// 客户端套接字		
    sockaddr_in sai_client;			// 存放客户端地址、端口
    sockaddr_in sai_server;			// 存放服务端发送的消息
    
    // 发送和接收的信息体
    struct umsg {
    	int type;					// 协议(1:登录,2:退出,3:发消息)
    	char name[64];				// 用户名字
    	char text[512];				// 文本
    };
    
    #pragma endregion
    
    #pragma region 依赖函数
    
    // 监听服务器消息
    void recvMessage()
    {
    	while (1){
    		umsg msg;
    		int len_server = sizeof(sockaddr);
    		int len = recvfrom(client, (char*)&msg,sizeof(msg),0,(sockaddr*)&sai_server,&len_server);
    		
    		cout << msg.name << ": " << msg.text << endl;
    	}
    }
    
    #pragma endregion
    
    int main()
    {
    	cout << "我是客户端" << endl;
    
    	// 初始化 WSA ,激活 socket
    	WSADATA wsaData;
    	if (WSAStartup(
    		MAKEWORD(2, 2), 	// 规定 socket 版本
    		&wsaData			// 接收 socket 的更多信息
    		)) {
    		cout << "WSAStartup failed : " << GetLastError() << endl;
    	}
    
    	// 初始化 socket、客户端信息
    	client = socket(
    		AF_INET,		// IPV4
    		SOCK_DGRAM,		// UDP
    		0				// 不指定协议
    		);
    	sai_client.sin_family = AF_INET;									// IPV4
    	inet_pton(AF_INET, "192.168.1.105", &sai_client.sin_addr);			// 服务器 IP地址
    	sai_client.sin_port = htons(8090);									// 端口
    
    	// 输入用户名
    	string name;
    	getline(cin, name);
    
    	// 发送登录消息
    	umsg msg;
    	msg.type = 1;
    	strncpy_s(msg.name, sizeof(msg.name), name.c_str(), 64);
    	strncpy_s(msg.text, sizeof(msg.text), "", 512);
    	sendto(
    		client, 						// 本地套接字
    		(char*)&msg, 					// 发送的消息
    		sizeof(msg), 
    		0, 								// 不修改函数调用行为
    		(sockaddr*) & sai_client,		// 消息目标
    		sizeof(sai_client)
    	);
    
    	// 接收服务器消息
    	HANDLE h_recvMes = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)recvMessage, 0, 0, 0);
    	if (!h_recvMes) { cout << "CreateThread failed :" << GetLastError() << endl; }
        
    	// 发送消息
    	while (1) {
    		string content;
    		getline(cin, content);
    		
    		// 如果是退出消息
    		if (content == "quit") {
    			msg.type = 2;
    			sendto(client, (char*)&msg, sizeof msg, 0, (struct sockaddr*) & sai_client, sizeof(sai_client));
    			closesocket(client);
    			WSACleanup();
    			return 0;
    		}
    
    		// 如果是会话消息
    		msg.type = 3;
    		strncpy_s(msg.text, sizeof(msg.text), content.c_str(), 512);
    		sendto(
    			client, 						// 本地套接字
    			(char*)&msg, 					// 要发送的消息
    			sizeof(msg), 
    			0, 								// 不修改函数调用行为
    			(sockaddr*) & sai_client, 		// 发送目标
    			sizeof(sai_client)
    		);
    	}
    
    
        getchar();
        return 0;
    }
    

    基于TCP实现

    服务端代码

    #include "stdafx.h"
    #include <iostream>
    #include <WinSock2.h>
    #include <WS2tcpip.h>
    #include <Windows.h>
    #include <thread>
    #include <cstdio>
    
    using namespace std;
    
    #pragma region 全局变量
    
    SOCKET server;				// 本地套接字
    sockaddr_in sai_server;		// 存放服务器IP、端口
    
    // 消息格式
    struct umsg {
    	int type;				// 协议(1:登录,2:退出,3:发消息)
    	char name[64];			// 用户名字
    	char text[512];			// 文本信息
    };
    
    // 客户端信息
    struct clientInfo {
    	SOCKET client;
    	sockaddr_in saddr;
    	umsg msg;
    };
    
    // 客户端链表
    typedef struct ucnode {
    	clientInfo cInfo;
    	ucnode* next;
    } *ucnode_t;
    
    ucnode* listHead;		// 客户端链表头
    ucnode* lp;				// 客户端链表指针
    
    #pragma endregion
    
    #pragma region 依赖函数
    
    // 链表插入数据
    ucnode* insertNode(ucnode* head,SOCKET client, sockaddr_in addr, umsg msg) {
    	ucnode* newNode = new ucnode();
    	newNode->cInfo.client = client;
    	newNode->cInfo.saddr = addr; 
    	newNode->cInfo.msg = msg;
    	ucnode* p = head;
    	if (p == nullptr) {
    		head = newNode;
    	}
    	else {
    		while (p->next != nullptr) {
    			p = p->next;
    		}
    		p->next = newNode;
    	}
    	return head;
    }
    
    // 链表删除数据
    ucnode* deleteNode(ucnode* head, SOCKET client) {
    	ucnode* p = head;
    	if (p == nullptr) {
    		return head;
    	}
    	if (p->cInfo.client == client) {
    		head = p->next;
    		delete p;
    		return head;
    	}
    	while (p->next != nullptr && p->next->cInfo.client != client) {
    		p = p->next;
    	}
    	if (p->next == nullptr) {
    		return head;
    	}
    	ucnode* deleteNode = p->next;
    	p->next = deleteNode->next;
    	delete deleteNode;
    	return head;
    }
    
    // 接收客户端消息(某个)
    void recvMessage(PVOID pParam) {
    	clientInfo* cInfo = (clientInfo*)pParam;
    
    	while (1) {
    		// 接收来自客户端的消息
    		umsg msg;
    		int len_client = sizeof(sockaddr);
    		int ret_recv = recv(
    			cInfo->client,	// 本地套接字	
    			(char*)&msg,	// 存放接收的消息
    			sizeof(msg),	// 消息大小
    			0				// 不修改函数调用行为
    		); 
    		if (ret_recv <= 0) { cout << msg.name << "断开连接: " << GetLastError() << endl; break; }
    		cInfo->msg = msg;
    
    		// sin_addr 转 char[](char[] 转 sin_addr 使用 inet_top)
    		char arr_ip[20];
    		inet_ntop(AF_INET, &cInfo->saddr.sin_addr, arr_ip, 16);
    
    		// 处理消息(1:登录,2:退出,3:会话)
    		switch (cInfo->msg.type) {
    		case 1:
    			// 插入数据到链表
    			insertNode(listHead,cInfo->client, cInfo->saddr,cInfo->msg);
    			// 打印消息
    			cout << "[" << arr_ip << ":" << ntohs(cInfo->saddr.sin_port) << "] " << msg.name << ":" << "---登录---" << endl;
    			break;
    		case 2:
    			// 从链表删除数据
    			deleteNode(listHead, /*cInfo->msg*/cInfo->client);
    			// 打印消息
    			cout << "[" << arr_ip << ":" << ntohs(cInfo->saddr.sin_port) << "] " << msg.name << ":" << "---退出---" << endl;
    			break;
    		case 3:
    			// 打印消息
    			cout << "[" << arr_ip << ":" << ntohs(cInfo->saddr.sin_port) << "] " << msg.name << ":" << cInfo->msg.text << endl;
    			// 向其他客户端广播(除自己之外)
    			lp = listHead;
    			while (lp) {
    				if (strcmp(lp->cInfo.msg.name, "") != 0 && strcmp(lp->cInfo.msg.name, cInfo->msg.name) != 0) {
    					send(
    						lp->cInfo.client,		// 本地套接字
    						(char*)&cInfo->msg,		// 发送的消息
    						sizeof(cInfo->msg),		// 消息大小
    						0						// 不指定调用方式
    					);
    					int error_send = GetLastError();
    					if (error_send != 0) { cout << "send failed:" << error_send << endl; }
    				}
    				lp = lp->next;
    			}
    			break;
    		}
    	}
    }
    
    #pragma endregion
    
    int main()
    {
    	cout << "我是服务端" << endl;
    
    	// 初始化 WSA ,激活 socket
    	WSADATA wsaData;
    	if (WSAStartup(
    		MAKEWORD(2, 2), 		// 规定 socket 版本为 2.2
    		&wsaData				// 接收关于套接字的更多信息
    	)) {
    		cout << "WSAStartup failed : " << GetLastError() << endl;
    	}
    
    	// 初始化 socket、服务器信息
    	server = socket(
    		AF_INET, 		// IPV4
    		SOCK_STREAM,	// TCP
    		0				// 不指定协议
    	);
    	sai_server.sin_addr.S_un.S_addr = 0;	// IP地址
    	sai_server.sin_family = AF_INET;		// IPV4
    	sai_server.sin_port = htons(8090);		// 传输协议端口
    
    	// 本地地址关联套接字
    	if (bind(
    		server, 					// 要与本地地址绑定的套接字
    		(sockaddr*)&sai_server, 	// 用来接收客户端消息的 sockaddr_in 结构体指针
    		sizeof(sai_server)
    	)) {
    		cout << "bind failed : " << GetLastError() << endl;
    		WSACleanup();
    	}
    
    	// 套接字进入监听状态
    	listen(
    		server,		// 本地套接字
    		SOMAXCONN	// 挂起连接队列的最大长度,SOMAXCONN:最大合理值
    	);
    
    	// 初始化客户端链表
    	listHead = new ucnode();
    	listHead->next = nullptr;
    	lp = listHead;
    		
    	// 接收消息
    	while (1) {
    		// 接收登录消息(首次连接是触发,之后发送消息不触发)
    		clientInfo* cInfo = new clientInfo();
    		int len_client = sizeof(sockaddr);
    		cInfo->client = accept(server, (sockaddr*) &cInfo->saddr, &len_client);	
    		if (GetLastError() != 0) { continue; }
    		
    		// 接收登录者的消息(每个客户端对应一个线程)
    		HANDLE h_recvMes = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)recvMessage, cInfo, 0, 0);
    		if (!h_recvMes) { cout << "CreateThread failed :" << GetLastError() << endl; }
    	}
    
    	// 禁用 socket
    	WSACleanup();
    
    	getchar();
        return 0;
    }
    

    客户端代码:

    #include <iostream>
    #include <WinSock2.h>
    #include <WS2tcpip.h>
    #include <Windows.h>
    #include <thread>
    #include <cstdio>
    #include <string>
    
    #pragma comment(lib,"ws2_32.lib")
    
    using namespace std;
    
    #pragma region 全局变量
    
    SOCKET client;					// 本地套接字
    sockaddr_in sai_client;			// 存放客户端IP地址、端口
    
    // 消息格式
    struct umsg {
    	int type;					// 协议(1:登录,2:退出,3:发消息)
    	char name[64];				// 用户名字
    	char text[512];				// 文本
    };
    
    #pragma endregion
    
    #pragma region 依赖函数
    
    // 监听服务器消息
    void recvMessage()
    {
    	while (1){
    		umsg msg;
    		int ret_recv = recv(
    			client, 		// 本地套接字
    			(char*)&msg,	// 存放接收的消息
    			sizeof(msg),	// 消息大小
    			0				// 不指定调用方式
    		);
    		if (ret_recv <= 0) { cout << "recv failed: " << GetLastError() << endl; break; }
    		
    		// 打印消息
    		cout << msg.name << ": " << msg.text << endl;
    	}
    }
    
    #pragma endregion
    
    int main()
    {
    	cout << "我是客户端" << endl;
    
    	// 初始化 WSA ,激活 socket
    	WSADATA wsaData;
    	if (WSAStartup(
    		MAKEWORD(2, 2), 	// 规定 socket 版本
    		&wsaData			// 接收 socket 的更多信息
    	)) {
    		cout << "WSAStartup failed : " << GetLastError() << endl;
    	}
    
    	// 初始化 socket、客户端信息
    	client = socket(
    		AF_INET,		// IPV4
    		SOCK_STREAM,	// TCP
    		0				// 不指定协议
    	);
    	sai_client.sin_family = AF_INET;									// IPV4
    	inet_pton(AF_INET, "192.168.1.100", &sai_client.sin_addr);			// 服务器 IP地址
    	sai_client.sin_port = htons(8090);									// 端口
    
    	// 连接服务器
    	int ret_connect = connect(
    		client, 					// 本地套接字
    		(sockaddr*) &sai_client, 	// 目标
    		sizeof(sai_client)
    	);if (ret_connect != 0) { cout << "connect failed:" << GetLastError() << endl; }
    
    	// 输入用户名
    	umsg msg;
    	msg.type = 1;
    	string name;
    	getline(cin, name);
    	strncpy_s(msg.name, sizeof(msg.name), name.c_str(), 64);
    	strncpy_s(msg.text, sizeof(msg.text), "", 512);
    
    	// 发送登录消息
    	send(
    		client,			// 本地套接字
    		(char*)&msg,	// 发送的消息
    		sizeof(msg),	// 消息大小
    		0				// 不指定调用方式
    	); 
    	int error_send = GetLastError();
    	if (error_send != 0) { cout << "send failed:" << error_send << endl; }
    	
    	// 接收服务器消息
    	HANDLE h_recvMes = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)recvMessage, 0, 0, 0);
    	if (!h_recvMes) { cout << "CreateThread failed :" << GetLastError() << endl; }
    
    	// 发送消息
    	while (1) {
    		string content;
    		getline(cin, content);
    
    		// 退出消息
    		if (content == "quit") {
    			msg.type = 2;
    			send(
    				client,			// 本地套接字
    				(char*)&msg,	// 发送的消息
    				sizeof(msg),	// 消息大小
    				0				// 不指定调用方式
    			);
    			error_send = GetLastError();
    			if (error_send != 0) { cout << "send failed:" << error_send << endl; }
    			closesocket(client);
    			WSACleanup();
    			return 0;
    		}
    		
    		// 会话消息
    		msg.type = 3;
    		strncpy_s(msg.text, sizeof(msg.text), content.c_str(), 512);
    		send(
    			client,			// 本体套接字
    			(char*)&msg,	// 发送的消息
    			sizeof(msg),	// 消息大小
    			0				// 不指定调用方式
    		);
    		error_send = GetLastError();
    		if (error_send != 0) { cout << "send failed:" << error_send << endl; }
    
    	}
    
        getchar();
        return 0;
    }
    

    许可协议: 文章中的代码均为学习时整理的笔记,博客中除去明确标注有参考文献的文章,其他文章【均为原创】作品,转载请务必【添加出处】,您添加出处是我创作的动力!
  • 相关阅读:
    jquery正则表达式验证:正整数(限制长度)
    H5页面快速搭建之高级字体应用实践
    如何用Python写一个贪吃蛇AI
    HashMap多线程并发问题分析
    为RecyclerView打造通用Adapter 让RecyclerView更加好用
    学好Mac常用命令,助力iOS开发
    使用 Realm 和 Swift 创建 ToDo 应用
    看Facebook是如何优化React Native性能
    利用github搭建个人maven仓库
    Objective-C Runtime之着魔的UIAlertView
  • 原文地址:https://www.cnblogs.com/LyShark/p/15019254.html
Copyright © 2011-2022 走看看