22级hnust计算机网络课设(运行环境viusal stdio 2022,建立cmake项目):

个人博客传送门:grey66.cn

1、网络聊天程序的设计与实现 :

服务端:服务端收到客户端的消息,对消息进行特征判断来决定是广播以实现群聊效果,还是仅转发给目的客户端以实现私聊效果,客户端之间并不直接相互通信,服务端利用一个map来实现进程与用户名的映射,引用线程但是未进行加锁操作。

运行结果:

服务端代码如下:

#include <winsock2.h>
#include <iostream>
#include <list>
#include <map>
#include <string>
#include <cstring>
/*
    creat by grey
    2205010216
    20241225
*/
#pragma comment(lib, "ws2_32.lib")
using namespace std;

// 线程处理函数
DWORD WINAPI ThreadFun(LPVOID lpThreadParameter);

// 初始化函数
void init_app();

// 发送消息到所有客户端
void Send_All(list<SOCKET> Client_List_Now, const char* msg, SOCKET This_Socket);

// 私聊功能:通过名字发送消息
void Send_Private(SOCKET clientSocket, const char* targetName, const char* msg);

// 获取客户端套接字,通过名字
SOCKET Get_Client_By_Name(const char* name);

// 获取客户端名称
const char* GetName(SOCKET clientSocket);

// 客户端名字和套接字的映射
map<SOCKET, string> ClientNames;

WSADATA wd;
SOCKET Socket;
list<SOCKET> Client_List;
sockaddr_in addrClient;
int len = sizeof(sockaddr_in);
// 默认服务器地址为 127.0.0.1
char serverIP[100] = "127.0.0.1";
int main() {
    init_app();

    // 主线程循环接收客户端的连接
    while (true) {
        SOCKET Client = accept(Socket, (SOCKADDR*)&addrClient, &len);
        if (Client != INVALID_SOCKET) {
            // 创建线程并传入与客户端通讯的套接字
            HANDLE hThread = CreateThread(NULL, 0, ThreadFun, (LPVOID)Client, 0, NULL);
            CloseHandle(hThread);  // 关闭对线程的引用
        }
    }

    // 关闭监听套接字
    closesocket(Socket);
    // 清理winsock2的环境
    WSACleanup();
    return 0;
}

DWORD WINAPI ThreadFun(LPVOID lpThreadParameter) {
    SOCKET This_Socket = (SOCKET)lpThreadParameter;

    // 获取客户端名称
    char name[100] = { 0 };
    int ret = recv(This_Socket, name, sizeof(name), 0);
    if (ret > 0) {
        ClientNames[This_Socket] = string(name);  // 记录客户端名称
        cout << "客户端 " << name << " 进入聊天室" << endl;
    }

    // 发送欢迎消息
    char buf[100] = { 0 };
    sprintf(buf, "********欢迎 %s 进入聊天室*******\n", ClientNames[This_Socket].c_str());
    Send_All(Client_List, buf, This_Socket);
    Client_List.push_back(This_Socket);

    // 循环接收客户端数据
    do {
        char buf2[100] = { 0 };
        ret = recv(This_Socket, buf2, 100, 0);

        if (ret > 0) {
            cout << ClientNames[This_Socket] << " : " << buf2 << endl;

            // 检查是否为私聊消息 (/private <用户名> <消息内容>)
            if (strncmp(buf2, "/private", 8) == 0) {
                char targetName[20] = { 0 };
                char privateMsg[80] = { 0 };
                sscanf(buf2, "/private %s %[^\n]", targetName, privateMsg);

                // 发送私聊消息
                Send_Private(This_Socket, targetName, privateMsg);
            }
            else {
                // 广播消息
                Send_All(Client_List, buf2, This_Socket);
            }
        }
    } while (ret != SOCKET_ERROR && ret != 0);

    // 客户端离开,移除列表
    Client_List.remove(This_Socket);
    ClientNames.erase(This_Socket);  // 移除客户端名字
    return 0;
}

void Send_All(list<SOCKET> Client_List_Now, const char* msg, SOCKET This_Socket) {
    // 获取发送者的名字
    const char* senderName = GetName(This_Socket);

    // 创建一个缓冲区来存储格式化后的消息
    char formattedMsg[200];

    // 将发送者的名字和消息内容格式化到一起
    snprintf(formattedMsg, sizeof(formattedMsg), "%s: %s", senderName, msg);

    // 移除自己,避免发送给自己
    Client_List_Now.remove(This_Socket);

    // 向其他客户端发送消息
    for (auto iter : Client_List_Now) {
        send(iter, formattedMsg, strlen(formattedMsg), 0);
    }
}

void Send_Private(SOCKET clientSocket, const char* targetName, const char* msg) {
    SOCKET targetSocket = Get_Client_By_Name(targetName);

    if (targetSocket != INVALID_SOCKET) {
        char privateMsg[150];
        snprintf(privateMsg, sizeof(privateMsg), "%s 说: %s(私聊)", ClientNames[clientSocket].c_str(), msg); // 发送者名字
        send(targetSocket, privateMsg, strlen(privateMsg), 0);
        cout << "私聊给 " << targetName << ": " << msg << endl;
    }
    else {
        const char* errorMsg = "目标用户不存在。\n";
        send(clientSocket, errorMsg, strlen(errorMsg), 0);
    }
}

SOCKET Get_Client_By_Name(const char* name) {
    for (auto& entry : ClientNames) {
        if (entry.second == name) {
            return entry.first;  // 返回匹配的客户端套接字
        }
    }
    return INVALID_SOCKET;  // 找不到目标客户端
}

const char* GetName(SOCKET clientSocket) {
    auto it = ClientNames.find(clientSocket);
    if (it != ClientNames.end()) {
        return it->second.c_str();  // 返回客户端名称
    }
    return "Unknown";  // 如果找不到客户端,返回 "Unknown"
}

void init_app() {
    cout << "===========服务端============" << endl;
    char falg;
    char* ip;
    if (WSAStartup(MAKEWORD(2, 2), &wd) != 0) {
        cout << "WSAStartup 错误:" << WSAGetLastError() << endl;
        return;
    }

    // 创建流式套接字
    Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (Socket == INVALID_SOCKET) {
        cout << "socket 错误:" << WSAGetLastError() << endl;
        return;
    }

    cout << "是否自定义当前服务器IP(y/n):";
    cin >> falg;
    switch (falg) {
    case 'n': {
        ip = "127.0.0.1";
        break;
    }
    case 'y': {
        cout << "请输入IP:";
        cin.ignore();
        cin.getline(serverIP, sizeof(serverIP));
         ip = serverIP;
         break;
          
    }
    }

    // 绑定端口和IP
    sockaddr_in addr;
    memset(&addr, 0, sizeof(sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8000);
    addr.sin_addr.s_addr = inet_addr(ip);
    cout << "服务端IP:" << ip << "\n";

    // 服务端bind绑定
    if (bind(Socket, (SOCKADDR*)&addr, len) == SOCKET_ERROR) {
        cout << "bind 错误:" << WSAGetLastError() << endl;
        return;
    }
    // 监听20个以下设备
    listen(Socket, 20);
}

客户端:仅和服务端进行连接,然后发送和接受来自客户端的消息

#include <winsock2.h>
#include <iostream>
#include <cstring>
#include <thread>
#include <ws2tcpip.h>
/*
    creat by grey
    2205010216
    20241225
*/
#pragma comment(lib, "ws2_32.lib")

using namespace std;

void init_app();
void* GetMsg(void* socket);
void SendMsg();
void SendMsgWithName(SOCKET sock, const char* msg);

#define BUFFSIZE 1024

WSADATA wd;
SOCKET Socket;
char clientName[100];
char serverIP[100] = "127.0.0.1";

int main() {
    init_app();
    thread recvThread(GetMsg, &Socket);
    thread sendThread(SendMsg);

    // 等待两个线程结束
    recvThread.join();
    sendThread.join();

    closesocket(Socket);
    WSACleanup();
    return 0;
}

// 向服务器发送消息
void SendMsg() {
    int ret = 0;
    do {
        char buf[BUFFSIZE] = { 0 };
        cin.getline(buf, sizeof(buf)); 
        if (strlen(buf) == 0) continue;  

        cout << "你发送了:" << buf << endl;
        SendMsgWithName(Socket, buf);  // 发送带名称的消息

    } while (true);
}

// 接收服务器的消息
void* GetMsg(void* socket) {
    SOCKET sock = *(SOCKET*)socket;
    do {
        int ret = 0;
        char buf[BUFFSIZE] = { 0 };
        ret = recv(sock, buf, sizeof(buf), 0);
        if (ret > 0) {
            cout << buf << endl;
        }
    } while (true);
}

// 发送带名称的消息
void SendMsgWithName(SOCKET sock, const char* msg) {
    char buf[BUFFSIZE] = { 0 };
    snprintf(buf, sizeof(buf), "%s", msg);  // 在消息前添加名称
    send(sock, buf, strlen(buf), 0);
}

// 初始化客户端套接字
void init_app() {
    cout << "=========== 客户端 ============" << endl;

    // 让用户输入自定义名称
    cout << "请输入您的名称: ";
    cin.getline(clientName, sizeof(clientName));

    // 让用户选择是否自定义服务器IP
    char falg;
    cout << "是否自定义当前服务器IP(y/n): ";
    cin >> falg;
    cin.ignore();  // 忽略换行符

    if (falg == 'y' || falg == 'Y') {
        cout << "请输入服务器IP: ";
        cin.getline(serverIP, sizeof(serverIP));
    }

    // 初始化 Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wd) != 0) {
        cout << "WSAStartup 错误:" << WSAGetLastError() << endl;
        return;
    }

    // 创建套接字
    Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (Socket == INVALID_SOCKET) {
        cout << "socket 错误:" << WSAGetLastError() << endl;
        return;
    }

    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8000);  

    // 使用 inet_pton 转换 IP 地址
    if (inet_pton(AF_INET, serverIP, &addr.sin_addr) <= 0) {
        cerr << "Invalid IP address format!" << endl;
        return;
    }

    cout << "连接到服务器 IP: " << serverIP << " 端口: 8000\n";

    // 连接到服务器
    if (connect(Socket, (SOCKADDR*)&addr, sizeof(addr)) == SOCKET_ERROR) {
        cout << "连接失败:" << WSAGetLastError() << endl;
        return;
    }

    // 发送客户端名称给服务器
    send(Socket, clientName, strlen(clientName), 0);
}

2.滑动窗口协议仿真

接收端:接受来自发送端的帧并将接收到帧的情况发送反馈给发送端(期望序号的更新存在bug 思路:可完成接收窗口以完成正确更新)

演示效果:

随机丢失帧2:

发送窗口满且未ack则重发:

接收端代码如下:

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <list>
#include <thread>
#include <windows.h>
#include <string.h>

/*
   
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值