summaryrefslogtreecommitdiff
path: root/fpcdocs/sockex/serverex.pp
blob: 8360b89daa3eb9877b5584b8ddc9220382c987ca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
Program Socket_Comms_Test;
{***************************************************************************
 TCP/IP Streaming Socket Server Example program.
 NumberofConnections is the number of consecutive active connections I will
 allow. I have not hit this limit yet.
 This defaults to port 5000
 The MOST Important thing to look at when doing socket calls of any kind
 is the byte order in the structure. Got caught big time with this in reference
 to the port number.
 This program runs as-is, just telnet localhost 5000 to connect to it.
 No warranty at all, I will not be responsible if this sets fire to your dog!

 This is exactly as I use it, I have just put the references to my db unit
 in curly brackets. It just echoes back what you type on a line by line basis
 Run it in X or on a seperate virtual console to the one you are telneting from
 as it prints a LOT of info to the console about what it is doing and such.
 I'm not a pretty coder at all, so please, no complaints about the lack of
 comments or coding style, unless they are very contructive ;p)

 type 'quit', minus the quotes and in lower case on the console to exit the
 program. The only problem I can see with this, is if you exit it, it does
 not shut down the connections to the telnet sessions cleanly, and therefore
 it leaves port 5000 in a TIME_WAIT state for a couple of minutes. This prevents
 you re-running the program immediately as it will not bind to the port.
 (Bind Error 98).
 If you know how to fix this, please let me know and I'll update the code.
 If you exit all your telnet sessions before shutting the server down, it
 works fine.

 Hope some of you find this usefull. I wrote it, purely because there is a
 big lack of examples of linux port use in FPC. And I know NO C, therefore
 the examples on the net meant nothing to me.

 All I ask is :-
 If you like it, use it or want to change it, please drop me an E-mail.

 Regards Brad Campbell
 bcampbel@omen.net.au
  ***************************************************************************}

{$mode ObjFPC}

Uses baseunix, unixtype, unix, Sockets, Sysutils;

Const
 NumberofConnections = 5;

Type ConnectionType = Record
                       IP : Cardinal;
                       Port : Word;
                       Handle : Integer;
                       Connected : Boolean;
                       IdleTimer : Integer;
                      End;


Var
 Connection : Array[1..NumberofConnections] Of ConnectionType;
 FDS        : TFDSet;
 S          : LongInt;
 PortNumber : Word;
 GreatestHandle : Integer;
 Quit       : Boolean;
 Command    : String;

Procedure ZeroConnection;

Var Loop : Integer;
Begin
 For Loop := 1 To NumberOfConnections Do
  Connection[Loop].Connected := False;
End;


Function FreeConnections : Integer;

Var Loop : Integer;

Begin
 Result := 0;
 For Loop := 1 To NumberOfConnections Do
  If Not Connection[Loop].Connected Then Inc(Result);
 FreeConnections := Result;
End;

Function GetFreeConnection : Integer;

Var Loop : Integer;
   Found : Boolean;
Begin
 Result := 0;
 Loop := 1;
 Found := False;
 While (Loop < NumberOfConnections + 1) and (Not Found) Do
 Begin
  If Not Connection[Loop].Connected Then
   Begin
    Found := True;
    Result := Loop;
   End;
  Inc(Loop);
  GetFreeConnection := Result;
 End;
End;

Procedure PError(S : String);
Begin
 Writeln(S,SocketError);
 Halt(100);
End;

Procedure PDebug(S : String);
Begin
 Writeln(S);
End;

Procedure PDebugNOLF(S: String);
Begin
 Write(S);
End;

Function SockAddrtoString(InAddr : LongWord) : String;

Var
 P1,P2,P3,P4 : Byte;
 S1,S2,S3,S4 : String;

Begin
 P1 := (InAddr And $ff000000) Shr 24;
 P2 := (InAddr And $ff0000) Shr 16;
 P3 := (InAddr And $ff00) Shr 8;
 P4 := InAddr And $FF;
 Str(P1,S1);
 Str(P2,S2);
 Str(P3,S3);
 Str(P4,S4);
 SockAddrtoString := S4+'.'+S3+'.'+S2+'.'+S1;
End;

Procedure WelcomeHandle(Handle, ConnNum : Integer);

Var Buffer : String;
 Sent : Integer;
Begin
 Buffer := 'Welcome to Brads Server 1.0'+#10+#13+'You Are Connection '+
           InttoStr(ConnNum)+' Of '+InttoStr(NumberofConnections)+
           ', With '+InttoStr(FreeConnections)+' Connections Free'#13+#10;
 Sent := fpSend(Handle,@Buffer[1],Length(Buffer),0);
 If Sent <> Length(Buffer) Then
  PDebug('Wanted to Send : ' +InttoStr(Length(Buffer))+' Sent Only : '
          +InttoStr(Sent)+' to Connection : '+InttoStr(ConnNum));
End;

Procedure AcceptNewConnection;

Var ConnectionNumber : Integer;
    Handle           : LongInt;
    FromAddrSize     : LongInt;
    FromAddr         : TInetSockAddr;

Begin
 FromAddrSize := Sizeof(FromAddr);
 If FreeConnections > 0 Then
  Begin
   ConnectionNumber := GetFreeConnection;
   PDebug('Accepting New Connection Number : '+InttoStr(ConnectionNumber));
   Handle := fpAccept(S,@FromAddr,@FromAddrSize);
   If Handle < 0 Then PError('Accept Error!!');
   PDebug('Accepted From : '+SockAddrtoString(FromAddr.Addr)+' Port : '
   +Inttostr(Swap(FromAddr.Port)));
   Connection[ConnectionNumber].Handle := Handle;
   Connection[ConnectionNumber].IP := FromAddr.Addr;
   Connection[ConnectionNumber].Port := FromAddr.Port;
   Connection[ConnectionNumber].Connected := True;
   Connection[ConnectionNumber].IdleTimer := 0;
   WelcomeHandle(Handle,ConnectionNumber);
  End;
End;

Procedure SetUpSocket;

Var
 SockAddr : TInetSockAddr;
 yes  : longint;

Begin
 SockAddr.Family := AF_INET;
 SockAddr.Port := Swap(PortNumber);
 SockAddr.Addr := 0;
 S := fpSocket(AF_INET,SOCK_STREAM,0);
 If SocketError <> 0 Then PError('Socket Error : ');
 yes := $1010101;  {Copied this from existing code. Value is empiric,
                    but works. (yes=true<>0) }
 fpSetSockOpt(s, SOL_SOCKET, SO_REUSEADDR,@yes,sizeof(yes));
 If -1=fpBind(S,@SockAddr,SizeOf(SockAddr)) Then PError('Bind Error : ');
 If fpListen(S,5)=-1 Then PError('Listen Error : ');
End;

Procedure LoadConnectedFDS;

Var Loop : Integer;
Begin
 For Loop := 1 To NumberOfConnections Do
  If Connection[Loop].Connected Then
   Begin
    fpFD_SET(Connection[Loop].Handle,FDS);
    If Connection[Loop].Handle > GreatestHandle Then
     GreatestHandle := Connection[Loop].Handle;
   End;
End;

Procedure ServiceHandle(Handle, ConnectionNum : Integer);

Var Buffer : String;
 Sent, BufferLength : Integer;

Begin
 Writeln('Service Handle : ',Handle);
 BufferLength := fpRecv(Handle,@Buffer[1],200,0);
 Setlength(Buffer,BufferLength);
 If SocketError <> 0 Then
  PDebug('Reciceved Socket Error : '
  +InttoStr(SocketError)+' OnHandle '+InttoStr(Handle));

 If BufferLength = 0 Then  {It's EOF, Socket has been closed}
  Begin
   PDebug('Socket Handle '+InttoStr(Handle)+' Closed');
   Connection[ConnectionNum].Connected := False;
   fpShutdown(Handle,2);
   fpClose(Handle);
  End

 Else
  Begin
   PDebug(InttoStr(BufferLength)+' Bytes Recieved');
  {Buffer := Db_Query(Buffer);}
   Sent := fpSend(Handle,@Buffer[1],Length(Buffer),0);
   If Sent <> Bufferlength Then
    PDebug('Wanted to Send : '+InttoStr(Length(Buffer))+' Only Sent : '+InttoStr(Sent));
  End;
End;

Procedure ServiceSockets;

Var Loop : Integer;

Begin
 For Loop := 1 To NumberOfConnections Do
  If Connection[Loop].Connected Then
   If fpFD_ISSET(Connection[Loop].Handle,FDS)>0 Then
    ServiceHandle(Connection[Loop].Handle,Loop);

 If fpFD_ISSET(S,FDS)>0 Then AcceptNewConnection;
End;

Procedure CloseAllOpen;

Var Loop : Integer;
Begin
 For Loop := 1 To NumberOfConnections Do
  Begin
   If Connection[Loop].Connected = True Then
    Begin
     fpShutdown(Connection[Loop].Handle,1);
{ fdClose(Connection[Loop].Handle);}
 {Connection[Loop].Connected := False;}
    End;
  End;
End;

Begin
 ZeroConnection;  {Clear Connected Array}
 Quit := False;
 PortNumber := 5000;
 SetupSocket;
 Repeat
  fpFD_ZERO(FDS);
  fpFD_SET(S,FDS); { Socket Looking for new connections }
  fpFD_SET(1,FDS); { Terminal }
  GreatestHandle := S;
  LoadConnectedFDS;
  If fpSelect(GreatestHandle+1,@FDS,Nil,Nil,1000) > 0 Then
   Begin
    ServiceSockets;
    If fpFD_ISSET(1,FDS)>0 Then
     Begin
      PDebug('Reading Console');
      Readln(Command);
      If Command='quit' Then quit := True;
{       Else Writeln(DB_Query(Command));}
      Command := '';
     End;
   End;
{DB_Tic;} {Updates Database Internals, Needs at Least 1 run per second}
 Until Quit = True;
 CloseAllOpen;

End.