Linux下libsocket库的使用

一、libsocket简介

libsocket是一个适用于C和C++的套接字库,支持Linux、FreeBSD、Solaris系统上的TCP、UDP及Unix套接字(DGRAM和STREAM),使套接字的使用变得简单而简洁。其github地址为:https://github.com/dermesser/libsocket

二、编译libsocket

从github下载libsocket源码,拷贝到Linux系统中,解压缩,进入源码目录,执行:

bash 复制代码
cmake . -DCMAKE_INSTALL_PREFIX=$(pwd)/install
make
make install

然后libsocket源码目录的install/bin目录下就会生成libsocket的库文件(.so文件):

三、libsocket的使用例程

libsocket源码目录的examples目录下存放着其C语言的使用例程,examples++存放着其C++的使用例程:

其中,对于C语言,examples目录下的:

echo_dgram_connect_client.c:演示如何使用UDP套接字,涵盖连接模式与非连接模式。

unix_stream_client.c, unix_stream_server.c:展示UNIX STREAM套接字作为回显服务器/客户端的应用。

unix_dgram_client.c, unix_dgram_server.c:演示UNIX DGRAM套接字作为传输文本的简单服务器/客户端。

multicast-listen.c:演示如何使用libinetsocket实现多播网络。

对于C++语言,examples++目录下的:

http.cpp, http_2.cpp:采用略有不同的两种简单HTTP客户端实现。

server.cpp, client.cpp:TCP客户端与服务器。

unix_client_dgram.cpp:使用UNIX DGRAM套接字向系统日志写入消息。

echo_server.cpp, echo_client_conn.cpp, echo_client_sndto.cpp:UDP客户端/服务器(两个客户端:一个使用sendto()函数,另一个使用已连接的数据报套接字)。

unix_client_stream.cpp, unix_server_stream.cpp:使用UNIX STREAM套接字的客户端/服务器。

四、例程echo_client_sndto.cpp演示

libsocket源码的examples++目录下的echo_client_sndto.cpp是UDP客户端的使用例子:

为了更便于演示,我们将echo_client_sndto.cpp修改为:

cpp 复制代码
#include <unistd.h>
#include <cstring>
#include <iostream>
#include "libsocket/exception.hpp"
#include "libsocket/inetclientdgram.hpp"


/*
 * Sends and receives messages using the sndto() and rcvfrom functions.
 */

int main(void) {
    using std::string;

    string host = "192.168.31.108";
    string port = "52381";

    string from1;
    string from2;

    from1.resize(64);
    from2.resize(64);

    string text = "Hello, Server!";
    string buf;

    buf.resize(32);

    libsocket::inet_dgram_client sock(LIBSOCKET_IPv4);

    try {
        sock.sndto(text, host, port);
        sock.rcvfrom(buf, from1, from2);
        std::cout << "Answer from " << from1 << ":" << from2 << " - " << buf
                    << " - " << buf.size() << std::endl;
        std::cout.flush();
    } catch (const libsocket::socket_exception& exc) {
        std::cerr << exc.mesg;
    }

    sock.destroy();
    
    return 0;
}

修改例子后,编译出来的echo_client_sndto会连接ip为192.168.31.108,端口为52381的UDP服务器。连接上后,会发送"Hello, Server!"给服务器,然后阻塞等待服务器的消息。

编译echo_client_sndto.cpp,其中/home/ev/source/libsocket-2.5.0为libsocket的源码目录。编译后,会生成echo_client_sndto:

bash 复制代码
g++ echo_client_sndto.cpp -o echo_client_sndto -g -I/home/ev/source/libsocket-2.5.0/install/include -L/home/ev/source/libsocket-2.5.0/install/lib -lsocket++

微软商店下载"TCP UDP网络调试工具"。通过该工具启动一个UDP服务器:

运行echo_client_sndto,可以看到该UDP客户端发送了"Hello, Server!"给服务器:

五、修改echo_client_sndto.cpp,使之可以设置接收超时时间

libsocket提供的UDP客户端例子echo_client_sndto有一个问题:如果不特意设置,inet_dgram::rcvfrom函数默认为阻塞模式,如果一直收不到UDP服务器的消息,该函数会一直阻塞在那里:

cpp 复制代码
/**
 * @brief rcvfrom for C++ strings, implemented consistently
 *
 * Works like every other `rcvfrom()` library call, but places the received
 memory to the C++ string `buf`.
 *
 * @param buf The string where the received data should be stored at. Its length
 determines how much data will be stored; the library
 * will not resize `buf`.
 * @param srchost String to place the remote host's name to
 * @param srcport Like `srchost` but for the remote port
 * @param rcvfrom_flags Flags to be passed to `recvfrom(2)`
 * @param numeric If remote host and port should be saved numerically

 * @retval >0 n bytes of data were read into `buf`.
 * @retval 0 Peer sent EOF
 * @retval -1 Socket is non-blocking and returned without any data.
 *
 * Every error makes the function throw an exception.
 */
ssize_t inet_dgram::rcvfrom(string& buf, string& srchost, string& srcport,
                            int rcvfrom_flags, bool numeric) {
    ssize_t bytes;

    using std::unique_ptr;
    unique_ptr<char[]> cbuf(new char[buf.size()]);

    memset(cbuf.get(), 0, buf.size());

    bytes = rcvfrom(cbuf.get(), static_cast<size_t>(buf.size()), srchost,
                    srcport, rcvfrom_flags,
                    numeric);  // calling inet_dgram::rcvfrom(void*, size_t,
                               // string&, string&, int, bool)

    buf.resize(bytes);

    buf.assign(cbuf.get(), bytes);

    return bytes;
}

通过查阅libsocket源码可以发现:inet_dgram::rcvfrom函数内部调用了recvfrom_inet_dgram_socket函数,而recvfrom_inet_dgram_socket函数内部调用了Linux底层的recvfrom函数。对于recvfrom函数,可以通过setsockopt或ioctl设置其接收超时时间:

cpp 复制代码
/**
 * @brief Receive data from a UDP/IP socket
 *
 * Receives data like `recvfrom(2)`. Pointers may be `NULL`, then the
 * information (e.g. the source port) is lost (you may use NULL pointers if
 * you're not interested in some information)
 *
 * @param sfd The socket file descriptor.
 * @param buffer Where the data will be written
 * @param size The size of `buffer`
 * @param src_host Where the sending host's name/IP will be stored
 * @param src_host_len `src_host`'s length
 * @param src_service Where the port on remote side will be written to
 * @param src_service_len `src_service`'s length
 * @param recvfrom_flags Flags for `recvfrom(2)`
 * @param numeric `LIBSOCKET_NUMERIC` if you want the names to remain
 * unresolved.
 *
 * @retval n *n* bytes of data were received.
 * @retval 0 Peer sent EOF.
 * @retval <0 An error occurred.
 */
ssize_t recvfrom_inet_dgram_socket(int sfd, void *buffer, size_t size,
                                   char *src_host, size_t src_host_len,
                                   char *src_service, size_t src_service_len,
                                   int recvfrom_flags, int numeric) {
    struct sockaddr_storage client;

#ifdef _TRADITIONAL_RDNS
    struct sockaddr_storage oldsockaddr;
    socklen_t oldsockaddrlen = sizeof(struct sockaddr_storage);
    struct hostent *he;
    void *addrptr;
    size_t addrlen;
    uint16_t sport = 0;
#endif

    ssize_t bytes;

#ifndef _TRADITIONAL_RDNS
    int retval;
#endif

#ifdef VERBOSE
    const char *errstr;
#endif
    if (sfd < 0) return -1;

    if (buffer == NULL || size == 0)
        return -1;
    else
        memset(buffer, 0, size);

    if (src_host) memset(src_host, 0, src_host_len);
    if (src_service) memset(src_service, 0, src_service_len);

    socklen_t stor_addrlen = sizeof(struct sockaddr_storage);

    if (-1 == check_error(bytes = recvfrom(sfd, buffer, size, recvfrom_flags,
                                           (struct sockaddr *)&client,
                                           &stor_addrlen)))
        return -1;

    if (src_host_len > 0 ||
        src_service_len >
            0)  // If one of the things is wanted. If you give a null pointer
                // with a positive _len parameter, you won't get the address.
    {
        if (numeric == LIBSOCKET_NUMERIC) {
            numeric = NI_NUMERICHOST | NI_NUMERICSERV;
        }

        // getnameinfo() doesn't work on FreeBSD (here)
#ifndef _TRADITIONAL_RDNS
        if (0 !=
            (retval = getnameinfo(
                 (struct sockaddr *)&client, sizeof(struct sockaddr_storage),
                 src_host, src_host_len, src_service, src_service_len,
                 numeric)))  // Write information to the provided memory
        {
#ifdef VERBOSE
            errstr = gai_strerror(retval);
            debug_write(errstr);
#endif
            return -1;
        }
#endif

        // so use traditional methods
#ifdef _TRADITIONAL_RDNS
        if (-1 == check_error(getsockname(sfd, (struct sockaddr *)&oldsockaddr,
                                          &oldsockaddrlen)))
            return -1;

        if (oldsockaddrlen >
            sizeof(struct sockaddr_storage))  // If getsockname truncated the
                                              // struct
            return -1;

        if (oldsockaddr.ss_family == AF_INET) {
            addrptr = &(((struct sockaddr_in *)&client)->sin_addr);
            addrlen = sizeof(struct in_addr);
            sport = ntohs(((struct sockaddr_in *)&client)->sin_port);
        } else if (oldsockaddr.ss_family == AF_INET6) {
            addrptr = &(((struct sockaddr_in6 *)&client)->sin6_addr);
            addrlen = sizeof(struct in6_addr);
            sport = ntohs(((struct sockaddr_in6 *)&client)->sin6_port);
        }

        if (NULL ==
            (he = gethostbyaddr(addrptr, addrlen, oldsockaddr.ss_family))) {
            check_error(-1);
            return -1;
        }

        strncpy(src_host, he->h_name, src_host_len);
        snprintf(src_service, src_service_len, "%u", sport);
#endif
    }

    return bytes;
}

libsocket源码提供了set_sock_opt函数,其内部调用了setsockopt,所以可以用它来设置接收超时时间:

cpp 复制代码
/**
 * @brief Set socket options on the underlying socket.
 *
 * @return The return value of setsockopt(2).
 *
 * Sets socket options using setsockopt(2). See setsockopt(2), tcp(7), udp(7),
 * unix(7) for documentation on how to use this function.
 */
int socket::set_sock_opt(int level, int optname, const char* optval,
                         socklen_t optlen) const {
    return setsockopt(sfd, level, optname, optval, optlen);
}

修改echo_client_sndto.cpp为:

cpp 复制代码
#include <unistd.h>
#include <cstring>
#include <iostream>
#include "libsocket/exception.hpp"
#include "libsocket/inetclientdgram.hpp"


/*
 * Sends and receives messages using the sndto() and rcvfrom functions.
 */

int main(void) {
    using std::string;

    string host = "192.168.31.108";
    string port = "52381";

    string from1;
    string from2;

    from1.resize(64);
    from2.resize(64);

    string text = "Hello, Server!";
    string buf;

    buf.resize(32);

    libsocket::inet_dgram_client sock(LIBSOCKET_IPv4);

    try {
        timeval tv = {3, 0};    //设置超时时间为3秒
        sock.set_sock_opt(SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(timeval));
        sock.sndto(text, host, port);
        sock.rcvfrom(buf, from1, from2);
        std::cout << "Answer from " << from1 << ":" << from2 << " - " << buf
                    << " - " << buf.size() << std::endl;
        std::cout.flush();
    } catch (const libsocket::socket_exception& exc) {
        std::cerr << exc.mesg;
    }

    sock.destroy();
    
    return 0;
}

修改后,当echo_client_sndto发送消息给UDP服务器后,如果超过3秒没有收到服务器的回复,则rcvfrom函数就会超时返回。catch块会捕获到超时的异常并处理:

相关推荐
HRTOS2 分钟前
HRTOS Shell继续完善:优化printf缓冲区与字符串越界保护,国庆后随新版本更新
运维·单片机·嵌入式硬件·算法·51单片机
智能RPA4 分钟前
农业与矿业行业智能体自动化平台对比评测(计量与巡检场景)
运维·人工智能·python·自动化·agent·rpa
沫璃染墨13 分钟前
《从零入门Linux系统篇(五十一):线程篇·四——pthread线程库详解:从线程创建到终止与分离》
linux·运维·服务器·开发语言·c++·系统架构·线程
YYRAN_ZZU16 分钟前
GMSL开发加解串配置介绍
linux·嵌入式硬件
墨家前任巨子25 分钟前
定时关机脚本
运维
云计算练习生25 分钟前
什么是上下文切换?进程切换时 CPU 到底在保存什么
linux·服务器·操作系统·进程切换
用户05101225729626 分钟前
RGA(二)——RK3588 的核拓扑与能力差异
linux·嵌入式
ESDWAN27 分钟前
企业组网后网络频繁掉线,常见故障排查指南
运维·网络·架构
用户05101225729627 分钟前
RGA(一)——基础知识
linux·嵌入式
qetfw31 分钟前
Debian 综合服务实训:Apache2、Samba、LDAP、vsftpd 与 OpenVPN
linux·服务器·debian