DOS下的网络编程
第一章 安装网卡的DOS驱动程序
网卡的DOS驱动程序一般由硬件的生产厂商提供,微软的DOS网络安装包<Microsoft Network Client Version3.zip>在本目录下,里面也包含了很多早期的网卡驱动。在本目录有一个WAFER-C400小主板上网卡的驱动程序<网卡RTL8139驱动(DOS).rar>,非常通用,我们大部分计算机都可以使用它。
<Microsoft Network Client Version3.zip>解压后,把里面的文件都拷贝到DOS系统盘c:\netsetup目录下,把<网卡RTL8139驱动(DOS).rar>解压后拷贝到c:\netsetup\driver目录下。
运行c:\netsetup\setup.exe,当然,是在MS-DOS系统中运行,按照步骤操作。
在下面的列表中选择自己的网卡,因为这些都是早期的型号,我们使用的网卡在这里一般是不会找到的,所以选择“Network adapter not shown on list below ...”。
然后输入网卡驱动所在的目录“c:\netsetup\driver”,程序自己就会找到目录下的驱动,回车进行下一步,随意输入个用户名,例如“yangzhpeng”。到这一步要对自己的网络细致地设置了,不能贸然进行下一步。
其实我们要设置的项也不多。修改用户名,机器名,工作组和域不用管。
修改网络配置,把NWLink协议去掉,添加上TCP/IP协议。并且修改TCP/IP协议的设置。
把Disable Automatic Configuration设置成1(很重要),修改IP地址和掩码。
现在就可以放心地安装了。安装之后重启计算机。
第二章 检查DOS下的网络配置
在上一章安装完成之后,有三个文件对我们很重要。第一个是c:\config.sys文件,有句话“device=C:\NET\ifshlp.sys”,是安装程序给写上的。
第二个文件是c:\autoexec.bat,安装程序在文件的最后添加了这些话:
C:\NET\netbind.com
C:\NET\umb.com
C:\NET\tcptsr.exe
C:\NET\tinyrfc.exe
C:\NET\nmtsr.exe
C:\NET\emsbfr.exe
C:\NET\net start
我们把修改成:
C:\NET\net initialize
C:\NET\netbind.com
C:\NET\umb.com
C:\NET\tcptsr.exe
C:\NET\tinyrfc.exe
C:\NET\nmtsr.exe
C:\NET\emsbfr.exe
C:\NET\sockets.exe
网络驱动安装完毕,下一步要编写程序。
第三章 编程环境
需要4步:(1)假定使用Borland C++ 4.5,假定安装在c:\bc45。
本目录下面有一个Microsoft TCPIP Sockets Development Kit Version 1.0的压缩包<MSTCPSDK.rar>。解压后,把MSTCPSDK\INCLUDE\中的全部文件拷贝到c:\bc45\INCLUDE\中。
(2)如果编写的是C++程序,还要修改\INCLUDE\SOCKDEFS.H文件,把全部内容用extern "C" {……}圈起来。
(3)程序中应该包括下面的头文件:
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
(4)还应该把MSTCPSDK\LIB\DOS_SOCK.LIB文件加入到程序工程中。
如果觉得这样做比较麻烦,那就使用我整理的文件,在本目录中的dos_sock.rar。只有两个文件dos_sock.h和dos_sock.lib,把这两个文件加入工程中,然后#include “dos_sock.h”就可以了。
第四章 SOCKET概述
1 TCP/IP网络模型
TCP/IP是Transmission Control Protocol / Internet Protocol(传输控制协议/网际协议)的缩写。它最初是在20世纪70年代由美国国防部出资为ARPA(美国高级研究项目局)开发的。经过多年的演变,以TCP/IP协议为基础构建的ARPA网逐渐成了今天的Internet。
TCP/IP协议的核心协议包括TCP,UDP和IP协议, 运行于传输层和Internet层上,其中TCP和UDP协议是以IP协议为基础而封装的,这两种协议提供了不同方式的数据通信服务。
如果说IP协议是道路,那么下一层网络访问层的各种协议就相当于不同的铺路材料,而上一层的TCP和UCP协议就相当于路上跑的不同类型的车辆;再上层应用层的各种协议就车上丰富多彩的货物,它们都是以TCP和UDP协议为载体完成的。比如,HTTP协议适使用TCP协议传输网页,POP3协议使用TCP协议传输邮件,而DNS协议使用UDP协议来传输域名和IP地址的翻译信息。
2 IP地址和端口
在使用TCP和UDP协议通信时,必须同时指定IP地址和端口号才能完整地标识一个通信地址,在编程中通常将这两个参数一起定义在一个sockaddr_in结构中来使用。
struct sockaddr_in
{
short sin_family; //地址格式
unsigned short sin_port; //端口号(使用网络字节顺序)
struct in_addr sin_addr; //IP地址(使用网络字节顺序)
char sin_zero[8]; //空字节
};
结构中的sin_family字段用来指定地址格式,在不同的操作系统下,取值可以指定为AF_UNSPEC,AF_UNIX或AF_OSI等不同的值,但是在这里我们只能使用AF_INET。
sin_addr字段是个in_addr结构,这个结构实际上就是4个字节。
3 网络字节顺序
不同的处理器对字节顺序的处理方式不同,有的是高位在前,有的是低位在前。TCP/IP协议统一规定使用高位在前的方式传输数据,很遗憾,这与Intel80x86系列处理器使用的方式不同,所以在80x86平台下的socket编程中,当需要在协议中使用参数时,必须首先将它们转换为Internet顺序。
在填写sockaddr_in结构的sin_port字段和sin_addr字段时,必须首先进行转换。下面就是一些字节转换函数:
16位:
unsigned shorthtons(unsigned short); //主机顺序-->网络顺序
unsigned shortntohs(unsigned short); //网络顺序-->主机顺序
32位:
unsigned long htonl(unsigned long); //主机顺序-->网络顺序
unsigned long ntohl(unsigned long); //网络顺序-->主机顺序
4 IP地址转换函数
unsigned long inet_addr(char far *);
将“aa.bb.cc.dd”类型的十进制字符串转换成32位的IP地址。如果失败,返回INADDR_NONE。
char far * inet_ntoa(struct in_addr);
将网络字节顺序的32位IP地址转换成字符串。返回一个指针,指向转换后的IP地址字符串,这个字符串位于socket接口的内部缓冲区,所以,在调用inet_ntoa后必须马上把字符串拷贝到自己定义的缓冲区中。
5 套接字
两个主机之间进行网络传输,首先必须建立一个用来通信的对象,这个对象就称为套接字(socket),套接字的定义是“通信的一端”,在通信的另一端必定有另一个套接字与之相对应,以便互相传递数据。仅从编程的角度来看,套接字就是一个整数标识符而已,但使用socket这个称呼好像更贴切。
套接字的种类有很多种,最主要的是流套接字(stream socket)和数据报套接字(datagram socket)。由于流套接字使用传输层的TCP协议进行通信,所以它具有TCP协议所拥有的各种特征,比如:它是面向连接的、稳定的,以及数据包是按顺序发送的;而数据包套接字使用UDP协议进行通信,所以数据包可能丢失,可能重复,以及可能不按顺序到达等。一般将这两种套接字更直观地称为TCP套接字和UDP套接字。
还有一种原始套接字(raw socket),可以直接在Internet层上处理IP数据包首部,所以可以用它是个各种特殊的功能,如伪造发送者地址等。
由于我们在工作实际应用中使用的是TCP协议,所以在本篇也只讨论TCP套接字。对应于dos_sock.lib文件,我们把MS-DOS编程中使用的socket叫做dos_sock。
6 套接字的创建和关闭
创建套接字使用socket函数:
int socket(int af, int type, int protocol);
函数使用的第一个参数af用来指定套接字使用的地址格式,和sockaddr_in结构的sin_family字段的定义是一样的,唯一可以使用的值是AF_INET。
第二个参数type用来指定套接字的类型。正如前面介绍的,套接字有流套接字(SOCK_STREAM)、数据报套接字(SOCK_DGRAM)和原始套接字(SOCK_RAW)等。
protocol参数配合type参数的使用,用来指定使用的协议类型,当type参数指定为SOCK_STREAM或者SOCK_DGRAM的时候,系统已经明确使用TCP和UDP协议来工作,所以,这时这个参数并没有什么意义,可以指定为0。但是当type参数指定为SOCK_RAW类型的时候,使用protocol参数可以更详细地指定原始套接字的工作方式。
当套接字被成功创建的时候,函数将返回一个套接字句柄,否则函数将返回INVALID_SOCKET(定义为-1或者~0)。
当不需要使用套接字的时候,使用closesocket函数将它关闭:
int closesocket(int s); s就是创建套接字时返回的套接字句柄。
7 套接字的工作模式
socket的使用分为两种模式:阻塞模式和非阻塞模式。阻塞模式也称为同步模式,socket函数会等待操作完成之后才返回。比如,使用recv函数接收数据时,如果函数被调用时没有数据可收,那么函数不会返回,直到收到一些数据为止。再比如使用send函数发送n字节的数据时,在全部n字节发送完之前,函数不会返回,所以这时,程序处在等待状态。
非阻塞模式又称为异步模式,同样是前面的情况,recv和send函数执行后会立即返回,等到数据数据到达或者链路空闲可以继续发送数据时,socket接口会通过某种形式通知应用程序。
Windows中使用的Socket(WinSock2)默认是阻塞模式,不知道dos_sock的默认状态是阻塞还是非阻塞,没有找到相关资料。设置阻塞模式的函数是:
int ioctl(int s, int cmd, char far *pArgument); s是套接字句柄,cmd是要执行的操作,pArgument是cmd要设置的参数。函数执行成功返回0,出错返回-1。
设置dos_sock为非阻塞模式:
DWORD value = 1; //非0非阻塞
int result = ioctl(s, FIONBIO, (char far *)(&value));
设置dos_sock为阻塞模式:
DWORD value = 0; //是0是阻塞
int result = ioctl(s, FIONBIO, (char far *)(&value));
8 使用setsockopt设置套接字的属性
函数的原形是:
int setsockopt(
int s, //套接字的句柄;
int level, //属性的分类;
int optname, //要设置的属性;
char far *optval, //要设置的属性值;
int optlen //属性值参数的长度。
);
比如,允许套接字绑定到已经使用的端口:
DWORD value = 1;
int result = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char far *)(&value), s