目录

第一版:

common

服务端:

客户端

第一版问题总结:

第二版

服务端:

客户端:

改进:

Windows客户端

一些小问题

还可以进行的改进


这篇文章我就先不讲网络基础的东西了,我讲讲在我进行制作我这个拉跨聊天室中遇到的问题,并写了三版代码.

第一版:

common

#pragma once
#include <iostream>
#include <fstream>
#include <cstdio>
#include <string>
#include <ctime>
#include <cstdarg>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "LockGuard.hpp"
enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    USAGE_ERROR
};

void Setserver(struct sockaddr_in &server,std::string &serverip,uint16_t &serverport)
{
    bzero(&server, sizeof(server));
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
    server.sin_family=AF_INET;
}


class InetAddr
{
    void GetAddress(uint16_t *Port, std::string *Ip)
    {
        *Port = ntohs(_Addr.sin_port);
        *Ip = inet_ntoa(_Addr.sin_addr);
    }

public:
    InetAddr(struct sockaddr_in &Addr)
        : _Addr(Addr)
    {
        GetAddress(&_Port, &_Ip);
    }
    uint16_t Port()
    {
        return _Port;
    }
    std::string Ip()
    {
        return _Ip;
    }

private:
    struct sockaddr_in _Addr;
    uint16_t _Port;
    std::string _Ip;
};

服务端:

#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"
#include "Common.hpp"
const static int defaultfd = -1;

class Udpserver
{

public:
    Udpserver(uint16_t port)
        : _socketfd(defaultfd), _prot(port), _isrunning(false)
    {
    }
    void InitServer()
    {
        _socketfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_socketfd < 0)
        {
            LOG(WARNING, "%s", "sockfd创建失败");
        }
        LOG(INFO, "%s", "sock创建成功");
        // 填充sockaddr_in结构
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;    // 设置家族协议
        local.sin_port = htons(_prot); // 设置端口号 换成网络序列
        // port要经过网络传输给对面,先到网络,_port:主机序列-> 主机序列,转成网络序列
        // a. 字符串风格的点分十进制的IP地址转成 4 字节IP
        // b. 主机序列,转成网络序列
        // in_addr_t inet_addr(const char *cp) -> 同时完成 a & b
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // "192.168.3.1" -> 字符串风格的点分十进制的IP地址 -> 4字节IP
        local.sin_addr.s_addr = INADDR_ANY; // 随机ip地址 一般不能绑定确定ip地址

        // 开始绑定
        int n = bind(_socketfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "%s", "bind error");
            exit(BIND_ERROR);
        }
        LOG(INFO, "%s", "bind success");
    }

    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            char Inbuffer[1024];
            struct sockaddr_in peer;
            socklen_t socklen = sizeof(peer); // 初始化为sock
            // 让server收取数据 获取客户端socket
            ssize_t n = recvfrom(_socketfd, Inbuffer, sizeof(Inbuffer), 0, (struct sockaddr *)&peer,&socklen); // 接受
            if (n > 0)
            {
                std::cout <<"client say:";
                std::cout << Inbuffer << std::endl; // 成功就打印出来

                // sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);
                LOG(DEBUG, "建立 客户IP:%s 连接端口:%s", netAddr.Ip().c_str(), netAddr.Port());
                // sendto(_socketfd,buffer,sizeof(buffer),0,(struct sockaddr*)&sockaddr_in,socklen);//发送
            }
            //
            ///发/
            //
            InetAddr netAddr(peer);
            std::string message;
            std::cout << "server:";
            getline(std::cin, message);
            sendto(_socketfd, message.c_str(), message.size(), 0, (struct sockaddr *)&peer, socklen); // 发送
        }
        _isrunning = false;
    }

private:
    int _socketfd;
    uint16_t _prot;
    bool _isrunning;
};

客户端

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"
#include "Common.hpp"

static bool isrunning = false;
static std::string Clientname;
void useagge(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
              << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        useagge(argv[0]);
        exit(USAGE_ERROR);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::atoi(argv[2]);
    std::cout << "你是?" << std::endl;
    std::cin >> Clientname;
    // 创建socket;
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        LOG(WARNING, "%s", "Sock错误创建失败");
    }
    LOG(INFO, "%s", "sock创建成功");
    isrunning = true;
    struct sockaddr_in server;
    Setserver(server, serverip, serverport);
    struct sockaddr_in local;
    bzero(&local, sizeof(local));
    std::cout << "可以进行通信了!" << std::endl;
    
    while (isrunning)
    {
      
        std::cout << "server:";
        std::string message;
        getline(std::cin, message);
        sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, socklen); // 发送
        
        struct sockaddr_in peer;
        socklen_t socklen = sizeof(peer); // 初始化为sock
        char buffer[1024];
        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &socklen);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "server: " << buffer << std::endl;
        }
    }
    return 0;
}

第一版问题总结:

我一开始是想着这个流程,因为一开始服务端只是接受客户端,服务端不会发消息给客户端,所以我想在原基础上,让两端都可以接受和发送,当时就有想可以多线程实行接受和发的任务,但是觉得上线程太麻烦就决定是服务端发->客户端收->客户端发->服务器收,这一条链路实行,但是问题是,我把收发是写在循环里,而 recvfrom是非阻塞等待的,所以双方实际上永远等不到对方信息

所以实际上仍然是要让多线程实行接受和发的任务

第二版

服务端:

#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <thread>
#include <mutex>
#include "log.hpp"
#include "Common.hpp"
const static int defaultfd = -1;
 public:
    Udpserver(uint16_t port)
        : _socketfd(defaultfd), _prot(port), _isrunning(false)
    {
        _socklen = sizeof(_peer);
        InitServer();
    }
    void InitServer()
    {
        _socketfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_socketfd < 0)
        {
            LOG(WARNING, "%s", "sockfd创建失败");
        }
        LOG(INFO, "%s", "sock创建成功");
        // 填充sockaddr_in结构
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;    // 设置家族协议
        local.sin_port = htons(_prot); // 设置端口号 换成网络序列
        // port要经过网络传输给对面,先到网络,_port:主机序列-> 主机序列,转成网络序列
        // a. 字符串风格的点分十进制的IP地址转成 4 字节IP
        // b. 主机序列,转成网络序列
        // in_addr_t inet_addr(const char *cp) -> 同时完成 a & b
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // "192.168.3.1" -> 字符串风格的点分十进制的IP地址 -> 4字节IP
        local.sin_addr.s_addr = INADDR_ANY; // 随机ip地址 一般不能绑定确定ip地址

        // 开始绑定
        int n = bind(_socketfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "%s", "bind error");
            exit(BIND_ERROR);
        }
        LOG(INFO, "%s", "bind success");
        _isrunning = true;
    }

    void Start()
    void receive()
    {
        _isrunning = true;
        while (_isrunning)
        {
            
            char Inbuffer[1024] = {0}; // 初始化缓冲区
            struct sockaddr_in tempPeer;
            socklen_t tempSocklen = sizeof(tempPeer);
            ssize_t n = recvfrom(_socketfd, Inbuffer, sizeof(Inbuffer) - 1, 0, (struct sockaddr *)&tempPeer, &tempSocklen);
            if (n > 0)
            {
                std::cout <<"client say:";
                std::cout << Inbuffer << std::endl; // 成功就打印出来

                // sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);
                LOG(DEBUG, "建立 客户IP:%s 连接端口:%s", netAddr.Ip().c_str(), netAddr.Port());
                // sendto(_socketfd,buffer,sizeof(buffer),0,(struct sockaddr*)&sockaddr_in,socklen);//发送
                Inbuffer[n] = 0;
                std::lock_guard<std::mutex> lock(_peerMutex);
                _peer = tempPeer;
                _socklen = tempSocklen;
                std::cout << "client says:" << Inbuffer << std::endl;
            }
            //
            ///发/
            //
            InetAddr netAddr(peer);
            else
            {
                perror("recvfrom error");
            }
        }
    }
    void sent()
    {
        while (_isrunning)
        {
            std::string message;
            std::cout << "server: ";
            std::cin >> message;
            std::lock_guard<std::mutex> lock(_peerMutex);
            ssize_t sent = sendto(_socketfd, message.c_str(), message.size(), 0, (struct sockaddr *)&_peer, _socklen);
            if (sent == -1)
            {
                perror("sendto error");
            }
        }
        _isrunning = false;
    }
    void Start()
    {
        std::thread recvThread(&Udpserver::receive, this);
        std::thread sendThread(&Udpserver::sent, this);

        recvThread.detach();
        sendThread.detach();
        while (_isrunning)
        {
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
        close(_socketfd);
    }

private:
    int _socketfd;
    uint16_t _prot;
    bool _isrunning;
    //
    struct sockaddr_in _peer;
    socklen_t _socklen;

    std::mutex _peerMutex;
};

客户端:

#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <memory>
#include <thread>
#include "log.hpp"
#include "Common.hpp"

static bool isrunning = false;
static std::string Clientname;

class Client
{
public:
    Client(const std::string &server_ip, uint16_t server_port)
    {
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd == -1)
        {
            perror("socket creation failed");
            exit(EXIT_FAILURE);
        }

        memset(&_serverAddr, 0, sizeof(_serverAddr));
        _serverAddr.sin_family = AF_INET;
        _serverAddr.sin_port = htons(server_port);
        if (inet_pton(AF_INET, server_ip.c_str(), &_serverAddr.sin_addr) <= 0)
        {
            perror("inet_pton failed");
            close(_sockfd);
            exit(EXIT_FAILURE);
        }

        _isrunning = true;
    }
    void start()
    {
        std::cout << "你是? ";
        std::cin >> _clientName;
        std::cout << "可以进行通信了!" << std::endl;

        std::thread recvThread(&Client::receive, this);
        std::thread sendThread(&Client::send, this);

        recvThread.detach();
        sendThread.detach();

        while (_isrunning)
        {
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }

        close(_sockfd);
    }

private:
    void receive()
    {
        while (_isrunning)
        {
            char buffer[1024] = {0};
            struct sockaddr_in peer;
            socklen_t socklen = sizeof(peer);

            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &socklen);
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "server: " << buffer << std::endl;
            }
            else if (n == -1)
            {
                perror("recvfrom error");
            }
        }
    }
    void send()
    {
        while (_isrunning)
        {
            std::cout << _clientName << ": ";
            std::string message;
            std::cin >> message;

            ssize_t sent = sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&_serverAddr, sizeof(_serverAddr));
            if (sent == -1)
            {
                perror("sendto error");
            }
        }
    }

private:
    int _sockfd;
    struct sockaddr_in _serverAddr;
    bool _isrunning;
    std::string _clientName;
};

改进:

这一版上,我添加了多线程和锁,能让客户端服务端进行并发的运行,并收发消息

Windows客户端

由于我想让Windows朋友也能与我建立通信,所以我在客户端上进行了修改成Windows版本

 #define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <thread>
#include <memory>

#pragma comment(lib, "Ws2_32.lib")

static bool isrunning = false;
static std::string Clientname;

class Client
{
public:
    Client(const std::string& server_ip, uint16_t server_port)
    {
        WSADATA wsaData;
        int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
        if (result != 0)
        {
            std::cerr << "WSAStartup failed: " << result << std::endl;
            exit(EXIT_FAILURE);
        }

        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd == INVALID_SOCKET)
        {
            std::cerr << "socket creation failed: " << WSAGetLastError() << std::endl;
            WSACleanup();
            exit(EXIT_FAILURE);
        }

        memset(&_serverAddr, 0, sizeof(_serverAddr));
        _serverAddr.sin_family = AF_INET;
        _serverAddr.sin_port = htons(server_port);
        if (inet_pton(AF_INET, server_ip.c_str(), &_serverAddr.sin_addr) <= 0)
        {
            std::cerr << "inet_pton failed: " << WSAGetLastError() << std::endl;
            closesocket(_sockfd);
            WSACleanup();
            exit(EXIT_FAILURE);
        }

        _isrunning = true;
    }

    void start()
    {
        std::cout << "你是? ";
        std::cin >> _clientName;
        std::cout << "可以进行通信了!" << std::endl;

        std::thread recvThread(&Client::receive, this);
        std::thread sendThread(&Client::send, this);

        recvThread.detach();
        sendThread.detach();

        while (_isrunning)
        {
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }

        closesocket(_sockfd);
        WSACleanup();
    }

private:
    void receive()
    {
        while (_isrunning)
        {
            char buffer[1024] = { 0 };
            struct sockaddr_in peer;
            int peerlen = sizeof(peer);

            int n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &peerlen);
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "server: " << buffer << std::endl;
            }
            else if (n == SOCKET_ERROR)
            {
                std::cerr << "recvfrom error: " << WSAGetLastError() << std::endl;
            }
        }
    }

    void send()
    {
        while (_isrunning)
        {
            std::cout << _clientName << ": ";
            std::string message;
            std::cin >> message;

            int sent = sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&_serverAddr, sizeof(_serverAddr));
            if (sent == SOCKET_ERROR)
            {
                std::cerr << "sendto error: " << WSAGetLastError() << std::endl;
            }
        }
    }

private:
    SOCKET _sockfd;
    struct sockaddr_in _serverAddr;
    bool _isrunning;
    std::string _clientName;
};

void useagge(const std::string& proc)
{
    std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
        << std::endl;
}

int main()
{
    std::string serverip;
    std::string portStr;
    std::cout << "输入服务器ip: ";
    std::cin >> serverip;
    std::cout << "输入服务器端口号: ";
    std::cin >> portStr;

    uint16_t serverport;
    try
    {
        serverport = static_cast<uint16_t>(std::stoi(portStr));
    }
    catch (const std::exception& e)
    {
        std::cerr << "无效的端口号: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    std::unique_ptr<Client> csvr = std::make_unique<Client>(serverip, serverport); // C++14
    csvr->start();
    return 0;
}

一些小问题

由于我用的云服务器,大部分端口号是默认禁用的,所以端口号要自己进行开放,我用的阿里云,开放端口的地方在这里

还可以进行的改进

1.目前服务端和客户端仍然是1对1的关系,如果有第二个用户上线,就会挤占第一个用户,所以这里可以用一个vector来对用户的ip进行管理,来统一收所有用户消息

2.目前还没有将用户的名字传输给服务端,后续可以加上

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部