一、基于UDP的网络聊天室

1.服务器端

sock.hpp

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
uint16_t defaultport=3333;
class Socket
{
public:
    Socket()
    {

    }
    void Init()
    {
        //1.创建套接字
        sockfd_=socket(AF_INET,SOCK_DGRAM,0);
        if(sockfd_<0)
        {
            std::cout<<"socket fail\n";
        }
        std::cout<<"socket success\n";
        sockaddr_in server;
        server.sin_family=AF_INET;
        server.sin_addr.s_addr=INADDR_ANY;//inaddr_any
        server.sin_port=htons(port_);
        //2.绑定套接字
        socklen_t addrlen=sizeof(server);
        if(bind(sockfd_,(sockaddr*)&server,addrlen)<0)
        {
            std::cout<<"bind fail\n";
        } 
        std::cout<<"bind success\n";


    }

    public:
    int sockfd_;
    uint16_t port_;

};

chatroom_server.hpp

#pragma once
#include "socket.hpp"
#include <unordered_map>
#include <string>
class ChatRoom
{
public:
    ChatRoom(uint16_t port=defaultport)
    {
        server.port_=port;
        server.Init();
        pthread_t rtid, wtid;

        int sockfd = server.sockfd_;
    }
    void Run()
    {
        while (1)
        {

            sockaddr_in client; // 储存客户端信息
            memset(&client, 0, sizeof(client));
            char buffer[1024];
            socklen_t addr_len = sizeof(client);
            ssize_t n = recvfrom(server.sockfd_, buffer, sizeof(buffer), 0, (sockaddr *)&client, &addr_len);
            if (n < 0)
            {
                std::cout << "read fail\n";
                continue;
            }
 
            buffer[n] = '\0';
            std::string clientip = std::to_string(ntohl(client.sin_addr.s_addr));
            uint16_t clientport = ntohl(client.sin_port);

            CheckClient(clientip, clientport, client);
            Broadcast(clientip, clientport, buffer);
        }
    }
    void CheckClient(std::string clientip, uint16_t clientport, sockaddr_in &client) // 已有成员信息无需添加
    {
        //服务器收到某一个客户端的信息,判断该客户端是否是自己的用户
        //如果不是,则添加
        auto pos = clients.find(clientip);
        if (pos == clients.end())
        {
            clients.insert({clientip, client});
            std::cout << "new client added,client ip is:" << clientip << ",clientport is:" << clientport << std::endl;
        }
    }
    void Broadcast(std::string clientip, uint16_t clientport, std::string info) // 任意成员发的消息都需要广播
    {   //将当前客户端的信息发送给所有客户端
        //std::cout<<"member size is:"<<clients.size()<<std::endl;
        for (auto client : clients)
        {
            std::string massage; // 将读取到的
            massage += "clientip:";
            massage += clientip;
            // 先将ip地址网络转主机,再转字符串
            massage += ",clientport:";
            massage += std::to_string(clientport);
            massage += ",client say:";
            massage += info;
            socklen_t len = sizeof(client.second);
            sendto(server.sockfd_, massage.c_str(), massage.size(), 0, (sockaddr *)(&client.second), len);
        }
    }

public:
    Socket server;
    std::unordered_map<std::string, struct sockaddr_in> clients;
};

server.cc

#include"chatroom_server.hpp"
void Useage()
{
    std::cout<<"./process port\n";
}
int main(int argc,char* args[])
{
    if(argc!=2)
    {
        Useage();
    }
    std::string port=args[1];
    uint16_t netport=std::stoi(port);
    ChatRoom ser(netport);//创建时已经创建和绑定套接字
    ser.Run();//接收信息和广播,死循环
    return 0;
}

2.客户端

myclient.cc

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

using namespace std;

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
              << std::endl;
}

struct ThreadData
{
    struct sockaddr_in server;
    int sockfd;
    std::string serverip;
};

void *recv_message(void *args)
{
    ThreadData *td = static_cast<ThreadData *>(args);
    char buffer[1024];
    while (true)
    {
        memset(buffer, 0, sizeof(buffer));
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);

        ssize_t s = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &len);
        if (s > 0)
        {
            buffer[s] = 0;
            cerr << buffer << endl;
        }
    }
}

void *send_message(void *args)
{
    ThreadData *td = static_cast<ThreadData *>(args);
    string message;
    socklen_t len = sizeof(td->server);

    std::string welcome = td->serverip;
    welcome += " comming...";
    sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&(td->server), len);

    while (true)
    {
        cout << "Please Enter@ ";
        getline(cin, message);

        // std::cout << message << std::endl;
        // 1. 数据 2. 给谁发
        sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&(td->server), len);
    }
}

// 多线程
// ./udpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    struct ThreadData td;
    bzero(&td.server, sizeof(td.server));
    td.server.sin_family = AF_INET;
    td.server.sin_port = htons(serverport); //?
    td.server.sin_addr.s_addr = inet_addr(serverip.c_str());

    td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (td.sockfd < 0)
    {
        cout << "socker error" << endl;
        return 1;
    }

    td.serverip = serverip;

    pthread_t recvr, sender;
    pthread_create(&recvr, nullptr, recv_message, &td);
    pthread_create(&sender, nullptr, send_message, &td);

    pthread_join(recvr, nullptr);
    pthread_join(sender, nullptr);

    close(td.sockfd);
    return 0;
}

客户端的读写必须是两个线程,只有这样,当前客户端才能收到从服务器广播而来的其他客户的消息。

3.效果演示

linux之基于UDP的网络聊天室_客户端

在不同云服务器上的两个客户端可以通过服务器的广播显示对方的信息。