欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C++实现多人聊天室

程序员文章站 2022-03-17 19:31:59
本文实例为大家分享了c++实现多人聊天室的具体代码,供大家参考,具体内容如下udp服务端代码:// test_console.cpp : 定义控制台应用程序的入口点。//#include "stdaf...

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

客户端代码:

// 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

服务器代码:

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

客户端代码:

// 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++实现多人聊天室

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

相关标签: C++ 聊天室