Avatar billede droa Novice
15. september 2011 - 21:21 Der er 36 kommentarer og
1 løsning

Java Server Socket Program

Så jeg har leget lidt med at lave et multi-threaded Server Program i java.. men jeg er begyndt at tvivle på min arkitektur, og skulle høre om denne måde jeg gør det på er inde for en god standart.


Jeg har min main thread, som indeholder
Array med alle client socket tråde
Socket variabel med main server socket tråd
Terminal input/output

Main starter en server socket i den egen tråd.

Server socket tråden, vil så og vente på en forbindelse, som den så vil slynge over i en af clientsocket arrays tomme eller døde Threads.

for at lave det lidt mere virsuelt, har jeg lavet et billede i paint, som kan forklare det lidt bedre, håber jeg :)

jeg vil så meget gerne høre om denne ide er "ok" eller om den absolut ikke skal bruges på den måde. og hvad det bedste alternativ kunne være.

billede:
http://bollerne.dk/multithreadserver.jpg
Avatar billede arne_v Ekspert
15. september 2011 - 21:27 #1
Laver main traaden noget eller venter den bare? Hvis den bare venter kunne du flytte server listener funktionaliteten over i den.

En traad per client er et helt standard design. Det er relativt nemt at implementere. Du boer ikke bruge den med mange klienter. Jeg ville skifte design omkring de 500 samtidige klienter.
Avatar billede droa Novice
15. september 2011 - 21:37 #2
main tråden ville jeg gerne bruge til at "broadcaste" beskeder til alle tåde... jeg har prøvet lidt at have det altsammen i Serversocket tråden, men siden den vil stå og vente med serversocket.accept.

var jeg nød til at lave en ny tråd, der kunne køre et

While(true)
{
thread.sleep(25);
}

hvor jeg ville kunne styre f.eks et spils logik i.

ligenu leger jeg bare med det, så har prøvet at lave en lille chat server.
hvor main tråden har et array, med et objekt jeg kalder "messages", med 2 data "dato" og "besked".

hver gang clientsocket får en ny besked i indputstream, sender den det som et object, og tilbføjer det til arrayet.

hver gang der er nye beskeder, som er yngre end sidste gange clientsocket checkede.. vil den hente dem, og udskrive dem til outputstream.

men må man det? altså få så mange tråde til at skrive til samme array? eller findes der en bedre måde at gøre det på?
Avatar billede arne_v Ekspert
15. september 2011 - 21:43 #3
Jeg ville nok lade hver client traad staa for den broadcast.

Hvis du skal have noget tidsbaseret spil logik har du brug for en traad til det - og det kunne main godt bruges til.

Hvis traade skal tilgaa samme data struktur skal du bruge synchronized til at synkronisere tilgangen til data strukturen.
Avatar billede droa Novice
15. september 2011 - 21:46 #4
jeg vil præcis os bruge hver tråd, ved at læse samme sted fra i min main tråd :)

hmmm jeg har ikke rigtigt arbjedet med synhronized før, så skal lige læse op på det..
men jeg går ud fra det har noget at gøre med at kun 1 tråd læser fra dataarkivet af gangen?
Avatar billede Slettet bruger
15. september 2011 - 21:47 #5
Har du kun én tråd til at skrive data ud til samtlige sockets der lytter? Du kan godt risikere at en enkelt socket blokerer denne tråd hvis den er sløv til at læse de data du skriver til den. Hvis det er til chat fungerer det sikkert fint, men til spil vil du opleve lag.

Java giver dig mulighed for at læse og skrive data asynkront med java.nio. Du slipper for 1-2 tråde per klient (read+write), og kan køre det hele med en enkelt tråd. Til gengæld skal du bruge nogle buffere til at lagre de stumper af data du får læst/sendt, så det gør det ikke nødvendigvis mere simpelt at lave, men serveren vil køre meget mere smidigt.
Avatar billede arne_v Ekspert
15. september 2011 - 21:55 #6
synchronized(someobject) {
  ...
}

goer at kun en traad kan koere den kode samtidigt (naar traadene altsaa bruger samme someobject!).
Avatar billede droa Novice
15. september 2011 - 23:09 #7
hmm.. jeg lagt lige hurtigt en kode sammen.. for at vise hvad jeg har gjort.

jeg ved godt min messages array kun har begrænset antal pladser, men jeg gad ikke til at lave roteringen færdig.

men håber i kan se min ide og tankegang


myServer (main class)

package server;

import java.net.*;

public class myServer {
   
    protected String version = "1.0"; //versions nummer for at tjekke protokol version
    protected Thread sst; //Variabel som holder ServerSocket Tråden
    protected Thread[] cst;  //Array med alle Client Socket Trådene
    private int port;  //Port
    private int slots;  //max antal clienter som kan tilslutte
   
    protected Message[] messages; //indeholder beskeder fraa clienttråde
   
    public static void main(String[] args) {
        myServer mS = new myServer();
        mS.setPort(18778);
        mS.setSlots(2);
        mS.run();
    }
   
    public void setPort(int port){
        this.port = port;
    }
    public int getPort(){
        return this.port;
    }
    public void setSlots(int slots){
        this.slots = slots;
    }
    public int getSlots(){
        return this.slots;
    }
   
    public void run(){
        cst = new Thread[this.getSlots()];
        this.messages = new Message[100];
        System.out.println("Starter Server");
        System.out.println("Lytter på port: "+ this.getPort());
        sst = new Thread(new ServerSocketThread(this));  //ligger ServerSocketThread i tråd, med ref til myServer
        sst.start(); //starter Serversocket tråd
    }
}


ServerSocketThread

package server;

import java.net.*;
import java.io.*;
public class ServerSocketThread implements Runnable{
   
    myServer server; //bliver ref til myServer main tråden
    ServerSocket ss;  //bliver ServerSocket
   
    public ServerSocketThread(myServer server){
        this.server = server; //laver referancer til main thread
    }

    @Override
    public void run() {
       
        try {
            ss = new ServerSocket(server.getPort()); //opretter ServerSocket
           
            while(true)
            {
                Socket cs; //opretter clientsocket
                cs = ss.accept(); //overføre forbindelse til clientsocket
                System.out.println("Forbindelse fra "+ cs.getInetAddress().getHostAddress());
               
                ClientSocketThread tempcst = new ClientSocketThread(cs,server);
                for(int i = 0;i<server.cst.length;i++) //køre array igennem af tråde
                {
                    if(server.cst[i] == null || !server.cst[i].isAlive()){  //hvis denne tråd ikke bliver brugt
                      server.cst[i] = new Thread(tempcst); //ligger den indkommende client socket i sin egen tråd
                      tempcst.isAssigned = true;
                      tempcst.clienID = i;
                      server.cst[i].start();
                      break;
                    }
                }
                if(tempcst.isAssigned == false){
                    System.out.println("SERVER: kunne ikke forbinde, ikke nok slots");
                    cs.close();
                }
            }
           
        } catch (IOException ex) {}
    }
}


ClientSocketThread

package server;

import java.net.*;
import java.io.*;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ClientSocketThread implements Runnable{

    public boolean isAssigned;  //om denne er blevet lagt i array på ServerSocketThread
    public int clienID = 0;
    protected Socket cs;  //client Socket ref
    protected myServer server;  //ref til Main Thread
   
    private double lastmessage = 0;
    private PrintWriter out = null; //udegående tekst
    private BufferedReader in = null; //indgående tekst
   
    public ClientSocketThread(Socket cs, myServer server){
        this.lastmessage = System.currentTimeMillis();
        this.isAssigned = false;
        this.cs = cs;
        this.server = server;
    }
   
    @Override
    public void run() {       
        try {
            out = new PrintWriter(this.cs.getOutputStream(), true);
            in = new BufferedReader(new InputStreamReader(this.cs.getInputStream()));
        } catch (IOException ex) {}
       
        System.out.println("Klient #"+this.clienID+" Forbundet");
       
        while(true)
        {
           
            try {
                if(in.ready())  //hvis der er data at skrive, som er klar
                {
                    String temp = in.readLine();
                    if(temp.equals("BYE")){
                        out.println("BYE BYE");
                        server.messages[0] = new Message(System.currentTimeMillis(),"Klient #"+this.clienID+" Afbrudt");
                        break;
                    }else{
                        server.messages[0] = new Message(System.currentTimeMillis(),"Klient #"+this.clienID+": "+temp);
                    }
                }
            } catch (IOException ex) {}
         
                for(int i = 0; i < server.messages.length;i++){
                    if(server.messages[i] != null && server.messages[i].date > this.lastmessage)
                    {
                        out.println(server.messages[i].message);
                    }
                }
                this.lastmessage = System.currentTimeMillis();
               
          try {
              Thread.sleep(20);
          } catch (InterruptedException ex) {}
        }
       
        System.out.println("Klient #"+this.clienID+" Afbrudt");
       
        try {
            this.cs.close();
        } catch (IOException ex) {}
    }
   
}


Message

package server;


public class Message {
    public long date;
    public String message;
   
    public Message(long date, String message)
    {
        this.date = date;
        this.message = message;
    }
}
Avatar billede Slettet bruger
16. september 2011 - 00:53 #8
Det eneste jeg lige kan kommentere på ud fra den kode du har vist, er at du bruger Reader og Writer uden at specificere hvilken encoding de skal bruge, og det kan give nogle problemer hvis din server f.eks. kører på Windows og bruger windows-1252 som default, mens en klient på en anden platform bruger noget andet som default. Du kan med InputStreamReader og OutputStreamWriter på server og klient kode fortælle hvilken encoding de skal bruge (f.eks. UTF-8), så det er ens uanset hvilken platform du kører server og klient på.
Avatar billede droa Novice
16. september 2011 - 01:02 #9
arh det viste jeg ikke noget om, som jeg vist ikke fik nævnt direkte, så er det min første gang i socket :)

hvad så med min referancer til main tråden? der blev der snakket noget om der kunne ske problemer?

var det i sådan et tilfælde?
Avatar billede arne_v Ekspert
16. september 2011 - 01:25 #10
Med den kode kunne du ligesaa godt have server listener i main thread.
Avatar billede arne_v Ekspert
16. september 2011 - 01:26 #11
ready() er generelt en tvivlsom metode.

Drop den og sleep'en.

Og lav en simpel blocking read (readLine).
Avatar billede arne_v Ekspert
16. september 2011 - 01:28 #12
fortsat - og enten skriv med det samme eller smid i kø til en speciel skriver traad
Avatar billede arne_v Ekspert
16. september 2011 - 01:29 #13
Det maa vaere en fejl at du altid gemmer i index 0.

Der maa skulle taelles et index op.

Og en ArrayList<Message> var nok bedre end Message[].
Avatar billede arne_v Ekspert
16. september 2011 - 01:31 #14
fortsat - og der skal synkroniseres paa den
Avatar billede droa Novice
16. september 2011 - 01:32 #15
jeg havde et problem med readline, at den stopper koden, til noget kommer i den.. jeg ved ikke om det stemmer?

også havde jeg også tænkt mig at udvikle på main tråden så den gav lidt mere mening, det var blot en demonstration på hvad jeg mente :)

men havde tænkt at ligge noget GameLogic i Main Tråden, og nogen lidt andre objekter..

også lave 2 typer variabler.. en med input.. og en til de behandlede data...

f.eks en lang liste af alle Clienters bevægelser, hvor Main TRåden så tog den sidste bevægelse, og lage dem til at blive hentet af da andre clienter...
Avatar billede arne_v Ekspert
16. september 2011 - 01:32 #16
Lidt andre smaating.

Og public fields er et stort no no.

Package fields er et stort non no.

Private fields og public getters/setters !

Protected er OK i nogen sammenhaenge, men da du ikke har noget arv kan jeg ikke se nogen grund til at bruge det.
Avatar billede arne_v Ekspert
16. september 2011 - 01:35 #17
readLine blokerer indtil der er en linie - er det ikke OK?

Ah - problemet er at du vil skrive i samme traad.

Jeg tror at du skal redesigne det.

Lad enten den klient der har teksten eller en decideret writer thread staa for at udskrive.

Maaske passer det sidst bedst med din struktur.
Avatar billede droa Novice
16. september 2011 - 01:37 #18
som sagt, jeg kunne have brugt længere tid på den kode, men det var kun et eksempel, for at se hvad i sagde til det.. jeg har aldrig tænkt mig at bruge det, men mere for at forstå ideen, med at kunne kommunikere mellem tråde :)

jeg syntes ikke der har været noget om at jeg laver reference til myServer ind over tråde? det er der måske intet problem i?
Avatar billede droa Novice
16. september 2011 - 01:42 #19
så det er meget normalt at splitte en client op i 2 sockets?

Writer udgående ClientSocket
ReadBuffer ingående bundet til ClientSocket, med data opdateringer?
Avatar billede droa Novice
16. september 2011 - 01:43 #20
tror jeg skal læse lidt mere om det her inden jeg nogensinde prøver på noget med det.. er ligefør det bliver skræmmende
Avatar billede droa Novice
16. september 2011 - 02:03 #21
nu er hele dette spørgsmål os mest om at lave nogen gode standarter, og ikke bare klaske en lagkage sammen med en spade.. og hvad jeg kan høre, så er der en del jeg ikke har styr på.. som read/write Streams, data sending og andre vigtige ting.

Jeg kan stadig ikke få synchronized til at virke.. jeg lavede denne methode, men noget siger mig jeg har misforstået ideen med det.


public synchronized void SendMessageGlobal(String message){
        server.messages[0] = new Message(System.currentTimeMillis(),"Klient #"+this.clienID+": "+message);
    }


for kan ikke helt forstå hvordan man så får synkroniseret en delt variabel..
Avatar billede droa Novice
16. september 2011 - 02:04 #22
har jeg misforstået dette?

public synchronized void SendMessageGlobal(String message){
        server.messages[0] = new Message(System.currentTimeMillis(),"Klient #"+this.clienID+": "+message);
    }
Avatar billede arne_v Ekspert
16. september 2011 - 02:16 #23
Du kan sagtens have en ref til myServer i flere traade.

Du skal bare huske at synkronisere naar du bruger noget fra den.
Avatar billede arne_v Ekspert
16. september 2011 - 02:16 #24
Som med saa meget andet: oevelse goer mester !!
Avatar billede droa Novice
16. september 2011 - 03:29 #25
jeg må lige finde nogen tutorials på den pokkers synchronized, for jeg prøvede lidt med det, og kunne ikke få den til at fungere med synchronized(){} jeg havde ingen ide om hvordan jeg skulle få det til at se ud :)
Avatar billede arne_v Ekspert
16. september 2011 - 03:53 #26
Det er faktisk et lidt komplekst emne.

Men proev og koer dette (og studer det saa bagefter):

public class ToSyncOrNotToSync {
    private static final int NTHREAD = 100;
    private static final int NREP = 1000000;
    private int n;
    public void incrN() {
        n++;
    }
    public void testNonSynch() throws InterruptedException {
        n = 0;
        Thread[] t = new Thread[NTHREAD];
        for(int i = 0; i < t.length; i++) {
            t[i] = new NonSyncThread(this, NREP);
        }
        long t1 = System.currentTimeMillis();
        for(int i = 0; i < t.length; i++) {
            t[i].start();
        }
        for(int i = 0; i < t.length; i++) {
            t[i].join();
        }
        long t2 = System.currentTimeMillis();
        System.out.println((NTHREAD*NREP) + "=" + n + " in " + (t2-t1) + " ms");
    }
    public void testSynch() throws InterruptedException {
        n = 0;
        Thread[] t = new Thread[NTHREAD];
        for(int i = 0; i < t.length; i++) {
            t[i] = new SyncThread(this, NREP);
        }
        long t1 = System.currentTimeMillis();
        for(int i = 0; i < t.length; i++) {
            t[i].start();
        }
        for(int i = 0; i < t.length; i++) {
            t[i].join();
        }
        long t2 = System.currentTimeMillis();
        System.out.println((NTHREAD*NREP) + "=" + n + " in " + (t2-t1) + " ms");
    }
    public static void main(String[] args) throws InterruptedException {
        ToSyncOrNotToSync o = new ToSyncOrNotToSync();
        o.testNonSynch();
        o.testSynch();
    }
}

class NonSyncThread extends Thread {
    private ToSyncOrNotToSync parent;
    private int nrep;
    public NonSyncThread(ToSyncOrNotToSync parent, int nrep) {
        this.parent = parent;
        this.nrep = nrep;
    }
    public void run() {
        for(int i = 0; i < nrep; i++) {
            parent.incrN();
        }
    }
}

class SyncThread extends Thread {
    private ToSyncOrNotToSync parent;
    private int nrep;
    public SyncThread(ToSyncOrNotToSync parent, int nrep) {
        this.parent = parent;
        this.nrep = nrep;
    }
    public void run() {
        for(int i = 0; i < nrep; i++) {
            synchronized(parent) {
                parent.incrN();
            }
        }
    }
}
Avatar billede droa Novice
16. september 2011 - 04:26 #27
jeg kom os lige selv til at kigge på den keyword blocken


public class SharedData {
   
    private String text;
    private int num;
   
    public synchronized void setText(String text){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {}
        this.num++;
        this.text = text;
    }
   
    public synchronized String getText(){
        return this.text;
    }
   
    public synchronized int getNum(){
        return this.num;
    }
}



så jeg tænker at keyword blocken sætter man på den methode som alle ens tråde skal kalde.

methode blocken sætter man i den tråd man kalder fra, til den referance man vil i kontakt med, og methode man gerne vil bruge?

så methoden er lidt en form for modsætning i placering a keyword blocken?

hvis jeg har fåetstået get rigtigt?

uhfff.. dette spårgsmål er ved at blive ret langt
Avatar billede droa Novice
16. september 2011 - 11:19 #28
vil man ikke få problemer med synchronized, hvis 100 tråde står og gerne vil læse og skrive data dertil.. + gamelogikken også skal kunne rotere?
Avatar billede arne_v Ekspert
16. september 2011 - 14:37 #29
public synchronized void foobar() {
    ...
}

is the same as:

public void foobar() {
    synchronized(this) {
      ...
    }
}
Avatar billede arne_v Ekspert
16. september 2011 - 14:38 #30
Hvis du koerer mit eksempel vil du se at det kan koste en del at synkronisere.

Men hvad er pointen i et program som koerer superhurtigt men giver forkerte resultater ??
Avatar billede droa Novice
16. september 2011 - 17:19 #31
jeg giver dig helt ret i det spørgsmål, heller sikker end usikker :)
jeg er glad for alle dine gode inputs, og jeg har taget godt hånd om dem... jeg prøver at få implementeret de tips så godt jeg kan.. vil du ligge et svar, så vi kan få afsluttet denne lange tråd?
jeg kan jo altid spørge videre i et nyt spørgsmål hvis jeg får problemer, men jeg tror denne er ved at være langt over lukketid, da jeg har fået meget mere end jeg forventet, rigtigt god input :)
Avatar billede arne_v Ekspert
16. september 2011 - 19:13 #32
svar
Avatar billede arne_v Ekspert
16. september 2011 - 19:13 #33
tolamaps havde jo ogsaa input
Avatar billede droa Novice
16. september 2011 - 19:16 #34
det er rigtigt.. venter lige til han os ligger et svar
Avatar billede Slettet bruger
16. september 2011 - 20:32 #35
Ellers tak. Og fortsæt endelig din eksperimentering med client/server programmering. Det har jeg selv haft meget sjovt med, og samtidig lærer man en hel del om hvordan man bruger threads (her er wait() og notify() metoderne også interessante at sætte sig ind i).
Avatar billede droa Novice
16. september 2011 - 20:37 #36
mange tak tolamaps, jeg har os fundet det meget sjovt at pille ved, nu hvor jeg har lært at bruge det rigtigt :)
Avatar billede arne_v Ekspert
17. september 2011 - 04:38 #37
wait/notify kan godt drille lidt.

Saa hvis man kan noejes med at bruge noget fra java.util.concurrent er det nok bedre.
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester