【发布时间】:2020-12-16 08:30:47
【问题描述】:
我在我的一个应用程序中使用 Unix 域套接字,我在写入套接字时观察到一种非常奇怪的行为。简短描述:有 2 个应用程序 - 客户端和服务器,每个应用程序同时读取和写入,即套接字以全双工模式工作。为了防止一些溢出,我检查了写缓冲区:
ioctl(socket_handler, TIOCOUTQ, &pending)
但有时我得到的结果很奇怪 - 768、1536、2304 等。一方面,这个数字看起来不像我写的字节数,另一方面看起来有点可疑 - 0x300、0x600等等,换句话说,它看起来像一个标志或错误代码。文档对此只字未提。
为了保持一致并防止出现问题,我提供了一个完整的代码示例来重现问题:
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/un.h>
#include <linux/sockios.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <pthread.h>
#include <poll.h>
static int sock;
static socklen_t len;
static struct sockaddr_un addr;
static std::string path = "@testsock";
static pthread_t thread;
static bool running;
static pollfd polls[2] = {};
static char buf[200];
static int err;
bool InitSocket()
{
sock = socket(AF_UNIX, SOCK_STREAM,0);
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path.c_str(), path.size());
*addr.sun_path = '\0';
len = static_cast<socklen_t>(__builtin_offsetof(struct sockaddr_un, sun_path) + path.length());
int opt_value = 1;
err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, static_cast<void *>(&opt_value), sizeof(int));
int on = 1;
err = ioctl(sock, FIONBIO, &on);
return true;
}
bool InitServer()
{
polls[0].fd = sock;
polls[0].events = POLLIN;
err = bind(sock, reinterpret_cast<struct sockaddr *>(&addr), len);
err = listen(sock, 10);
return true;
}
bool InitClient()
{
polls[1].fd = sock;
polls[1].events = POLLIN;
err = connect(sock, reinterpret_cast<struct sockaddr *>(&addr), len);
return true;
}
uint32_t GetRand(uint32_t min, uint32_t max)
{
return static_cast<uint32_t>(rand()) % (max - min) + min;
}
void *ReadThread(void*)
{
while(running)
{
int status = poll(polls, 2, 1000);
if(status > 0)
{
for(int i = 0;i < 2;i ++)
{
if(polls[i].revents == 0)
continue;
if(i == 0)
{
struct sockaddr_un cli_addr;
socklen_t clilen = sizeof(cli_addr);
int new_socket = accept(sock, reinterpret_cast<struct sockaddr *>(&cli_addr), &clilen);
int on = 1;
ioctl(new_socket, FIONBIO, &on);
polls[1].fd = new_socket;
polls[1].events = POLLIN;
std::cout << "client connected" << std::endl;
}
else
{
ssize_t readBytes = read(polls[1].fd, &buf, 100);
if(readBytes > 0)
{
//std::string str(buf, readBytes);
std::cout << " read " << readBytes << " bytes: " << std::endl;
int p = GetRand(10, 100); //
usleep(p * 1000); // removing these lines solves the problem, but ...
}
else
{
std::cout << "read error" << std::endl;
running = false;
}
}
}
}
}
return nullptr;
}
int main(int argc, char *argv[])
{
if(argc < 2)
return 1;
srand(static_cast<unsigned int>(time(nullptr)));
InitSocket();
std::string app_type(argv[1]);
if(app_type == "-s")
InitServer();
else if(app_type == "-c")
InitClient();
else
return 1;
running = true;
pthread_create(&thread, nullptr, &ReadThread, nullptr);
if(app_type == "-s")
{
while(polls[1].fd == 0) usleep(100);
}
char sendbuf[100];
for(int i = 0;i < 100;i ++)
{
int length = GetRand(10, 100);
int delay = GetRand(1, 10);
for(int j = 0;j < length;j ++)
{
sendbuf[j] = GetRand('a', 'z');
}
unsigned long pending = 0;
err = ioctl(polls[1].fd, TIOCOUTQ, &pending);
std::cout << "pending " << pending << std::endl;
err = send(polls[1].fd, sendbuf, length, MSG_NOSIGNAL);
std::cout << "write " << length << " bytes" << std::endl;
usleep(delay * 1000);
}
running = false;
pthread_join(thread, nullptr);
if(app_type == "-s")
close(polls[0].fd);
close(polls[1].fd);
return 0;
}
运行代码: 服务器:
test_app -s
然后是客户端应用程序(在另一个控制台窗口中):
test_app -c
可能的输出:
服务器:
client connected
read 83 bytes:
pending 0
write 66 bytes
pending 0
write 18 bytes
pending 768 <----- ????
write 17 bytes
pending 1536 <----- ????
write 79 bytes
read 100 bytes:
pending 2304 <----- ????
write 51 bytes
pending 3072
write 25 bytes
read 100 bytes:
pending 2304
write 96 bytes
pending 3072
write 19 bytes
客户:
pending 0
write 83 bytes
read 66 bytes:
pending 0
write 19 bytes
pending 768
write 94 bytes
pending 1536
write 52 bytes
pending 2304
write 11 bytes
pending 3072
write 39 bytes
pending 3072
write 35 bytes
read 100 bytes:
pending 3840
write 20 bytes
pending 2304
write 92 bytes
pending 3072
write 53 bytes
pending 3840
write 88 bytes
pending 4608
write 84 bytes
read 100 bytes:
我注意到在读取线程中添加人为延迟后会出现此问题。在我的实际应用程序中,我处理一条消息,这会产生一个小的延迟。但我绝对不明白这些数字从何而来(待定)以及读取线程中的延迟如何影响这一点?
我怎样才能避免这种情况?我怎样才能得到真正的写入缓冲区,而不是这些奇怪的数字?
【问题讨论】:
-
我只看到一个 IOCTL 记录在 unix(7) 手册页 (SIOCINQ) 中。这表明其他 IOCTL 要么不受支持,要么由实现定义,这意味着任何事情都可能发生。
-
您需要检查写入缓冲区大小的原因是什么?如果内部缓冲区已满,套接字上的操作不会阻塞吗?
-
@rveerd 我想防止写缓冲区溢出
-
@rveerd,SIOCINQ 给出了相同的结果。实际上这个调用是针对输入缓冲区的,我需要输出一个。
-
为什么你需要防止缓冲区溢出?写操作不只是阻塞吗?还是返回错误,您可以稍后再试?
标签: c++ sockets pthreads polling unix-socket