【Linux网络】UDP套接字【实现英汉转化】

发布于:2025-05-29 ⋅ 阅读:(20) ⋅ 点赞:(0)

------实现一个简单的英译汉的功能------

一:Udp Server服务端

先看Udp Server服务端的整体框架:

Log lg;

enum{
    SOCKET_ERR = 1,
    BIND_ERR
};

class UdpServer
{
public:
    UdpServer(uint16_t port)
        :port_(port)
    {}
    ~UdpServer()
    {}
public:
private:
    int sockfd_;    // 网络文件描述符
    std::string ip_;    // IP
    uint16_t port_;     // 表明服务器进程的端口号
    bool isrunning;     // 服务器是否运行
};

1.1、socket—创建套接字

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
  • 第一个参数 domain:协议域,决定了 socket 套接字的地址类型,在通信时必须采用相应的地址。例如 AF_INET 决定了采用 IPv4 地址(32位)与端口号(16位)的组合;AF_INET6 决定了采用 IPv6 地址(128位)与端口号(16位)的组合;AF_UNIX 决定了采用一个绝对路径名作为地址。
  • 第二个参数 type:指定 socket 套接字的类型,是 SOCK_STREAM(流式套接字)还是 SOCK_DGRAM(数据报式套接字)等。
  • 第三个参数 protocol: 协议字段,表明要指定的协议,如 IPPROTO_TCP(TCP传输协议)、PPTOTO_UDP(UDP 传输协议)等等。
  • 返回值:返回一个文件描述符,因为创建套接字的本质就是打开一个文件。
    void Init()
    {
        // 1.创建udp socket套接字
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
        if(sockfd_ < 0)
        {
            lg(Fatal, "socket create error, errno: %d, error message: %s", errno, strerror(errno));
            exit(SOCKET_ERR);
        }
        lg(Info, "socket create success, sockfd: %d", sockfd_);
    }

1.2、bind—将套接字与特定IP和特定端口号port进行绑定

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

第一个参数 sockfd:socket 函数的返回值,一个文件描述符。

第二个参数 addr:要传输的套接字种类,取地址之后需要强转成统一套接字(struct sosckaddr*)

第三个参数 addrlen:传输的套接字的大小 

    void Init()
    {
        // 1.创建udp socket套接字
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
        if(sockfd_ < 0)
        {
            lg(Fatal, "socket create error, errno: %d, error message: %s", errno, strerror(errno));
            exit(SOCKET_ERR);
        }
        lg(Info, "socket create success, sockfd: %d", sockfd_);

        // 2.绑定端口号
        struct sockaddr_in local;
        bzero(&local, sizeof(local));   // 将local内部清零
        // 填充 socket套接字相关字段
        local.sin_family = AF_INET;     // 当前结构体的地址类型
        local.sin_port = htons(port_);  // 当前服务器的端口号,须将主机序列转换成网络序列
        local.sin_addr.s_addr = inet_addr(ip_.c_str());     // 网络中的ip序列是4字节传输的,所以将字符串风格的ip地址转换成可以在网络中传输的4字节Ip网络序列
        // 开始绑定,bind本质就是将上面一系列参数设置进内核,到指定的套接字中
        int n = bind(sockfd_, (struct sockaddr*)&local, sizeof(local));
        if(n < 0)
        {
            lg(Fatal, "bind error, error: %d, err message: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success, errno: %d, err message: %s", errno, strerror(errno));
    }

要网络通信, 就要使用到 socket套接字,那么首先就必须定义一个 struct sockaddr_in 类型的结构体对象,该对象中有4个字段:sin_family 字段表示当前结构体的地址类型,sin_port 字段表示端口号,sin_addr 字段表示 IP 地址,sin_zero 字段表示填充。其中 sin_zero 字段一般我们不做处理,只处理其他三个字段即可。首先 sin_family 字段必须和 socket 函数中的 domain 字段保持一致,因为是在网络中通信传输的,所以 sin_port 和 sin_addr 必须将我们当前的 port 和 ip 转换成可以在网络中通信的序列,即使用 htons 函数将端口号从主机转成网络序列,使用 inet_addr 函数将 我们平常方便查看的字符串 ip 情况转成可以在网络中传输的 4 字节的 ip 情况。而 sin_addr 中只有 s_addr 字段,最总就是将该字段填充即可。

所以,所谓 bind,本质就是将我们的套接字与一个特定的 ip 和一个特定的端口号 port 相关联,这样就能与世界上的特定主机上的某一个进程相互连通。

1.3、recvfrom—从服务器的套接字中读取数据

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
  • 第一个参数 sockfd:表示当前被读取数据的服务器的套接字 
  • 第二个参数 buf:表示要接收数据的缓冲区
  • 第三个参数 len:要接收的缓冲区的大小
  • 第四个参数 flags:默认设为0,表示阻塞
  • 第五个参数 src_addr:输出型参数,表示获取客户端的套接字信息,因为我们是从服务器的套接字读取数据的,读取的数据给谁呢?毫无疑问是给指定的客户端,所以就要获取到客户端的 ip 和端口号。因为是 UDP 网络通信,所以传的是 struct sockaddr_in 类型的对象地址,并将其强转。
  • 第六个参数 addrlen:struct sockaddr_in 对象的大小
  • 返回值:成功返回获取到数据的字节数,失败返回 -1
    void Run()
    {
        isrunning = true;
        char inbuffer[SIZE];
        while (isrunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer), 0, (struct sockaddr*)&client, &len);
            if(n < 0)
            {
                lg(Warning, "recvfrom error, errno: %d, err message: %s", errno, strerror(errno));
                continue;
            }
            // 此时服务端的数据,我们已经拿到了并将其存至了 inbuffer里面
            inbuffer[n] = 0;    //在结尾添加'\0'
            std::string info = inbuffer;   
            std::string echo_string = "server say@ " + info;
        }
    }

1.4、sendto—向指定套接字中发送数据

#include <sys/types.h>
#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
  • 第一个参数 sockfd:表示当前被读取数据的服务器的套接字  
  • 第二个参数 buf:要发送的数据缓冲区
  • 第三个参数 len:发送的数据缓冲区的大小
  • 第四个参数 flags:默认设为0
  • 第五个参数 src_addr:接收方的套接字信息。这里的接收方是客户端
  • 第六个参数 addrlen:struct sockaddr_in 对象的大小
  • 返回值:成功返回获取到数据的字节数,失败返回 -1
    void Run()
    {
        isrunning = true;
        char inbuffer[SIZE];
        while (isrunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer), 0, (struct sockaddr*)&client, &len);
            if(n < 0)
            {
                lg(Warning, "recvfrom error, errno: %d, err message: %s", errno, strerror(errno));
                continue;
            }
            // 此时服务端的数据,我们已经拿到了并将其存至了 inbuffer里面
            inbuffer[n] = 0;    //在结尾添加'\0'
            std::string info = inbuffer;   
            std::string echo_string = "server say@ " + info;

            // 向客户端发送信息
            ssize_t r = sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (const sockaddr*)&client, len);
            if(r < 0)
            {
                lg(Warning, "sendto error, errno: %d, err message: %s", errno, strerror(errno));
                continue;
            }
        }
    }

netstat -nlup 【net表示网络,stat表示状态,n 表示将所有能显示成数字的信息都以数字的形式显示出来,u表示udp,p 表示PID信息】测试一下:

注意:对于网络信息的发送和接收,我们不需要自己来进行主机转网络,网络转主机来转换的。数据内容会自动由 recvfrom 和 sendto 函数来转换解决。

1.5、绑定 IP 与端口号

云服务器禁止直接 bind 绑定公网 IP

一般建议服务端代码在 ip 绑定时使用 0,0,0,0,表示任意地址绑定。因为这样只要是发送到这台主机的数据信息,我们的这个服务器进程都能收到,在根据端口号向上交付。且一个服务器可能含有多个 ip,所以如果服务端的进程只绑定一个固定的 ip 的话,那么通过其他 ip 发送到这个服务器的数据,这个进程就无法收到该信息。所以,我们一般在设置服务器 ip 时通常设置服务端套接字的 local.sin_addr 字段:

local.sin_addr.s_addr = 0
// 或者
local.sin_addr.s_addr = INADDR_ANY

本地环回地址 IP(通常用来cs)

本地环回地址:127.0.0.1。

任何服务器都可以绑定 127.0.0.1 这个 ip 地址,绑定了这个地址后,该进程不会向网络中发送数据,但还是会通过网络协议栈,通常用来进程本主机的测试。

端口号的绑定

服务端的端口在被绑定时,不能想绑定哪个就绑定哪个。一般端口号 [0,1023] 是系统内定的端口号,有其自己固定的应用层协议使用。例如:http 的端口号是80,https 的端口号是443等等。端口号的选择一般范围在 [1024,65535] 之间。

1.6、Udp Server服务端总代码

// UdpServer.hpp
#pragma once

#include <iostream>
#include <string>
#include <string.h>
#include <strings.h>    // bzero
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>

#include "Log.hpp"
Log lg;

uint16_t defaultport = 8888;
std::string defaultip = "0.0.0.0";
using func_t = std::function<std::string(const std::string&)>;

enum{
    SOCKET_ERR = 1,
    BIND_ERR
};

class UdpServer
{
public:
    UdpServer(uint16_t &port = defaultport, const std::string &ip = defaultip)
        :sockfd_(0), port_(port), ip_(ip), isrunning(false)
    {}
    ~UdpServer()
    {
        if(sockfd_ > 0)
            close(sockfd_);
    }
public:
    void Init()
    {
        // 1.创建udp socket套接字
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
        if(sockfd_ < 0)
        {
            lg(Fatal, "socket create error, errno: %d, error message: %s", errno, strerror(errno));
            exit(SOCKET_ERR);
        }
        lg(Info, "socket create success, sockfd: %d", sockfd_);

        // 2.绑定端口号
        struct sockaddr_in local;
        bzero(&local, sizeof(local));   // 将local内部清零
        // 填充 socket套接字相关字段
        local.sin_family = AF_INET;     // 当前结构体的地址类型
        local.sin_port = htons(port_);  // 当前服务器的端口号,须将主机序列转换成网络序列
        local.sin_addr.s_addr = inet_addr(ip_.c_str());     // 网络中的ip序列是4字节传输的,所以将字符串风格的ip地址转换成可以在网络中传输的4字节Ip网络序列
        // 开始绑定,bind本质就是将上面一系列参数设置进内核,到指定的套接字中
        int n = bind(sockfd_, (struct sockaddr*)&local, sizeof(local));
        if(n < 0)
        {
            lg(Fatal, "bind error, error: %d, err message: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success, errno: %d, err message: %s", errno, strerror(errno));
    }

    void Run(func_t func)   // 让服务器执行特定的函数功能
    {
        isrunning = true;
        char inbuffer[SIZE];
        while (isrunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer), 0, (struct sockaddr*)&client, &len);
            if(n < 0)
            {
                lg(Warning, "recvfrom error, errno: %d, err message: %s", errno, strerror(errno));
                continue;
            }
            // 此时服务端的数据,我们已经拿到了并将其存至了 inbuffer里面
            inbuffer[n] = 0;    //在结尾添加'\0'
            std::string info = inbuffer;   
            // std::string echo_string = "server say@ " + info;
            std::string echo_string = func(info);

            // 向客户端发送信息
            ssize_t r = sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (const sockaddr*)&client, len);
            if(r < 0)
            {
                lg(Warning, "sendto error, errno: %d, err message: %s", errno, strerror(errno));
                continue;
            }
        }
    }
private:
    int sockfd_;    // 网络文件描述符
    std::string ip_;    // IP
    uint16_t port_;     // 表明服务器进程的端口号
    bool isrunning;     // 服务器是否运行
};
// UdpServer.cc

#include <iostream>
#include <string>
#include "UdpServer.hpp"

void Usage(std::string proc)
{
    std::cout << "proc: " << proc << ", port[1024+]" << std::endl;
}

std::string Handler(const std::string& str)
{   
    std::string res = "Server a message: ";
    res += str;
    std::cout << res << std::endl;

    return res;
}

// ./udpserver port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t port = std::stoi(argv[1]);
    UdpServer* usvr = new UdpServer(port);
    usvr->Init();
    usvr->Run(Handler);
    return 0;
}

二:Udp Client客户端

一个端口号只能被一个进程bind,对 server 是如此,对 client 也是。所以客户端也要绑定端口号,只不过不需要用户显示的绑定,而是由操作系统自由随机选择。这样可以避免端口号发生冲突。其次其实对客户端来讲,端口号是多少并不重要,只要能够保证该进程在主机上的唯一性就可以。因为一般都是客户端主动的向服务端发送信息数据。所以客户端一定要能知道服务端的端口号。相反服务端的端口号是确定的。所以在编写客户端代码时,无需进行绑定端口号,直接往服务器中发送数据。在 UDP 首次发送数据的时候,系统会自动地为我们动态bind绑定。

// UdpClient.cc

#include "Log.hpp"
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <cstring>
#include <string>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

using namespace std;
Log lg;

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


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

    // 建立套接字,向服务器当中发信息
    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);    
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
    socklen_t len = sizeof(server);

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        std::cout << "socket error" << std::endl;
        return 1;
    }
    // 套接字创建完毕,直接就可以发信息了
    std::string message;
    char buffer[1024];
    while(true)
    {
        std::cout << "Please Enter@ ";
        getline(cin, message);      // 要发送给服务端的数据
        
        // 数据有了,给谁发?已经有了服务端的结构体信息了,那就给它发
        sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, len);

        // 只要给服务器发了数据,服务器就会很快的识别到并作出处理
        // 接收服务器数据
        struct sockaddr_in temp;    //从服务器获取下来给我们应答的数据
        socklen_t tlen = sizeof(temp);
        ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &tlen);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << buffer << std::endl;
        }
    }
    close(sockfd);

    return 0;
}

三:指令处理

因为服务端收到来自客户端的数据后,会对数据进行加工处理,之后再返回给客户端。那么我们就可以将对数据处理的方法独立出来,作为一个函数来传给服务端的 Run 方法。这样就大大提高了代码的维护性。例如,客户端输入命令,服务端接收到该命令,并将执行结果返还给客户端。

看一个函数 popen:

#include <stdio.h>

FILE *popen(const char *command, const char *type);

int pclose(FILE *stream);

因为对于指令的执行,需要单独新起一个进程,即需要 fork 创建子进程。而 popen 函数不仅仅能将一个文件打开,还可以调用 fork 函数创建子进程,让子进程进行程序替换执行对应的命令。

  • 参数 command:表示要执行的命令
  • 参数 type:" r "表示读取数据," w "表示写入数据。
  •  返回值:成功返回文件指针,失败返回 NULL,错误原因存至 errno 全局变量中。

所以,只需要将要执行的方法函数做为参数传进 Run 中即可。

// 指令处理
bool SafeCheck(const std::string &cmd)
{
    std::vector<std::string> v = {"mv", "cp", "git", "su", "top"};
    for(auto& e : v)
    {
        if(cmd.find(e) != std::string::npos)    // 找到啦对应的指令
        {
            return false;
        }
    }
    return true;
}

std::string ExcuteCommand(const std::string& cmd)
{
    if(!SafeCheck(cmd))     
    {
        return "The command is prohibited";
    }

    FILE* fp = popen(cmd.c_str(), "r");
    if(fp == nullptr)
    {
        perror("popen");
        return "error";
    }

    // 读取执行的结果
    std::string result;
    char buffer[4096];
    while (true)
    {
        char* ok = fgets(buffer, sizeof(buffer), fp);
        if(ok == nullptr)
            break;
        result += buffer;
    }
    pclose(fp);
    return result;
}

// ./udpserver port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t port = std::stoi(argv[1]);
    // UdpServer* usvr = new UdpServer(port);
    std::unique_ptr<UdpServer> usvr(new UdpServer(port));
    usvr->Init();
    // usvr->Run(Handler);
    usvr->Run(ExcuteCommand);
    return 0;
}

四:基于 Udp 套接字的群聊

4.1、Server 服务端

因为是群聊,那么聊天的人就不只是一两个人。即再群聊中发送的信息是会被群聊中的所有用户所看到的,所以就需要在客户端维持一个在线用户列表,这里采用 unordered_map 结构,结构的 key 值填入用户的 ip 值,结构的 value 填入用户端的套接字。即当用户进入群聊的时候,客户端进行用户检查,该用户是否在群聊中,若不在群聊中,就将其添加到在线群聊列表中,之后客户端将该用户发送的消息在转发给群聊中的所有用户。

#pragma once

#include <iostream>
#include <string>
#include <string.h>
#include <strings.h>    // bzero
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <unordered_map>

#include "Log.hpp"
Log lg;

uint16_t defaultport = 8888;
std::string defaultip = "0.0.0.0";
using func_t = std::function<std::string(const std::string&)>;

enum{
    SOCKET_ERR = 1,
    BIND_ERR
};

class UdpServer
{
public:
    UdpServer(uint16_t &port = defaultport, const std::string &ip = defaultip)
        :sockfd_(0), port_(port), ip_(ip), isrunning(false)
    {}
    ~UdpServer()
    {
        if(sockfd_ > 0)
            close(sockfd_);
    }
public:
    void Init()
    {
        // 1.创建udp socket套接字
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
        if(sockfd_ < 0)
        {
            lg(Fatal, "socket create error, errno: %d, error message: %s", errno, strerror(errno));
            exit(SOCKET_ERR);
        }
        lg(Info, "socket create success, sockfd: %d", sockfd_);

        // 2.绑定端口号
        struct sockaddr_in local;
        bzero(&local, sizeof(local));   // 将local内部清零
        // 填充 socket套接字相关字段
        local.sin_family = AF_INET;     // 当前结构体的地址类型
        local.sin_port = htons(port_);  // 当前服务器的端口号,须将主机序列转换成网络序列
        local.sin_addr.s_addr = inet_addr(ip_.c_str());     // 网络中的ip序列是4字节传输的,所以将字符串风格的ip地址转换成可以在网络中传输的4字节Ip网络序列
        // 开始绑定,bind本质就是将上面一系列参数设置进内核,到指定的套接字中
        int n = bind(sockfd_, (struct sockaddr*)&local, sizeof(local));
        if(n < 0)
        {
            lg(Fatal, "bind error, error: %d, err message: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success, errno: %d, err message: %s", errno, strerror(errno));
    }

    void CheckUser(const struct sockaddr_in& client, const std::string clientip, const uint16_t clientport)
    {
        auto iter = online_user_.find(clientip);
        if(iter == online_user_.end())
        {
            // 找不到
            online_user_.insert({clientip, client});
            std::cout << "[" << clientip << ":" << clientport << "] add to online_user..." << std::endl;
        }
    }

    void Broadcast(const std::string& info, const std::string clientip, const uint16_t clientport)
    {
        for(const auto& user : online_user_)
        {
            std::string message = "[" + clientip + ":" + std::to_string(clientport) + "]#";
            message += info;
            socklen_t len = sizeof(user.second);
            sendto(sockfd_, message.c_str(), message.size(), 0, (const sockaddr*)(&user.second), len);
        }
    }

    // 让服务器执行特定的函数功能
    // void Run(func_t func)   
    void Run()   
    {
        isrunning = true;
        char inbuffer[SIZE];
        while (isrunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer), 0, (struct sockaddr*)&client, &len);
            if(n < 0)
            {
                lg(Warning, "recvfrom error, errno: %d, err message: %s", errno, strerror(errno));
                continue;
            }

            uint16_t clientport = ntohs(client.sin_port);
            std::string clientip = inet_ntoa(client.sin_addr);
            CheckUser(client, clientip, clientport);    // 判断用户是否在群聊中

            std::string info = inbuffer;
            Broadcast(info, clientip, clientport);      // 给每一个群聊中的用户都发送消息


            // // 此时服务端的数据,我们已经拿到了并将其存至了 inbuffer里面
            // inbuffer[n] = 0;    //在结尾添加'\0'
            // std::string info = inbuffer;   
            // // std::string echo_string = "server say@ " + info;
            // std::string echo_string = func(info);

            // // 向客户端发送信息
            // ssize_t r = sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (const sockaddr*)&client, len);
            // if(r < 0)
            // {
            //     lg(Warning, "sendto error, errno: %d, err message: %s", errno, strerror(errno));
            //     continue;
            // }
        }
    }
private:
    int sockfd_;    // 网络文件描述符
    std::string ip_;    // IP
    uint16_t port_;     // 表明服务器进程的端口号
    bool isrunning;     // 服务器是否运行

    std::unordered_map<std::string, struct sockaddr_in> online_user_;
};
// Main.cc
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include "UdpServer.hpp"

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << ", port[1024+]" << std::endl;
}

// ./udpserver port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<UdpServer> usvr(new UdpServer(port));
    usvr->Init();
    usvr->Run();
    return 0;
}

4.2、Client 客户端

群聊中的客户端共有两个功能,一是用户发送数据,二是获取群聊中其他用户发送的信息,即本质就是获取服务端中的信息,因为其他用户发送信息数据是往服务器上发送的。所以就需要两个线程来执行两个不同的功能。一线程来发送数据给服务器,二线程是获取服务器上其他用户发送的信息。

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <cstring>
#include <string>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

using namespace std;
Log lg;

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << ", serverip serverport" << 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;
            std::cerr << buffer << std::endl;
        }

    }
    
}

// 给服务端发送消息
void* sender_message(void* args)
{
    ThreadData* td = static_cast<ThreadData*>(args);
    std::string message;
    socklen_t len = sizeof(td->server);

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

        // 给谁发
        sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)(&td->server), len);
    }
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    // 获取 serverip 和 serverport
    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());
    socklen_t len = sizeof(td.server);

    td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(td.sockfd < 0)
    {
        std::cout << "socket error" << std::endl;
        return 1;
    }
    
    td.serverip = serverip;
    pthread_t recvr, sender;    // 两个线程
    pthread_create(&recvr, nullptr, recv_message, &td);
    pthread_create(&sender, nullptr, sender_message, &td);

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

    close(td.sockfd);
    return 0;
}

五:结语

今天的分享到这里就结束了,今日给大家分享了关于进程的终止情况。如果各位看官觉得还不错的话,可以三连支持一下欧。各位的支持就是捣蛋鬼前进的最大动力!


网站公告

今日签到

点亮在社区的每一天
去签到