| ||
In this section, I provide an example that comprises two programs: client and server. Client and server parts of the application communicate using TCP/IP. Program code is presented in Listing 17.4 (server) and Listing 17.5 (client). Note that this is the simplest implementation of a client and server system, which, nevertheless, contains all the main mechanisms of application's communication using sockets. The server waits for the client call and, in response to a client request, sends to it the string that the client displays on the console. The client also sends a message in response to the server, which, in turn , displays it on the console. The server waits in a loop for requests and can reply to ten client requests that directly follow each other. To connect the server, the client must know the network name of the computer, in which the server component of the application runs. Before sending the request, the client determines the server's IP address by its name and displays it on the console.
; The SERVER.ASM file .586P ; Flat memory model .MODEL FLAT, stdcall ; Constants STD_OUTPUT_HANDLE equ -11 ; Prototypes of external procedures IFDEF MASM EXTERN shutdowns@8:NEAR EXTERN recv@16:NEAR EXTERN send@16:NEAR EXTERN accept@12:NEAR EXTERN listen@8:NEAR EXTERN bind@12:NEAR EXTERN closesocket@4:NEAR EXTERN socket@12:NEAR EXTERN CharToOemA@8:NEAR EXTERN WSAStartup@8:NEAR EXTERN wsprintfA:NEAR EXTERN GetLastError@0:NEAR EXTERN ExitProcess@4:NEAR EXTERN lstrlenA@4:NEAR EXTERN WriteConsoleA@20:NEAR EXTERN GetStdHandle@4:NEAR ELSE EXTERN shutdown:NEAR EXTERN recv:NEAR EXTERN send:NEAR EXTERN accept:NEAR EXTERN listen:NEAR EXTERN bind:NEAR EXTERN closesocket:NEAR EXTERN socket:NEAR EXTERN CharToOemA:NEAR EXTERN WSAStartup:NEAR EXTERN _wsprintfA:NEAR EXTERN GetLastError:NEAR EXTERN ExitProcess:NEAR EXTERN lstrlenA:NEAR EXTERN WriteConsoleA:NEAR EXTERN GetStdHandle:NEAR shutdown@8 = shutdown recv@16 = recv send@16 = send accept@12 = accept listen@8 = listen bind@12 = bind closesocket@4 = closesocket socket@12 = socket CharToOemA@8 = CharToOemA WSAStartup@8 = WSAStartup GetStdHandle@4 = GetStdHandle WriteConsoleA@20 = WriteConsoleA lstrlenA@4 = lstrlenA wsprintfA = _wsprintfA GetLastError@0 = GetLastError ExitProcess@4 = ExitProcess ENDIF ; INCLUDELIB directives for the linker IFDEF MASM includelib d:\masm32\lib\user32.lib includelib d:\masm32\lib\kernel32.lib includelib d:\masm32\lib\ws2_32.lib ELSE includelib c:\tasm32\lib\import32.lib ENDIF ;--------------------------------------------- WSADATA STRUCT wVersion WORD ? wHighVersion WORD ? szDescription BYTE 257 dup (?) szSystemStatus BYTE 129 dup (?) iMaxSockets WORD ? iMaxUdpDg WORD ? lpVendorInfo DWORD ? WSADATA ENDS ;--------------------------------------------- S_UN_B STRUCT s_b1 BYTE 0 s_b2 BYTE 0 s_b3 BYTE 0 s_b4 BYTE 0 S_UN_B ENDS S_UN_W STRUCT S_w1 WORD 0 S_w2 WORD 0 S_UN_W ENDS ADDRESS_UNION UNION s_u_b S_UN_b <> s_u_w S_UN_w <> s_addr DWORD 0 ADDRESS_UNION ENDS in_addr STRUCT s_un ADDRESS_UNION <> in_addr ENDS sockaddr_in STRUCT sin_family WORD 0 sin_port WORD 0 sin_addr in_addr <> sin_zero BYTE 8 dup (0) sockaddr_in ENDS ;------------------------------------- ; Data segment _DATA SEGMENT HANDL DD ? LENS DD ? ERRS DB "Error %u ",.0 ;------------------------------------- S1 DD ? S2 DD ? LEN DD ? BUF DB 100 DUP(O) BUF1 DB 100 DUP(0) txt DB 'Call accepted. Please send acknowledgment.', 0 msg DB 'The server is down', 0 sin1 sockaddr_in <0> sin2 sockaddr_in <0> wsd WSADATA <0> len1 DD ? _DATA ENDS ; ode segment _TEXT SEGMENT START: ; Get the descriptor of the output console PUSH STD_OUTPUT_HANDLE CALL GetStdHandle@4 MOV HANDL, EAX ; Activate the sockets library PUSH OFFSET wsd MOV EAX, 0 MOV AX, 0202H PUSH EAX CALL WSAStartup@8 CMP EAX, 0 JZ NO_ER1 CALL ERRO JMP EXI NO_ER1: ; Create a socket PUSH 0 PUSH 1 ; SOCK_STREAM PUSH 2 ; AF_INET CALL socket@12 CMP EAX, NOT 0 JNZ N0_ER2 CALL ERRO JMP EXI N0_ER2: MOV s1, EAX ; Connect a socket MOV sin1.sin_family, 2 ; AF_INET MOV sin1.sin_addr.s_un.s_addr, 0 ; INADDR_ANY MOV sin1.sin_port, 2000 ; Port number PUSH sizeof(sockaddr_in) PUSH OFFSET sin1 PUSH s1 CALL bind@12 CMP EAX, 0 JZ NO_ER3 CALL ERRO JMP CLOS NO_ER3: ; Switch the socket to the listening state MOV ESI, 10 PUSH 5 PUSH s1 CALL listen@8 CMP EAX, 0 JZ N0_ER4 CALL ERRO JMP CLOS NO_ER4: MOV len1, sizeof(sockaddr_in) ; Wait for the client requests PUSH OFFSET len1 PUSH OFFSET sin2 PUSH s1 CALL accept@12 MOV s2, EAX ; The request has arrived, now sending the information PUSH 0 PUSH OFFSET txt CALL lstrlenA@4 PUSH EAX PUSH OFFSET txt PUSH s2 CALL send@16 ; Waiting for response PUSH 0 PUSH 100 PUSH OFFSET buf PUSH s2 CALL recv@16 ; EAX contains the message length ; First, it is necessary to convert the string PUSH OFFSET buf1 PUSH OFFSET buf CALL CharToOemA@8 ; Output LEA EAX, BUF1 MOV EDI, 1 CALL WRITE ; Close the connection PUSH 0 PUSH s2 CALL shutdown@8 ; Close the socket PUSH S2 CALL closesocket@4 DEC ESI JNZ N0_ER4 ; End of the loop PUSH OFFSET buf1 PUSH OFFSET msg CALL CharToOemA@8 ; Now the conclusion LEA EAX, BUF1 MOV EDI, 1 CALL WRITE CLOSE: PUSH S1 CALL closesocket@4 EXIT: ; Exiting when all services are terminated PUSH 0 CALL ExitProcess@4 ; Display the string terminated by the line feed ; EAX - To the start of the string ; EDI - With or without the line feed WRITE PROC PUSH ESI ; Get the parameter length PUSH EAX PUSH EAX CALL lstrlenA@4 MOV ESI, EAX POP EBX CMP EDI, 1 JNE NO_ENT ; Line feed in the end MOV BYTE PTR [EBX+ESI], 13 MOV BYTE PTR [EBX+ESI+1], 10 MOV BYTE PTR [EBX+ESI+2], 0 ADD EAX, 2 NO_ENT: ; String output PUSH 0 PUSH OFFSET LENS PUSH EAX PUSH EBX PUSH HANDL CALL WriteConsoleA@20 POP ESI RET WRITE ENDP ; The procedure for error code output ERRO PROC CALL GetLastError@0 PUSH EAX PUSH OFFSET ERRS PUSH OFFSET BUF1 CALL wsprintfA ADD ESP, 12 LEA EAX, BUF1 MOV EDI, 1 CALL WRITE RET ERRO ENDP _TEXT ENDS END START
To translate the SERVER.ASM (Listing 17.4), issue the following commands for MASM32:
ml /c /coff /DMASM server.asm link /subsystem:console server.obj
Issue the following commands for TASM32:
tasm32 /ml server.asm tlink32 -ap server.obj
The program presented in Listing 17.4 requires some comments.
The SERVER.ASM program waits for the client programs to contact it. It listens to port 2000. The waiting is implemented by the accept function. This function returns control when a client attempts to connect this server. If the connection was successfully established, the function returns the newly-created socket, through which the server will communicate to the client program.
The accept function has one specific feature. It returns control immediately after receiving a message from the client. This doesn't guarantee yet that the connection has been established; this is especially true for WANs. Consequently, if you build the communications design according to this approach, it is necessary to provide additional features for acknowledging the connection.
This program implements the simplest method of communication, in which the server can communicate only with one client at a time. To ensure the possibility of simultaneous operation with multiple clients, it is necessary to use another approach: When receiving a message from the client, the server must create a new thread and pass the newly-created socket to it. This thread will further server this client. The main thread will again switch to waiting for new client requests.
; The CLIENT.ASM program .586P ; Flat memory model .MODEL FLAT, stdcall ; Constants STD_OUTPUT_HANDLE equ -11 ; Prototypes of external procedures IFDEF MASM EXTERN connect@12:NEAR EXTERN gethostbyname@4:NEAR EXTERN shutdown@8:NEAR EXTERN recv@16:NEAR EXTERN send@16:NEAR EXTERN accept@12:NEAR EXTERN listen@8:NEAR EXTERN bind@12:NEAR EXTERN closesocket@4:NEAR EXTERN socket@12:NEAR EXTERN CharToOemA@8:NEAR EXTERN WSAStartup@8:NEAR EXTERN wsprintfA:NEAR EXTERN GetLastError@0:NEAR EXTERN ExitProcess@4:NEAR EXTERN lstrlenA@4:NEAR EXTERN WriteConsoleA@20:NEAR EXTERN GetStdHandle@4:NEAR ELSE EXTERN connect:NEAR EXTERN gethostbyname:NEAR EXTERN shutdown:NEAR EXTERN recv:NEAR EXTERN send:NEAR EXTERN accept:NEAR EXTERN listen :NEAR EXTERN bind:NEAR EXTERN closesocket:NEAR EXTERN socket:NEAR EXTERN CharToOemA:NEAR EXTERN WSAStartup:NEAR EXTERN _wsprintfA:NEAR EXTERN GetLastError:NEAR EXTERN ExitProcess:NEAR EXTERN lstrlenA:NEAR EXTERN WriteConsoleA:NEAR EXTERN GetStdHandle:NEAR connect@12 = connect gethostbyname@4 = gethostbyname shutdown@8 = shutdown recv@16 = recv send@16 = send accept@12 = accept listen@8 = listen bind@12 = bind closesocket@4 = closesocket socket@12 = socket CharToOemA@8 = CharToOemA WSAStartup@8 = WSAStartup GetStdHandle@4 = GetStdHandle WriteConsoleA@20 = WriteConsoleA lstrlenA@4 = lstrlenA wsprintfA = _wsprintfA GetLastError@0 = GetLastError ExitProcess@4 = ExitProcess ENDIF ; INCLUDELIB directives for the linker IFDEF MASM includelib d:\masm32\lib\user32.lib includelib d:\masm32\lib\kernel32.lib includelib d:\masm32\lib\ws2_32.lib ELSE includelib c:\tasm32\lib\import32.lib ENDIF ;------------------------------------------------ WSADATA STRUCT wVersion WORD ? wHighVersion WORD ? szDescription BYTE 257 dup (?) szSystemStatus BYTE 129 dup (?) iMaxSockets WORD ? iMaxUdpDg WORD ? lpVendorInfo DWORD ? WSADATA ENDS ;-------------------------------------------- S_UN_B STRUCT s_b1 BYTE 0 s_b2 BYTE 0 s_b3 BYTE 0 s_b4 BYTE 0 S_UN_B ENDS S_UN_W STRUCT S_w1 WORD 0 s_w2 WORD 0 S_UN_W ENDS ADDRESS_UNION UNION s_u_b S_UN_b <> s_u_w S_UN_w <> s_addr DWORD 0 ADDRESS_UNION ENDS in_addr STRUCT s_un ADDRESS_UNION <> in_addr ENDS sockaddr_in STRUCT sin_family WORD 0 sin_port WORD 0 sin_addr in_addr <> sin_zero BYTE 8 dup (0) sockaddr_in ENDS hostent STRUCT h_name DWORD ? h_alias DWORD ? h_addr WORD ? h_len WORD ? h_list DWORD ? hostent ENDS ;-------------------------------------------- ; Data segment _DATA SEGMENT HANDL DD ? LENS DD ? ERRS DB "Error %u ", 0 IP DB "IP address %hu.%hu.%hu.%hu", 0 IPA DD ? S1 DD ? comp DB "pvju1", 0 txt DB "Call acknowledged", 0 txt1 DB "Host address", 0 LEN DD ? sin2 sockaddr_in <0> hp hostent <0> BUF DB 100 DUP(0) BUF1 DB 100 DUP(0) wsd WSADATA <0> _DATA ENDS ; Code segment _TEXT SEGMENT START: ; Define the output console handle PUSH STD_OUTPUT_HANDLE CALL GetStdHandle@4 MOV HANDL, EAX ; Activate the sockets library PUSH OFFSET wsd MOV EAX, 0 MOV AX, 0202H PUSH EAX CALL WSAStartup@8 CMP EAX, 0 JZ NO_ER1 CALL ERRO JMP EXI NO_ER1: ; Determine the server address using the host name PUSH OFFSET comp CALL gethostbyname@4 CMP EAX, 0 JNZ NO_ER2 CALL ERRO JMP EXI NO_ER2: ; Display the address MOV EBX, [EAX+12] ; h_list in the hostent structure MOV EDX, [EBX] MOV EDX, [EDX] MOV IPA, EDX SHR EDX, 24 AND EDX, 000000FFH PUSH EDX MOV EDX, IPA SHR EDX, 16 AND EDX, 000000FFH PUSH EDX MOV EDX, IPA SHR EDX, 8 AND EDX, 000000FFH PUSH EDX MOV EDX, IPA AND EDX, 000000FFH PUSH EDX PUSH OFFSET IP PUSH OFFSET BUF1 CALL wsptrintfA ADD ESP, 24 LEA EAX, BUF1 MOV EDI, 1 CALL WRITE MOV EDX, IPA MOV sin2.sin_addr.s_un.s_addr, EDX MOV sin2.sin_port, 2000 MOV sin2.sin_family, 2 ; AF_INET ; Create a socket PUSH 0 PUSH 1 ; SOCK_STREAM PUSH 2 ; AF_INET CALL socket@12 CMP EAX, NOT 0 JNZ N0_ER3 CALL ERRO JMP EXI N0_ER3: MOV s1, EAX ; Try to connect the server PUSH sizeof(sockaddr_in) PUSH OFFSET sin2 PUSH s1 CALL connect@12 CMP EAX, 0 JZ NO_ER4 CALL ERRO JMP CLOS NO_ER4: ; Wait for information PUSH 0 PUSH 100 PUSH OFFSET buf PUSH s1 CALL recv@16 ; Message length in EAX ; First, it is necessary to convert the string PUSH OFFSET buf1 PUSH OFFSET buf CALL CharToOemA@8 ; Output LEA EAX, BUF1 MOV EDI, 1 CALL WRITE ; Send information PUSH 0 PUSH OFFSET txt CALL lstrlenA@4 PUSH EAX PUSH OFFSET txt PUSH s1 CALL send@16 CLOSE: PUSH S1 CALL closesocket@4 EXIT: ; Exiting after all services have terminated PUSH 0 CALL ExitProcess@4 ; Display the string (line feed in the end) ; EAX - To the start of the string ; EDI - With or without the line feed WRITE PROC ; Get the parameter length PUSH EAX PUSH EAX CALL lstrlenA@4 MOV ESI, EAX POP EBX CMP EDI, 1 JNE NO_ENT ; Line feed in the end of the string MOV BYTE PTR [EBX+ESI], 13 MOV BYTE PTR [EBX+ESI+1], 10 MOV BYTE PTR [EBX+ESI+2], 0 ADD EAX, 2 NO_ENT: ; String output PUSH 0 PUSH OFFSET LENS PUSH EAX PUSH EBX PUSH HANDL CALL WriteConsoleA@20 RET WRITE ENDP ; Display error code ERRO PROC CALL GetLastError@0 PUSH EAX PUSH OFFSET ERRS PUSH OFFSET BUF1 CALL wsprintfA ADD ESP, 12 LEA EAX, BUF1 MOV EDI, 1 CALL WRITE RET ERRO ENDP _TEXT ENDS END START
To translate the program presented in Listing 17.5, issue the following commands for MASM32:
ml /c /coff /DMASM CLIENT.asm link /subsystem:console CLIENT.obj
Issue the following commands for TASM32:
tasm32 /ml CLIENT.asm tlink32 -ap CLIENT.obj
The program presented in Listing 17.5 requires some comments.
Before trying to access the server, the client must get the IP address of the host that runs the server component of the application. For this purpose, the gethostbyname function is used. If this function completes successfully, it returns the pointer to the hostent structure that I described earlier. The definition of this structure is placed into the program code, although it doesn't directly use a variable of this type. To make it easier to understand how the IP address is then retrieved, consider the appropriate lines from the program code:
MOV EBX, [EAX+12] ; h_list is in the hostent structure MOV EDX, [EBX] MOV EDX, [EDX] MOV IPA, EDX
Note that the h_list field resides exactly at 12-byte offset from the starting point of the structure. This field contains the pointer to the string of bytes. This string contains several 4-byte chains, each of which represents an IP address of the host (recall that the host can have several IP addresses). A zero code serves as an indication of the end of this sequence. At the same time, you need to be interested only in the first 4-byte chain; therefore, you read it using the MOV EDX, [EDX] command. The least significant byte is the leftmost byte in the IP address.
You can modify this program by taking the IP address instead of the host name as a basis. To achieve this, it is necessary to form a double word so that the least significant byte will be the leftmost component of the address and the most significant byte will be the rightmost component. Then, it will be necessary to load this double word into a register (e.g., EDX ) and execute the following command:
MOV sin2.sin_addr.S_un.s_addr, EDX
To conclude the examples of working with sockets, consider Fig. 17.4, which shows a schematic diagram of communications between the SERVER.ASM and CLIENT.ASM programs.
| ||