PageRenderTime 54ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/netmsg-fb/netmsg.bas

http://segin-utils.googlecode.com/
Basic | 576 lines | 421 code | 85 blank | 70 comment | 23 complexity | 2f9cce00ab64e5cf2b941dcc550c009c MD5 | raw file
Possible License(s): 0BSD, GPL-2.0
  1. '
  2. ' Network messaging server/client.
  3. ' Copyright (C) 2007, Segin<segin2005@gmail.com>
  4. '
  5. ' Licensed under the GPLv2
  6. '
  7. ' Beware, heavy wizardry at work!
  8. '
  9. ' Win32 audio support Copyright (C) ?-2006 mpg123 project - http://mpg123.de
  10. '
  11. ' Default to building a client.
  12. '
  13. #if not defined(NETMSG_CLIENT_BUILD) and not defined(NETMSG_SERVER_BUILD)
  14. #define NETMSG_CLIENT_BUILD
  15. #endif
  16. #ifdef NETMSG_CLIENT_BUILD
  17. #define BUILDING_CLIENT
  18. #endif
  19. #ifdef NETMSG_SERVER_BUILD
  20. #define BUILDING_SERVER
  21. #endif
  22. #include once "netmsg.bi"
  23. #ifdef __FB_WIN32__
  24. #include "win/winsock2.bi"
  25. #include "win/mmsystem.bi"
  26. #endif
  27. #ifdef __FB_DOS__
  28. #error "Please provide info on Waterloo TCP/IP to maintainer"
  29. #endif
  30. #if defined(__FB_LINUX__) or defined(__FB_FREEBSD__)
  31. #include once "crt/netinet/in.bi"
  32. #include once "crt/arpa/inet.bi"
  33. #include once "crt/netdb.bi"
  34. #include once "crt/sys/socket.bi"
  35. #include once "crt/errno.bi"
  36. #define TRUE 1
  37. #define FALSE 0
  38. 'Linux console is not appropriate for attempting to get input due to
  39. 'the fact it is a data stream, and there's no API to get things like
  40. 'keycodes, only hacks. Pity.
  41. '
  42. 'This does have advantages over Windows, though.
  43. '
  44. 'gfxlib also supports the use of a framebuffer so X11 is not needed
  45. 'to be running, only installed.
  46. '
  47. 'Please note that running both the server and client on a Linux box sends CPU
  48. 'usage up a LOT, on my machine, to about 50%. 40% of it is X. It's cause of
  49. 'an inefficency in X11 or gfxlib. Could be my text entry loop. Would someone
  50. 'care to rewrite it to use events without changing it much?
  51. screen 12
  52. #endif
  53. #ifdef __FB_WIN32__
  54. function hStart( byval verhigh as integer = 2, byval verlow as integer = 0 ) as integer export
  55. dim wsaData as WSAData
  56. if( WSAStartup( MAKEWORD( verhigh, verlow ), @wsaData ) <> 0 ) then
  57. return FALSE
  58. end if
  59. if( wsaData.wVersion <> MAKEWORD( verhigh, verlow ) ) then
  60. WSACleanup( )
  61. return FALSE
  62. end if
  63. function = TRUE
  64. end function
  65. function hShutdown( ) as integer export
  66. function = WSACleanup( )
  67. end function
  68. #define hPrintError(e) print "error calling "; #e; ": "; WSAGetLastError( )
  69. #endif
  70. #ifdef __FB_LINUX__
  71. '
  72. ' Linux does NOT need socket library inits, unlike Windows...
  73. '
  74. function hStart() as integer export
  75. return TRUE
  76. end function
  77. function hShutdown() as Integer export
  78. return hStart()
  79. end function
  80. #define hPrintError(e) print "error calling "; #e; "."
  81. #endif
  82. function hResolve( byval hostname as string ) as integer export
  83. dim ia as in_addr
  84. dim hostentry as hostent ptr
  85. '' check if it's an ip address
  86. ia.S_addr = inet_addr( hostname )
  87. if ( ia.S_addr = INADDR_NONE ) then
  88. '' if not, assume it's a name, resolve it
  89. hostentry = gethostbyname( hostname )
  90. if ( hostentry = 0 ) then
  91. exit function
  92. end if
  93. function = *cast( integer ptr, *hostentry->h_addr_list )
  94. else
  95. '' just return the address
  96. function = ia.S_addr
  97. end if
  98. end function
  99. '':::::
  100. function hOpen( byval proto as integer = IPPROTO_TCP ) as SOCKET export
  101. dim s as SOCKET
  102. s = opensocket( AF_INET, SOCK_STREAM, proto )
  103. if( s = NULL ) then
  104. return NULL
  105. end if
  106. function = s
  107. end function
  108. '':::::
  109. function hConnect( byval s as SOCKET, byval ip as integer, byval port as integer ) as integer export
  110. dim sa as sockaddr_in
  111. sa.sin_port = htons( port )
  112. sa.sin_family = AF_INET
  113. sa.sin_addr.S_addr = ip
  114. function = connect( s, cast( PSOCKADDR, @sa ), len( sa ) ) <> SOCKET_ERROR
  115. end function
  116. '':::::
  117. function hBind( byval s as SOCKET, byval port as integer ) as integer export
  118. dim sa as sockaddr_in
  119. sa.sin_port = htons( port )
  120. sa.sin_family = AF_INET
  121. sa.sin_addr.S_addr = INADDR_ANY
  122. function = bind( s, cast( PSOCKADDR, @sa ), len( sa ) ) <> SOCKET_ERROR
  123. end function
  124. '':::::
  125. function hListen( byval s as SOCKET, byval timeout as integer = SOMAXCONN ) as integer export
  126. function = listen( s, timeout ) <> SOCKET_ERROR
  127. end function
  128. '':::::
  129. function hAccept( byval s as SOCKET, byval sa as sockaddr_in ptr ) as SOCKET export
  130. dim salen as integer
  131. salen = len( sockaddr_in )
  132. function = accept( s, cast( PSOCKADDR, sa ), @salen )
  133. end function
  134. '':::::
  135. function hClose( byval s as SOCKET ) as integer export
  136. shutdown( s, 2 )
  137. #ifdef __FB_WIN32__
  138. function = closesocket( s )
  139. #endif
  140. #if defined(__FB_LINUX__) or defined(__FB_FREEBSD__)
  141. function = close(s)
  142. #endif
  143. end function
  144. '':::::
  145. function hSend( byval s as SOCKET, byval buffer as zstring ptr, byval bytes as integer ) as integer export
  146. function = send( s, buffer, bytes, 0 )
  147. end function
  148. '':::::
  149. function hReceive( byval s as SOCKET, byval buffer as zstring ptr, byval bytes as integer ) as integer export
  150. function = recv( s, buffer, bytes, 0 )
  151. end function
  152. '':::::
  153. function hIp2Addr( byval ip as integer ) as zstring ptr export
  154. dim ia as in_addr
  155. ia.S_addr = ip
  156. function = inet_ntoa( ia )
  157. end function
  158. #define CLIENTADDR(c) *hIp2Addr( c.sin_addr.S_addr ) & "(" & c.sin_addr & ")"
  159. Dim shared sock As SOCKET
  160. Dim ret As Integer
  161. Dim Shared user As ZString * 30
  162. Dim shared packet as Proto
  163. Dim msg As ZString * 256
  164. Dim shared sa As sockaddr_in
  165. Dim shared s As SOCKET
  166. Dim char As Byte
  167. Dim shared nick As String
  168. Dim Shared Connected As Integer
  169. Dim Shared Threads(5) as Any Ptr
  170. Dim Shared Running As Integer
  171. #ifdef BUILDING_CLIENT
  172. Dim Shared serv As String * 60
  173. #endif
  174. Dim Shared mutex As Any Ptr
  175. Dim Shared timestamps As Integer
  176. Dim Shared audiodev As Any Ptr
  177. const SERVER_PORT = 1337
  178. cls
  179. #ifdef BUILDING_SERVER
  180. function serverIni( ) as integer
  181. #elseif defined(BUILDING_CLIENT)
  182. function clientIni( ) as integer
  183. #endif
  184. '' start winsock
  185. if( hStart( ) = FALSE ) then
  186. hPrintError( hStart )
  187. return FALSE
  188. end if
  189. '' create a socket for listening/connecting
  190. sock = hOpen( )
  191. if( sock = NULL ) then
  192. hPrintError( hOpen )
  193. return FALSE
  194. end if
  195. #ifdef BUILDING_SERVER
  196. '' bind it to the server port
  197. if( hBind( sock, SERVER_PORT ) = FALSE ) then
  198. hPrintError( hBind )
  199. return FALSE
  200. end if
  201. #endif
  202. function = TRUE
  203. end function
  204. function GetString Alias "GetString" (Prompt As String = "> ") As String Export
  205. Dim char As Byte
  206. Dim Path As String
  207. Dim X As Integer
  208. Dim Y As Integer
  209. Dim TY As Integer
  210. Dim TX As Integer
  211. MutexLock(mutex)
  212. X = CsrLin()
  213. Y = Pos()
  214. Locate 1,1
  215. Print Space(80);
  216. Locate 1,1
  217. Print Prompt;
  218. MutexUnlock(mutex)
  219. do while char <> 13
  220. char = Asc(inkey$)
  221. if char > 31 And char < 127 then
  222. If Len(Path) = 255 Then Goto StartScreenUpdate
  223. If Len(Path) > 255 Then Path = Left(Path,255)
  224. Path+=Chr(char)
  225. Goto StartScreenUpdate
  226. end if
  227. if char = 8 Then
  228. Path = Left(Path, Len(Path) - 1)
  229. Goto StartScreenUpdate
  230. End If
  231. StartScreenUpdate:
  232. MutexLock(mutex)
  233. ' Screenlock to prevent flicker
  234. ' Useless in console modes
  235. ScreenLock
  236. Locate 1,1
  237. Print Space(80);
  238. Locate 1,1
  239. Print Prompt;
  240. Locate 1,1+Len(Prompt)
  241. Print Right(Path,80 - Len(Prompt));
  242. ScreenUnlock
  243. EndScreenUpdate:
  244. TY = Pos()
  245. TX = CsrLin()
  246. Locate X, Y
  247. sleep 50
  248. X = CsrLin
  249. Y = Pos()
  250. Locate TX, TY
  251. MutexUnlock(mutex)
  252. loop
  253. Locate 1,1
  254. Print Space(80);
  255. Locate X, Y
  256. Return Path
  257. End Function
  258. Sub Quit Alias "Quit" (ret As Integer = 0) Export
  259. MutexDestroy(mutex)
  260. End ret
  261. End Sub
  262. Sub UpdateStatusBar Alias "UpdateStatusBar" () Export
  263. MutexLock(mutex)
  264. Dim X As Integer
  265. Dim Y As Integer
  266. X = CsrLin()
  267. Y = Pos()
  268. Locate 2,1
  269. Color 0,7
  270. Print Space(80)
  271. Locate 2,2
  272. #ifdef BUILDING_SERVER
  273. Print "-- Connection from " + _
  274. *hIp2Addr( sa.sin_addr.S_addr ) + " (" + nick + ") [" + _
  275. "server" + "] --"
  276. #elseif defined(BUILDING_CLIENT)
  277. Print "-- Connected to " + _
  278. serv + " (" + nick + ") [client] --"
  279. #endif
  280. Color 7,0
  281. Locate X, Y
  282. MutexUnlock(mutex)
  283. End Sub
  284. Color 7,0
  285. mutex = MutexCreate
  286. Running = 1
  287. #ifdef BUILDING_SERVER
  288. ret = serverIni( )
  289. #elseif defined(BUILDING_CLIENT)
  290. ret = clientIni( )
  291. #endif
  292. if ret = FALSE then
  293. #ifdef BUILDING_SERVER
  294. print "Error in netmsgd server init"
  295. hShutdown
  296. Quit
  297. end if
  298. if( hListen( sock ) = FALSE ) then
  299. hPrintError( hListen )
  300. #else
  301. print "Error in netmsg client init"
  302. #endif
  303. hShutdown
  304. Quit FALSE
  305. end if
  306. locate 3,1
  307. UpdateStatusBar()
  308. Nickname:
  309. Print "Please enter a nick."
  310. msg = GetString("nick> ")
  311. nick = Left(msg,30)
  312. if Len(nick) = 0 goto Nickname
  313. UpdateStatusBar()
  314. Print "Using nickname " + nick
  315. #ifdef BUILDING_SERVER
  316. print "Waiting for connection on port 1337"
  317. s = hAccept( sock, @sa )
  318. if( s = -1 ) then
  319. hPrintError( hAccept )
  320. #else
  321. Print "Please enter the server address (IP or hostname)"
  322. serv = Left(GetString("server> "),60)
  323. UpdateStatusBar()
  324. print "Connecting to " + serv + " on port 1337"
  325. if( hConnect( sock, hResolve( serv ), 1337 ) = FALSE ) then
  326. hPrintError( hConnect )
  327. quit
  328. #endif
  329. end if
  330. UpdateStatusBar()
  331. #ifdef BUILDING_SERVER
  332. print "Connection from " + *hIp2Addr( sa.sin_addr.S_addr ) + "(" & sa.sin_port & ")"
  333. #else
  334. print "Connected to " + serv + "(" & 1337 & ")"
  335. #endif
  336. Connected = 1
  337. Sub SendProtoMsg(mType As String, msg As String) Export
  338. MutexLock(mutex)
  339. If Len(msg) = 0 Then Exit Sub
  340. packet.msg = msg
  341. packet.type = mType
  342. packet.uname = Cast(String, Left(Nick,30))
  343. #ifdef BUILDING_SERVER
  344. hSend(s, @packet, Sizeof(packet))
  345. #else
  346. hSend(sock, @packet, Sizeof(packet))
  347. #endif
  348. Color 2
  349. if mType = "CHAT" then
  350. Print packet.uname + ": " + packet.msg
  351. End If
  352. color 7
  353. MutexUnlock(mutex)
  354. UpdateStatusBar()
  355. End Sub
  356. Sub RecvThread Alias "RecvThread" () Export
  357. Dim msg As Proto
  358. Dim bytes As Integer
  359. Dim msgType As String
  360. Dim msgUser As String
  361. Dim msgMsg As String
  362. Dim i as Integer
  363. Do
  364. #ifdef BUILDING_SERVER
  365. bytes = hReceive( s, @packet, Sizeof(packet) )
  366. msgUser = Str(bytes)
  367. #else
  368. msgType = "" : msgUser = "" : msgMsg = ""
  369. bytes = hReceive( sock, @packet, Sizeof(packet) )
  370. #endif
  371. if bytes <> Sizeof(packet) Then
  372. If bytes = -1 Or bytes = 0 Then
  373. 'Server has died without telling us.
  374. #ifdef BUILDING_SERVER
  375. print "Connection closed by client."
  376. #else
  377. print serv + ": Connection closed by remote host."
  378. #endif
  379. Connected = 0
  380. cls
  381. hShutdown
  382. quit
  383. Goto EndThread
  384. End If
  385. print "hRecieve() returned " & bytes & " bytes."
  386. Goto EndRecv
  387. End If
  388. Select Case packet.type
  389. Case "CHAT"
  390. ' This is the only screen operation IN threaded section
  391. ' of program execution in which locking mutex = bad stuff
  392. Color 6
  393. Print packet.uNAME + ": " + packet.msg
  394. color 7
  395. UpdateStatusBar()
  396. #ifdef BUILDING_SERVER
  397. Case "BYE!"
  398. SendProtoMsg("CYA!","server to client: clear to disconnect.")
  399. #else
  400. Case "CYA!"
  401. #endif
  402. Connected = 0
  403. cls
  404. hShutdown
  405. quit
  406. Goto EndThread
  407. Case "AUDp"
  408. ' NOTE: If you are using a rather dated Linux system (at the latest, from
  409. ' 2006) and your system uses LinuxThreads for threading, you will be sorry.
  410. ' Each and every audio packet is processed and played back in a new thread
  411. ' for each packet received. Since the packet can only hold 200 bytes or so
  412. ' per packet, this means that at a 8,000Hz sampling rate, 8 bit, mono,
  413. ' you will have 40 threads that only run for 2% of a second being started
  414. ' every second. Since LinuxThreads uses clone(), it incurrs a rather hefty
  415. ' time penalty for setting up and cleaning up each thread. What all this
  416. ' means is, if you use LinuxThreads (type in "/lib/libc.so.6" at your shell
  417. ' and look for "LinuxThreads" in the output, if you see it, you are using it)
  418. ' this program's VoIP features will suck since your system's already slow
  419. ' POSIX threads implementation (LinuxThreads) is getting the shit beat out
  420. ' of it, although this is true on a few other systems, since the VoIP here
  421. ' is going to torture your OS's thread system.
  422. Case else
  423. print !"Unknown packet type:\"" + packet.type + !"\"."
  424. Print "Sizeof(packet) = " & Sizeof(packet) & ", bytes = " & bytes
  425. Sleep(1)
  426. End Select
  427. EndRecv:
  428. Loop
  429. EndThread:
  430. Running = 0
  431. End Sub
  432. Function AudioInit Alias "AudioInit" () As Integer Export
  433. '
  434. ' WinMM, I'm gonna be sick...
  435. '
  436. #ifdef __FB_WIN32__
  437. Dim As MMRESULT res
  438. Dim As WAVEFORMATEX outFormatex
  439. if waveOutGetNumDevs() = -1 Then
  440. print "audio: WinMM reports no audio device present!"
  441. return FALSE
  442. End If
  443. With outFormatex
  444. .wFormatTag = WAVE_FORMAT_PCM
  445. .wBitsPerSample = 8
  446. .nChannels = 1
  447. .nSamplesPerSec = 8000
  448. .nAvgBytesPerSec = .nSamplesPerSec * .nChannels * .wBitsPerSample/8
  449. .nBlockAlign = .nChannels * .wBitsPerSample/8
  450. End With
  451. #endif
  452. '
  453. ' Both Linux and FreeBSD support the Open Sound System; however it's
  454. ' depreciated on Linux. Who cares, though, since FreeBSD uses it,
  455. ' people will code for it (also even for Linux cause ALSA is confusing)
  456. '
  457. #if defined(__FB_LINUX__) Or defined(__FB_FREEBSD__)
  458. #endif
  459. End Function
  460. Sub AudioThread() Export
  461. Dim ret As Integer
  462. ret = AudioInit()
  463. if ret = FALSE Then
  464. return
  465. end if
  466. End Sub
  467. Threads(2) = ThreadCreate(@RecvThread)
  468. Threads(1) = ThreadCreate(@AudioThread)
  469. Do While Connected
  470. msg = GetString
  471. if Left(msg, 5) = "/quit" Then
  472. #ifdef BUILDING_SERVER
  473. SendProtoMsg("CYA!","server to client: please disconnect now.")
  474. Connected = 0
  475. #else
  476. SendProtoMsg("BYE!","client to server: request to disconnect.")
  477. Sleep(4000)
  478. #endif
  479. Goto EndSend
  480. End If
  481. If Len(msg) > 1 Then
  482. SendProtoMsg("CHAT", msg)
  483. End If
  484. EndSend:
  485. Loop
  486. Running = 0
  487. hClose(s)
  488. hClose(sock)
  489. hShutdown()
  490. cls
  491. quit