我正在Unix套接字上使用SOCK_SEQPACKET类型。
我用来阅读的代码是经典的
ssize_t recv_size = recv(sd, buffer, sizeof(buffer), 0);
if (recv_size < 0) {
handle_error("recv", errno);
} else if (recv_size > 0) {
handle_packet(buffer, recv_size);
} else {
// recv_size == 0 => peer closed socket.
handle_end_of_stream();
}
虽然这很好用,但我注意到它无法区分套接字关闭和大小为0的消息。换句话说,如果在另一端,我发出如下调用序列:
send(sd, "hello", strlen("hello"), 0);
send(sd, "", 0, 0);
send(sd, "world", strlen("world"), 0);
…阅读器将只收到"hello"
并通过套接字关闭对第二条消息作出反应,而完全丢失了"world"
消息。
我想知道这两种情况之间是否存在某种歧义。
答案 0 :(得分:1)
如果您在两端进行某种“确认”功能怎么办。例如,代替以下代码:handle_end_of_stream:
Item
<如果连接仍在,您会收到“ UNIQUE_RESPONSE” ,如果不是,则可以确定另一端已关闭。只需在“确认”功能中过滤掉某种“ UNIQUE_MESSAGE”和“ UNIQUE_RESPONSE”即可。
答案 1 :(得分:1)
正如我在评论中提到的那样,零长度的seqpackets(以及零长度的数据报)的行为可能很奇怪,通常会误认为是断开连接。因此,我绝对建议不要出于任何目的使用零长度seqpackets或数据报。
为了说明主要问题并探索细节,我创建了两个测试程序。首先是 receive.c ,它在Unix域seqpacket套接字上侦听,接受一个连接,并描述接收的内容:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <signal.h>
#include <string.h>
#include <poll.h>
#include <time.h>
#include <stdio.h>
#include <errno.h>
static volatile sig_atomic_t done = 0;
static void handle_done(int signum)
{
done = 1;
}
static int install_done(int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = handle_done;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}
static inline unsigned int digit(const int c)
{
switch (c) {
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'A': case 'a': return 10;
case 'B': case 'b': return 11;
case 'C': case 'c': return 12;
case 'D': case 'd': return 13;
case 'E': case 'e': return 14;
case 'F': case 'f': return 15;
default: return 16;
}
}
static inline unsigned int octbyte(const char *src)
{
if (src) {
const unsigned int o0 = digit(src[0]);
if (o0 < 4) {
const unsigned int o1 = digit(src[1]);
if (o1 < 8) {
const unsigned int o2 = digit(src[2]);
if (o2 < 8)
return o0*64 + o1*8 + o2;
}
}
}
return 256;
}
static inline unsigned int hexbyte(const char *src)
{
if (src) {
const unsigned int hi = digit(src[0]);
if (hi < 16) {
const unsigned int lo = digit(src[1]);
if (lo < 16)
return 16*hi + lo;
}
}
return 256;
}
size_t set_unix_path(const char *src, struct sockaddr_un *addr)
{
char *dst = addr->sun_path;
char *const end = addr->sun_path + sizeof (addr->sun_path) - 1;
unsigned int byte;
if (!src || !addr)
return 0;
memset(addr, 0, sizeof *addr);
addr->sun_family = AF_UNIX;
while (*src && dst < end)
if (*src == '\\')
switch (*(++src)) {
case '0':
byte = octbyte(src);
if (byte < 256) {
*(dst++) = byte;
src += 3;
} else {
*(dst++) = '\0';
src++;
}
break;
case '1':
byte = octbyte(src);
if (byte < 256) {
*(dst++) = byte;
src += 3;
} else
*(dst++) = '\\';
break;
case '2':
byte = octbyte(src);
if (byte < 256) {
*(dst++) = byte;
src += 3;
} else
*(dst++) = '\\';
break;
case '3':
byte = octbyte(src);
if (byte < 256) {
*(dst++) = byte;
src += 3;
} else
*(dst++) = '\\';
break;
case 'x':
byte = hexbyte(src + 1);
if (byte < 256) {
*(dst++) = byte;
src += 3;
} else
*(dst++) = '\\';
break;
case 'a': *(dst++) = '\a'; src++; break;
case 'b': *(dst++) = '\b'; src++; break;
case 't': *(dst++) = '\t'; src++; break;
case 'n': *(dst++) = '\n'; src++; break;
case 'v': *(dst++) = '\v'; src++; break;
case 'f': *(dst++) = '\f'; src++; break;
case 'r': *(dst++) = '\r'; src++; break;
case '\\': *(dst++) = '\\'; src++; break;
default: *(dst++) = '\\';
}
else
*(dst++) = *(src++);
*(dst++) = '\0';
return (size_t)(dst - (char *)addr);
}
int main(int argc, char *argv[])
{
struct sockaddr_un addr, conn;
socklen_t addrlen, connlen;
int socketfd, connfd;
if (argc != 2 || !argv[1][0] || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s SOCKET_PATH\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
if (install_done(SIGINT) ||
install_done(SIGHUP) ||
install_done(SIGTERM)) {
fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
socketfd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (socketfd == -1) {
fprintf(stderr, "Cannot create an Unix domain seqpacket socket: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
addrlen = set_unix_path(argv[1], &addr);
if (bind(socketfd, (const struct sockaddr *)&addr, addrlen) == -1) {
fprintf(stderr, "Cannot bind to %s: %s.\n", argv[1], strerror(errno));
close(socketfd);
return EXIT_FAILURE;
}
if (listen(socketfd, 1) == -1) {
fprintf(stderr, "Cannot listen for incoming connections: %s.\n", strerror(errno));
close(socketfd);
return EXIT_FAILURE;
}
memset(&conn, 0, sizeof conn);
connlen = sizeof conn;
connfd = accept(socketfd, (struct sockaddr *)&conn, &connlen);
if (connfd == -1) {
close(socketfd);
fprintf(stderr, "Canceled.\n");
return EXIT_SUCCESS;
}
if (connlen > 0)
fprintf(stderr, "Connected, peer address size is %d.\n", (int)connlen);
else
fprintf(stderr, "Connected; no peer address.\n");
while (!done) {
char buffer[65536];
ssize_t n;
int r;
n = recv(connfd, buffer, sizeof buffer, 0);
if (n > 0)
fprintf(stderr, "Received %zd bytes.\n", n);
else
if (n == 0) {
struct pollfd fds[1];
fds[0].fd = connfd;
fds[0].events = 0;
fds[0].revents = 0;
r = poll(fds, 1, 0);
if (r > 0 && (fds[0].revents & POLLHUP)) {
fprintf(stderr, "Disconnected (revents = %d).\n", fds[0].revents);
break;
} else
if (r > 0)
fprintf(stderr, "recv() == 0, poll() == %d, revents == %d\n", r, fds[0].revents);
else
if (r == 0)
fprintf(stderr, "Received a zero-byte seqpacket.\n");
else
fprintf(stderr, "recv() == 0, poll() == %d, revents == %d\n", r, fds[0].revents);
}
}
close(connfd);
close(socketfd);
return EXIT_SUCCESS;
}
您可以使用例如gcc -Wall -O2 receive.c -o receive
。要运行,请给它提供Unix域名地址以进行监听。在Linux中,您可以通过在地址前面加上\0
来使用抽象名称空间。例如,通过运行./receive '\0example'
。否则,套接字地址将在文件系统中可见,并且您需要在使用相同的套接字地址再次运行rm
之前,将其删除(就好像它是一个文件,使用./receive
)。
我们还需要一个实用程序来发送seqpackets。以下 send.c 非常相似(重复使用了许多相同的代码)。您指定要连接的Unix域地址,以及一个或多个seqpacket长度。您还可以指定以毫秒为单位的延迟(只需在-
前面添加;即,负整数是以毫秒为单位的延迟):
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <signal.h>
#include <string.h>
#include <poll.h>
#include <time.h>
#include <stdio.h>
#include <errno.h>
static volatile sig_atomic_t done = 0;
static void handle_done(int signum)
{
done = 1;
}
static int install_done(int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = handle_done;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}
static inline unsigned int digit(const int c)
{
switch (c) {
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'A': case 'a': return 10;
case 'B': case 'b': return 11;
case 'C': case 'c': return 12;
case 'D': case 'd': return 13;
case 'E': case 'e': return 14;
case 'F': case 'f': return 15;
default: return 16;
}
}
static inline unsigned int octbyte(const char *src)
{
if (src) {
const unsigned int o0 = digit(src[0]);
if (o0 < 4) {
const unsigned int o1 = digit(src[1]);
if (o1 < 8) {
const unsigned int o2 = digit(src[2]);
if (o2 < 8)
return o0*64 + o1*8 + o2;
}
}
}
return 256;
}
static inline unsigned int hexbyte(const char *src)
{
if (src) {
const unsigned int hi = digit(src[0]);
if (hi < 16) {
const unsigned int lo = digit(src[1]);
if (lo < 16)
return 16*hi + lo;
}
}
return 256;
}
size_t set_unix_path(const char *src, struct sockaddr_un *addr)
{
char *dst = addr->sun_path;
char *const end = addr->sun_path + sizeof (addr->sun_path) - 1;
unsigned int byte;
if (!src || !addr)
return 0;
memset(addr, 0, sizeof *addr);
addr->sun_family = AF_UNIX;
while (*src && dst < end)
if (*src == '\\')
switch (*(++src)) {
case '0':
byte = octbyte(src);
if (byte < 256) {
*(dst++) = byte;
src += 3;
} else {
*(dst++) = '\0';
src++;
}
break;
case '1':
byte = octbyte(src);
if (byte < 256) {
*(dst++) = byte;
src += 3;
} else
*(dst++) = '\\';
break;
case '2':
byte = octbyte(src);
if (byte < 256) {
*(dst++) = byte;
src += 3;
} else
*(dst++) = '\\';
break;
case '3':
byte = octbyte(src);
if (byte < 256) {
*(dst++) = byte;
src += 3;
} else
*(dst++) = '\\';
break;
case 'x':
byte = hexbyte(src + 1);
if (byte < 256) {
*(dst++) = byte;
src += 3;
} else
*(dst++) = '\\';
break;
case 'a': *(dst++) = '\a'; src++; break;
case 'b': *(dst++) = '\b'; src++; break;
case 't': *(dst++) = '\t'; src++; break;
case 'n': *(dst++) = '\n'; src++; break;
case 'v': *(dst++) = '\v'; src++; break;
case 'f': *(dst++) = '\f'; src++; break;
case 'r': *(dst++) = '\r'; src++; break;
case '\\': *(dst++) = '\\'; src++; break;
default: *(dst++) = '\\';
}
else
*(dst++) = *(src++);
*(dst++) = '\0';
return (size_t)(dst - (char *)addr);
}
static inline long sleep_ms(const long ms)
{
struct timespec t;
if (ms > 0) {
t.tv_sec = ms / 1000;
t.tv_nsec = (ms % 1000) * 1000000;
if (nanosleep(&t, &t) == -1 && errno == EINTR)
return 1000 * (unsigned long)(t.tv_sec)
+ (unsigned long)(t.tv_nsec / 1000000);
return 0;
} else
return ms;
}
static int parse_long(const char *src, long *dst)
{
const char *end = src;
long val;
if (!src || !*src)
return errno = EINVAL;
errno = 0;
val = strtol(src, (char **)&end, 0);
if (errno)
return errno;
if (end == src)
return errno = EINVAL;
while (*end == '\t' || *end == '\n' || *end == '\v' ||
*end == '\f' || *end == '\r' || *end == ' ')
end++;
if (*end)
return errno = EINVAL;
if (dst)
*dst = val;
return 0;
}
int main(int argc, char *argv[])
{
char buffer[65536];
struct sockaddr_un conn;
socklen_t connlen;
int connfd, arg;
ssize_t n;
long val, left;
if (argc < 3 || !argv[1][0] || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s SOCKET_PATH [ LEN | -MS ] ...\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "All arguments except the first one, SOCKET_PATH, are integers.\n");
fprintf(stderr, "A positive integer causes a seqpacket of that length to be sent,\n");
fprintf(stderr, "a negative value causes a delay (magnitude in milliseconds).\n");
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
if (install_done(SIGINT) ||
install_done(SIGHUP) ||
install_done(SIGTERM)) {
fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* Fill buffer with some data. Anything works. */
{
size_t i = sizeof buffer;
while (i-->0)
buffer[i] = (i*i) ^ i;
}
connfd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (connfd == -1) {
fprintf(stderr, "Cannot create an Unix domain seqpacket socket: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
connlen = set_unix_path(argv[1], &conn);
if (connect(connfd, (const struct sockaddr *)&conn, connlen) == -1) {
fprintf(stderr, "Cannot connect to %s: %s.\n", argv[1], strerror(errno));
close(connfd);
return EXIT_FAILURE;
}
/* To avoid output affecting the timing, fully buffer stdout. */
setvbuf(stdout, NULL, _IOFBF, 65536);
for (arg = 2; arg < argc; arg++)
if (parse_long(argv[arg], &val)) {
fprintf(stderr, "%s: Not an integer.\n", argv[arg]);
close(connfd);
return EXIT_FAILURE;
} else
if (val > (long)sizeof buffer) {
fprintf(stderr, "%s: Seqpacket size too large. Current limit is %zu.\n", argv[arg], sizeof buffer);
close(connfd);
return EXIT_FAILURE;
} else
if (val >= 0) {
n = send(connfd, buffer, (size_t)val, 0);
if (n == (ssize_t)val)
printf("Sent %ld-byte seqpacket successfully.\n", val);
else
if (n != (ssize_t)val && n >= 0)
fprintf(stderr, "Sent %zd bytes of a %ld-byte seqpacket.\n", n, val);
else
if (n < -1) {
fprintf(stderr, "C library bug: send() returned %zd.\n", n);
close(connfd);
return EXIT_FAILURE;
} else
if (n == -1) {
fprintf(stderr, "Send failed: %s.\n", strerror(errno));
close(connfd);
return EXIT_FAILURE;
}
} else {
left = sleep_ms(-val);
if (left)
fprintf(stderr, "Slept %ld milliseconds (out of %ld ms).\n", -val-left, -val);
else
printf("Slept %ld milliseconds.\n", -val);
}
if (close(connfd) == -1) {
fprintf(stderr, "Error closing connection: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
printf("All done, connection closed.\n");
fflush(stdout);
return EXIT_SUCCESS;
}
使用例如gcc -Wall -O2 send.c -o send
。
为了进行测试,建议您使用两个终端窗口。一个运行send
,另一个运行receive
。为简单起见,我将并排显示相应的命令和输出。运行的计算机是Core i5 7200U笔记本电脑(HP EliteBook 830),运行Ubuntu 16.04.4 LTS,64位Linux内核版本4.15.0-24-通用,使用GCC-5.4.0 20160609(5.4 .0-6ubuntu1〜16.04.10)和上述命令(gcc -Wall -O2
)。
当我们在最终发送之前使用一小段延迟时,一切似乎都可以正常工作:
$ ./send '\0example' 1 0 3 0 0 -1 6 │ $ ./receive '\0example'
│ Connected, peer address size is 2.
Sent 1-byte seqpacket successfully. │ Received 1 bytes.
Sent 0-byte seqpacket successfully. │ Received a zero-byte seqpacket.
Sent 3-byte seqpacket successfully. │ Received 3 bytes.
Sent 0-byte seqpacket successfully. │ Received a zero-byte seqpacket.
Sent 0-byte seqpacket successfully. │ Received a zero-byte seqpacket.
Slept 1 milliseconds. │
Sent 6-byte seqpacket successfully. │ Received 6 bytes.
All done, connection closed. │ Disconnected (revents = 16).
但是,当发件人发送最后几个seqpackets(以零长度的seqpackets开始)且之间没有任何延迟时,我会观察到:
$ ./send '\0example' 1 0 3 0 0 6 │ ./receive '\0example'
│ Connected, peer address size is 2.
Sent 1-byte seqpacket successfully. │ Received 1 bytes.
Sent 0-byte seqpacket successfully. │ Received a zero-byte seqpacket.
Sent 3-byte seqpacket successfully. │ Received 3 bytes.
Sent 0-byte seqpacket successfully. │
Sent 0-byte seqpacket successfully. │
Sent 6-byte seqpacket successfully. │
All done, connection closed. │ Disconnected (revents = 16).
查看如何丢失两个零字节seqpacket和6字节seqpacket(因为poll()
返回了revents == POLLHUP
。(POLLHUP
== 0x0010 == 16,因此任何一次都没有设置其他标志。)
我个人不确定这是否是 bug 。在我看来,这是公正的,这表明使用零长度seqpackets是有问题的,应该避免。
(对等地址长度为2以上,因为发件人未绑定任何地址,因此使用了未命名的Unix域套接字地址(如man unix
手册页中所述)。我认为这很重要,但我还是以防万一。)
讨论了一种通过MSG_EOR
解决当前问题的方法(因为recvmsg()
应该在{{1的MSG_EOR
字段中添加msg_flags
}}结构;并且由于“应该为所有seqpackets设置它”,即使是零长度的seqpackets,这也是从输入端/读取侧关闭/断开连接检测零长度seqpackets的可靠方法。 2007年5月的Linux内核邮件列表(和linux-netdev列表)。(存档的thread at Marc.info is here)
但是,根据最初的发布者Sam Kumar的说法,在Linux Unix域seqpacket套接字中,msghdr
没有设置或传递。讨论没有进行到任何地方。在我阅读本文时,没有人能确定预期的行为。
查看Unix domain sockets的Linux内核更改日志,自该线程以来(截至2018年7月23日)也没有任何相关更改。
以上程序是一站式编写,未经审查;因此,他们很容易在其中包含错误或想法。如果您发现任何问题或获得了截然不同的结果(但请注意有时很难复制基于时序的效果),请在评论中告知我,以便我检查并在必要时进行修复。