The article is pasted from my blog, and the pictures may not come through. To see the full content, visit my blog:
Click to enter "DOS Programming Technology"
In the previous article, we did a lot of preparatory work for network programming under DOS. We installed the WATT-32 library under DJGPP and configured the network environment. Now we use an example to illustrate the method of network programming under DOS.
In the previous article, we compiled a sample program ftpsrv.c in the WATT-32 library, which is a sample program of an FTP server. Now we also compile a program of an FTP server, but there are two differences. First, we mainly use the standard functions of BSD network programming, which is a specification for network programming under UNIX. The WATT-32 library implements most of the BSD programming functions. In "Network Programming under DOS (Part 1)", an article "Beej's Guide to Network Programming Using internet Sockets" is introduced. The programming method introduced in this article is also based on this specification. For the introduction of functions under this specification, you can download from the following website or refer to the books on network programming under UNIX.
http://blog.hengch.com/Manual/BSDsocket.pdf
Next, continue with our FTP server. To write an FTP server program, first, we need to understand the FTP protocol. The complete specification of the FTP protocol can be downloaded from the following website:
http://blog.hengch.com/specification/rtfc765-ftp.pdf
In actual operation, it is not as complicated as in the protocol. Moreover, our example does not want to complete all the protocols. Our example plans to complete the following functions:
Listen on FTP port 21 (listen)
Accept connection requests from FTP clients (accept)
Accept the login of FTP clients, but do not verify the login information
Accept the quit command sent by FTP clients, and close the connection (close)
The entire program only accepts the request of one FTP client. When it has provided service for one FTP client, it will ignore new connection requests.
Okay, now we can start. The following is the source program of our example. For the convenience of explanation, we add line numbers in front.
01 #include <stdio.h>
02 #include <string.h>
03 #include <sys/socket.h>
04 int FtpServer(int s);
05 int main (void) {
06 struct sockaddr_in my_addr; // my address information
07 struct sockaddr_in their_addr; // connector's address information
08 int sockfd, new_fd; // listen on sockfd, new connection on new_fd
09 int sin_size;
10 int Loop;
11 char tempStr;
12 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
13 printf("Socket Error!\n");
14 return 1;
15 }
16 my_addr.sin_family = AF_INET; // host byte order
17 my_addr.sin_port = htons(21); // short, network byte order
18 my_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
19 memset(&my_addr.sin_zero, 0, 8); // zero the rest of the struct
20 if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
21 printf("Bind Error!\n");
22 return 1;
23 }
24 if (listen(sockfd, 5) == -1) {
25 printf("Listen Error!\n");
26 return 1;
27 }
28 new_fd = -1;
29 sin_size = sizeof(struct sockaddr_in);
30 new_fd = accept(sockfd, (struct sockaddr*)&their_addr, &sin_size);
31 if (new_fd == -1) {
32 printf("Accept Error!\n");
33 return 1;
34 }
35 printf ("Got connection from %s\n", inet_ntoa(their_addr.sin_addr));
36 strcpy(tempStr, "220 FTP Server, service ready.\r\n");
37 send(new_fd, tempStr, strlen(tempStr), 0);
38 Loop = 1;
39 while (Loop) {
40 Loop = FtpServer(new_fd);
41 }
42 sleep(5);
43 close(new_fd);
44 close(sockfd);
45 return 0;
46 }
47 int FtpServer(int s) {
48 char szBuf;
49 char tempStr;
50 int iBytes;
51 char *p, *p2;
52 iBytes = recv(s, szBuf, 30, 0);
53 if (iBytes >= 2) {
54 iBytes -= 2;
55 szBuf = NULL;
56 } else {
57 return 0;
58 }
59 p = szBuf;
60 while (*p != ' ' && *p != NULL) {
61 p++;
62 }
63 if (p) {
64 *p = NULL;
65 p2 = p + 1; // p2 point to the second parameter
66 }
67 if (stricmp("user", szBuf) == 0) { // start to process FTP commands
68 sprintf(tempStr, "331 Password required for %s.\r\n", p2);
69 send(s, tempStr, strlen(tempStr), 0);
70 printf("Received 'user' command. User is %s\n", p2);
71 } else if(stricmp("pass", szBuf) == 0) {
72 strcpy(tempStr, "230 Logged in okay.\r\n");
73 send(s, tempStr, strlen(tempStr), 0);
74 printf("Received 'pass' command. Password is %s\n", p2);
75 } else if (stricmp("quit", szBuf) == 0) {
76 strcpy(tempStr, "221 Bye!\r\n");
77 send(s, tempStr, strlen(tempStr), 0);
78 printf("Received 'quit' command!\n");
79 return 0;
80 } else {
81 strcpy(tempStr, "500 Command not understood.\r\n");
82 send(s, tempStr, strlen(tempStr), 0);
83 printf("Received a unknown command: %s\n", szBuf);
84 }
85 return 1;
86 }
The entire program is very short, only 86 lines. To highlight the main line, most of the error handling is removed in the program, so the entire program is only a general framework, but it can illustrate the problem.
If you have FTP client software at hand (such as CUTEFTP, LEAFFTP, etc.), you might as well try to connect to any FTP server, and you can simply observe the communication process of FTP. The port number of FTP is 21. The communication process is roughly as follows (only the processes related to the example):
The client software first requests a connection to port 21 of the server.
After the server accepts the connection, it sends a string starting with "220 " to the client. This program sends "220 FTP Server, service ready."
After the client receives the information of "220 ", it logs in and sends the command "user xxxxxx", where xxxxxx is the user name.
After the server checks that the user name is legal, it requests the client to enter the password and sends a string starting with "331 ". This program sends "331 Password required for xxxxxx", where xxxxxx is the received user name.
After the client receives the information of "331 ", it sends the password to the server and sends the command "pass xxxxxx", where xxxxxx is the password.
After the server checks that the password is correct, it sends a string starting with "230 " to the client, indicating that the login is successful and other commands can be received. This program sends "230 Logged in okay."
After that, a lot of interaction is required between the client and the server for file transfer, directory, etc.
When ending the service, the client sends the "quit" command to the server, and both parties disconnect.
First, we need to understand two data structures, struct sockaddr and struct sockaddr_in.
struct sockaddr {
unsigned short sa_family; // address family
char sa_data; // 14 bytes of protocol address
}
This structure is used to manage the address information of the socket. Among them, sa_family is the category of the address, we can fill in "AF_INET", this constant has been defined in the header file of WATT-32. sa_data is 14 bytes of address information, which should contain address and port information. For the convenience of use, a structure equivalent to sockaddr is established, struct sockaddr_in
struct sockaddr_in {
short int sin_family; // address family
unsigned short int sin_port; // port number
struct in_addr sin_addr; // internet address
unsigned char sin_zero; // Same size as struct sockaddr
}
The sin_family of this structure is the same as sa_family in sockaddr, and we can fill in "AF_INET". sin_port is the port number, and the port number of FTP is 21. The structure of struct in_addr is as follows:
struct in_addr {
unsigned long s_addr;
}
It is a 32-bit IP address. To convert a conventional IP address into a 32-bit IP address, the following method needs to be used:
xx.sin_addr.s_addr = inet_addr("192.168.0.20");
Regarding the byte order problem, "Beej's Guide to Network Programming Using internet Sockets" also mentions this problem. One kind of byte order is called "Host Byte Order", and the other is called "Network Byte Order". Because the explanation of this problem is not very clear in this article, so I will say a few more words here. A number, such as the Short int type, occupies two bytes. Suppose this number is 0x6789, and it is stored in the memory address 0x1000. Then there are two representation methods. One is that 0x67 is placed at 0x1000 and 0x89 is placed at 0x1001. The other representation method is that 0x89 is placed at 0x1000 and 0x67 is placed at 0x1001. The first storage method is called big-endian, and the second storage method is called little-endian. In the machine with CPU x86, the little-endian order is used, and the network transmission protocol TCP/IP uses the big-endian. In our specific environment, Host Byte Order refers to the character order of our PC, that is, the little-endian order, and Network Byte Order refers to the network transmission order, that is, the big-endian. Due to the different byte orders used, 1 conversion is often required. For this reason, there is a set of conversion functions specially. The "h" in the function refers to Host Byte Order, "n" refers to Network Byte Order, "s" refers to short int, and "l" refers to long int. So the meaning of this set of functions is as follows:
htons()----"Host to Network Short"
htonl()----"Host to Network Long"
ntohs()----"Network to Host Short"
ntohl()----"Network to Host Long"
Therefore, in network programming, once there is an integer and other numerical operations, we must think about whether conversion is required. I hope my explanation can not only make you understand the reason, but also remember these conversion functions.
In our example, two such data structures are needed. One is used to manage our local network address, and the other is used to manage the network address of the remote node connected to us. These two structures are respectively named: my_addr and their_addr, see lines 06 and 07 of the source program.
In our example, we also need two sockets. One is used to represent the local network we are listening to, and the other is used to represent the network connection with the remote FTP client. We don't need to ask what a socket is, just simply understand it as something similar to a file handle. In fact, a socket is just an integer.
There are many types of sockets, but only two are commonly used. One is "Stream Sockets", and the other is "Datagram Sockets". The former is used for TCP connection, and the latter is used for UDP connection. It is enough to understand these for the time being.
At the beginning of our program, we first initialize a socket. The prototype of the socket function is as follows:
int socket(int domain, int type, int protocol);
In general cases, domain is filled with "AF_INET". type refers to the type of the socket. If it is Stream Sockets, fill in SOCK_STREAM. If it is Datagram Sockets, fill in SOCK_DGRAM. In this program, it should be SOCK_DGRAM, and protocol is set to 0. The return value of socket() is an available socket value. In line 12 of the program, we get a socket: sockfd.
Lines 16-19 describe the local network address structure my_addr. It should be noted that 21 in line 17 is the dedicated port of FTP. Since my_addr.sin_port is a short int type, we need to use htons() for conversion. In line 18, my_addr.sin_addr.s_addr is filled with the constant INADDR_ANY, which means using the IP address set in WATTCP.CFG of the local machine. It should be noted that the type of s_addr is long int, but here the htonl() function is not used for conversion. Strictly speaking, here the htonl() function is indeed needed for conversion. This should be particularly noted. If you want to fill in the IP address yourself, pay attention to using the inet_addr() function to convert a common IP address, as follows:
my_addr.sin_addr.s_addr = inet_addr("192.168.0.20");
To convert a 32 bits IP address into the common form we see, the function inet_ntoa() is used, as follows:
printf("IP address is %s", my_addr.sin_addr.s_addr);
The printed result is the common IP address form of xxx.xxx.xxx.xxx.
Line 19 just fills the rest of the structure with 0, which has no meaning.
In line 20, we bind the just obtained sockfd and the just filled structure my_addr together using bind(). The prototype of the bind() function is as follows:
int bind(int sockfd, struct sockaddr *my_addr, int addr_len);
There doesn't seem to be much to explain.
In line 24, we set to listen on the socket sockfd, with a maximum of 5 connections allowed. Actually, we only accept one connection. The prototype of listen() is as follows:
int listen(int sockfd, int backlog);
The parameter backlog can specify how many connection requests are allowed for this listening; there is no more to explain.
In line 30, we are waiting for a connection request. Note that the accept() function is a blocking function. The program will stop at this function and wait until there is a connection request before returning. In some occasions, this cannot be used like this. The prototype of the accept() function is as follows:
int accept(int sockfd, void *addr, int *addrlen);
Normally, the accept function returns a new socket descriptor, new_sock in this program. This new socket represents the connection with a remote node. In the future, when operating this connection, this socket will be used. At the same time, the accept function will fill the address information of the remote node into addr, which is their_addr in this program.
In line 37, we send the first message to the remote computer, using the send() function to send to new_sock. The prototype of the send() function is as follows:
int send(int sockfd, const void *msg, int len, int flags);
The last parameter of the function is generally set to 0.
After sending a message to the remote computer, the program enters a loop. In the loop, it continuously calls the function FtpServer(), and exits the loop until the function returns 0. In FtpServer, the program tries to receive information from new_sock, then analyzes and processes the information, and returns 0 until the "quit" command is received, so that the main program can exit the loop.
In line 52, the recv() function is used to receive information from new_sock. This function is also a blocking function, that is, if no information is received, this function will not return. This is not allowed when constructing a real-time system. Another problem is that if the network is interrupted for some reason after the program enters the recv() function, the program will not return from the recv() function, and the program will hang in the recv() function. Therefore, in actual applications, this function cannot be used like this. The prototype of the recv() function is as follows:
int recv(int sockfd, void *buf, int len, unsigned int flags);
Like the send() function, flags is filled with 0. len is the maximum length of the received information, which should be determined with reference to the length of buf, otherwise a boundary error will occur. Actually, when receiving, it does not return until len characters are received. This function will return the actual number of received characters.
In line 53, we limit that the number of received characters is at least 2, because all FTP transmission commands are followed by carriage return and line feed, that is, ASCII codes 0x0d and 0x0a. If both of these characters are not present, the received content is meaningless.
Lines 59-66 analyze the received content simply. Because the command format of ftp is: cmd para1 para2...., this segment of the program separates the cmd part of the command specially. After the execution of this segment of the program, szBuf points to cmd, and p2 points to the following parameters. Of course, this sample program does not need to analyze the parameters, so actually p2 is not useful to us.
Lines 67--80 handle three commands, and give legal returns or actions according to the protocol. For commands other than "user", "pass" and "quit", we handle them as unknown commands, and according to the protocol, return information like "500 ......".
The program is explained here. This program has no practicality because it lacks necessary parts such as error handling, but its framework is complete. After processing, it can be completely turned into a complete Ftp server program.
Finally, I need to say how to test. First, set up the network data, which has been explained earlier. Then connect two machines with a HUB. We cannot use general FTP software (such as CUTEFTP or LeafFTP) because the commands we handle are too few. These software will automatically send many instructions, and since our program responds with "500 ...", a normal FTP software will have an error message like "protocol error" and terminate. We cannot use software like telnet to test because this kind of software is a terminal emulation software. Each character entered will be sent immediately, and the keyboard input speed is extremely slow, which will cause our program to not receive a complete command at one time (recv() function), thus leading to operation failure. Please use the following method to test:
In Windows, click "Start" --> "Run", and enter: ftp 192.168.0.20 (if your IP address is different, please change it)
After pressing "OK", the following window appears. We see that the second line "220 FTP..." is sent by our program
We enter "abcd", of course, you can enter others, because our program does not verify. After pressing Enter, the following window appears, and the fourth line is returned by our program after receiving the user name
In the fifth line, enter a few alphanumeric characters arbitrarily, such as "1234", and press Enter. Since it is a password, the content you entered is not displayed on the screen. After pressing Enter, the following window is seen, and the content of the sixth line is returned by our program after receiving the pass command
Finally, we enter the quit command after ftp>, and after pressing Enter, the screen flashes and then closes, so we can't see the returned content clearly.
The entire process is also clearly shown on the FTP server side running our program.
Okay, we have finished this specific example. Probably, you should understand the method of network programming under DOS. Note that since we generated the program under DJGPP, it is in 32-bit protected mode, so it can only run on a machine with DPMI service. Of course, this kind of programming method is also applicable to real mode. Moreover, although the WATT-32 library is 32-bit, it actually supports 16-bit real mode, so it is also possible to use turbo C, etc. We will talk about the method of network programming under DOS in more detail in the future, or introduce the writing specification and method of Packet Driver, or introduce DPMI, etc.
For more articles on DOS programming, see my blog
Click to enter "DOS Programming Technology"
[ Last edited by whowin on 2008-5-9 at 11:50 AM ]
Click to enter "DOS Programming Technology"
In the previous article, we did a lot of preparatory work for network programming under DOS. We installed the WATT-32 library under DJGPP and configured the network environment. Now we use an example to illustrate the method of network programming under DOS.
In the previous article, we compiled a sample program ftpsrv.c in the WATT-32 library, which is a sample program of an FTP server. Now we also compile a program of an FTP server, but there are two differences. First, we mainly use the standard functions of BSD network programming, which is a specification for network programming under UNIX. The WATT-32 library implements most of the BSD programming functions. In "Network Programming under DOS (Part 1)", an article "Beej's Guide to Network Programming Using internet Sockets" is introduced. The programming method introduced in this article is also based on this specification. For the introduction of functions under this specification, you can download from the following website or refer to the books on network programming under UNIX.
http://blog.hengch.com/Manual/BSDsocket.pdf
Next, continue with our FTP server. To write an FTP server program, first, we need to understand the FTP protocol. The complete specification of the FTP protocol can be downloaded from the following website:
http://blog.hengch.com/specification/rtfc765-ftp.pdf
In actual operation, it is not as complicated as in the protocol. Moreover, our example does not want to complete all the protocols. Our example plans to complete the following functions:
Listen on FTP port 21 (listen)
Accept connection requests from FTP clients (accept)
Accept the login of FTP clients, but do not verify the login information
Accept the quit command sent by FTP clients, and close the connection (close)
The entire program only accepts the request of one FTP client. When it has provided service for one FTP client, it will ignore new connection requests.
Okay, now we can start. The following is the source program of our example. For the convenience of explanation, we add line numbers in front.
01 #include <stdio.h>
02 #include <string.h>
03 #include <sys/socket.h>
04 int FtpServer(int s);
05 int main (void) {
06 struct sockaddr_in my_addr; // my address information
07 struct sockaddr_in their_addr; // connector's address information
08 int sockfd, new_fd; // listen on sockfd, new connection on new_fd
09 int sin_size;
10 int Loop;
11 char tempStr;
12 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
13 printf("Socket Error!\n");
14 return 1;
15 }
16 my_addr.sin_family = AF_INET; // host byte order
17 my_addr.sin_port = htons(21); // short, network byte order
18 my_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
19 memset(&my_addr.sin_zero, 0, 8); // zero the rest of the struct
20 if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
21 printf("Bind Error!\n");
22 return 1;
23 }
24 if (listen(sockfd, 5) == -1) {
25 printf("Listen Error!\n");
26 return 1;
27 }
28 new_fd = -1;
29 sin_size = sizeof(struct sockaddr_in);
30 new_fd = accept(sockfd, (struct sockaddr*)&their_addr, &sin_size);
31 if (new_fd == -1) {
32 printf("Accept Error!\n");
33 return 1;
34 }
35 printf ("Got connection from %s\n", inet_ntoa(their_addr.sin_addr));
36 strcpy(tempStr, "220 FTP Server, service ready.\r\n");
37 send(new_fd, tempStr, strlen(tempStr), 0);
38 Loop = 1;
39 while (Loop) {
40 Loop = FtpServer(new_fd);
41 }
42 sleep(5);
43 close(new_fd);
44 close(sockfd);
45 return 0;
46 }
47 int FtpServer(int s) {
48 char szBuf;
49 char tempStr;
50 int iBytes;
51 char *p, *p2;
52 iBytes = recv(s, szBuf, 30, 0);
53 if (iBytes >= 2) {
54 iBytes -= 2;
55 szBuf = NULL;
56 } else {
57 return 0;
58 }
59 p = szBuf;
60 while (*p != ' ' && *p != NULL) {
61 p++;
62 }
63 if (p) {
64 *p = NULL;
65 p2 = p + 1; // p2 point to the second parameter
66 }
67 if (stricmp("user", szBuf) == 0) { // start to process FTP commands
68 sprintf(tempStr, "331 Password required for %s.\r\n", p2);
69 send(s, tempStr, strlen(tempStr), 0);
70 printf("Received 'user' command. User is %s\n", p2);
71 } else if(stricmp("pass", szBuf) == 0) {
72 strcpy(tempStr, "230 Logged in okay.\r\n");
73 send(s, tempStr, strlen(tempStr), 0);
74 printf("Received 'pass' command. Password is %s\n", p2);
75 } else if (stricmp("quit", szBuf) == 0) {
76 strcpy(tempStr, "221 Bye!\r\n");
77 send(s, tempStr, strlen(tempStr), 0);
78 printf("Received 'quit' command!\n");
79 return 0;
80 } else {
81 strcpy(tempStr, "500 Command not understood.\r\n");
82 send(s, tempStr, strlen(tempStr), 0);
83 printf("Received a unknown command: %s\n", szBuf);
84 }
85 return 1;
86 }
The entire program is very short, only 86 lines. To highlight the main line, most of the error handling is removed in the program, so the entire program is only a general framework, but it can illustrate the problem.
If you have FTP client software at hand (such as CUTEFTP, LEAFFTP, etc.), you might as well try to connect to any FTP server, and you can simply observe the communication process of FTP. The port number of FTP is 21. The communication process is roughly as follows (only the processes related to the example):
The client software first requests a connection to port 21 of the server.
After the server accepts the connection, it sends a string starting with "220 " to the client. This program sends "220 FTP Server, service ready."
After the client receives the information of "220 ", it logs in and sends the command "user xxxxxx", where xxxxxx is the user name.
After the server checks that the user name is legal, it requests the client to enter the password and sends a string starting with "331 ". This program sends "331 Password required for xxxxxx", where xxxxxx is the received user name.
After the client receives the information of "331 ", it sends the password to the server and sends the command "pass xxxxxx", where xxxxxx is the password.
After the server checks that the password is correct, it sends a string starting with "230 " to the client, indicating that the login is successful and other commands can be received. This program sends "230 Logged in okay."
After that, a lot of interaction is required between the client and the server for file transfer, directory, etc.
When ending the service, the client sends the "quit" command to the server, and both parties disconnect.
First, we need to understand two data structures, struct sockaddr and struct sockaddr_in.
struct sockaddr {
unsigned short sa_family; // address family
char sa_data; // 14 bytes of protocol address
}
This structure is used to manage the address information of the socket. Among them, sa_family is the category of the address, we can fill in "AF_INET", this constant has been defined in the header file of WATT-32. sa_data is 14 bytes of address information, which should contain address and port information. For the convenience of use, a structure equivalent to sockaddr is established, struct sockaddr_in
struct sockaddr_in {
short int sin_family; // address family
unsigned short int sin_port; // port number
struct in_addr sin_addr; // internet address
unsigned char sin_zero; // Same size as struct sockaddr
}
The sin_family of this structure is the same as sa_family in sockaddr, and we can fill in "AF_INET". sin_port is the port number, and the port number of FTP is 21. The structure of struct in_addr is as follows:
struct in_addr {
unsigned long s_addr;
}
It is a 32-bit IP address. To convert a conventional IP address into a 32-bit IP address, the following method needs to be used:
xx.sin_addr.s_addr = inet_addr("192.168.0.20");
Regarding the byte order problem, "Beej's Guide to Network Programming Using internet Sockets" also mentions this problem. One kind of byte order is called "Host Byte Order", and the other is called "Network Byte Order". Because the explanation of this problem is not very clear in this article, so I will say a few more words here. A number, such as the Short int type, occupies two bytes. Suppose this number is 0x6789, and it is stored in the memory address 0x1000. Then there are two representation methods. One is that 0x67 is placed at 0x1000 and 0x89 is placed at 0x1001. The other representation method is that 0x89 is placed at 0x1000 and 0x67 is placed at 0x1001. The first storage method is called big-endian, and the second storage method is called little-endian. In the machine with CPU x86, the little-endian order is used, and the network transmission protocol TCP/IP uses the big-endian. In our specific environment, Host Byte Order refers to the character order of our PC, that is, the little-endian order, and Network Byte Order refers to the network transmission order, that is, the big-endian. Due to the different byte orders used, 1 conversion is often required. For this reason, there is a set of conversion functions specially. The "h" in the function refers to Host Byte Order, "n" refers to Network Byte Order, "s" refers to short int, and "l" refers to long int. So the meaning of this set of functions is as follows:
htons()----"Host to Network Short"
htonl()----"Host to Network Long"
ntohs()----"Network to Host Short"
ntohl()----"Network to Host Long"
Therefore, in network programming, once there is an integer and other numerical operations, we must think about whether conversion is required. I hope my explanation can not only make you understand the reason, but also remember these conversion functions.
In our example, two such data structures are needed. One is used to manage our local network address, and the other is used to manage the network address of the remote node connected to us. These two structures are respectively named: my_addr and their_addr, see lines 06 and 07 of the source program.
In our example, we also need two sockets. One is used to represent the local network we are listening to, and the other is used to represent the network connection with the remote FTP client. We don't need to ask what a socket is, just simply understand it as something similar to a file handle. In fact, a socket is just an integer.
There are many types of sockets, but only two are commonly used. One is "Stream Sockets", and the other is "Datagram Sockets". The former is used for TCP connection, and the latter is used for UDP connection. It is enough to understand these for the time being.
At the beginning of our program, we first initialize a socket. The prototype of the socket function is as follows:
int socket(int domain, int type, int protocol);
In general cases, domain is filled with "AF_INET". type refers to the type of the socket. If it is Stream Sockets, fill in SOCK_STREAM. If it is Datagram Sockets, fill in SOCK_DGRAM. In this program, it should be SOCK_DGRAM, and protocol is set to 0. The return value of socket() is an available socket value. In line 12 of the program, we get a socket: sockfd.
Lines 16-19 describe the local network address structure my_addr. It should be noted that 21 in line 17 is the dedicated port of FTP. Since my_addr.sin_port is a short int type, we need to use htons() for conversion. In line 18, my_addr.sin_addr.s_addr is filled with the constant INADDR_ANY, which means using the IP address set in WATTCP.CFG of the local machine. It should be noted that the type of s_addr is long int, but here the htonl() function is not used for conversion. Strictly speaking, here the htonl() function is indeed needed for conversion. This should be particularly noted. If you want to fill in the IP address yourself, pay attention to using the inet_addr() function to convert a common IP address, as follows:
my_addr.sin_addr.s_addr = inet_addr("192.168.0.20");
To convert a 32 bits IP address into the common form we see, the function inet_ntoa() is used, as follows:
printf("IP address is %s", my_addr.sin_addr.s_addr);
The printed result is the common IP address form of xxx.xxx.xxx.xxx.
Line 19 just fills the rest of the structure with 0, which has no meaning.
In line 20, we bind the just obtained sockfd and the just filled structure my_addr together using bind(). The prototype of the bind() function is as follows:
int bind(int sockfd, struct sockaddr *my_addr, int addr_len);
There doesn't seem to be much to explain.
In line 24, we set to listen on the socket sockfd, with a maximum of 5 connections allowed. Actually, we only accept one connection. The prototype of listen() is as follows:
int listen(int sockfd, int backlog);
The parameter backlog can specify how many connection requests are allowed for this listening; there is no more to explain.
In line 30, we are waiting for a connection request. Note that the accept() function is a blocking function. The program will stop at this function and wait until there is a connection request before returning. In some occasions, this cannot be used like this. The prototype of the accept() function is as follows:
int accept(int sockfd, void *addr, int *addrlen);
Normally, the accept function returns a new socket descriptor, new_sock in this program. This new socket represents the connection with a remote node. In the future, when operating this connection, this socket will be used. At the same time, the accept function will fill the address information of the remote node into addr, which is their_addr in this program.
In line 37, we send the first message to the remote computer, using the send() function to send to new_sock. The prototype of the send() function is as follows:
int send(int sockfd, const void *msg, int len, int flags);
The last parameter of the function is generally set to 0.
After sending a message to the remote computer, the program enters a loop. In the loop, it continuously calls the function FtpServer(), and exits the loop until the function returns 0. In FtpServer, the program tries to receive information from new_sock, then analyzes and processes the information, and returns 0 until the "quit" command is received, so that the main program can exit the loop.
In line 52, the recv() function is used to receive information from new_sock. This function is also a blocking function, that is, if no information is received, this function will not return. This is not allowed when constructing a real-time system. Another problem is that if the network is interrupted for some reason after the program enters the recv() function, the program will not return from the recv() function, and the program will hang in the recv() function. Therefore, in actual applications, this function cannot be used like this. The prototype of the recv() function is as follows:
int recv(int sockfd, void *buf, int len, unsigned int flags);
Like the send() function, flags is filled with 0. len is the maximum length of the received information, which should be determined with reference to the length of buf, otherwise a boundary error will occur. Actually, when receiving, it does not return until len characters are received. This function will return the actual number of received characters.
In line 53, we limit that the number of received characters is at least 2, because all FTP transmission commands are followed by carriage return and line feed, that is, ASCII codes 0x0d and 0x0a. If both of these characters are not present, the received content is meaningless.
Lines 59-66 analyze the received content simply. Because the command format of ftp is: cmd para1 para2...., this segment of the program separates the cmd part of the command specially. After the execution of this segment of the program, szBuf points to cmd, and p2 points to the following parameters. Of course, this sample program does not need to analyze the parameters, so actually p2 is not useful to us.
Lines 67--80 handle three commands, and give legal returns or actions according to the protocol. For commands other than "user", "pass" and "quit", we handle them as unknown commands, and according to the protocol, return information like "500 ......".
The program is explained here. This program has no practicality because it lacks necessary parts such as error handling, but its framework is complete. After processing, it can be completely turned into a complete Ftp server program.
Finally, I need to say how to test. First, set up the network data, which has been explained earlier. Then connect two machines with a HUB. We cannot use general FTP software (such as CUTEFTP or LeafFTP) because the commands we handle are too few. These software will automatically send many instructions, and since our program responds with "500 ...", a normal FTP software will have an error message like "protocol error" and terminate. We cannot use software like telnet to test because this kind of software is a terminal emulation software. Each character entered will be sent immediately, and the keyboard input speed is extremely slow, which will cause our program to not receive a complete command at one time (recv() function), thus leading to operation failure. Please use the following method to test:
In Windows, click "Start" --> "Run", and enter: ftp 192.168.0.20 (if your IP address is different, please change it)
After pressing "OK", the following window appears. We see that the second line "220 FTP..." is sent by our program
We enter "abcd", of course, you can enter others, because our program does not verify. After pressing Enter, the following window appears, and the fourth line is returned by our program after receiving the user name
In the fifth line, enter a few alphanumeric characters arbitrarily, such as "1234", and press Enter. Since it is a password, the content you entered is not displayed on the screen. After pressing Enter, the following window is seen, and the content of the sixth line is returned by our program after receiving the pass command
Finally, we enter the quit command after ftp>, and after pressing Enter, the screen flashes and then closes, so we can't see the returned content clearly.
The entire process is also clearly shown on the FTP server side running our program.
Okay, we have finished this specific example. Probably, you should understand the method of network programming under DOS. Note that since we generated the program under DJGPP, it is in 32-bit protected mode, so it can only run on a machine with DPMI service. Of course, this kind of programming method is also applicable to real mode. Moreover, although the WATT-32 library is 32-bit, it actually supports 16-bit real mode, so it is also possible to use turbo C, etc. We will talk about the method of network programming under DOS in more detail in the future, or introduce the writing specification and method of Packet Driver, or introduce DPMI, etc.
For more articles on DOS programming, see my blog
Click to enter "DOS Programming Technology"
[ Last edited by whowin on 2008-5-9 at 11:50 AM ]

www.ecgui.com