本文实例为大家分享了C++实现多人聊天室的具体代码,供大家参考,具体内容如下
UDP
服务端代码:
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
// 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;
}
|
客户端代码:
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
// Test_Console_2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#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
服务器代码:
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
// 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]; // 文本信息
};
// 客户端信息
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;
}
|
客户端代码:
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
// Test_Console_2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#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;
}
|
效果图:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/Simon798/article/details/104200998








发表评论
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。