linux – 同时读/写同一个unix socket?
作者:互联网
是否可以同时读取/写入同一个unix套接字的两个或多个进程?
我做了一些测试.
这是我的sock_test.sh,它产生了50个客户端,每个客户端同时写入5K消息:
#! /bin/bash --
SOC='/tmp/tst.socket'
test_fn() {
soc=$1
txt=$2
for x in {1..5000}; do
echo "${txt}" | socat - UNIX-CONNECT:"${soc}"
done
}
for x in {01..50}; do
test_fn "${SOC}" "Test_${x}" &
done
然后我创建一个unix套接字并捕获文件sock_test.txt的所有流量:
# netcat -klU /tmp/tst.socket | tee ./sock_test.txt
最后,我运行我的测试脚本(sock_test.sh),并在屏幕上监视所有50名工作人员的工作.最后,我检查所有消息是否已到达目的地:
# ./sock_test.sh
# sort ./sock_test.txt | uniq -c
令我惊讶的是,没有任何错误,所有50名工作人员都成功发送了所有5K消息.
我想我必须得出结论,同时写入unix套接字是可以的吗?
我的并发级别是否太低而无法看到冲突?
我的测试方法有问题吗?那我怎么测试得当呢?
编辑
关于这个问题的优秀答案,对于那些更熟悉python的人来说,我的测试平台是:
#! /usr/bin/python3 -u
# coding: utf-8
import socket
from concurrent import futures
pow_of_two = ['B','KB','MB','GB','TB']
bytes_dict = {x: 1024**pow_of_two.index(x) for x in pow_of_two}
SOC = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
SOC.connect('/tmp/tst.socket')
def write_buffer(
char: 'default is a' = 'a',
sock: 'default is /tmp/tst.socket' = SOC,
step: 'default is 8KB' = 8 * bytes_dict['KB'],
last: 'default is 2MB' = 2 * bytes_dict['MB']):
print('## Dumping to the socket: {0}'.format(sock))
while True:
in_memory = bytearray([ord(char) for x in range(step)])
msg = 'Dumping {0} bytes of {1}'
print(msg.format(step, char))
sock.sendall(bytes(str(step), 'utf8') + in_memory)
step += step
if last % step >= last:
break
def workers(concurrency=5):
chars = concurrency * ['a', 'b', 'c', 'd']
with futures.ThreadPoolExecutor() as executor:
for c in chars:
executor.submit(write_buffer, c)
def parser(chars, file='./sock_test.txt'):
with open(file=file, mode='rt', buffering=8192) as f:
digits = set(str(d) for d in range(0, 10))
def is_digit(d):
return d in digits
def printer(char, size, found, junk):
msg = 'Checking {}, Expected {:8s}, Found {:8s}, Junk {:8s}, Does Match: {}'
print(msg.format(char, size, str(found), str(junk), size == str(found)))
char, size, found, junk = '', '', 0, 0
prev = None
for x in f.read():
if is_digit(x):
if not is_digit(prev) and prev is not None:
printer(char, size, found, junk)
size = x
else:
size += x
else:
if is_digit(prev):
char, found, junk = x, 1, 0
else:
if x==char:
found += 1
else:
junk += 1
prev = x
else:
printer(char, size, found, junk)
if __name__ == "__main__":
workers()
parser(['a', 'b', 'c', 'd'])
然后在输出中,您可以观察如下行:
Checking b, Expected 131072 , Found 131072 , Junk 0 , Does Match: True
Checking d, Expected 262144 , Found 262144 , Junk 0 , Does Match: True
Checking b, Expected 524288 , Found 219258 , Junk 0 , Does Match: False
Checking d, Expected 524288 , Found 219258 , Junk 0 , Does Match: False
Checking c, Expected 8192 , Found 8192 , Junk 0 , Does Match: True
Checking c, Expected 16384 , Found 16384 , Junk 0 , Does Match: True
Checking c, Expected 32768 , Found 32768 , Junk 610060 , Does Match: True
Checking c, Expected 524288 , Found 524288 , Junk 0 , Does Match: True
Checking b, Expected 262144 , Found 262144 , Junk 0 , Does Match: True
您可以看到在某些情况下(b,d)的有效负载不完整,但稍后会收到丢失的片段(c).简单的数学证明了这一点:
# Expected
b + d = 524288 + 524288 = 1048576
# Found b,d + extra fragment on the other check on c
b + d + c = 219258 + 219258 + 610060 = 1048576
因此,同时写入unix套接字是可以的.
解决方法:
这是一个非常短的测试线.尝试大于netcat或socat使用的缓冲区大小的东西,并从多个测试实例中多次发送该字符串;这是一个发送程序,它执行此操作:
#!/usr/bin/env expect
package require Tcl 8.5
set socket [lindex $argv 0]
set character [string index [lindex $argv 1] 0]
set length [lindex $argv 2]
set repeat [lindex $argv 3]
set fh [open "| socat - UNIX-CONNECT:$socket" w]
# avoid TCL buffering screwing with our results
chan configure $fh -buffering none
set teststr [string repeat $character $length]
while {$repeat > 0} {
puts -nonewline $fh $teststr
incr repeat -1
}
然后一个启动器调用那一堆(25)使用不同长度(9999)的测试字符多次(100),希望能够很好地吹过任何缓冲区边界:
#!/bin/sh
# NOTE this is a very bad idea on a shared system
SOCKET=/tmp/blabla
for char in a b c d e f g h i j k l m n o p q r s t u v w x y; do
./sender -- "$SOCKET" "$char" 9999 100 &
done
wait
嗯,我没有netcat希望在Centos 7上使用nc就足够了:
$nc -klU /tmp/blabla > /tmp/out
然后我们在其他地方提供数据
$./launcher
现在我们的/ tmp / out会很尴尬,因为没有换行符(有些东西缓冲基于换行符,所以新行可以影响测试结果,如果是这种情况,请参阅setbuf(3)以了解基于行的缓冲的可能性)所以我们需要查找字符更改的代码,并计算前一个相同字符序列的长度.
#include <stdio.h>
int main(int argc, char *argv[])
{
int current, previous;
unsigned long count = 1;
previous = getchar();
if (previous == EOF) return 1;
while ((current = getchar()) != EOF) {
if (current != previous) {
printf("%lu %c\n", count, previous);
count = 0;
previous = current;
}
count++;
}
printf("%lu %c\n", count, previous);
return 0;
}
哦,男孩C!让我们编译并解析输出…
$make parse
cc parse.c -o parse
$./parse < /tmp/out | head
49152 b
475136 a
57344 b
106496 a
49152 b
49152 a
38189 r
57344 b
57344 a
49152 b
$
嗯,哦.这看起来不对. 9999 * 100应该是999,900连续单个字母,相反我们得到……不是那样的. a和b很早就开始了,但看起来不知何故得到了一些早期的镜头.那是你的工作安排.换句话说,输出已损坏.在文件的末尾附近怎么样?
$./parse < /tmp/out | tail
8192 l
8192 v
476 d
476 g
8192 l
8192 v
8192 l
8192 v
476 l
16860 v
$echo $((9999 * 100 / 8192))
122
$echo $((9999 * 100 - 8192 * 122))
476
$
看起来8192是此系统上的缓冲区大小.无论如何!您的测试输入太短,无法超过缓冲区长度,并给出错误的印象,即多个客户端写入都可以.增加来自客户端的数据量,您将看到混合且因此损坏的输出.
标签:linux,unix-sockets 来源: https://codeday.me/bug/20190810/1637541.html