![]() |
中国DOS联盟-- 联合DOS 推动DOS 发展DOS --联盟域名:www.cn-dos.net 论坛域名:www.cn-dos.net/forum |
| 游客 | 登录 | 注册 | 会员 | 搜索 | 中国DOS联盟 |
|
中国DOS联盟论坛 现在时间是 2026-09-15 00:10 |
共 47,812 主题排行 / 349,917 发帖 / 今日 0 篇 / 48,268 会员排行 |
| DOS开发编程 & 发展交流 (开发室) » [原创]在DOS下进行网络编程(下) |
| 可打印版本 3,541 / 8 |
| 第1楼 whowin | 发表于 2008-05-09 11:36 |
| 初级用户 发帖 37 积分 174 | |
|
[原创]在DOS下进行网络编程(下) 文章是从我的网志中贴过来的,其中的图片可能过不来,看完整内容,请访问我的网志:
点击进入《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 ] |
|
| 第2楼 wzy19806206 | 发表于 2008-05-09 16:21 |
| 初级用户 发帖 9 积分 20 | |
|
还不顶
|
|
| 第3楼 leejonny | 发表于 2008-09-22 15:58 |
| 新手上路 发帖 1 积分 2 | |
|
更多关于DOS编程的文章看我的网志
|
|
| 第4楼 acejoo | 发表于 2008-10-22 15:11 |
| 新手上路 发帖 4 积分 7 | |
|
好,不错,不知道有没有其他的,譬如音频传输等的。p2p的
|
|
| 第5楼 52book | 发表于 2009-04-09 00:46 |
| 新手上路 发帖 2 积分 4 | |
|
LZ高人,一直关注你的BOKE
|
|
| 第6楼 alphatruly | 发表于 2009-04-11 15:51 |
| 新手上路 发帖 3 积分 6 | |
|
哈哈!whowin来了,一直很关注你的文章啊!
简直就是神贴! |
|
| 第7楼 djxxt | 发表于 2009-11-29 19:48 |
| 新手上路 发帖 2 积分 4 | |
|
多谢,学习
|
|
| 第8楼 messen | 发表于 2010-05-05 19:04 |
| 新手上路 发帖 8 积分 17 | |
|
追着你学
|
|
| 第9楼 ecurb2006 | 发表于 2010-05-18 04:25 |
| 中级用户 发帖 123 积分 272 | |
|
好帖!
|
|
|
[ 联系联盟系统管理团队 -
中国DOS联盟 -
标准版 ] Sponsored by ifanr Inc | © 2001–2023 |