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块会捕获到超时的异常并处理:

相关推荐
郝学胜-神的一滴5 分钟前
深入理解Linux中的Try锁机制
linux·服务器·开发语言·c++·程序人生
Anakki5 分钟前
企业级 Elastic Stack 集成架构:Spring Boot 3.x 与 Elasticsearch 8.x 深度实践指南
运维·jenkins·springboot·elastic search
Java后端的Ai之路6 分钟前
【AutoDL算力平台】-MobaXterm 连接 AutoDL 并上传文件资源(图文 + 实操)
服务器·网络·mobaxterm·autodl算力平台
阿巴~阿巴~9 分钟前
NAT技术:互联网连接的隐形桥梁
服务器·网络·网络协议·架构·智能路由器·nat·正反向代理
DevOps-IT11 分钟前
HTTP状态码(常见 HTTP Status Code 查询)
运维·服务器·网络·网络协议·http
sim202012 分钟前
把etcd分区挂到SSD盘
linux·etcd
YJlio13 分钟前
Registry Usage (RU) 学习笔记(15.5):注册表内存占用体检与 Hive 体量分析
服务器·windows·笔记·python·学习·tcp/ip·django
释怀不想释怀17 分钟前
Docker(安装软件)
运维·docker·容器
EndingCoder17 分钟前
函数基础:参数和返回类型
linux·前端·ubuntu·typescript
阿巴~阿巴~18 分钟前
打通局域网“最后一公里”:ARP协议原理、流程与安全解析
服务器·网络·网络协议·tcp/ip·tcp·ipv4·arp