C++實(shí)現(xiàn)多人聊天室
更新時(shí)間:2021年06月30日 15:53:14 作者:(-: LYSM :-)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)多人聊天室,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
本文實(shí)例為大家分享了C++實(shí)現(xiàn)多人聊天室的具體代碼,供大家參考,具體內(nèi)容如下
UDP
服務(wù)端代碼:
// Test_Console.cpp : 定義控制臺(tái)應(yīng)用程序的入口點(diǎn)。
//
#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; // 服務(wù)端套接字
sockaddr_in sai_server; // 服務(wù)端信息(ip、端口)
// 消息格式
struct umsg {
int type; // 協(xié)議(1:加入 2:退出 3:發(fā)消息)
char name[64]; // 用戶名字
char text[512]; // 文本信息
};
// 客戶端鏈表
typedef struct ucnode {
sockaddr_in addr; // 客戶端的地址和端口號(hào)
umsg msg; // 客戶端傳來的消息
ucnode* next;
} *ucnode_t;
#pragma endregion
#pragma region 依賴函數(shù)
// 鏈表插入數(shù)據(jù)
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;
}
// 鏈表刪除數(shù)據(jù)
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 << "我是服務(wù)端" << endl;
// 初始化 WSA ,激活 socket
WSADATA wsaData;
if (WSAStartup(
MAKEWORD(2, 2), // 規(guī)定 socket 版本為 2.2
&wsaData // 接收關(guān)于套接字的更多信息
)) {
cout << "WSAStartup failed : " << GetLastError() << endl;
}
// 初始化 socket、服務(wù)器信息
server = socket(
AF_INET, // IPV4
SOCK_DGRAM, // UDP
0 // 不指定協(xié)議
);
sai_server.sin_addr.S_un.S_addr = 0; // IP地址
sai_server.sin_family = AF_INET; // IPV4
sai_server.sin_port = htons(8090); // 傳輸協(xié)議端口
// 本地地址關(guān)聯(lián)套接字
if (bind(
server, // 要與本地地址綁定的套接字
(sockaddr*)&sai_server, // 用來接收客戶端消息的 sockaddr_in 結(jié)構(gòu)體指針
sizeof(sai_server)
)) {
cout << "bind failed : " << GetLastError() << endl;
WSACleanup();
}
// 初始化客戶端鏈表
ucnode* listHead = new ucnode();
listHead->next = nullptr;
ucnode* lp = listHead;
// 監(jiān)聽消息
while (1) {
// 接收來自客戶端的消息
umsg msg;
int len_client = sizeof(sockaddr);
recvfrom(
server, // 本地套接字
(char*)&msg, // 存放接收到的消息
sizeof(msg),
0, // 不修改函數(shù)調(diào)用行為
(sockaddr*)&sai_server, // 接收客戶端的IP、端口
&len_client // 接收消息的長度,必須初始化,否則默認(rèn)為0 收不到消息
);
// sin_addr 轉(zhuǎn) char[](char[] 轉(zhuǎn) sin_addr 使用 inet_top)
char arr_ip[20];
inet_ntop(AF_INET, &sai_server.sin_addr, arr_ip, 16);
// 處理消息(1:用戶登錄,2:用戶退出,3:普通會(huì)話)
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, // 消息結(jié)構(gòu)體
sizeof(msg),
0, // 不修改函數(shù)調(diào)用行為
(sockaddr*) & lp->addr, // 目標(biāo)客戶端地址
sizeof(lp->addr)
);
}
lp = lp->next;
}
break;
}
}
// 禁用 socket
WSACleanup();
getchar();
return 0;
}
客戶端代碼:
// Test_Console_2.cpp : 此文件包含 "main" 函數(shù)。程序執(zhí)行將在此處開始并結(jié)束。
//
#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; // 存放服務(wù)端發(fā)送的消息
// 發(fā)送和接收的信息體
struct umsg {
int type; // 協(xié)議(1:登錄,2:退出,3:發(fā)消息)
char name[64]; // 用戶名字
char text[512]; // 文本
};
#pragma endregion
#pragma region 依賴函數(shù)
// 監(jiān)聽服務(wù)器消息
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), // 規(guī)定 socket 版本
&wsaData // 接收 socket 的更多信息
)) {
cout << "WSAStartup failed : " << GetLastError() << endl;
}
// 初始化 socket、客戶端信息
client = socket(
AF_INET, // IPV4
SOCK_DGRAM, // UDP
0 // 不指定協(xié)議
);
sai_client.sin_family = AF_INET; // IPV4
inet_pton(AF_INET, "192.168.1.105", &sai_client.sin_addr); // 服務(wù)器 IP地址
sai_client.sin_port = htons(8090); // 端口
// 輸入用戶名
string name;
getline(cin, name);
// 發(fā)送登錄消息
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, // 發(fā)送的消息
sizeof(msg),
0, // 不修改函數(shù)調(diào)用行為
(sockaddr*) & sai_client, // 消息目標(biāo)
sizeof(sai_client)
);
// 接收服務(wù)器消息
HANDLE h_recvMes = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)recvMessage, 0, 0, 0);
if (!h_recvMes) { cout << "CreateThread failed :" << GetLastError() << endl; }
// 發(fā)送消息
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;
}
// 如果是會(huì)話消息
msg.type = 3;
strncpy_s(msg.text, sizeof(msg.text), content.c_str(), 512);
sendto(
client, // 本地套接字
(char*)&msg, // 要發(fā)送的消息
sizeof(msg),
0, // 不修改函數(shù)調(diào)用行為
(sockaddr*) & sai_client, // 發(fā)送目標(biāo)
sizeof(sai_client)
);
}
getchar();
return 0;
}
效果圖:

TCP
服務(wù)器代碼:
// Test_Console.cpp : 定義控制臺(tái)應(yīng)用程序的入口點(diǎn)。
//
#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; // 存放服務(wù)器IP、端口
// 消息格式
struct umsg {
int type; // 協(xié)議(1:登錄,2:退出,3:發(fā)消息)
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 依賴函數(shù)
// 鏈表插入數(shù)據(jù)
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;
}
// 鏈表刪除數(shù)據(jù)
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;
}
// 接收客戶端消息(某個(gè))
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 // 不修改函數(shù)調(diào)用行為
);
if (ret_recv <= 0) { cout << msg.name << "斷開連接: " << GetLastError() << endl; break; }
cInfo->msg = msg;
// sin_addr 轉(zhuǎn) char[](char[] 轉(zhuǎn) sin_addr 使用 inet_top)
char arr_ip[20];
inet_ntop(AF_INET, &cInfo->saddr.sin_addr, arr_ip, 16);
// 處理消息(1:登錄,2:退出,3:會(huì)話)
switch (cInfo->msg.type) {
case 1:
// 插入數(shù)據(jù)到鏈表
insertNode(listHead,cInfo->client, cInfo->saddr,cInfo->msg);
// 打印消息
cout << "[" << arr_ip << ":" << ntohs(cInfo->saddr.sin_port) << "] " << msg.name << ":" << "---登錄---" << endl;
break;
case 2:
// 從鏈表刪除數(shù)據(jù)
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, // 發(fā)送的消息
sizeof(cInfo->msg), // 消息大小
0 // 不指定調(diào)用方式
);
int error_send = GetLastError();
if (error_send != 0) { cout << "send failed:" << error_send << endl; }
}
lp = lp->next;
}
break;
}
}
}
#pragma endregion
int main()
{
cout << "我是服務(wù)端" << endl;
// 初始化 WSA ,激活 socket
WSADATA wsaData;
if (WSAStartup(
MAKEWORD(2, 2), // 規(guī)定 socket 版本為 2.2
&wsaData // 接收關(guān)于套接字的更多信息
)) {
cout << "WSAStartup failed : " << GetLastError() << endl;
}
// 初始化 socket、服務(wù)器信息
server = socket(
AF_INET, // IPV4
SOCK_STREAM, // TCP
0 // 不指定協(xié)議
);
sai_server.sin_addr.S_un.S_addr = 0; // IP地址
sai_server.sin_family = AF_INET; // IPV4
sai_server.sin_port = htons(8090); // 傳輸協(xié)議端口
// 本地地址關(guān)聯(lián)套接字
if (bind(
server, // 要與本地地址綁定的套接字
(sockaddr*)&sai_server, // 用來接收客戶端消息的 sockaddr_in 結(jié)構(gòu)體指針
sizeof(sai_server)
)) {
cout << "bind failed : " << GetLastError() << endl;
WSACleanup();
}
// 套接字進(jìn)入監(jiān)聽狀態(tài)
listen(
server, // 本地套接字
SOMAXCONN // 掛起連接隊(duì)列的最大長度,SOMAXCONN:最大合理值
);
// 初始化客戶端鏈表
listHead = new ucnode();
listHead->next = nullptr;
lp = listHead;
// 接收消息
while (1) {
// 接收登錄消息(首次連接是觸發(fā),之后發(fā)送消息不觸發(fā))
clientInfo* cInfo = new clientInfo();
int len_client = sizeof(sockaddr);
cInfo->client = accept(server, (sockaddr*) &cInfo->saddr, &len_client);
if (GetLastError() != 0) { continue; }
// 接收登錄者的消息(每個(gè)客戶端對(duì)應(yīng)一個(gè)線程)
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;
}
客戶端代碼:
// Test_Console_2.cpp : 此文件包含 "main" 函數(shù)。程序執(zhí)行將在此處開始并結(jié)束。
//
#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; // 協(xié)議(1:登錄,2:退出,3:發(fā)消息)
char name[64]; // 用戶名字
char text[512]; // 文本
};
#pragma endregion
#pragma region 依賴函數(shù)
// 監(jiān)聽服務(wù)器消息
void recvMessage()
{
while (1){
umsg msg;
int ret_recv = recv(
client, // 本地套接字
(char*)&msg, // 存放接收的消息
sizeof(msg), // 消息大小
0 // 不指定調(diào)用方式
);
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), // 規(guī)定 socket 版本
&wsaData // 接收 socket 的更多信息
)) {
cout << "WSAStartup failed : " << GetLastError() << endl;
}
// 初始化 socket、客戶端信息
client = socket(
AF_INET, // IPV4
SOCK_STREAM, // TCP
0 // 不指定協(xié)議
);
sai_client.sin_family = AF_INET; // IPV4
inet_pton(AF_INET, "192.168.1.100", &sai_client.sin_addr); // 服務(wù)器 IP地址
sai_client.sin_port = htons(8090); // 端口
// 連接服務(wù)器
int ret_connect = connect(
client, // 本地套接字
(sockaddr*) &sai_client, // 目標(biāo)
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);
// 發(fā)送登錄消息
send(
client, // 本地套接字
(char*)&msg, // 發(fā)送的消息
sizeof(msg), // 消息大小
0 // 不指定調(diào)用方式
);
int error_send = GetLastError();
if (error_send != 0) { cout << "send failed:" << error_send << endl; }
// 接收服務(wù)器消息
HANDLE h_recvMes = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)recvMessage, 0, 0, 0);
if (!h_recvMes) { cout << "CreateThread failed :" << GetLastError() << endl; }
// 發(fā)送消息
while (1) {
string content;
getline(cin, content);
// 退出消息
if (content == "quit") {
msg.type = 2;
send(
client, // 本地套接字
(char*)&msg, // 發(fā)送的消息
sizeof(msg), // 消息大小
0 // 不指定調(diào)用方式
);
error_send = GetLastError();
if (error_send != 0) { cout << "send failed:" << error_send << endl; }
closesocket(client);
WSACleanup();
return 0;
}
// 會(huì)話消息
msg.type = 3;
strncpy_s(msg.text, sizeof(msg.text), content.c_str(), 512);
send(
client, // 本體套接字
(char*)&msg, // 發(fā)送的消息
sizeof(msg), // 消息大小
0 // 不指定調(diào)用方式
);
error_send = GetLastError();
if (error_send != 0) { cout << "send failed:" << error_send << endl; }
}
getchar();
return 0;
}
效果圖:

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C語言實(shí)現(xiàn)最簡(jiǎn)單的剪刀石頭布小游戲示例
這篇文章主要介紹了C語言實(shí)現(xiàn)最簡(jiǎn)單的剪刀石頭布小游戲,涉及C語言數(shù)組、隨機(jī)數(shù)與數(shù)值運(yùn)算等相關(guān)操作技巧,需要的朋友可以參考下2017-09-09
C字符串操作函數(shù)的實(shí)現(xiàn)詳細(xì)解析
以下是對(duì)C語言中字符串操作函數(shù)的實(shí)現(xiàn)進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過來參考下2013-08-08
深入學(xué)習(xí)C++智能指針之shared_ptr與右值引用的方法
智能指針的核心實(shí)現(xiàn)技術(shù)是引用計(jì)數(shù),每使用它一次,內(nèi)部引用計(jì)數(shù)加1,每析構(gòu)一次內(nèi)部的引用計(jì)數(shù)減1,減為0時(shí),刪除所指向的堆內(nèi)存,今天通過本文給大家分享C++智能指針之shared_ptr與右值引用的方法,需要的朋友跟隨小編一起看看吧2021-07-07
史上最貼心的 VS code C++ 環(huán)境配置超詳細(xì)教程
這篇文章主要介紹了史上最貼心的 VS code C++ 環(huán)境配置超詳細(xì)教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02
Matlab實(shí)現(xiàn)繪制立體玫瑰花的示例代碼
這篇文章主要介紹了如何利用Matlab實(shí)現(xiàn)繪制更立體的玫瑰花,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Matlab有一定的幫助,需要的可以參考一下2023-02-02

