wxruby-ruby19 1.9.8-x86-darwin-9 → 1.9.10-x86-darwin-9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +53 -0
- data/README +297 -0
- data/lib/wx.rb +2 -2
- data/lib/wx/accessors.rb +7 -1
- data/lib/wx/classes/app.rb +10 -4
- data/lib/wx/classes/bitmap.rb +29 -1
- data/lib/wx/classes/clipboard.rb +19 -3
- data/lib/wx/classes/colour.rb +6 -4
- data/lib/wx/classes/data_object.rb +14 -0
- data/lib/wx/classes/data_object_simple.rb +6 -0
- data/lib/wx/classes/dataformat.rb +23 -0
- data/lib/wx/classes/evthandler.rb +79 -4
- data/lib/wx/classes/genericdirctrl.rb +36 -0
- data/lib/wx/classes/grid.rb +8 -0
- data/lib/wx/classes/hboxsizer.rb +6 -0
- data/lib/wx/classes/icon.rb +12 -1
- data/lib/wx/classes/image.rb +13 -1
- data/lib/wx/classes/listctrl.rb +12 -0
- data/lib/wx/classes/point.rb +8 -0
- data/lib/wx/classes/rect.rb +10 -1
- data/lib/wx/classes/richtextctrl.rb +63 -0
- data/lib/wx/classes/size.rb +9 -0
- data/lib/wx/classes/sizer.rb +18 -3
- data/lib/wx/classes/standardpaths.rb +9 -0
- data/lib/wx/classes/texturlevent.rb +14 -2
- data/lib/wx/classes/toolbar.rb +4 -6
- data/lib/wx/classes/vboxsizer.rb +6 -0
- data/lib/wx/classes/window.rb +7 -0
- data/lib/wx/classes/xmlresource.rb +17 -0
- data/lib/wx/helpers.rb +16 -1
- data/lib/wx/keyword_ctors.rb +3 -2
- data/lib/wx/keyword_defs.rb +56 -5
- data/lib/wx/version.rb +1 -1
- data/lib/wxruby2.bundle +0 -0
- data/samples/SAMPLES-LICENSE.TXT +18 -0
- data/samples/aui/aui.rb +1356 -0
- data/samples/bigdemo/About.rbw +39 -0
- data/samples/bigdemo/ColorPanel.rbw +23 -0
- data/samples/bigdemo/GridSimple.rbw +78 -0
- data/samples/bigdemo/MDIDemo.rbw +57 -0
- data/samples/bigdemo/PopupMenu.rbw +149 -0
- data/samples/bigdemo/ShapedWindow.rbw +131 -0
- data/samples/bigdemo/Sizers.rbw +543 -0
- data/samples/bigdemo/bigdemo.rb +823 -0
- data/samples/bigdemo/demoTemplate.rbw +33 -0
- data/samples/bigdemo/helpfile.htb +0 -0
- data/samples/bigdemo/icons/Test 015.jpg +0 -0
- data/samples/bigdemo/icons/Test 015.png +0 -0
- data/samples/bigdemo/icons/choice.bmp +0 -0
- data/samples/bigdemo/icons/choice.xpm +27 -0
- data/samples/bigdemo/icons/combo.bmp +0 -0
- data/samples/bigdemo/icons/combo.xpm +27 -0
- data/samples/bigdemo/icons/copy.xpm +25 -0
- data/samples/bigdemo/icons/cut.xpm +24 -0
- data/samples/bigdemo/icons/gauge.bmp +0 -0
- data/samples/bigdemo/icons/gauge.xpm +27 -0
- data/samples/bigdemo/icons/help.xpm +25 -0
- data/samples/bigdemo/icons/list.bmp +0 -0
- data/samples/bigdemo/icons/list.xpm +27 -0
- data/samples/bigdemo/icons/mondrian.ico +0 -0
- data/samples/bigdemo/icons/mondrian.xpm +44 -0
- data/samples/bigdemo/icons/new.xpm +24 -0
- data/samples/bigdemo/icons/ogl.ico +0 -0
- data/samples/bigdemo/icons/ogl.xpm +45 -0
- data/samples/bigdemo/icons/open.xpm +26 -0
- data/samples/bigdemo/icons/paste.bmp +0 -0
- data/samples/bigdemo/icons/paste.xpm +38 -0
- data/samples/bigdemo/icons/pointy.png +0 -0
- data/samples/bigdemo/icons/preview.xpm +26 -0
- data/samples/bigdemo/icons/print.xpm +26 -0
- data/samples/bigdemo/icons/radio.bmp +0 -0
- data/samples/bigdemo/icons/radio.xpm +27 -0
- data/samples/bigdemo/icons/robert.xpm +415 -0
- data/samples/bigdemo/icons/ruby.png +0 -0
- data/samples/bigdemo/icons/sashtest.ico +0 -0
- data/samples/bigdemo/icons/save.xpm +25 -0
- data/samples/bigdemo/icons/smiles.bmp +0 -0
- data/samples/bigdemo/icons/smiles.xpm +39 -0
- data/samples/bigdemo/icons/smiley.ico +0 -0
- data/samples/bigdemo/icons/smiley.xpm +42 -0
- data/samples/bigdemo/icons/stattext.xpm +24 -0
- data/samples/bigdemo/icons/test2.bmp +0 -0
- data/samples/bigdemo/icons/test2.png +0 -0
- data/samples/bigdemo/icons/test2.xpm +79 -0
- data/samples/bigdemo/icons/text.bmp +0 -0
- data/samples/bigdemo/icons/text.xpm +27 -0
- data/samples/bigdemo/icons/tog1.bmp +0 -0
- data/samples/bigdemo/icons/tog1.xpm +38 -0
- data/samples/bigdemo/icons/tog2.bmp +0 -0
- data/samples/bigdemo/icons/tog2.xpm +38 -0
- data/samples/bigdemo/icons/wxwin.ico +0 -0
- data/samples/bigdemo/icons/wxwin16x16.png +0 -0
- data/samples/bigdemo/icons/wxwin16x16.xpm +25 -0
- data/samples/bigdemo/icons/wxwin32x32.png +0 -0
- data/samples/bigdemo/icons/wxwin48x48.png +0 -0
- data/samples/bigdemo/run.rb +90 -0
- data/samples/bigdemo/tips.txt +7 -0
- data/samples/bigdemo/utils.rb +12 -0
- data/samples/bigdemo/wxArtProvider.rbw +281 -0
- data/samples/bigdemo/wxBitmapButton.rbw +65 -0
- data/samples/bigdemo/wxButton.rbw +64 -0
- data/samples/bigdemo/wxCalendarCtrl.rbw +60 -0
- data/samples/bigdemo/wxCheckBox.rbw +50 -0
- data/samples/bigdemo/wxCheckListBox.rbw +65 -0
- data/samples/bigdemo/wxChoice.rbw +47 -0
- data/samples/bigdemo/wxChoicebook.rbw +78 -0
- data/samples/bigdemo/wxColourDialog.rbw +31 -0
- data/samples/bigdemo/wxComboBox.rbw +77 -0
- data/samples/bigdemo/wxCursor.rbw +136 -0
- data/samples/bigdemo/wxDialog.rbw +74 -0
- data/samples/bigdemo/wxDirDialog.rbw +29 -0
- data/samples/bigdemo/wxDragImage.rbw +70 -0
- data/samples/bigdemo/wxFileDialog.rbw +37 -0
- data/samples/bigdemo/wxFileDialog_Save.rbw +35 -0
- data/samples/bigdemo/wxFindReplaceDialog.rbw +82 -0
- data/samples/bigdemo/wxFontDialog.rbw +173 -0
- data/samples/bigdemo/wxFrame.rbw +53 -0
- data/samples/bigdemo/wxGauge.rbw +71 -0
- data/samples/bigdemo/wxGenericDirCtrl.rbw +74 -0
- data/samples/bigdemo/wxGrid.rbw +66 -0
- data/samples/bigdemo/wxHtmlHelpController.rbw +52 -0
- data/samples/bigdemo/wxListBox.rbw +140 -0
- data/samples/bigdemo/wxListCtrl_virtual.rbw +112 -0
- data/samples/bigdemo/wxMDIWindows.rbw +50 -0
- data/samples/bigdemo/wxMenu.rbw +236 -0
- data/samples/bigdemo/wxMessageDialog.rbw +27 -0
- data/samples/bigdemo/wxMiniFrame.rbw +70 -0
- data/samples/bigdemo/wxMultipleChoiceDialog.rbw +32 -0
- data/samples/bigdemo/wxNotebook.rbw +136 -0
- data/samples/bigdemo/wxProgressDialog.rbw +43 -0
- data/samples/bigdemo/wxRadioBox.rbw +72 -0
- data/samples/bigdemo/wxRadioButton.rbw +125 -0
- data/samples/bigdemo/wxSashWindow.rbw +141 -0
- data/samples/bigdemo/wxScrolledMessageDialog.rbw +57 -0
- data/samples/bigdemo/wxScrolledWindow.rbw +199 -0
- data/samples/bigdemo/wxSingleChoiceDialog.rbw +33 -0
- data/samples/bigdemo/wxSlider.rbw +42 -0
- data/samples/bigdemo/wxSpinButton.rbw +50 -0
- data/samples/bigdemo/wxSpinCtrl.rbw +51 -0
- data/samples/bigdemo/wxSplitterWindow.rbw +63 -0
- data/samples/bigdemo/wxStaticBitmap.rbw +51 -0
- data/samples/bigdemo/wxStaticText.rbw +55 -0
- data/samples/bigdemo/wxStatusBar.rbw +126 -0
- data/samples/bigdemo/wxTextCtrl.rbw +149 -0
- data/samples/bigdemo/wxTextEntryDialog.rbw +31 -0
- data/samples/bigdemo/wxToggleButton.rbw +49 -0
- data/samples/bigdemo/wxToolBar.rbw +131 -0
- data/samples/bigdemo/wxTreeCtrl.rbw +191 -0
- data/samples/calendar/calendar.rb +256 -0
- data/samples/caret/caret.rb +282 -0
- data/samples/caret/mondrian.xpm +44 -0
- data/samples/controls/controls.rb +1136 -0
- data/samples/controls/get_item_sample.rb +87 -0
- data/samples/controls/icons/choice.xpm +27 -0
- data/samples/controls/icons/combo.xpm +27 -0
- data/samples/controls/icons/gauge.xpm +27 -0
- data/samples/controls/icons/list.xpm +27 -0
- data/samples/controls/icons/radio.xpm +27 -0
- data/samples/controls/icons/stattext.xpm +24 -0
- data/samples/controls/icons/text.xpm +27 -0
- data/samples/controls/mondrian.ico +0 -0
- data/samples/controls/mondrian.xpm +44 -0
- data/samples/controls/test2.bmp +0 -0
- data/samples/dialogs/dialogs.rb +797 -0
- data/samples/dialogs/tips.txt +18 -0
- data/samples/dragdrop/dragdrop.rb +177 -0
- data/samples/drawing/graphics_drawing.rb +235 -0
- data/samples/drawing/images.rb +37 -0
- data/samples/drawing/paperclip.png +0 -0
- data/samples/etc/activation.rb +102 -0
- data/samples/etc/choice.rb +67 -0
- data/samples/etc/miniframe.rb +79 -0
- data/samples/etc/sash.rb +130 -0
- data/samples/etc/scrollwin.rb +110 -0
- data/samples/etc/system_settings.rb +252 -0
- data/samples/etc/threaded.rb +72 -0
- data/samples/etc/toolbar_sizer_additem.rb +55 -0
- data/samples/etc/wizard.rb +74 -0
- data/samples/event/event.rb +182 -0
- data/samples/event/update_ui_event.rb +70 -0
- data/samples/grid/grid.rb +198 -0
- data/samples/grid/gridtablebase.rb +148 -0
- data/samples/html/html.rb +262 -0
- data/samples/listbook/listbook.rb +174 -0
- data/samples/listbook/listbook.xrc +370 -0
- data/samples/mdi/mdi.rb +85 -0
- data/samples/media/mediactrl.rb +167 -0
- data/samples/minimal/minimal.rb +77 -0
- data/samples/minimal/mondrian.ico +0 -0
- data/samples/minimal/mondrian.png +0 -0
- data/samples/minimal/nothing.rb +16 -0
- data/samples/opengl/cube.rb +117 -0
- data/samples/printing/mondrian.ico +0 -0
- data/samples/printing/mondrian.xpm +44 -0
- data/samples/printing/printing.rb +487 -0
- data/samples/sockets/SocketPackets.rb +27 -0
- data/samples/sockets/res/message-new.png +0 -0
- data/samples/sockets/res/user.png +0 -0
- data/samples/sockets/wxClient.rb +395 -0
- data/samples/sockets/wxServer.rb +422 -0
- data/samples/sockets/wxSocketGUI.rb +97 -0
- data/samples/text/format-text-bold.png +0 -0
- data/samples/text/format-text-italic.png +0 -0
- data/samples/text/format-text-underline.png +0 -0
- data/samples/text/mondrian.ico +0 -0
- data/samples/text/mondrian.xpm +44 -0
- data/samples/text/rich_textctrl.rb +98 -0
- data/samples/text/scintilla.rb +169 -0
- data/samples/text/textctrl.rb +111 -0
- data/samples/text/unicode.rb +242 -0
- data/samples/text/utf8.txt +15 -0
- data/samples/treectrl/icon1.xpm +79 -0
- data/samples/treectrl/icon2.xpm +53 -0
- data/samples/treectrl/icon3.xpm +79 -0
- data/samples/treectrl/icon4.xpm +43 -0
- data/samples/treectrl/icon5.xpm +79 -0
- data/samples/treectrl/treectrl.rb +1166 -0
- data/samples/xrc/samples.xrc +46 -0
- data/samples/xrc/xrc_sample.rb +76 -0
- metadata +17 -3
@@ -0,0 +1,27 @@
|
|
1
|
+
# wxRuby2 Sample Code. Copyright (c) 2007-???? Mario J. Steele
|
2
|
+
# Freely reusable code: see SAMPLES-LICENSE.TXT for details
|
3
|
+
|
4
|
+
class Socket
|
5
|
+
# Socket#recv_packet()
|
6
|
+
#
|
7
|
+
# This method retrives 1kb of data from the socket. It uses
|
8
|
+
# IO#readpartial due to the fact, that IO#read() uses buffered IO,
|
9
|
+
# which can cause errors. Where as the IO#readpartial() will read up
|
10
|
+
# to the maximum of 1024 bytes from the socket, and return what it
|
11
|
+
# retrives.
|
12
|
+
def recv_packet()
|
13
|
+
self.readpartial(1024)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Socket#send_packet()
|
17
|
+
#
|
18
|
+
# This method will send the packet, and ensure that a LF (0x0A) is at
|
19
|
+
# the end of the data to be sent, as this is used as the determination
|
20
|
+
# of the end of a packet.
|
21
|
+
def send_packet(msg)
|
22
|
+
if !msg.index("\n")
|
23
|
+
msg += "\n"
|
24
|
+
end
|
25
|
+
self.write(msg)
|
26
|
+
end
|
27
|
+
end
|
Binary file
|
Binary file
|
@@ -0,0 +1,395 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# wxRuby2 Sample Code. Copyright (c) 2007-???? Mario J. Steele
|
3
|
+
# Freely reusable code: see SAMPLES-LICENSE.TXT for details
|
4
|
+
|
5
|
+
# This sample is of a client impelmentation using Ruby's green
|
6
|
+
# threads, Ruby's Asynchronous Sockets, and wxRuby Widgets, so as
|
7
|
+
# to not block the GUI while waiting for data.
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'wx'
|
11
|
+
rescue LoadError => no_wx_err
|
12
|
+
begin
|
13
|
+
require 'rubygems'
|
14
|
+
require 'wx'
|
15
|
+
rescue LoadError
|
16
|
+
raise no_wx_err
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'thread' # For Thread and Mutex
|
21
|
+
require 'socket' # For TCP/IP Communications
|
22
|
+
|
23
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
24
|
+
require 'wxSocketGUI' # Our GUI Interface
|
25
|
+
require 'SocketPackets' # For simplfying Socket communciation
|
26
|
+
|
27
|
+
class String
|
28
|
+
# String#index_from(index,to_index)
|
29
|
+
# This finds the to_index from the index onwards till the end of the string.
|
30
|
+
# Returns the find_index + offset value of index.
|
31
|
+
def index_from(i,mtch)
|
32
|
+
self[i..-1].index(mtch) + i
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
class MyClientFrame < SocketGUI
|
38
|
+
include Socket::Constants
|
39
|
+
@@constants = %w[
|
40
|
+
ID_CONNECT
|
41
|
+
ID_DISCONNECT
|
42
|
+
ID_NAME
|
43
|
+
ID_EXIT
|
44
|
+
ID_TIMER_POLL
|
45
|
+
]
|
46
|
+
|
47
|
+
# Creates the menus used by the Client.
|
48
|
+
def create_menus()
|
49
|
+
mb = Wx::MenuBar.new
|
50
|
+
|
51
|
+
fileMenu = Wx::Menu.new
|
52
|
+
fileMenu.append(ID_CONNECT,"&Connect\tCtrl+S","Connect to a server")
|
53
|
+
fileMenu.append(ID_DISCONNECT,"&Disconnect\tCtrl+G","Disconnect from a server")
|
54
|
+
fileMenu.append_separator()
|
55
|
+
fileMenu.append(ID_NAME,"&Nickname\tCtrl+N","Set your nickname")
|
56
|
+
fileMenu.append_separator()
|
57
|
+
fileMenu.append(ID_EXIT,"E&xit\tCtrl+X","Exit the client")
|
58
|
+
|
59
|
+
mb.append(fileMenu,"&File")
|
60
|
+
|
61
|
+
set_menu_bar(mb)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Called from SocketGUI, to initialize our client window.
|
65
|
+
def on_init()
|
66
|
+
@nick = "Client"
|
67
|
+
@port = "1234"
|
68
|
+
@addr = $LOCALHOST_NAME
|
69
|
+
@mutex = Mutex.new
|
70
|
+
@buffer = ""
|
71
|
+
@timer = Wx::Timer.new(self,ID_TIMER_POLL)
|
72
|
+
|
73
|
+
evt_menu(ID_CONNECT) { on_connect }
|
74
|
+
evt_menu(ID_DISCONNECT) { on_disconnect }
|
75
|
+
evt_menu(ID_NAME) { on_name }
|
76
|
+
evt_menu(ID_EXIT) { on_exit }
|
77
|
+
|
78
|
+
evt_close { |evt| on_close(evt) }
|
79
|
+
evt_text_enter(@input.get_id) { on_text }
|
80
|
+
evt_timer(ID_TIMER_POLL) { Thread.pass }
|
81
|
+
|
82
|
+
get_menu_bar.enable(ID_CONNECT,true)
|
83
|
+
get_menu_bar.enable(ID_DISCONNECT,false)
|
84
|
+
end
|
85
|
+
|
86
|
+
# MyClientFrame#on_connect()
|
87
|
+
# Called from File > Connect, which prompts the user where they want to connect
|
88
|
+
# to, and starts the process for connecting to the server. Since we're looking
|
89
|
+
# at a possibility of the connection not being completed immediately, we delegate
|
90
|
+
# the confirmation from the connection, to our polling loop here. And we utilize
|
91
|
+
# the less known method Socket#connect_nonblock(). Oviously, Ruby will throw
|
92
|
+
# an exception if the connection isn't completed immedately, which is alright,
|
93
|
+
# we are expecting such. So we ignore any errors, and process thoes in our poll
|
94
|
+
# loop for connecting.
|
95
|
+
def on_connect
|
96
|
+
unless @socket.nil?
|
97
|
+
Wx::message_box("You are already connected to a server!","Connect to...",Wx::ID_OK|Wx::ICON_ERROR)
|
98
|
+
return
|
99
|
+
end
|
100
|
+
msgbox = Wx::TextEntryDialog.new(self,"Enter the server you wish to connect to: (Default: #{$LOCALHOST_NAME}:1234)",
|
101
|
+
"Connect to server",@addr + ":" + @port.to_s)
|
102
|
+
ret = msgbox.show_modal
|
103
|
+
if ret == Wx::ID_CANCEL
|
104
|
+
return
|
105
|
+
end
|
106
|
+
@socket = Socket.new(AF_INET,SOCK_STREAM,0)
|
107
|
+
sockaddr = msgbox.get_value
|
108
|
+
unless sockaddr.index(":")
|
109
|
+
sockaddr += ":1234"
|
110
|
+
end
|
111
|
+
@host,@port = sockaddr.split(":")
|
112
|
+
sockaddr = Socket.sockaddr_in(@port,@host)
|
113
|
+
begin
|
114
|
+
@socket.connect_nonblock(sockaddr)
|
115
|
+
rescue
|
116
|
+
# We're polling for connection anyways
|
117
|
+
end
|
118
|
+
append_prog_msg("Attempting to connect to #{@host}:#{@port}")
|
119
|
+
get_menu_bar.enable(ID_CONNECT,false)
|
120
|
+
get_menu_bar.enable(ID_DISCONNECT,false)
|
121
|
+
start_client_thread
|
122
|
+
end
|
123
|
+
|
124
|
+
# MyClientFrame#disconnect()
|
125
|
+
# This method utitilizes the closing of the socket connection, and killing
|
126
|
+
# any thread that may be running, and since we have no threads, we don't need
|
127
|
+
# the timer to be running, so we stop that as well.
|
128
|
+
def disconnect()
|
129
|
+
return if @socket.nil?
|
130
|
+
Thread.kill(@thread) unless @thread.nil?
|
131
|
+
@socket.send_packet("QUIT")
|
132
|
+
@socket.close()
|
133
|
+
@socket = nil
|
134
|
+
@timer.stop
|
135
|
+
get_menu_bar.enable(ID_CONNECT,true)
|
136
|
+
get_menu_bar.enable(ID_DISCONNECT,false)
|
137
|
+
end
|
138
|
+
|
139
|
+
# MyClientFrame#on_disconnect()
|
140
|
+
# This method is ran from File > Disconnect and processes the disconnection from
|
141
|
+
# the server.
|
142
|
+
def on_disconnect()
|
143
|
+
disconnect()
|
144
|
+
append_prog_msg("You have disconnected from the server.")
|
145
|
+
end
|
146
|
+
|
147
|
+
# MyClientFrame#on_name()
|
148
|
+
# This method is ran from File > Nickname, and processes the nickname that the
|
149
|
+
# client will use.
|
150
|
+
def on_name
|
151
|
+
msgbox = Wx::TextEntryDialog.new(self,"Enter the new nickname you want to be (Default: Client)",
|
152
|
+
"Change Nickname",@nick)
|
153
|
+
ret = msgbox.show_modal
|
154
|
+
if ret == Wx::ID_CANCEL
|
155
|
+
return
|
156
|
+
end
|
157
|
+
|
158
|
+
nick = msgbox.get_value
|
159
|
+
nick = nick.split(" ").join("_") if nick.index(" ")
|
160
|
+
@socket.send_packet("NICK #{nick}") unless @socket.nil?
|
161
|
+
append_prog_msg("You changed nicknames from #{@nick} to #{nick}")
|
162
|
+
@nick = nick
|
163
|
+
@socket.send_packet("WHO") unless @socket.nil?
|
164
|
+
end
|
165
|
+
|
166
|
+
# MyClientFrame#on_exit()
|
167
|
+
# This method is ran from File > Exit, and delegates the control over to
|
168
|
+
# MyClientFrame#on_close() to do it's checks to see if they are still connected
|
169
|
+
# or not.
|
170
|
+
def on_exit
|
171
|
+
self.close
|
172
|
+
end
|
173
|
+
|
174
|
+
# MyClientFrame#on_close(evt)
|
175
|
+
# This method is ran from the OnCloseEvent, and checks to see if the client is still
|
176
|
+
# connected to the server. If they are, it will confirm if they want to disconnect
|
177
|
+
# from the server or not. If they don't want to disconnect, it will veto the close
|
178
|
+
# event.
|
179
|
+
def on_close(evt)
|
180
|
+
unless @socket.nil?
|
181
|
+
ret = Wx::message_box("You are still connected to the server, do you wish to disconnect?","Disconnect",Wx::YES|Wx::NO|Wx::ICON_QUESTION)
|
182
|
+
if ret == Wx::NO
|
183
|
+
evt.veto
|
184
|
+
else
|
185
|
+
disconnect()
|
186
|
+
Wx::get_app.exit_main_loop()
|
187
|
+
end
|
188
|
+
else
|
189
|
+
Wx::get_app.exit_main_loop()
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# MyClientFrame#on_text()
|
194
|
+
# This method is executed from when the user presses the Enter button from the
|
195
|
+
# input box. It will processes it for any /commands, and run them as nesscary
|
196
|
+
# otherwise, it will send the data to the server.
|
197
|
+
def on_text
|
198
|
+
msg = @input.get_value
|
199
|
+
@input.set_value("")
|
200
|
+
case msg
|
201
|
+
when /\/connect/
|
202
|
+
on_connect
|
203
|
+
when /\/disconnect/
|
204
|
+
on_disconnect
|
205
|
+
when /\/nick/
|
206
|
+
on_name
|
207
|
+
when /\/exit/
|
208
|
+
self.close
|
209
|
+
when /\/eval/
|
210
|
+
evl_str = msg[5..-1]
|
211
|
+
append_prog_msg(self.instance_eval(evl_str).inspect)
|
212
|
+
when /\/me/
|
213
|
+
append_msg("<-- #{@nick} #{msg[4..-1]}")
|
214
|
+
msg = "EMOTE " + msg[4..-1]
|
215
|
+
@socket.send_packet(msg)
|
216
|
+
else
|
217
|
+
append_msg("<-- #{@nick} says: #{msg}")
|
218
|
+
msg = "MSG " + msg
|
219
|
+
@socket.send_packet(msg)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# MyClientFrame#poll_connect()
|
224
|
+
# This method is called from within the polling thread, and will run till
|
225
|
+
# it either finds that the socket has been completed, and connected to a server,
|
226
|
+
# otherwise, it will check to see if there is an error. If there is, then that
|
227
|
+
# means we weren't able to connect to the server. This will then run the method
|
228
|
+
# Socket#connect() inside a rescue condition, so that we can see why we wasn't
|
229
|
+
# able to connect to the server.
|
230
|
+
def poll_connect()
|
231
|
+
loop do
|
232
|
+
@mutex.lock
|
233
|
+
ret = IO::select(nil,[@socket],[@socket],0.3)
|
234
|
+
unless ret.nil?
|
235
|
+
if Wx::PLATFORM == "WXMSW"
|
236
|
+
unless ret[2] == []
|
237
|
+
# We have an error
|
238
|
+
error = nil
|
239
|
+
begin
|
240
|
+
error = @socket.connect(Socket.sockaddr_in(@port,@host))
|
241
|
+
rescue => e
|
242
|
+
error = e
|
243
|
+
end
|
244
|
+
append_error("Error attempting to connect to #{@host}:#{@port}")
|
245
|
+
append_error("Message: #{e} (#{e.class})")
|
246
|
+
@mutex.unlock
|
247
|
+
@socket.close()
|
248
|
+
@socket = nil
|
249
|
+
get_menu_bar.enable(ID_CONNECT,true)
|
250
|
+
get_menu_bar.enable(ID_DISCONNECT,false)
|
251
|
+
@timer.stop()
|
252
|
+
Thread.kill(@thread)
|
253
|
+
@thread.exit
|
254
|
+
return
|
255
|
+
end
|
256
|
+
else # For WXGTK and WXMAC
|
257
|
+
# Since on WXGTK and WXMAC doesn't return the error part in ret, we need
|
258
|
+
# to check and see if we are connected.
|
259
|
+
error = nil
|
260
|
+
begin
|
261
|
+
error = @socket.connect(Socket.sockaddr_in(@port,@host))
|
262
|
+
rescue => e
|
263
|
+
error = e
|
264
|
+
end
|
265
|
+
if error.class != Fixnum && !error.nil?
|
266
|
+
# We're not connected!
|
267
|
+
append_error("Error attempting to connect to #{@host}:#{@port}")
|
268
|
+
append_error("Message: #{e} (#{e.class})")
|
269
|
+
@mutex.unlock
|
270
|
+
@socket.close()
|
271
|
+
@socket = nil
|
272
|
+
get_menu_bar.enable(ID_CONNECT,true)
|
273
|
+
get_menu_bar.enable(ID_DISCONNECT,false)
|
274
|
+
@timer.stop()
|
275
|
+
Thread.kill(@thread)
|
276
|
+
@thread.exit
|
277
|
+
return
|
278
|
+
end
|
279
|
+
end
|
280
|
+
append_prog_msg("Connected to #{@host}:#{@port}")
|
281
|
+
get_menu_bar.enable(ID_CONNECT,false)
|
282
|
+
get_menu_bar.enable(ID_DISCONNECT,true)
|
283
|
+
@socket.send_packet("CONNECT #{@nick}")
|
284
|
+
@socket.send_packet("WHO")
|
285
|
+
@mutex.unlock
|
286
|
+
break
|
287
|
+
end
|
288
|
+
@mutex.unlock
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# MyClientFrame#parse_message(msg)
|
293
|
+
# This method checks to see what message is being received from the server
|
294
|
+
# add display it to the user, as needed.
|
295
|
+
def parse_message(msg)
|
296
|
+
case msg
|
297
|
+
when /CONNECT/
|
298
|
+
append_msg("--> #{msg[8..-1]} has joined the chat server.")
|
299
|
+
#@users.insert_item(0,msg[8..-1],0)
|
300
|
+
add_user(msg[8..-1])
|
301
|
+
when /DISCONN/
|
302
|
+
append_msg("--> #{msg[8..-1]} has been removed from the chat server.")
|
303
|
+
when /CONN/
|
304
|
+
append_msg("<!> New connection incoming to the server.")
|
305
|
+
when /WHO/
|
306
|
+
cmd, *usrs = msg.split(" ")
|
307
|
+
clear_users()
|
308
|
+
usrs.each do |usr|
|
309
|
+
add_user(usr)
|
310
|
+
end
|
311
|
+
when /NICK/
|
312
|
+
a = msg.index_from(5," ")
|
313
|
+
old = msg[5..a-1]
|
314
|
+
new = msg[a+1..-1]
|
315
|
+
append_msg("--> #{old} has changed their nickname to #{new}")
|
316
|
+
@socket.send_packet("WHO")
|
317
|
+
when /MSG/
|
318
|
+
a = msg.index_from(4," ")
|
319
|
+
user = msg[4..a-1]
|
320
|
+
txt = msg[a+1..-1]
|
321
|
+
append_msg("--> #{user} says: #{txt}")
|
322
|
+
when /EMOTE/
|
323
|
+
a = msg.index_from(6," ")
|
324
|
+
user = msg[6..a-1]
|
325
|
+
txt = msg[a+1..-1]
|
326
|
+
append_msg("--> #{user} #{txt}")
|
327
|
+
when /QUIT/
|
328
|
+
append_msg("--> #{msg[5..-1]} is quitting the chat server.")
|
329
|
+
@socket.send_packet("WHO")
|
330
|
+
when /SHUTDOWN/
|
331
|
+
append_msg("--> #{msg[9..-1]}")
|
332
|
+
else
|
333
|
+
append_error("Unknown message received from the server.")
|
334
|
+
append_error("Message: #{msg}")
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
# MyClientFrame#poll_data()
|
339
|
+
# This method will poll the connection for incoming data, and to see if it
|
340
|
+
# has been disconnected from the server or not. It will continue to poll
|
341
|
+
# till a connection has been closed, either by the server, or by the user.
|
342
|
+
def poll_data()
|
343
|
+
loop do
|
344
|
+
@mutex.lock
|
345
|
+
ret = IO::select([@socket],nil,nil,0.3)
|
346
|
+
unless ret.nil?
|
347
|
+
if @socket.eof?
|
348
|
+
append_prog_msg("You have been disconnected from the server!")
|
349
|
+
get_menu_bar.enable(ID_CONNECT,true)
|
350
|
+
get_menu_bar.enable(ID_DISCONNECT,false)
|
351
|
+
@socket.close
|
352
|
+
@socket = nil
|
353
|
+
@timer.stop
|
354
|
+
@mutex.unlock
|
355
|
+
@thread.exit
|
356
|
+
break
|
357
|
+
end
|
358
|
+
msg = @socket.recv_packet()
|
359
|
+
@buffer += msg
|
360
|
+
loop do
|
361
|
+
at = @buffer.index("\n")
|
362
|
+
if at.nil?
|
363
|
+
break
|
364
|
+
end
|
365
|
+
data = @buffer[0..at-1]
|
366
|
+
@buffer = @buffer[at+1..-1]
|
367
|
+
parse_message(data)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
@mutex.unlock
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
# MyClientFrame#start_client_thread()
|
375
|
+
# This method will actually start the polling of a connection for the connection
|
376
|
+
# and data. First it will start the polling of the connection, after it successfully
|
377
|
+
# receives the connection, it will start polling for incoming data.
|
378
|
+
def start_client_thread
|
379
|
+
@thread = Thread.new do
|
380
|
+
@timer.start(25)
|
381
|
+
poll_connect()
|
382
|
+
poll_data()
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
class MySocketApp < Wx::App
|
388
|
+
def on_init
|
389
|
+
Thread.abort_on_exception = true
|
390
|
+
frame = MyClientFrame.new(nil,-1,"Socket Demo >> Client")
|
391
|
+
frame.show
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
MySocketApp.new.main_loop
|
@@ -0,0 +1,422 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# wxRuby2 Sample Code. Copyright (c) 2007-???? Mario J. Steele
|
3
|
+
# Freely reusable code: see SAMPLES-LICENSE.TXT for details
|
4
|
+
|
5
|
+
# This sample is a demonstration of how to implement a Multi-Threaded
|
6
|
+
# Server, using Ruby's green threads, Ruby's Asynchronous Sockets and
|
7
|
+
# wxRuby, as to not block the GUI while receiving connections and data
|
8
|
+
# from clients.
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'wx'
|
12
|
+
rescue LoadError => no_wx_err
|
13
|
+
begin
|
14
|
+
require 'rubygems'
|
15
|
+
require 'wx'
|
16
|
+
rescue LoadError
|
17
|
+
raise no_wx_err
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'thread' # For Thread and Mutex
|
22
|
+
require 'socket' # For TCP/IP Communications
|
23
|
+
|
24
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
25
|
+
require 'wxSocketGUI' # Our GUI Interface
|
26
|
+
require 'SocketPackets' # For simplfying Socket communciation
|
27
|
+
|
28
|
+
class MyServerFrame < SocketGUI
|
29
|
+
include Socket::Constants
|
30
|
+
@@constants = %w[
|
31
|
+
ID_START
|
32
|
+
ID_STOP
|
33
|
+
ID_NAME
|
34
|
+
ID_EXIT
|
35
|
+
ID_TIMER_POLL
|
36
|
+
]
|
37
|
+
# ClientInfo holds information about each client connection to the server
|
38
|
+
# making it easier to track information, and use that throughout the
|
39
|
+
# server process.
|
40
|
+
ClientInfo = Struct.new(
|
41
|
+
:name, # User's Nickname
|
42
|
+
:host, # User's Host Address
|
43
|
+
:port, # User's Port
|
44
|
+
:sock, # User's Sock
|
45
|
+
:buffer) # Buffer for the socket
|
46
|
+
|
47
|
+
# Creates the Menus used by the server.
|
48
|
+
def create_menus()
|
49
|
+
mb = Wx::MenuBar.new
|
50
|
+
|
51
|
+
fileMenu = Wx::Menu.new
|
52
|
+
fileMenu.append(ID_START,"&Start Server\tCtrl+S","Start listening for incomming connections")
|
53
|
+
fileMenu.append(ID_STOP,"S&top Server\tCtrl+G","Stop listening for incomming connections, and disconnect all current connections")
|
54
|
+
fileMenu.append_separator()
|
55
|
+
fileMenu.append(ID_NAME,"&Nickname\tCtrl+N","Set your Nickname")
|
56
|
+
fileMenu.append_separator()
|
57
|
+
fileMenu.append(ID_EXIT,"E&xit\tCtrl+X","Exit the demo")
|
58
|
+
|
59
|
+
mb.append(fileMenu,"&File")
|
60
|
+
set_menu_bar(mb)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Our initialize sequence, called by SocketGUI.
|
64
|
+
def on_init()
|
65
|
+
@nick = "Server" # Server user's Nickname
|
66
|
+
@port = 1234 # Port we're listening on
|
67
|
+
@mutex = Mutex.new # For synchronization, so there's no corruption of memory
|
68
|
+
@clients = [] # For the sockets that are connected
|
69
|
+
@timer = Wx::Timer.new(self,ID_TIMER_POLL)
|
70
|
+
# Used to poll for data
|
71
|
+
|
72
|
+
evt_menu(ID_START) { on_start }
|
73
|
+
evt_menu(ID_STOP) { on_stop }
|
74
|
+
evt_menu(ID_NAME) { on_name }
|
75
|
+
evt_menu(ID_EXIT) { on_exit }
|
76
|
+
|
77
|
+
evt_close { |evt| on_close(evt) }
|
78
|
+
evt_text_enter(@input.get_id) { on_text }
|
79
|
+
evt_timer(ID_TIMER_POLL) { Thread.pass }
|
80
|
+
|
81
|
+
get_menu_bar.enable(ID_START,true)
|
82
|
+
get_menu_bar.enable(ID_STOP,false)
|
83
|
+
end
|
84
|
+
|
85
|
+
# MyServerFrame#on_start()
|
86
|
+
# This function sets up the socket for listening to incoming connections, as
|
87
|
+
# well as starting the Thread that will do the underline work for polling for
|
88
|
+
# incoming connections as well as incoming data.
|
89
|
+
def on_start
|
90
|
+
if !@socket.nil?
|
91
|
+
Wx::message_box("The server is already started!","Server startup error",Wx::ID_OK|Wx::ICON_ERROR)
|
92
|
+
return
|
93
|
+
end
|
94
|
+
|
95
|
+
mbox = Wx::TextEntryDialog.new(self,"Enter the port you want to listen on (Default: 1234)",
|
96
|
+
"Start Server","1234")
|
97
|
+
if mbox.show_modal == Wx::ID_CANCEL
|
98
|
+
return
|
99
|
+
end
|
100
|
+
@port = mbox.get_value.to_i
|
101
|
+
@socket = Socket.new(AF_INET,SOCK_STREAM,0)
|
102
|
+
@socket.bind(Socket.sockaddr_in(@port,$LOCALHOST_NAME))
|
103
|
+
@socket.listen(5)
|
104
|
+
append_prog_msg("[#{Time.now}] - Server started on port #{@port}.")
|
105
|
+
update_users()
|
106
|
+
get_menu_bar.enable(ID_START,false)
|
107
|
+
get_menu_bar.enable(ID_STOP,true)
|
108
|
+
start_server_thread()
|
109
|
+
end
|
110
|
+
|
111
|
+
# MyServerFrame#on_stop()
|
112
|
+
# This is executed from the File > Stop menu item, which will first check to see
|
113
|
+
# if we still have Active clients. If so, confirm that the user wants to shutdown
|
114
|
+
# the server. If the user does, it'll start the MyServerFraem#shutdown() sequence.
|
115
|
+
def on_stop()
|
116
|
+
if @socket.nil?
|
117
|
+
Wx::message_box("The server is currently not active!","Server shutdown",Wx::OK|Wx::ICON_ERROR)
|
118
|
+
return
|
119
|
+
end
|
120
|
+
if @clients.size > 0
|
121
|
+
ret = Wx::message_box("There are still clients connected, do you wish to shutdown still?","Server shutdown",Wx::YES|Wx::NO|Wx::ICON_QUESTION)
|
122
|
+
if ret == Wx::NO
|
123
|
+
return
|
124
|
+
end
|
125
|
+
end
|
126
|
+
shutdown()
|
127
|
+
end
|
128
|
+
|
129
|
+
# MyServerFrame#on_name()
|
130
|
+
# This is executed from the File > Nickname menu item, which will change the nickname
|
131
|
+
# of the Server itself. If there's active connections to the server, it will notify
|
132
|
+
# all of the clients of the server's new nickname.
|
133
|
+
def on_name
|
134
|
+
mbox = Wx::TextEntryDialog.new(self,"Enter the new nickname you want to be (Default: Server)",
|
135
|
+
"Change Nickname",@nick)
|
136
|
+
ret = mbox.show_modal
|
137
|
+
if ret == Wx::ID_CANCEL
|
138
|
+
return
|
139
|
+
end
|
140
|
+
nick = mbox.get_value
|
141
|
+
nick = nick.split(" ").join("_") if nick.index(" ")
|
142
|
+
append_prog_msg("[#{Time.now}] - You changed your nickname to #{nick}.")
|
143
|
+
@clients.each do |ci|
|
144
|
+
ci.sock.send_packet("NICK #{@nick} #{nick}")
|
145
|
+
end
|
146
|
+
@nick = nick
|
147
|
+
update_users()
|
148
|
+
end
|
149
|
+
|
150
|
+
# MyServerFrame#on_exit()
|
151
|
+
# This is executed from the File > Exit menu item, it will simply delegate the actual
|
152
|
+
# processing to MyServerFrame#on_close() event, by closing the window.
|
153
|
+
def on_exit
|
154
|
+
self.close
|
155
|
+
end
|
156
|
+
|
157
|
+
# MyServerFrame#on_close(evt)
|
158
|
+
# This is executed from the OnCloseEvent that comes from when the user closes the
|
159
|
+
# window, or executes the File > Exit menu item. It will first check to see if the
|
160
|
+
# if there's an Active Socket. If there is, then it'll check and see if there are
|
161
|
+
# any clients connected. If there are clients connected, it will confirm with the
|
162
|
+
# user that they want to shutdown the server. If they don't, it will veto the
|
163
|
+
# close event, and the server will continue to run.
|
164
|
+
def on_close(evt)
|
165
|
+
ret = Wx::YES
|
166
|
+
if !@socket.nil?
|
167
|
+
if @clients.size > 0
|
168
|
+
ret = Wx::message_box("There are still clients connected to the server, do you wish to continue?","Shutdown",Wx::YES|Wx::NO|Wx::ICON_INFORMATION)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
if ret == Wx::NO
|
172
|
+
evt.veto
|
173
|
+
else
|
174
|
+
shutdown()
|
175
|
+
Wx::get_app.exit_main_loop
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# MyServerFrame#on_text()
|
180
|
+
# This is executed when the user presses the Enter button from the input box.
|
181
|
+
# It does small parsing to check what /commands are used, if any, then if it
|
182
|
+
# doesn't find any, it'll pass through to actually sending the data.
|
183
|
+
def on_text()
|
184
|
+
msg = @input.get_value
|
185
|
+
@input.set_value("")
|
186
|
+
case msg
|
187
|
+
when /\/start/
|
188
|
+
on_start
|
189
|
+
when /\/shutdown/
|
190
|
+
on_stop
|
191
|
+
when /\/nick/
|
192
|
+
on_name
|
193
|
+
when /\/exit/
|
194
|
+
self.close
|
195
|
+
when /\/me/
|
196
|
+
append_msg("--> #{@nick} " + msg[4..-1])
|
197
|
+
msg = "EMOTE #{@nick} " + msg[4..-1]
|
198
|
+
@mutex.lock
|
199
|
+
@clients.each do |ci|
|
200
|
+
ci.sock.send_packet(msg)
|
201
|
+
end
|
202
|
+
@mutex.unlock
|
203
|
+
else
|
204
|
+
append_msg("--> #{@nick} says: " + msg)
|
205
|
+
msg = "MSG #{@nick} " + msg
|
206
|
+
@mutex.lock
|
207
|
+
@clients.each do |ci|
|
208
|
+
ci.sock.send_packet(msg)
|
209
|
+
end
|
210
|
+
@mutex.unlock
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# MyServerFrame#shutdown()
|
215
|
+
# This is the actual clean up function that will shutdown the Server, and
|
216
|
+
# disconnect any connected clients. When this is executed, there should
|
217
|
+
# already have a Mutex lock done, so that we can safely clean everything up.
|
218
|
+
def shutdown()
|
219
|
+
Thread.kill(@thread) unless @thread.nil?
|
220
|
+
@timer.stop
|
221
|
+
|
222
|
+
@clients.each do |ci|
|
223
|
+
ci.sock.send_packet("SHUTDOWN Server is going down now!")
|
224
|
+
ci.sock.close
|
225
|
+
end
|
226
|
+
@clients = []
|
227
|
+
@socket.close unless @socket.nil?
|
228
|
+
@socket = nil
|
229
|
+
append_prog_msg("[#{Time.now}] - Server has been shutdown.")
|
230
|
+
get_menu_bar.enable(ID_START,true)
|
231
|
+
get_menu_bar.enable(ID_STOP,false)
|
232
|
+
end
|
233
|
+
|
234
|
+
# MyServerFrame#poll()
|
235
|
+
# This function collects the sockets for clients, as well as the server
|
236
|
+
# socket to poll for incoming connections or data from these sockets.
|
237
|
+
# This returns three arrays of sockets from [read,write,error/oob]
|
238
|
+
def poll
|
239
|
+
pollers = [@socket]
|
240
|
+
@clients.each do |ci|
|
241
|
+
pollers << ci.sock
|
242
|
+
end
|
243
|
+
IO::select(pollers,nil,nil,0.3)
|
244
|
+
end
|
245
|
+
|
246
|
+
# MyServerFrame#check_server(socks)
|
247
|
+
# This is the core of the processing for the server side. It will check to
|
248
|
+
# see if our server socket is in the return for MyServerFrame#poll(), if it's
|
249
|
+
# there, then there's an incoming connection, and will run the accepting of
|
250
|
+
# the new client connection, and tell the other clients that there is a new
|
251
|
+
# incoming connection. It will then remove the server socket from the array
|
252
|
+
# so that it's not processed, as well as converting all the remaining sockets
|
253
|
+
# if any, back into ClientInfo structures, there by returning them to be
|
254
|
+
# processed by the MyServerFrame#check_clients()
|
255
|
+
def check_server(socks)
|
256
|
+
if socks.index(@socket)
|
257
|
+
socks.delete(@socket) # Don't need it in socks
|
258
|
+
sock, sockaddr = @socket.accept
|
259
|
+
sockaddr = Socket.unpack_sockaddr_in(sockaddr)
|
260
|
+
ci = ClientInfo.new("",sockaddr[1],sockaddr[0],sock,"")
|
261
|
+
@clients.each do |sci|
|
262
|
+
sci.sock.send_packet("CONN #{ci.host} #{ci.port}")
|
263
|
+
end
|
264
|
+
@clients << ci
|
265
|
+
append_msg("[#{Time.now}] - New connection from #{ci.host}:#{ci.port}")
|
266
|
+
update_users()
|
267
|
+
end
|
268
|
+
new_ci = []
|
269
|
+
socks.each do |sock|
|
270
|
+
@clients.each do |ci|
|
271
|
+
if ci.sock == sock
|
272
|
+
new_ci << ci
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
new_ci
|
277
|
+
end
|
278
|
+
|
279
|
+
# MyServerFrame#update_users()
|
280
|
+
# This function actually updates the User listing of thoes currently connected
|
281
|
+
# to the server.
|
282
|
+
def update_users()
|
283
|
+
clear_users()
|
284
|
+
add_user(@nick)
|
285
|
+
@clients.each do |ci|
|
286
|
+
add_user(ci.name)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# MyServerFrame#parse_message(ClientInfo,Message)
|
291
|
+
# This function parses the incoming data from a Client, to see what is being
|
292
|
+
# requested. There are currently 6 commands that the server implemented.
|
293
|
+
# CONNECT identifies the Client with their nickname.
|
294
|
+
# NICK identifies the client's new nickname if they change it.
|
295
|
+
# EMOTE identifies the message as being an Emote, or Action.
|
296
|
+
# MSG identifies the message as being a normal message to the server.
|
297
|
+
# WHO is an internal request to update who is connected to the server.
|
298
|
+
# QUIT identifies that the client is closing the connection to the server.
|
299
|
+
#
|
300
|
+
# This function parses the message, and responds appropriately, weither it
|
301
|
+
# be an internal message to be handled, or it's a repeater, that needs to be
|
302
|
+
# sent to all the connected clients, as well as being displayed to the server.
|
303
|
+
def parse_message(ci,msg)
|
304
|
+
new_msg = nil
|
305
|
+
case msg
|
306
|
+
when /CONNECT/
|
307
|
+
ci.name = msg[8..-1]
|
308
|
+
append_msg("<-> #{ci.name} has joined the server!")
|
309
|
+
new_msg = msg
|
310
|
+
update_users()
|
311
|
+
when /NICK/
|
312
|
+
new_msg = "NICK #{ci.name} #{msg[5..-1]}"
|
313
|
+
append_msg("<-> #{ci.name} has changed nickname to #{msg[5..-1]}")
|
314
|
+
ci.name = msg[5..-1]
|
315
|
+
update_users()
|
316
|
+
when /EMOTE/
|
317
|
+
new_msg = "EMOTE #{ci.name} #{msg[6..-1]}"
|
318
|
+
append_msg("<-> #{ci.name} #{msg[6..-1]}")
|
319
|
+
when /MSG/
|
320
|
+
new_msg = "MSG #{ci.name} #{msg[4..-1]}"
|
321
|
+
append_msg("<-> #{ci.name} says: #{msg[4..-1]}")
|
322
|
+
when /WHO/
|
323
|
+
who = "WHO #{@nick} "
|
324
|
+
@clients.each do |cci|
|
325
|
+
who += cci.name + " "
|
326
|
+
end
|
327
|
+
ci.sock.send_packet(who[0..-2])
|
328
|
+
when /QUIT/
|
329
|
+
new_msg = "QUIT #{ci.name}"
|
330
|
+
append_msg("<-> #{ci.name} is dis-connecting.")
|
331
|
+
else
|
332
|
+
append_error("<-- Unknown message from #{ci.name}")
|
333
|
+
append_error("<-- Message: #{msg}")
|
334
|
+
end
|
335
|
+
@clients.each do |cci|
|
336
|
+
if cci != ci
|
337
|
+
cci.sock.send_packet(new_msg)
|
338
|
+
end
|
339
|
+
end unless new_msg.nil?
|
340
|
+
end
|
341
|
+
|
342
|
+
# MyServerFrame#check_clients()
|
343
|
+
# This method, will run through the collected client connections that show that
|
344
|
+
# they have data available in their buffer. It will first check to see if the
|
345
|
+
# socket has been closed by utilizing the IO#eof? method. If there is an end
|
346
|
+
# of file detected, it means the client connection has been closed. It will then
|
347
|
+
# remove the client connection from our pool, and send the rest of the clients
|
348
|
+
# that the user has indeed disconnected from the server.
|
349
|
+
# After confirming that the connection hasn't been closed, it will check to
|
350
|
+
# see what data is available, append it to the client's buffer, where it will
|
351
|
+
# then process the data, for the LF (0x0A) termination, saying that this is the
|
352
|
+
# end of the message, and then process that data through MyServerFrame#parse_message()
|
353
|
+
def check_clients(aci)
|
354
|
+
aci.each do |ci|
|
355
|
+
if ci.sock.eof?
|
356
|
+
ci.sock.close
|
357
|
+
@clients.delete(ci)
|
358
|
+
@clients.each do |cci|
|
359
|
+
cci.sock.send_packet("DISCONN #{ci.name}")
|
360
|
+
end
|
361
|
+
append_prog_msg("[#{Time.now}] - Socket closed for #{ci.name}")
|
362
|
+
update_users()
|
363
|
+
else
|
364
|
+
msg = ci.sock.recv_packet()
|
365
|
+
ci.buffer += msg
|
366
|
+
loop do
|
367
|
+
at = ci.buffer.index("\n")
|
368
|
+
if at.nil?
|
369
|
+
break
|
370
|
+
end
|
371
|
+
data = ci.buffer[0..at-1]
|
372
|
+
ci.buffer = ci.buffer[at+1..-1]
|
373
|
+
parse_message(ci,data)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
# MyServerFrame#start_server_thread()
|
380
|
+
# This is the meat and bones of the server, where it will continiously poll for
|
381
|
+
# data in a seperate Thread, that will utilize the above methods for processing
|
382
|
+
# the incoming connections, and data being inputed from clients. This thread
|
383
|
+
# does no yielding, or anything of the sort to wxRuby, as it's in a seperate
|
384
|
+
# thread being controlled by Ruby. This thread is given time to run, by the
|
385
|
+
# Thread#yield() method, that is put into a context of a Timer that executes
|
386
|
+
# every 300 Milliseconds, and so forth. We start the timer here, so that way
|
387
|
+
# we ensure that it get's started, and stopped at the correct times.
|
388
|
+
def start_server_thread()
|
389
|
+
@thread = Thread.new do
|
390
|
+
@timer.start(25)
|
391
|
+
loop do # We just run a continious loop. on_stop() will kill this thread, and stop the timer.
|
392
|
+
begin # We run here, just incase there's some kind of error
|
393
|
+
@mutex.lock # We lock the data, so there's no corruption between threads
|
394
|
+
socks = poll() # We run a poll for any incoming data, or any new incoming connections
|
395
|
+
unless socks.nil?
|
396
|
+
ci = check_server(socks[0]) # Check to see if we have any incoming connections
|
397
|
+
check_clients(ci) # Check for client activity
|
398
|
+
end
|
399
|
+
@mutex.unlock # We can now release it, as we've done everything needed here
|
400
|
+
rescue => e # We have an error, so we need to process it.
|
401
|
+
msg = ["An error has occured!"]
|
402
|
+
msg << "#{e.backtrace.delete_at(0)}: #{e} (#{e.class})"
|
403
|
+
e.backtrace.each do |line|
|
404
|
+
msg << line
|
405
|
+
end
|
406
|
+
msg.each do |line|
|
407
|
+
append_error(line)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
class MySocketApp < Wx::App
|
416
|
+
def on_init
|
417
|
+
frame = MyServerFrame.new(nil,-1,"Sockets Demo >> Server")
|
418
|
+
frame.show
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
MySocketApp.new.main_loop
|