服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C++实现多人聊天室

C++实现多人聊天室

2021-11-19 15:54(-: LYSM :-) C/C++

这篇文章主要为大家详细介绍了C++实现多人聊天室,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

本文实例为大家分享了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;
}

效果图:

C++实现多人聊天室

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;
}

效果图:

C++实现多人聊天室

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/Simon798/article/details/104200998

延伸 · 阅读

精彩推荐