Bestanden tussen werkplekken synchroniseren

Hoe ik de client ontwierp en bouwde die een lokale map met andere werkplekken verbindt.

Applicatieontwikkeling 9 min leestijd

Een lokale map op meerdere werkplekken bijhouden

Voor de module Systems & Security bij Avans moesten we een applicatie voor bestandssynchronisatie ontwerpen en bouwen. Zoals bij OneDrive moest een gebruiker bestanden in een lokale map kunnen bewerken, waarna die wijzigingen ook op andere werkplekken terechtkomen. Op iedere werkplek was daarvoor een client nodig: een programma dat de map bijhoudt en bestanden uitwisselt met een centrale server.

Met een team van drie studenten bouwden we een Python-client en een server in C# met .NET. Ik ontwierp en bouwde de client, waaraan ook teamgenoten bijdroegen. De opdracht vroeg om een programmeertaal waarmee we nog niet vertrouwd waren. Omdat ik vooral PHP kende en al kort met Python had kennisgemaakt, koos ik Python voor de client. WebSockets kozen we als team voor de communicatie; de berichtverwerking werkten we zonder framework uit.

De server moest meerdere clients kunnen bedienen, ook op verschillende besturingssystemen. Client en server moesten bovendien op afzonderlijke computers draaien. Naast de uitwisseling van grote bestanden stelde de opdracht eisen aan het voorkomen van dubbele, corrupte en onvolledige bestanden. Die eisen raken direct aan mijn clientwerk: een download verandert dezelfde map die de client bewaakt. Daardoor moest ik zowel wijzigingen van de gebruiker als de gevolgen van de eigen bestandsoverdracht verwerken.

Voordat we bouwden, beschreven we de gebruikersbehoeften en eisen. Per functionele eis legden we een prioriteit en een voorgenomen testmethode vast. Voor bestandssynchronisatie was dat bijvoorbeeld een integratietest, waarin client en server samen worden beproefd. Zo koppelden we het gewenste gedrag vooraf aan de manier waarop we het wilden controleren.

Ook de afspraken over berichten werkten we eerst uit. Sequentiediagrammen lieten de volgorde van aanmelden, synchroniseren, uploaden en downloaden zien; de protocolspecificatie beschreef de berichttypen, velden en voorbeelden. Voor een extra punt moest onze client kunnen samenwerken met de server van een andere groep. We stemden het protocol daarom met die groep af. Die afstemming gaf ons gedeelde afspraken voor de bouw, maar toont op zichzelf geen geslaagde compatibiliteitsproef aan.

Probeer de browserdemo om de uitwisseling tussen twee werkplekken te volgen. Deze reconstructie is voor het portfolio gemaakt.

De client houdt de synchronisatiemap op een werkplek bij. De centrale server ontvangt bestanden en geeft wijzigingen door aan de andere clients. Mijn ontwerp en implementatie lagen aan de clientkant.

C1 · Systeemcontext Systeemcontext
Onderdeel · type in het blok Extern onderdeel Systeem- of containergrens Gerichte relatie · uitleg bij de pijl
Systeemcontext · De client verbindt de lokale werkplek met de centrale server.

Eerst een wijziging melden, daarna het bestand versturen

Als een lokaal bestand verandert, meldt de client dat aan de server. De server beoordeelt de versie en vraagt zo nodig om een upload. Na de overdracht geeft hij de wijziging door aan alle clients die zich voor meldingen hebben aangemeld, inclusief de bronclient. Iedere ontvanger bepaalt vervolgens of zijn lokale kopie moet worden bijgewerkt.

We hielden meldingen en bestandsinhoud daarbij gescheiden. Een melding bevat onder meer de bestandsnaam, grootte en inhoudshash: een berekende herkenningswaarde van de inhoud. Daarmee kan de ontvanger eerst beoordelen of een overdracht nodig is. Meldingen en opdrachten gaan over een blijvende WebSocketverbinding. Pas voor de upload of download zelf opent de client een aparte verbinding, zoals in de procesflow uit onze presentatie hieronder.

UML · Sequencediagram WIJZIGING · eerst melden, dan overdragen Teamontwerp · berichten van boven naar beneden
WIJZIGING · eerst melden, dan overdragen Na een wijzigingsmelding vergelijkt de server de versies en vraagt zo nodig een upload. De client opent een aparte verbinding voor de bestandsblokken en sluit af met EOF. Zodra het bestand is opgeslagen, meldt de server de wijziging aan de aangemelde clients, ook aan de bronclient. Bronclient .NET-server Andere client opt [upload nodig] loop [per bestandsblok] Lokale wijziging detecteren notification · bestandsmetadata Versies vergelijken Uploadverzoek UPLOAD · aparte verbinding Binaire blokken EOF Bestand opslaan notification · wijziging notification · wijziging
↓ Tijd · van boven naar beneden → Bericht · open pijlpunt opt · voorwaarde loop · herhaling alt · alternatieven
WIJZIGING · eerst melden, dan overdragen · Teamontwerp uit dia 20: de server beoordeelt de melding voordat hij een upload vraagt.

In de implementatie: In de code filtert de Python-client lokale events voordat hij ze meldt. De server vergelijkt eerst de hash en daarna de timestamp; het uploadverzoek heet REQUEST_UPLOAD.

Mapbewaking verbinden met netwerkverwerking

In de client komen lokale gebeurtenissen en berichten van de server samen. Watchdog bewaakt de map, terwijl asyncio de netwerktaken aanstuurt. Als een van die taken op netwerkverkeer wacht, kunnen andere taken verdergaan. Beide onderdelen draaien binnen dezelfde Python-applicatie; de .NET-server staat daarbuiten.

C2 · Containerdiagram Applicatie en lokale opslag
Onderdeel · type in het blok Extern onderdeel Systeem- of containergrens Gerichte relatie · uitleg bij de pijl
Applicatie en lokale opslag · De client bestaat uit de Python-applicatie en de lokale synchronisatiemap. De server draait daarbuiten.

De overgang via wachtrijen uitwerken

Bij een wijziging roept Watchdog een callback aan die op de bestandsgebeurtenis reageert. Die callbacks volgen het Observer-principe en draaien in een eigen thread. De netwerktaken werken onder de eventloop van asyncio. Om een bestandsgebeurtenis daar te verwerken, moet de client haar eerst vanuit de Watchdog-thread overdragen.

Voor die overgang stelde ik het producer–consumer-patroon voor. Het ene onderdeel levert werk aan via een wachtrij, het andere leest dat werk eruit en verwerkt het. In de client werkte ik dit uit met een FileEvent, waarin de eventhandler de bestandsgebeurtenis vastlegt. Via asyncio.run_coroutine_threadsafe(...) plant hij het plaatsen van dat object in watcher_queue op de eventloop. Een afzonderlijke taak leest de queue en handelt de gebeurtenis af. Als daaruit een melding aan de server volgt, komt die in websocket_queue voor de verzendtaak.

In EventHandler.handle_file_event() in watcher.py staat de overdracht naar de eventloop:

asyncio.run_coroutine_threadsafe(self.queue.put(file_event), self.loop)

De wachtrijen geven iedere stap een eigen verantwoordelijkheid: de watcher levert een gebeurtenis, de client verwerkt haar en de verzendtaak verstuurt het bericht. Zo vindt de netwerkoverdracht buiten de Watchdog-callback plaats. Bestandscontroles en hashing blijven wel synchroon in die callback staan; de watcher-thread kan daar dus nog op wachten.

C3 · Componentdiagram De verantwoordelijkheden binnen de client
Onderdeel · type in het blok Extern onderdeel Systeem- of containergrens Gerichte relatie · uitleg bij de pijl
De verantwoordelijkheden binnen de client · EventHandler, Client en Utils uit het architectuurdiagram op dia 15, opnieuw uitgewerkt en aan de broncode getoetst.

Berichten die van de server komen, volgen een ander pad. De ontvangende taak, websocket_consumer, beoordeelt na een melding over een nieuw of gewijzigd bestand of een download nodig is. Een uploadopdracht start rechtstreeks een upload. Voor beide opent de taak een aparte verbinding en wacht zij op de overdracht. De verzendtaak, websocket_producer, kan intussen lokale meldingen uit haar eigen wachtrij versturen. Inkomende serverberichten gaan dus niet vanzelf via die queue weer terug.

Dat onderscheid bepaalt ook hoeveel werk tegelijk gebeurt. Zolang de ontvangende taak op een overdracht wacht, handelt zij geen volgend serverbericht af. Tijdens een synchronisatieronde worden bestanden eveneens achter elkaar overgedragen. De queues scheiden de verantwoordelijkheden, maar maken niet iedere bewerking gelijktijdig.

C4 · Codediagram Van Watchdog-event naar WebSocketbericht
Onderdeel · type in het blok Extern onderdeel Systeem- of containergrens Gerichte relatie · uitleg bij de pijl
Van Watchdog-event naar WebSocketbericht · De overdracht via twee queues, uitgewerkt met klassen en methoden uit watcher.py, models.py en client.py.

Vaststellen of de lokale kopie afwijkt

Om te bepalen of een download nodig is, heeft de client meer nodig dan alleen een bestandsnaam. Bij het starten vraagt hij met SYNC de bestandsgegevens van de server op. Hij vergelijkt die met de grootte en SHA-256-inhoudshash van het lokale bestand. Voor die lokale vergelijking is geen wijzigingstijd nodig, al moet de client voor de hash wel de bestandsinhoud lezen.

Daarmee kan de client een verschil herkennen. Welke versie moet winnen als twee werkplekken hetzelfde bestand wijzigen, is een afzonderlijke vraag. De server gebruikt in zijn versieafweging ook tijdstippen; de lokale hashvergelijking lost zulke conflicten dus niet in het algemeen op.

UML · Sequencediagram SYNC · de lokale kopieën vergelijken Teamontwerp · berichten van boven naar beneden
SYNC · de lokale kopieën vergelijken Op SYNC via de blijvende verbinding antwoordt de server met SYNC_DATA. De client vergelijkt deze gegevens met zijn lokale bestanden en opent waar nodig een aparte verbinding voor een download. Python-client .NET-server loop [per serverbestand] opt [lokale kopie ontbreekt of wijkt af] SYNC · blijvende verbinding SYNC_DATA · metadata Lokale metadata vergelijken DOWNLOAD · aparte verbinding Bestandsinhoud … EOF
↓ Tijd · van boven naar beneden → Bericht · open pijlpunt opt · voorwaarde loop · herhaling alt · alternatieven
SYNC · de lokale kopieën vergelijken · Teamontwerp uit dia 19: de client vraagt bestandsgegevens op om lokale kopieën te vergelijken.

In de implementatie: De implementatie vergelijkt SHA-256 en bytegrootte. Zij uploadt ook bestanden die alleen lokaal bestaan. Die uploadstap staat niet in de oorspronkelijke flow.

Voor alleen een overzicht van bestandsnamen bevat het protocol daarnaast LIST. Dat verzoek staat op zichzelf: de Python-client hoeft het niet uit te voeren voordat hij met SYNC begint.

UML · Sequencediagram LIST · alleen de bestandsnamen opvragen Teamontwerp · berichten van boven naar beneden
LIST · alleen de bestandsnamen opvragen Met LIST vraagt de client via een actieve WebSocketverbinding de bestandsnamen op. De server stuurt ze als JSON terug. Dit verzoek is geen verplichte stap vóór SYNC. Python-client .NET-server LIST · actieve verbinding Bestandsnamen verzamelen LIST · JSON met bestandsnamen
↓ Tijd · van boven naar beneden → Bericht · open pijlpunt opt · voorwaarde loop · herhaling alt · alternatieven
LIST · alleen de bestandsnamen opvragen · Aanvullend teamontwerp uit dia 23: een zelfstandig verzoek om de bestandsnamen op de server.

De watcher reageert ook op een download

Zodra een download naar de lokale map schrijft, kan Watchdog daarop reageren, ook als het bestand nog niet volledig binnen is. Ik werkte daarom aan bestandslocks om bewerkingen aan dezelfde bestandsnaam op elkaar af te stemmen. De client gebruikt één asyncio.Lock per naam. Zolang een bewerking die lock vasthoudt, slaat de watcher gebeurtenissen voor dat bestand over. Dat beperkt overlap, maar kan ook een wijziging van de gebruiker overslaan. Er is geen buffer om die gebeurtenis na de overdracht alsnog te verwerken.

Bij het afronden van uploads begrenst de client het wachten op een antwoord. Nadat hij EOF, het einde-van-bestandssignaal, heeft verstuurd, wacht hij maximaal tien seconden op een status. Bij een timeout of ongeldige status probeert hij het nog één keer: maximaal twee pogingen in totaal. Die grens geldt voor de bevestiging na EOF, niet voor de duur van de volledige upload.

Volg een wijziging in de browserdemo

De demo laat zien hoe een wijziging tussen twee werkplekken wordt doorgegeven. Voor deze portfolioreconstructie houdt een relay, het tussenstation voor de uitwisseling, de toestand in het geheugen bij. Er draait geen oorspronkelijke Python-client of .NET-server achter en er wordt geen echte projectmap gesynchroniseerd.

Kies Start omgeving, bewerk een bestand op de bronclient en kies Opslaan en synchroniseren. In de relayterminal kun je de tussenstappen volgen. De bestandsverkenner en mappaden zijn onderdeel van deze reconstructie; de oorspronkelijke watcher bewaakt alleen de bovenste map. De demo toont de gegevensstroom, zonder netwerkverlies of versieconflicten te beproeven.

Interactieve bestandssynchronisatie

Kies Start omgeving om de demo te laden. Nog niet geladen
SSP WorkspaceCachyOS · KDE Plasma
Dolphinmert@cachyos:~/Sync
/home/mert/Sync
01 Bronclient

Bewerk een lokaal bestand

Watchdog → asyncio.Queue → client task

notes/readme.md Markdown · 48 bytes
UTF-8 · LF
Sync-map actiefLokale wijzigingen worden bewaakt
Konsolerelay@cachyos:~/ssp/Server

SSP-relayterminal

  1. relay@cachyos:~/ssp/Server$
stand-byLaatste gebeurtenis: systeem gereed 2 clients2 bestandenidlestand-byUTF-8
Dolphinteam@cachyos:~/Sync
/home/team/Sync Alleen-lezen
03 Tweede client

Controleer de lokale kopie

WebSocket consumer → hashcontrole → FileStore

notes/readme.mdLokale kopie
# Shared notes

A small sync surface for the team.
Alleen-lezenOntvangen via relay
Wacht op synchronisatieLokale kopie · alleen-lezen

Waar de afhandeling van bestanden nog tekortschiet

Een onderbroken download raakt het doelbestand

De client schrijft een download rechtstreeks naar het doelbestand. EOF geeft aan dat de overdracht is afgerond, maar daarna controleert de implementatie de hash en grootte niet opnieuw. Bij een mislukte overdracht probeert de client het onvolledige bestand te verwijderen. De oude inhoud kan dan al overschreven zijn. Daarmee dekt de implementatie de opdrachteis om onvolledige bestanden te voorkomen nog niet volledig af.

UML · Sequencediagram DOWNLOAD · ontvangen, afronden of opruimen Teamontwerp · berichten van boven naar beneden
DOWNLOAD · ontvangen, afronden of opruimen Na DOWNLOAD via een aparte verbinding stuurt de server binaire blokken, gevolgd door EOF. Het ontwerp voorziet in opruimen als de overdracht zonder EOF stopt. De implementatie probeert het onvolledige bestand te verwijderen; dat is geen garantie dat de oude kopie behouden blijft. Python-client .NET-server loop [zolang blokken binnenkomen] alt [EOF ontvangen] [onderbroken zonder EOF] DOWNLOAD · aparte verbinding Binair blok Naar doelbestand schrijven EOF Download afronden Onvolledig bestand opruimen
↓ Tijd · van boven naar beneden → Bericht · open pijlpunt opt · voorwaarde loop · herhaling alt · alternatieven
DOWNLOAD · ontvangen, afronden of opruimen · Teamontwerp uit dia 21: bestandsblokken ontvangen tot EOF, met een opruimpad bij onderbreking.

Verwijderingen hebben een eigen bericht

Een verwijderd bestand kan de client niet meer lezen om de inhoudshash en grootte vast te stellen. Het lokale verwijder-event krijgt daarom een lege hash en grootte nul. Aan de ontvangende kant verwerkt de client een verwijdering via een deleted-notification. De procesflow noemt deze route DELETE; in de Python-code loopt zij via een melding, terwijl opdrachten de uploads en downloads starten.

UML · Sequencediagram DELETE · een verwijdering doorgeven Teamontwerp · berichten van boven naar beneden
DELETE · een verwijdering doorgeven De client meldt de verwijdering via de actieve verbinding. De server verwijdert zijn bestand en verspreidt de melding, waarna een ontvangende client een nog aanwezige lokale kopie verwijdert. Bronclient .NET-server Andere client opt [lokale kopie aanwezig] Lokale verwijdering detecteren notification · deleted Serverbestand verwijderen notification · deleted Lokale kopie verwijderen
↓ Tijd · van boven naar beneden → Bericht · open pijlpunt opt · voorwaarde loop · herhaling alt · alternatieven
DELETE · een verwijdering doorgeven · Aanvullend teamontwerp uit dia 22: de server geeft een verwijdering door aan andere werkplekken.

Wat de tests aantonen

De Python-client bevat unit-tests voor hulpfuncties en mapbewaking. Met mocks, vervangingen van afhankelijkheden, controleren ze afzonderlijke stukken logica. Bij de broncodecontrole op 7 september 2026 slaagden veertien tests. De twee watcher-tests raken alleen negatieve paden en toetsen de werkelijke overdracht naar de asyncio-queue niet. Deze controle toont ook geen volledige client-serverproef, overdracht van meerdere gigabytes of werking op alle gevraagde besturingssystemen aan.

Mijn bijdrage: van bestandsgebeurtenis naar uitwisseling

Binnen ons teamsysteem ontwierp en bouwde ik de Python-client die de lokale map met de server verbindt. Mijn queuevoorstel gaf de overdracht tussen mapbewaking en netwerkverwerking een concrete plaats in de implementatie. Daarnaast werkte ik aan uploads, downloads en locks: de bewerkingen waarmee die berichten gevolgen krijgen voor de lokale bestanden.

De client laat daarmee zien hoe ik gebeurtenissen uit verschillende onderdelen samenbracht en de verwerking verdeelde. De afhandeling van de bestanden zelf bleef daarbij een afzonderlijke verantwoordelijkheid. Vooral de rechtstreeks overschreven downloads laten de grens van het resultaat zien: de scheiding tussen taken alleen voldeed nog niet aan alle eisen rond onvolledige bestanden.

TERUG NAAR SITE