Recent

Author Topic: (SOLVED - THANKS FOR HELP)connecting a server socket with several client sockets  (Read 8152 times)

Nukualofa

  • New member
  • *
  • Posts: 7
Hey,

I want to write a simple Chat using the Sockets unit. I have four files: Server, ServerThread, Client & ClientThread.
The server creates a socket and waits for clients to connect to it. As soon as a client connects, a new serverThread is started that manages the communication with the client.
The client also creates a socket, connects to the server and starts a clientThread that waits for incoming messages from the corresponding serverThread.
Everything works fine if I use a different port for each client. But Google says, the normal way is to have only one port (on the server side) which can handle several clients. If a second client tries to connect to the port, there is no SocketError, but it doesn't work anyway. Apparently the fpaccept-function that waits for a client to connect isn't reacting to the client's fpconnect-function.

I spent a few days reading everything I found about the topic and tried almost everything but failed miserably. I would be really glad if someone could tell me what's wrong with my code. Here it is (sorry, the "insert code" button seems not to work):



unit Server;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, Sockets, ServerThread;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  public
    procedure handle(ID, message: String);
  end;

var
  Form1: TForm1;
  threads: Array[1..4] of TServerThread;
  serverSocket: Longint;
  clientSocket: Longint;
  serverAddr: TInetSockAddr;
  opt: Integer = 1;
  addrSize: Longint;
  clientCount: Integer = 0;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
begin
  repeat
    serverSocket:= fpSocket(AF_INET, SOCK_STREAM, 0);
    if fpSetSockOpt(serverSocket, SOL_SOCKET, SO_REUSEADDR, @opt, sizeOf(opt)) = SOCKET_ERROR then showMessage('Server : Multi : ' + intToStr(socketError));
    if serverSocket = SOCKET_ERROR then showMessage('Server : Socket : ' + intToStr(socketError));
    serverAddr.sin_family:= AF_INET;
    serverAddr.sin_port:= htons(50000);
    serverAddr.sin_addr.s_addr:= htonl($7F000001);
    if fpBind(serverSocket, @serverAddr, sizeOf(serverAddr)) = SOCKET_ERROR then showMessage('Server : Bind : ' + intToStr(socketError));
    if fpListen(serverSocket, 4) = SOCKET_ERROR then showMessage('Server : Listen : ' + intToStr(socketError));
    showMessage('Waiting for connect from Client...');
    addrSize:= sizeOf(serverAddr);
    clientSocket:= fpaccept(serverSocket, @serverAddr, @addrSize);
    if clientSocket = SOCKET_ERROR then showMessage('Server : Accept : ' + intToStr(socketError)) else clientCount:= clientCount + 1;
    threads[clientCount]:= TServerThread.create(true, clientSocket);
    threads[clientCount].start;
  until clientCount = 4;
end;

procedure TForm1.handle(ID, message: String);
var
  i, toTerminate: Integer;
  MyCriticalSection: TRTLCriticalSection;
begin
  InitCriticalSection(MyCriticalSection);
  EnterCriticalSection(MyCriticalSection);
  try
    for i:= 1 to clientCount do
    begin
      threads.send(ID + ': ' + message);
      if threads.getID = ID then toTerminate:= i;
    end;
    if message = 'ciao' then
    begin
      threads[toTerminate].send('ciao');
      threads[toTerminate].close;
      clientCount:= clientCount - 1;
      for i:= toTerminate to clientCount do threads:= threads[i + 1];
    end;
  finally
    LeaveCriticalSection(MyCriticalSection);
  end;
end;

end.



unit ServerThread;

{$mode objfpc}{$H+}

interface

uses Classes, Dialogs, Sockets, SysUtils;

type
  TServerThread = class(TThread)
  private
    ID: String;
    clientSocket: Longint;
  protected
    procedure execute; override;
  public
    constructor create(createSuspended: Boolean; client: Longint);
    procedure send(msg: String);
    function getID: String;
    procedure close;
  end;

var
  buffer: String[255];
  count: Longint;

implementation

uses Server;

constructor TServerThread.create(createSuspended: Boolean; client: Longint);
begin
  freeOnTerminate:= true;
  inherited create(createSuspended);
  clientSocket:= client;
end;

procedure TServerThread.execute;
begin
  count:= fprecv(clientSocket, @buffer[1], 255, 0);
  if (count <> SOCKET_ERROR) and (count > 0) then
  begin
    setLength(buffer, count);
    ID:= buffer;
  end;
  buffer:= 'Herzlich willkommen im Chat, ' + ID;
  count:= length(buffer);
  if fpsend(clientSocket, @buffer[1], count, 0) = count then
  begin
    repeat
      count:= fprecv(clientSocket, @buffer[1], 255, 0);
      if (count <> SOCKET_ERROR) and (count > 0) then
      begin
        setLength(buffer, count);
        Form1.handle(ID, buffer);
      end;
    until (count = SOCKET_ERROR) or (count = 0);
  end;
end;

procedure TServerThread.send(msg: String);
begin
  fpsend(clientSocket, @msg[1], length(msg), 0);
end;

function TServerThread.getID: String;
begin
  result:= ID;
end;

procedure TServerThread.close;
begin
  closeSocket(clientSocket);
end;

end.



unit Client;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, Sockets, ClientThread;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Edit1: TEdit;
    Edit2: TEdit;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Edit1Change(Sender: TObject);
    procedure handle(msg: String);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;
  thread: TClientThread;
  serverAddr: TInetSockAddr;
  serverSocket: Longint;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
begin
  serverSocket:= fpSocket(AF_INET, SOCK_STREAM, 0);
  if serverSocket = SOCKET_ERROR then showMessage('Client : Socket : ' + intToStr(socketError));
  serverAddr.sin_family:= AF_INET;
  serverAddr.sin_port:= htons(50000);
  serverAddr.sin_addr.s_addr:= htonl($7F000001);
  //funktioniert beim zweiten Client nicht, da kein Error, obwohl die Verbindung nicht zustande kommt (fpaccept reagiert nicht)
  if fpconnect(serverSocket, @serverAddr, sizeOf(serverAddr)) = SOCKET_ERROR then showMessage('Client : Connect : ' + intToStr(socketError));
  thread:= TClientThread.create(true, serverSocket);
  thread.start;
  buffer:= Edit1.Text;
  fpsend(serverSocket, @buffer[1], length(buffer), 0);
  Button2.Enabled:= true;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  buffer: String;
begin
  buffer:= Edit2.Text;
  fpsend(serverSocket, @buffer[1], length(buffer), 0);
end;

procedure TForm1.Edit1Change(Sender: TObject);
begin
  if length(Edit1.Text) > 0 then Button1.Enabled:= true else Button1.Enabled:= false;
end;

procedure TForm1.handle(msg: String);
begin
  if msg = 'ciao' then closeSocket(serverSocket)
  else Memo1.Lines.Add(msg);
end;

end.



unit ClientThread;

{$mode objfpc}{$H+}

interface

uses Classes, Dialogs, Sockets, SysUtils;

type
  TClientThread = class(TThread)
  private
    serverSocket: Longint;
  protected
    procedure execute; override;
  public
    constructor create(createSuspended: Boolean; server: Longint);
  end;

var
  buffer: String[255];
  count, i: Longint;

implementation

uses Client;

constructor TClientThread.create(createSuspended: Boolean; server: Longint);
begin
  freeOnTerminate:= true;
  inherited create(createSuspended);
  serverSocket:= server;
end;

procedure TClientThread.execute;
begin
  repeat
    count:= fprecv(serverSocket, @buffer[1], 255, 0);
    if count <> SOCKET_ERROR then
    begin
      setLength(buffer, count);
      Form1.handle(buffer);
    end;
  until buffer = 'ciao';
  closeSocket(serverSocket);
end;

end.
« Last Edit: November 24, 2015, 11:52:26 am by Nukualofa »

SymbolicFrank

  • Hero Member
  • *****
  • Posts: 1313
Re: connecting a server socket with several client sockets
« Reply #1 on: November 24, 2015, 09:09:17 am »
If you want multiple connections, you need multiple ports.

I think the issue is, that there has to be a single port on the server that accepts incoming requests. But the server will then spawn a new thread and assign it a new port, exclusively for that session.

Nukualofa

  • New member
  • *
  • Posts: 7
Re: connecting a server socket with several client sockets
« Reply #2 on: November 24, 2015, 10:32:42 am »
Ok, but if Client A has "used" the single port on the server that accepts incoming requests, this won't work for Client B, except there is a way to "reset" the socket or the port after Client A has been assigned a new port. Is there a simpler solution than closing and reopening the socket?

JD

  • Hero Member
  • *****
  • Posts: 1848
Re: connecting a server socket with several client sockets
« Reply #3 on: November 24, 2015, 11:01:46 am »
Pardon me but why don't you use Indy or Synapse to do this? They already have this multithreaded functionality built-in.

JD
Windows - Lazarus 2.1/FPC 3.2 (built using fpcupdeluxe),
Linux Mint - Lazarus 2.1/FPC 3.2 (built using fpcupdeluxe)

mORMot; Zeos 8; SQLite, PostgreSQL & MariaDB; VirtualTreeView

rvk

  • Hero Member
  • *****
  • Posts: 6163
Re: connecting a server socket with several client sockets
« Reply #4 on: November 24, 2015, 11:05:53 am »
Ok, but if Client A has "used" the single port on the server that accepts incoming requests, this won't work for Client B, except there is a way to "reset" the socket or the port after Client A has been assigned a new port. Is there a simpler solution than closing and reopening the socket?
??
A server can receive multiple connection to one port. It's not the receiving port that makes it unique but the incoming and outgoing port (and incoming and outgoing IP). So a connection is made up of 4 numbers. All these numbers make a unique connection. So you can have this (every line with 4 numbers must be unique):

192.168.1.77:80 -> 192.168.1.1:80
192.168.1.77:81 -> 192.168.1.1:80
192.168.1.66:80 -> 192.168.1.1:80
192.168.1.66:81 -> 192.168.1.1:80

So when I (192.168.1.55) try to make a connection to 192.168.1.1:80 I give a local unique port at my end (for instance 1234) and connect to port 80 of the server, you get
192.168.1.55:1234 -> 192.168.1.1:80

See the question and answers here:
http://stackoverflow.com/questions/3329641/how-do-multiple-clients-connect-simultaneously-to-one-port-say-80-on-a-server

If this doesn't work for you there must be something else wrong in the code.


(you can put [ code ] [/ code ] (without the spaces) around your code.)

rvk

  • Hero Member
  • *****
  • Posts: 6163
Re: connecting a server socket with several client sockets
« Reply #5 on: November 24, 2015, 11:32:56 am »
The problem you're having is that your trying to bind multiple listening-sockets to the same port in Button1Click.

You should only create ONE listening-socket. When a client connects you get a unique Clientsocket from fpaccept and that one you put in your server thread. The listening server-socket keeps checking for new incoming connections.

So you should put the initialization of the listening-socket outside the repeat loop.
Something like this: (not tested)
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. begin
  3.  
  4.   serverSocket:= fpSocket(AF_INET, SOCK_STREAM, 0);
  5.   if fpSetSockOpt(serverSocket, SOL_SOCKET, SO_REUSEADDR, @opt, sizeOf(opt)) = SOCKET_ERROR then showMessage('Server : Multi : ' + intToStr(socketError));
  6.   if serverSocket = SOCKET_ERROR then showMessage('Server : Socket : ' + intToStr(socketError));
  7.   serverAddr.sin_family:= AF_INET;
  8.   serverAddr.sin_port:= htons(50000);
  9.   serverAddr.sin_addr.s_addr:= htonl($7F000001);
  10.   if fpBind(serverSocket, @serverAddr, sizeOf(serverAddr)) = SOCKET_ERROR then showMessage('Server : Bind : ' + intToStr(socketError));
  11.   if fpListen(serverSocket, 4) = SOCKET_ERROR then showMessage('Server : Listen : ' + intToStr(socketError));
  12.   showMessage('Waiting for connect from Client...');
  13.  
  14.   repeat
  15.     addrSize:= sizeOf(serverAddr);
  16.     clientSocket:= fpaccept(serverSocket, @serverAddr, @addrSize);
  17.     if clientSocket = SOCKET_ERROR then showMessage('Server : Accept : ' + intToStr(socketError)) else clientCount:= clientCount + 1;
  18.     threads[clientCount]:= TServerThread.create(true, clientSocket);
  19.     threads[clientCount].start;
  20.   until clientCount = 4;
  21.  
  22. end;

(B.T.W. I would put a TMemo on the form and instead of showing ShowMessage put the messages in that TMemo.)
« Last Edit: November 24, 2015, 11:37:08 am by rvk »

Nukualofa

  • New member
  • *
  • Posts: 7
Re: connecting a server socket with several client sockets
« Reply #6 on: November 24, 2015, 11:50:18 am »
@rvk: that's it! I moved the "repeat" behind the "bind" and it works perfectly! Thank you so much!

@everyone else: Thanks for your advice, too!

 

TinyPortal © 2005-2018