Intro-D
I've not written anything useful in a while and I've been thinking of stuff to write about. I did get to remembering my days of trying to learn sockets and how difficult it was to even find out about async sockets, let alone learn/use them. It seems to make sense, then, that it would be a good subject for a tutorial... and here it is.
About tute
This tute will cover basic sockets as well as the a-sync related functions. The language used will be assembly (MASM) but the layout is quite similar to C/++. You've got more of a chance of understanding this tute if you know some kinda programming language and are familiar with Win32 programming, as this won't be normal console stuff. If not, read it anyway as you still may learn something
Template
I won't be covering Win32 ASM here but I will provide you with a template for you to build the rest of the code into.
.686.model flat, stdcalloption casemap: noneinclude \masm32\include\windows.incinclude \masm32\include\kernel32.incinclude \masm32\include\user32.incinclude \masm32\include\ws2_32.incincludelib \masm32\lib\kernel32.libincludelib \masm32\lib\user32.libincludelib \masm32\lib\ws2_32.libWinMain proto.data WindowClass db 'std', 0.data? hInst HINSTANCE ?.const WM_SOCKET equ WM_USER+1.codestart: invoke GetModuleHandle, NULL mov hInst, eax invoke WinMain invoke ExitProcess, eaxWinMain proc LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hWnd:HWND; winsock struct ; startup function mov wc.cbSize, sizeof WNDCLASSEX mov wc.style, CS_HREDRAW + CS_VREDRAW mov wc.lpfnWndProc, offset WndProc mov wc.cbClsExtra, NULL mov wc.cbWndExtra, NULL push hInst pop wc.hInstance mov wc.hbrBackground, COLOR_BTNFACE+1 mov wc.lpszMenuName, NULL mov wc.lpszClassName, offset WindowClass invoke LoadIcon, NULL, IDI_WINLOGO mov wc.hIcon, eax mov wc.hIconSm, eax invoke LoadCursor, NULL, IDC_ARROW mov wc.hCursor, eax invoke RegisterClassEx, addr wc invoke CreateWindowEx, NULL, addr WindowClass, NULL, \ NULL, \ 0, 0, 0, 0, \ NULL, NULL, hInst, NULL mov hWnd, eax invoke ShowWindow, hWnd, SW_HIDE invoke UpdateWindow, hWnd .while TRUE invoke GetMessage, addr msg, NULL, 0, 0 .break .if (!eax) invoke TranslateMessage, addr msg invoke DispatchMessage, addr msg .endw mov eax, msg.wParam retWinMain endpWndProc proc hWnd:HWND, msg:UINT, wParam:WPARAM, lParam:LPARAM; socket var .if msg == WM_CREATE ; socket functions .elseif msg == WM_DESTROY invoke PostQuitMessage, NULL ; socket msg .else invoke DefWindowProc, hWnd, msg, wParam, lParam ret .endif xor eax, eax retWndProc endpend start
LibsBefore you can use anything winsock-related, you must include certain files. Unlike C and its variants, MASM requires two files. Firstly, there's the "include" file which contains the function prototypes. Secondly, there's the "lib" file, which contains the code. In the above template, you'll see that I've already included the files.
Startup
Before winsock can be used, it must be initialized. We do this by calling the function WSAStartup and passing two arguments. The first argument is the winsock version to request and the second argument is the address to a winsock struct. Firstly, let's create the struct. In the template there's the comment "winsock struct". Replace this with the following code.
LOCAL ws:WSADATA
WSADATA is the datatype and ws is the name which will be used to refer to it.
Now we have the struct, we can call the winsock init function. Below the previous comment is another. Replace that one with the following.
invoke WSAStartup, 0202h, addr ws
Here, we call the WSAStartup function and request winsock version 2.2, followed by the address of the struct we made earlier. You should note that the requested version isn't necessarily the version you'll get, although the function won't fail so you can still use it.
Socket creation
Now we're free to use winsock, we can create a socket. We're going to do this after the WM_CREATE message has been sent to the window. Just above here, you'll notice a 'socket var' comment. Replace this with...
LOCAL _ls:SOCKET
You should be able to work out by now that SOCKET is the datatype and _ls is the lable SOCKET isn't really a datatype - it's just the same as a DWORD, but which you refer to doesn't make any difference.
Now we have somewhere to store the socket info, we can call the socket function to create it. Under WM_CREATE is another comment.
invoke socket, 2, 1, 6mov [_ls], eax
I've used numbers here because it makes me look k00l. Actually, I just found them easier to remember. Most programmers would be more used to AF_INET, SOCK_STREAM and IPPROTO_TCP. Really, they're just the same. Anyway, the first argument (2) is the family to use. In this case, we're going for network/internet. The second argument (1) is the type of socket. There's DGRAM and RAW, but we just want a STREAM for simplicity. The final argument (6) is the protocol number. As we're working with connection-oriented sockets, we'll be using TCP.
When socket is called, it returns a value to be used as an identifier, if you will. We'll refer to this value when operating on this socket. As such, we'll need to store it somewhere. Where better than our SOCKET var from earlier?
Socket info
A function we're gonna call in the future is gonna need some information as to what we need from our socket. This information is stored in another struct, which we'll need to create. Under our SOCKET creation...
LOCAL _li:sockaddr_in
With this done, we can start filling. Below are several lines of code to put under the socket call.
mov _li.sin_family, 2
Firstly, we need to specify the family. Like before, we're using the i-net.
mov _li.sin_addr.S_un.S_addr, 0
Secondly, we must specify an incoming IP address. As we're making a server, all IP addresses must be welcome so we use '0'.
invoke htons, 80mov _li.sin_port, ax
Lastly, we specify a port to listen on. Before we can assign the value to the struct, we need to convert the numeric port number into something more suitable. For this, we pass it to htons which returns the proper minerals in the accumulator.
Bind
Before we listen, we bind the socket. This function is where our sockaddr_in struct is needed. We pass 3 arguments to bind. By now, they should make sense.
invoke bind, [_ls], addr _li, sizeof _li
ListenNow the socket is bound, we can begin to listen. Listen is quite easy to understand and only has 2 arguments. There's the socket to listen on and the maximum connection que. Place the following under bind.
invoke listen, [_ls], 10
Sink?With the socket created, bound and set to listen, we can now turn this socket into an a-sync socket. To do so, we call WSAAsyncSelect.
invoke WSAAsyncSelect, [_ls], [hWnd], WM_SOCKET, FD_ACCEPT + FD_READ
The first argument is obviously the socket to be a-sync'd. The second argument is a handle to a window to send messages to. In this case, we'll be sending socket messages to the main window. The third argument is the message to send to the window. Here, a constant is used which has been defined under '.const'. The fourth argument is a set of 'flags' to show which events should be reported. As we're only creating a simple server, we'll only be using FD_ACCEPT and FD_READ.
WM_SOCKET
Under the WM_DESTROY message is another comment. Replace this with the following code.
.elseif msg == WM_SOCKET ; events
This adds another message handler for the WM_SOCKET message, which is needed for handling the FD_ messages.
FD_ACCEPT
Under the WM_SOCKET message-conditional-thingy, we'll be adding more code. In higher languages, you'll need to call functions to get different words from lParam. As we're using ASM, we can just shift lParam into EAX and refer to AX for our event code. Code to be added?
mov eax, [lParam].if ax == FD_ACCEPT
Below here, we'll be handling the FD_ACCEPT event. This event occurs when a connection is made on the port we bound our socket to. The only thing we really have to do is... accept the connection.
invoke accept, [wParam], NULL, NULL
There's a bit more to be said here. For starters, it should be noted that when a socket event occurs, the socket 'ID' will be stored in wParam. Secondly, the 2nd and 3rd arguments don't need to be NULL. Here you can specify struct info, similar to our _li. This can be used if you want to see the client's IP, etc. For the sake of our example, I'm leaving this out. Now we've accepted the connection, we just have to wait...
FD_READ
We've waited long enough and our client has decided to send us something. So, the FD_READ event is gonna kick off. That's no use if we don't know how to handle it, though. So, under the accept call, add...
.elseif ax == FD_READ
Now something has entered our domain, we can play with it...
Recv
To pull data from a socket so we can toy with it, we must call recv. This function requires an address for a buffer to store the data. We don't have one, so we must make one. Put the following under our sockaddr_in creation.
LOCAL recvb[384]:BYTE
Simply put, this just provides us with 384 bytes to shove data into. Now we have it, we can call the function. Below the last FD_READ entry...
invoke recv, [wParam], addr recvb, 383, 0mov [recvb][eax], 0
The first argument is obviously the socket ID, stored in wParam as I said before. The second argument is the address to the buffer we made earlier, followed by the third argument which is the number of bytes to move into the buffer. Our buffer is only 384 bytes and we need room for a NULL terminator, so we're only going to allow 383. The final argument is a set of flags, but they're rarely necessary so we can just set this to 0. The line below just addresses a byte in recvb and sets the value to 0. EAX will contain the number of bytes received, so it can be used as a pointer to the byte just after the final received byte.
Now we have the data in a buffer and we've made sure it's NULL terminated, we're going to display it on the screen. For this, we'll call the MessageBox function. The following goes under our recv.
invoke MessageBox, NULL, addr recvb, addr WindowClass, MB_OK
The arguments here aren't all that important, so I won't explain them. Anyway, this will display a message box with the content in recvb displayed in the main window and the window class as the caption.
Close
Now we've accepted, received and printed, it's time to say night-night to our client. For this, we call closesocket with the socket ID as the only argument.
invoke closesocket, [wParam]
With this done, it's also wise to close off the conditional.
.endif
End productJust so you know you've got it right, here's the template with the functions and such added.
.686.model flat, stdcalloption casemap: noneinclude \masm32\include\windows.incinclude \masm32\include\kernel32.incinclude \masm32\include\user32.incinclude \masm32\include\ws2_32.incincludelib \masm32\lib\kernel32.libincludelib \masm32\lib\user32.libincludelib \masm32\lib\ws2_32.libWinMain proto.data WindowClass db 'std', 0.data? hInst HINSTANCE ?.const WM_SOCKET equ WM_USER+1.codestart: invoke GetModuleHandle, NULL mov hInst, eax invoke WinMain invoke ExitProcess, eaxWinMain proc LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hWnd:HWND LOCAL ws:WSADATA invoke WSAStartup, 0202h, addr ws mov wc.cbSize, sizeof WNDCLASSEX mov wc.style, CS_HREDRAW + CS_VREDRAW mov wc.lpfnWndProc, offset WndProc mov wc.cbClsExtra, NULL mov wc.cbWndExtra, NULL push hInst pop wc.hInstance mov wc.hbrBackground, COLOR_BTNFACE+1 mov wc.lpszMenuName, NULL mov wc.lpszClassName, offset WindowClass invoke LoadIcon, NULL, IDI_WINLOGO mov wc.hIcon, eax mov wc.hIconSm, eax invoke LoadCursor, NULL, IDC_ARROW mov wc.hCursor, eax invoke RegisterClassEx, addr wc invoke CreateWindowEx, NULL, addr WindowClass, NULL, \ NULL, \ 0, 0, 0, 0, \ NULL, NULL, hInst, NULL mov hWnd, eax invoke ShowWindow, hWnd, SW_HIDE invoke UpdateWindow, hWnd .while TRUE invoke GetMessage, addr msg, NULL, 0, 0 .break .if (!eax) invoke TranslateMessage, addr msg invoke DispatchMessage, addr msg .endw mov eax, msg.wParam retWinMain endpWndProc proc hWnd:HWND, msg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL _ls:SOCKET LOCAL _li:sockaddr_in LOCAL recvb[384]:BYTE .if msg == WM_CREATE invoke socket, 2, 1, 6 mov [_ls], eax mov _li.sin_family, 2 mov _li.sin_addr.S_un.S_addr, 0 invoke htons, 80 mov _li.sin_port, ax invoke bind, [_ls], addr _li, sizeof _li invoke listen, [_ls], 10 invoke WSAAsyncSelect, [_ls], [hWnd], WM_SOCKET, FD_ACCEPT + FD_READ .elseif msg == WM_DESTROY invoke PostQuitMessage, NULL .elseif msg == WM_SOCKET mov eax, [lParam] .if ax == FD_ACCEPT invoke accept, [wParam], NULL, NULL .elseif ax == FD_READ invoke recv, [wParam], addr recvb, 383, 0 mov [recvb][eax], 0 invoke MessageBox, NULL, addr recvb, addr WindowClass, MB_OK invoke closesocket, [wParam] .endif .else invoke DefWindowProc, hWnd, msg, wParam, lParam ret .endif xor eax, eax retWndProc endpend start
Now, if you assemble this, run it and visit http://forums.xisto.com/no_longer_exists/ in your browser, you'll see a GET request in a message box. As I've not created a visual window, to close the app you'll have to use the ol' CTRL + DEL.
Quick note...
In this example, we've only used a few winsock functions and a-sync events. For more information on the others, you can search the MSDN library or locate some other tutorials. Maybe I'll write another socket tutorial covering client creation.
Closing
That concludes my introduction to a-sync sockets. Hopefully with this information, you'll be more prepared for creating applications that require multiple connections on a single port.
Got anything to say? Say it ;p