文章是从我的网志中贴过来的,其中的图片可能过不来,看完整内容,请访问我的网志:
点击进入《DOS编程技术》
在上一篇中,我们为在DOS下进行网络编程做了大量的准备工作,我们在DJGPP下安装了WATT-32库,同时,配置好了网络环境,下面我们用一个实例来说明在DOS下进行网络编程的方法。
上一篇中,我们编译了WATT-32库中的一个范例程序ftpsrv.c,这是一个FTP服务器的范例程序,下面我们也编一个FTP服务器的程序,但我们有两点不同,第一,我们主要使用BSD网络编程的标准函数,这是一个UNIX下进行网络编程的规范,WATT-32库中实现了绝大多数的BSD编程函数,在《在DOS下进行网络编程(上)》中介绍了一篇文章《Beej's Guide to Network Programming Using internet Sockets》,这篇文章中介绍的编程方法也是基于这个规范,有关在这个规范下的函数介绍可以从下面这个网址下载,也可以参考UNIX下网络编程的书籍。
http://blog.hengch.com/Manual/BSDsocket.pdf
下面继续我们的FTP服务器,要编写一个FTP服务器程序,首先要了解一下FTP协议,有关FTP协议的完整规范,可以在下面网址下载:
http://blog.hengch.com/specification/rtfc765-ftp.pdf
实际操作上并没有协议中那么复杂,况且我们的实例也并不想完成所有的协议,我们的实例计划完成下面的功能:
侦听FTP端口21(listen)
接受来自FTP客户端的连接请求(accept)
接受FTP客户端的登录,但并不对登录信息做验证
接受FTP客户端发来的退出(quit)命令,关闭连接(close)
整个程序只接受一个FTP客户端的请求,当已经为一个FTP客户端提供服务时,如果有新的连接请求将不予理睬。
好我们现在可以开始了,以下是我们这个例子的源程序,为了说明方便,我们在前面加了行号。
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 }
整个程序很短,只有86行,为了突出主线,程序中去掉了大部分的错误处理,所以整个程序只是一个大致的框架,但能够说明问题。
如果你手头有FTP客户端软件(比如CUTEFTP、LEAFFTP等),不妨试着连接一下任意一个FTP服务器,可以简单观察一下FTP的通讯过程,FTP的端口号是21,其通讯过程大致如下(仅与例子有关的过程):
客户端软件首先向服务器21端口请求连接
服务器接受连接后向客户端发送以“220 ”为开始的字符串,本程序发出“220 FTP Server, service ready.”
客户端收到“220 ”的信息后进行登录,发送“user xxxxxx”的命令,其中xxxxxx为用户名
服务器检验该用户名合法后,请求客户输入密码,发送“331 ”为开始的字符串,本程序发送“331 Password required for xxxxxx”,其中xxxxxx为收到的用户名
客户端收到“331 ”的信息后发送密码到服务器,发送“pass xxxxxx”命令,其中xxxxxx为密码
服务器在检验密码正确后,向客户端发送“230 ”开头的字符串,表示登录成功,可以接收其他命令,本程序发送“230 Logged in okay.”
之后客户端与服务器间为传送文件、目录等要做大量的交互
结束服务时,客户端向服务器发送“quit”命令,双方断开连接
首先我们来了解两个数据结构,struct sockaddr和struct sockaddr_in。
struct sockaddr {
unsigned short sa_family; // address family
char sa_data; // 14 bytes of protocol address
}
这个结构用来管理socket的地址信息,其中sa_family是地址的类别,我们填入“AF_INET”就可以了,该常数已经在WATT-32的头文件里定义好了,sa_data是14字节的地址信息,其中应该包含地址和端口信息。为了方便使用,建立了一个与sockaddr等同的结构,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
}
该结构的sin_family与sockaddr中的sa_family是相同的,填“AF_INET”就可以了,sin_port是端口号,FTP的端口号是21;struct in_addr的结构如下:
struct in_addr {
unsigned long s_addr;
}
是一个32位的IP地址,要把一个常规的IP地址转换成一个32位的IP地址,需要用到下面的方法:
xx.sin_addr.s_addr = inet_addr("192.168.0.20");
关于字节顺序问题,在《Beej's Guide to Network Programming Using internet Sockets》中也提到这个问题,其中一种字节顺序叫做“Host Byte Order”,另一种叫做“Network Byte Order”,因为该文中,对这个问题说得并不是很清楚,所以在这里多说几句,一个数字,比如Short int类型,占两个字节,假定这个数是0x6789,存放在内存地址为0x1000的位置,则有两种表示方法,一种是0x1000处放0x67,0x1001处放0x89;另一种表示方法是0x1000处放0x89,0x1001处放0x67,第一种存放方式叫big-endian,第二种存放方式叫little-endian,在CPU为x86的机器中,使用的是little-endian的顺序,而网络传输协议TCP/IP采用的是big-endian,在我们这个特定的环境中,Host Byte Order指的就是我们PC机的字符顺序,也就是little-endian顺序,而Network Byte Order则指的是网络传输顺序,即big-endian,由于采用的字节顺序不同,所以要经常进行1转换,为此专门有一组转换函数,函数中的“h”指Host Byte Order,“n”指Network Byte Order,“s”指short int,“l”指long int,所以,这组函数的意义如下:
htons()----"Host to Network Short"
htonl()----"Host to Network Long"
ntohs()----"Network to Host Short"
ntohl()----"Network to Host Long"
所以,在网络编程中,一旦遇到整数等数值操作时,一定要想一想是否需要进行转换。我希望我的解释不仅能让你明白其中的道理,同时记住这几个转换函数。
在我们这个例子中,需要两个这样的数据结构,一个用来管理我们本地的网络地址,一个用来管理与我们连接的远端节点的网络地址,这两个结构,我们分别命名为:my_addr和their_addr,见源程序第06和07行。
在我们这个例子中,我们还需要两个socket,一个用来表示是我们本地正在侦听的网络,一个用来表示与远端FTP客户端的网络连接,我们不必追问什么是socket,仅仅把它理解成一个类似文件handle的东西就可以了,实际上socket就是一个整数而已。
socket有很多种,但常用的只有两种,一种是“Stream Sockets”,另一种是“Datagram Sockets”,前一种用于TCP连接,后一种用于UDP连接,了解这些暂时就足够了。
我们程序的一开始,首先初始化一个socket,socket函数的原型如下:
int socket(int domain, int type, int protocol);
domain一般情况下均填“AF_INET”,type指的就是socket的类型,如果是Stream Sockets,请填SOCK_STREAM,如果是Datagram Sockets则填SOCK_DGRAM,本程序中应该为SOCK_DGRAM,protocol置为0即可。socket()的返回值为一个可用的socket值,程序的第12行,我们得到了一个socket:sockfd。
第16-19行,我们描述了本地的网络地址结构my_addr,要说明的是,第17行中的21是FTP的专用端口,由于my_addr.sin_port是一个short int类型,所以要使用htons()进行一下转换,第18行把my_addr.sin_addr.s_addr填入常数INADDR_ANY其含义是使用本机在WATTCP.CFG中设置的IP地址,要注意的是s_addr的类型是long int,但这里却没有使用htonl()函数进行转换,这是因为我们知道INADDR_ANY的值是0,严格意义上说,这里的确需要使用htonl()函数进行转换,这点要特别注意,如果要自己填写IP地址,注意要使用inet_addr()函数来转换一个普通的IP地址,如下:
my_addr.sin_addr.s_addr = inet_addr("192.168.0.20");
把一个32 bits的IP地址转换成我们常见的形式要使用函数inet_ntoa(),如下:
printf("IP address is %s", my_addr.sin_addr.s_addr);
打印出来的是xxx.xxx.xxx.xxx的我们常见的IP地址形式。
第19行仅仅是把结构的其余部分填上了0,没有任何含义。
第20行我们把刚得到的sockfd和刚填好的结构my_addr使用bind()绑定在一起,bind()函数的原型如下:
int bind(int sockfd, struct sockaddr *my_addr, int addr_len);
好像没有什么好解释的。
第24行设定在sockfd这个socket上侦听,最大允许5个连接,实际我们只接受一个连接。listen()的原型如下:
int listen(int sockfd, int backlog);
参数backlog可以指定该侦听允许多少个连接请求;没有更多需要解释的。
第30行在等待一个连接请求,注意,accept()这个函数是一个阻塞函数,程序将停在这个函数里,一直等到有连接请求时才能返回,在某些场合是不能这样用的,accept()函数的原型如下:
int accept(int sockfd, void *addr, int *addrlen);
正常情况下,accept函数返回一个新的socket描述符,本程序中的new_sock,这个新的socket表示和一个远端节点的连接,以后当要操作这个连接时都会使用这个socket,同时,accept函数会把远端节点的地址信息填写到addr中,在本程序中是their_addr。
第37行,我们向远端计算机发出了第一条信息,使用send()函数向new_sock上发送,send()函数的原型如下:
int send(int sockfd, const void *msg, int len, int flags);
函数的最后一个参数,一般情况下置为0即可。
在向远端计算机发出一条信息后,程序进入一个循环,循环中不断地调用函数FtpServer(),直到该函数返回0才退出循环,FtpServer中,程序试图从new_sock上接收信息,然后分析处理信息,直到收到“quit”命令后返回0,使主程序可以退出循环。
第52行使用recv()函数接收来自new_sock的信息,这个函数也是一个阻塞函数,也就是说,如果没有收到信息,这个函数是不会返回的,这在构造一个实时系时时不能允许的,另外一个问题就是当程序进入recv()函数后网络由于某种原因中断,程序是不会从recv()函数中返回的,程序将吊死在recv()函数内,所以,实际应用中是不能这样使用这个函数的;recv()函数的原型如下:
int recv(int sockfd, void *buf, int len, unsigned int flags);
和send()函数一样,flags填0就好了,len是接收信息的最大长度,这要参考buf的长度来确定,否则会出现越界的错误,实际接收时并不是要接收到len个字符才返回,这个函数将返回实际接收到的字符数。
第53行我们限定收到的字符数至少要2个,这是因为所有FTP传送的命令后面都带有回车换行,也就是ascii码0x0d和0x0a,如果两这两个字符都没有,那收到的内容是没有意义的。
第59-66行我们对收到的内容作了一个简单的分析,因为ftp的命令格式是:cmd para1 para2....,这段程序我们把命令部分的cmd专门分了出来,这段程序执行完毕后,szBuf指向cmd,而p2指向后面的参数,当然我们这个范例程序并不需要分析参数,所以实际上p2对我们并没有什么用。
第67--80行我们处理了三个命令,并且按照协议给出了合法的返回或者动作,对于“user”、“pass”和“quit”以外的命令,我们都按照未知命令处理,并按照协议,返回了“500 ......”这样的信息。
程序到此就解释完了,这个程序由于缺少错误处理等必要的部分,实际没有什么实用性,但其架构是完整的,经过加工,完全可以变成一个完整的Ftp服务器端程序。
最后还要说一下怎么测试,首先设置好网络数据,这在前面有说明,然后用HUB将两台机器连接起来,我们不能用一般的FTP软件(比如CUTEFTP或者LeafFTP),因为我们处理的命令是在太少了,这些软件会自动地发送许多指令,由于我们的程序均回应“500 ...”,将导致一个正常的FTP软件出现“协议错误”之类的错误信息并终止运行,我们也不能使用telnet这样的软件来进行测试,因为这种软件是仿终端的软件,每输入一个字符将立即发送出去,而键盘输入的速度极慢,将导致我们的程序一次无法收到一个完整的命令(recv()函数),从而导致运行失败,请用下面方法测试:
在windows下点“开始”-->“运行”,输入:ftp 192.168.0.20(如果你的IP地址不一样,请更改)
按下“确定”后,出现下面窗口,我们看到第二行的“220 FTP...”就是我们的程序发过来的
我们输入“abcd”,当然输入其它的也可以,因为我们的程序并不检验,按回车后出现下面的窗口,其中第4行是我们的程序在收到用户名后返回的
在第5行任意输入几个字母数字,比如“1234”,按回车,由于是密码,屏幕并不显示你输入的内容,回车后看到如下窗口,其中,第6行的内容是我们程序在收到pass命令后返回的
最后,我们在ftp>的后面输入退出命令:quit,按回车后屏幕闪一下就关闭了,所以我们看不清返回的内容
整个过程在运行我们程序的FTP服务器端也表现得很清楚。
好了,这个具体的例子我们说完了,大概在DOS下进行网络编程的方法你应该了解了,要注意,由于我们是在DJGPP下生成的程序,是32位保护模式的,所以要在有DPMI服务的机器上才能运行,当然这种变成方式也适用于实模式,而且,尽管WATT-32库是32位的,但实际也支持16位的实模式,所以使用turbo C等也是可以的,我们以后有机会会更进一步地谈在DOS下进行网络编程的方法,或者介绍Packet Driver的编写规范和方法,或者介绍一下DPMI等等。
更多关于DOS编程的文章看我的网志
点击进入《DOS编程技术》
Last edited by whowin on 2008-5-9 at 11:50 AM ]
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 ]