Gleichzeitig in denselben Unix-Socket lesen / schreiben?

8

Ist es in Ordnung, wenn zwei oder mehr Prozesse gleichzeitig lesen / schreiben unix socket?

Ich habe einige Tests durchgeführt.

Hier ist meine sock_test.sh, die 50 Clients hervorbringt, von denen jeder gleichzeitig 5K-Nachrichten schreibt:

#! /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

Ich erstelle dann ein unix socketund erfasse den gesamten Datenverkehr zur Datei sock_test.txt:

# netcat -klU /tmp/tst.socket | tee ./sock_test.txt

Schließlich führe ich mein Testskript ( sock_test.sh) aus und überwache auf dem Bildschirm alle 50 Mitarbeiter, die ihre Arbeit erledigen. Am Ende überprüfe ich, ob alle Nachrichten ihr Ziel erreicht haben:

# ./sock_test.sh
# sort ./sock_test.txt | uniq -c

Zu meiner Überraschung gab es keine Fehler und alle 50 Mitarbeiter haben alle 5K-Nachrichten erfolgreich gesendet.

Ich muss wohl zu dem Schluss kommen, dass das gleichzeitige Schreiben an unix socketsOK ist?

War mein Parallelitätsgrad zu niedrig, um Kollisionen zu erkennen?

Stimmt etwas mit meiner Testmethode nicht? Wie teste ich es dann richtig?

BEARBEITEN

Nach der hervorragenden Antwort auf diese Frage gibt es für diejenigen, die sich besser auskennen python, meinen Prüfstand:

#! /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'])

Dann können Sie in der Ausgabe Zeilen wie die folgenden beobachten:

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

Sie können sehen, dass die Nutzdaten in einigen Fällen ( b, d) unvollständig sind, fehlende Fragmente jedoch später empfangen werden ( c). Einfache Mathematik beweist es:

# Expected
b + d = 524288 + 524288 = 1048576
# Found b,d + extra fragment on the other check on c
b + d + c = 219258 + 219258 + 610060 = 1048576

Daher gleichzeitiges Schreiben unix socketsist OK nicht OK.

NarūnasK
quelle

Antworten:

5

Das ist eine sehr kurze Testlinie. Versuchen Sie etwas Größeres als die von entweder netcatoder verwendete Puffergröße socat, und senden Sie diese Zeichenfolge mehrmals von den mehreren Testinstanzen. Hier ist ein senderProgramm, das das macht:

#!/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
}

Und dann ein launcherpaar Mal (25) mit verschiedenen Testzeichen von großer Länge (9999) ein paar Mal (100), um hoffentlich jede Puffergrenze zu überschreiten:

#!/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

Hmm, ich habe netcathoffentlich keine ncauf Centos 7 genügen:

$ nc -klU /tmp/blabla > /tmp/out

Und dann geben wir anderswo Daten dazu

$ ./launcher

Jetzt /tmp/outwird es unangenehm, da es keine Zeilenumbrüche gibt (einige Dinge puffern basierend auf Zeilenumbrüchen, sodass Zeilenumbrüche die Testergebnisse beeinflussen können, wenn dies der Fall ist, sehen Sie setbuf(3)nach dem Potenzial für zeilenbasierte Pufferung), sodass wir Code benötigen, der nach einer Änderung von a sucht Zeichen und zählt, wie lange die vorherige Folge identischer Zeichen war.

#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;
}

Oh Junge C! Lassen Sie uns unsere Ausgabe kompilieren und analysieren ...

$ 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
$ 

Oh, oh. Das sieht nicht richtig aus. 9999 * 100sollte 999.900 eines einzelnen Buchstabens in einer Reihe sein, und stattdessen haben wir ... nicht das. aund bhabe früh angefangen, aber es sieht so raus, als hätte ich ein paar frühe Aufnahmen gemacht. Das ist Jobplanung für dich. Mit anderen Worten, die Ausgabe ist beschädigt. Wie wäre es gegen Ende der Datei?

$ ./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
$

Es sieht so aus, als ob 8192 die Puffergröße auf diesem System ist. Sowieso! Ihre Testeingabe war zu kurz, um die Pufferlängen zu überschreiten, und vermittelt den falschen Eindruck, dass mehrere Client-Schreibvorgänge in Ordnung sind. Wenn Sie die Datenmenge von Clients erhöhen, wird eine gemischte und daher beschädigte Ausgabe angezeigt.

Thrig
quelle
Ist es möglich, dass ncdie ausgegebenen Daten verwechselt werden? Gemäß diesem Beitrag sollte stackoverflow.com/questions/9644251/… für jede Verbindung auf der Serverseite einen separaten fd erstellen, und daher sollte für jede Verbindung ein separater Serverpuffer vorhanden sein.
Artashes Aghajanyan
@ArtashesAghajanyan Die Daten aus all diesen verschiedenen Puffern werden durch die Shell- nc ... > fileUmleitung zusammengeführt. Diese Puffer sind von begrenzter Größe und werden zufällig gefüllt, je nachdem, wann Daten über das Netzwerk eingehen . Der Ansatz ist fehlerhaft und wird zu
Datenkorruption
Schlagen Sie zur Verdeutlichung vor, dass mehrere Clients keine Verbindung zu derselben Socket-Datei herstellen und Daten nicht zuverlässig an einen Unix-Domain-Socket-Server senden können? Weil ich denke, dass der Autor der Frage aufgrund Ihrer Antwort eine Schlussfolgerung gezogen hat, ist das gleichzeitige Schreiben in Unix-Sockets NICHT in Ordnung. Ich denke, diese Aussage ist nicht korrekt. Sie sehen Probleme nicht, weil das gleichzeitige Schreiben in Unix-Sockets nicht in Ordnung ist, sondern weil die von Ihnen verwendeten Tools und die Art und Weise, wie Sie sie verwenden, Probleme verursachen.
Artashes Aghajanyan
Wo habe ich das vorgeschlagen?
Thrig
Entschuldigung, wenn ich die Schlussfolgerung Ihrer Antwort falsch interpretiert habe. Beachten Sie, dass die ursprüngliche Frage lautete: Ist es in Ordnung, wenn zwei oder mehr Prozesse gleichzeitig in denselben Unix-Socket lesen / schreiben? und dann verweist der Autor auf Ihre Antwort, um zu dem Schluss zu kommen, dass das gleichzeitige Schreiben in Unix-Sockets NICHT in Ordnung ist . Ich denke, wir sind uns beide einig, dass diese Schlussfolgerung falsch ist. Vielleicht lohnt es sich, in Ihrem Beitrag deutlicher zu machen, dass das Problem in der Verwendung von Tools wie socat und redirect liegt.
Artashes Aghajanyan