C++ 多人聊天室

服务端代码:

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值