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