- 博客(0)
- 资源 (7)
- 收藏
- 关注
PHP小舍微信拼团独立商城系统v6.1多商户版
PHP小舍微信拼团独立商城系统v6.1多商户版,非常不错的独立微信拼团商城,可以团购也可以直接购买,并且带有微信分销分佣功能!但是此程序只能适用微信端的团购活动,喜欢简单的朋友可以试试,如果又想用在微信和电脑端的,建议选择其余综合型的商城系统,此程序只适用于微信端!
2019-04-29
DOS编程工具包
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), sizeof(value));
更详细的内容请参考本目录MSTCPSDK.rar中的winsock.txt。或者查看MSDN,内容大致相同。
9 错误代码errno
errno是Borland C++编译器定义的一个整数型全局变量,很多函数把执行时发生的错误写到errno变量中,也会把执行中执行后的状态写到errno变量中,经常遇到的有以下这些:
#define EISCONN 118 //socket已经连接上
#define ENOTCONN 119 //socket没有连接上
#define EINPROGRESS 126 //函数正在执行中
第五章 TCP编程模型
和UDP协议有所区别,TCP协议的特点在于:
TCP采用超时重传及机制来保证不丢失数据,当一个TCP发送一个数据包后,它启动一个定时器,等待对端确认收到这个包,如果在指定的时间内没有得到确认,将重发这个包。而接收数据包的时候,它将发送一个确认,如果检测到数据包有错,TCP协议丢弃这个数据包,并且不发送确认,那么对端会因为超时而重新发送这个数据包。
一组有序的数据包,到达对端时可能会有后发的数据先到的情况,TCP协议在包首部保存数据包序号,如果有必要,它将对收到的数据重新排序,并以正确的顺序交给应用层。
1 客户端和服务器端的工作模型
TCP的工作方式用上图的客户端/服务器端模型来描述,通信的发起方称为客户端(Client),通信的等待方称为服务器端(Server)。客户端可以随时使用connect函数连接到服务器端,服务器检测到这个连接后,需要使用accept函数接受这个连接,当物务器接受连接后,一个稳定的连接就建立了,双方可以开始互相通过send和recv函数收发数据了,这时通信的两端并没有任何区别。
DOS系统通常用作客户端,服务端通常用Window系统,所以本文主要写DOS操作系统下的TCP客户端编程。
2 连接到服务端
使用connect函数:
int connect(int s, struct sockaddr far *name, int namelen); s是套接字的句柄;name是服务端的地址;namelen是name数据结构的长度;函数执行成功返回0,不成功返回SOCKET_ERROR(-1),然后使用errno变量得到具体的出错原因。
struct sockaddr和struct sockaddr_in的内容一样,长度一样,所以使用struct sockaddr_in定义变量name,在调用connect函数的时候强制转换成struct sockaddr。
当套接字工作在非阻塞模式下的时候,不管连接成功与否,connect函数会马上返回并返回SOCKET_ERROR(-1),这时并不意味着连接失败,而是表示函数返回的时候连接尚未成功。这时查询errno变量如果不等于EINPROGRESS(126,表示操作正在进行中),才表示连接失败。
非阻塞模式下的连接:
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(6000);
addr.sin_addr.s_addr = inet_addr(“10.0.0.1”);
connect(s, (struct sockaddr far *)(&addr), sizeof(addr));
delay(100);
if(errno != 118)
printf(“fail to connect”);
3 发送数据
使用send函数:
int send(int s, char far *pBuf, int len, int flags); s是套接字句柄;pBuf是数据缓冲区;len是要发送的数据长度;flags是发送选项,这个参数一般指定为0。如果发送失败,函数返回SOCKET_ERROR(-1),否则返回成功发送的字节数。
dos_sock为每个socket分配一个发送缓冲区和接受缓冲区,用send函数发送数据时,数据并没有马上在网络上进行传递,而是先放到socket的发送缓冲区中,数据会在合时的时候被发送出去。所以前面的“成功发送”指的是成功放入发送缓冲区而已。
函数在阻塞模式和非阻塞模式下的表现有些不同,下面已send函数发送n字节数据为例说明。
在阻塞模式下,如果发送缓冲区的空闲空间足够大,能容纳n字节的数据,这时函数会将数据全部放入发送缓冲区,然后马上返回;如果缓冲区不够大,函数会一边放入数据一边等待,直到把全部数据放入缓冲区为止。在这两种情况下,返回值都是实际发送的字节数n。这时程序比较简单:
int m = send(s, pBuf, n, 0); //m = n
在非阻塞模式下,如果发送缓冲区的空闲空间也能容纳n字节的数据,这时函数也会将数据全部放入发送缓冲区,然后马上返回,返回值就是实际发送的字节数n。当缓冲区不够大,函数也不会等待,而是把一部分数据放入缓冲区后马上返回,这时返回值是实际发送的字节数m。程序可以这样写。
while(n > 0)
{
m = send(s, pBuf, n, 0);
if(m == SOCKET_ERROR)
{
printf(“send error”);
break;
}
pBuf += m; //移动指针,指向剩余的数据
n -= m; //剩余的长度
delay(10);
}
4 接收数据
使用recv函数:
int recv(int s, char far *pBuf, int len, int flags); s是套接字句柄;pBuf是用来返回数据的缓冲区;len是要接受的数据长度;flags是接收选项,一般也指定为0。如果接收失败,返回SOCKET_ERROR(-1),否则返回实际接收的字节数。
在阻塞模式下,函数等待直到有数据到达为止(接收缓冲区不为空),有多少数据到达就返回多少数据。要接收n字节长度的数据,程序如下:
while(n > 0)
{
m = recv(s, pBuf, n, 0);
if(m == SOCKET_ERROR)
{
printf(“recv error”);
break;
}
pBuf += m; //移动指针,指向剩余的数据
n -= m; //剩余的长度
}
在非阻塞模式下,如果接收缓冲区中已经有数据,recv的表现方式和阻塞模式相同,函数会马上返回,并视缓冲区中的数据数量返回1到n之间的数据。如果接收缓冲区空,函数不会等待,而是马上返回SOCKET_ERROR(-1)。
5 TCP服务端的介绍
TCP服务端一般应用在Windows系统下,所以下面描述的函数都来自MSDN,并且可以在VC++6.0中使用。
服务端在创建了socket后需要绑定到本地的一个端口上,等待客户端连接到这个端口。使用bind函数:
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(6000); //本机上的一个端口
addr.sin_addr.s_addr = INADDR_ANY; //表示本机
bind(s, (struct sockaddr far *)(&addr), sizeof(addr));
在绑定之后使用listen函数使TCP套接字进入监听状态:
listen(s, 5); 能够可以同时发现5个客户端连接而不遗漏。然后套接字就处于等待连接进入的状态了。
当有客户端向监听中的套接字发起连接后,必须对监听中的套接字调用accept函数,连接才最后被确认。accept函数将新建一个套接字并返回它的句柄,这个新套接字还是和客户端连接的,程序以后可以使用它来和客户端之间收发数据了。
while(1)
{
SOCKET newSocket = accept(s, 0, 0);
if(newSocket != INVALID_SOCKET)
{
//创建一个新线程
AfxBeginThread(newThread, LPVOID(newSocket));
}
}
DWORD WINAPI newThread(LPVOID lParam)
{
SOCKET hSocket = (SOCKET)lParam;
// recv(hSocket, pBuf, len, 0);
// send(hSocket, pBuf, len, 0);
}
总结
文章详细介绍了在MS-DOS操作系统下开发网络客户端程序,也介绍了Windows中的服务端。具体代码请参考董琐英编写的过电压下位机程序。有了上面这些知识,然后仿照过电压程序将会很容易开发出自己的网络应用。
2016-01-19
dos开发工具包
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), sizeof(value));
更详细的内容请参考本目录MSTCPSDK.rar中的winsock.txt。或者查看MSDN,内容大致相同。
9 错误代码errno
errno是Borland C++编译器定义的一个整数型全局变量,很多函数把执行时发生的错误写到errno变量中,也会把执行中执行后的状态写到errno变量中,经常遇到的有以下这些:
#define EISCONN 118 //socket已经连接上
#define ENOTCONN 119 //socket没有连接上
#define EINPROGRESS 126 //函数正在执行中
第五章 TCP编程模型
和UDP协议有所区别,TCP协议的特点在于:
TCP采用超时重传及机制来保证不丢失数据,当一个TCP发送一个数据包后,它启动一个定时器,等待对端确认收到这个包,如果在指定的时间内没有得到确认,将重发这个包。而接收数据包的时候,它将发送一个确认,如果检测到数据包有错,TCP协议丢弃这个数据包,并且不发送确认,那么对端会因为超时而重新发送这个数据包。
一组有序的数据包,到达对端时可能会有后发的数据先到的情况,TCP协议在包首部保存数据包序号,如果有必要,它将对收到的数据重新排序,并以正确的顺序交给应用层。
1 客户端和服务器端的工作模型
TCP的工作方式用上图的客户端/服务器端模型来描述,通信的发起方称为客户端(Client),通信的等待方称为服务器端(Server)。客户端可以随时使用connect函数连接到服务器端,服务器检测到这个连接后,需要使用accept函数接受这个连接,当物务器接受连接后,一个稳定的连接就建立了,双方可以开始互相通过send和recv函数收发数据了,这时通信的两端并没有任何区别。
DOS系统通常用作客户端,服务端通常用Window系统,所以本文主要写DOS操作系统下的TCP客户端编程。
2 连接到服务端
使用connect函数:
int connect(int s, struct sockaddr far *name, int namelen); s是套接字的句柄;name是服务端的地址;namelen是name数据结构的长度;函数执行成功返回0,不成功返回SOCKET_ERROR(-1),然后使用errno变量得到具体的出错原因。
struct sockaddr和struct sockaddr_in的内容一样,长度一样,所以使用struct sockaddr_in定义变量name,在调用connect函数的时候强制转换成struct sockaddr。
当套接字工作在非阻塞模式下的时候,不管连接成功与否,connect函数会马上返回并返回SOCKET_ERROR(-1),这时并不意味着连接失败,而是表示函数返回的时候连接尚未成功。这时查询errno变量如果不等于EINPROGRESS(126,表示操作正在进行中),才表示连接失败。
非阻塞模式下的连接:
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(6000);
addr.sin_addr.s_addr = inet_addr(“10.0.0.1”);
connect(s, (struct sockaddr far *)(&addr), sizeof(addr));
delay(100);
if(errno != 118)
printf(“fail to connect”);
3 发送数据
使用send函数:
int send(int s, char far *pBuf, int len, int flags); s是套接字句柄;pBuf是数据缓冲区;len是要发送的数据长度;flags是发送选项,这个参数一般指定为0。如果发送失败,函数返回SOCKET_ERROR(-1),否则返回成功发送的字节数。
dos_sock为每个socket分配一个发送缓冲区和接受缓冲区,用send函数发送数据时,数据并没有马上在网络上进行传递,而是先放到socket的发送缓冲区中,数据会在合时的时候被发送出去。所以前面的“成功发送”指的是成功放入发送缓冲区而已。
函数在阻塞模式和非阻塞模式下的表现有些不同,下面已send函数发送n字节数据为例说明。
在阻塞模式下,如果发送缓冲区的空闲空间足够大,能容纳n字节的数据,这时函数会将数据全部放入发送缓冲区,然后马上返回;如果缓冲区不够大,函数会一边放入数据一边等待,直到把全部数据放入缓冲区为止。在这两种情况下,返回值都是实际发送的字节数n。这时程序比较简单:
int m = send(s, pBuf, n, 0); //m = n
在非阻塞模式下,如果发送缓冲区的空闲空间也能容纳n字节的数据,这时函数也会将数据全部放入发送缓冲区,然后马上返回,返回值就是实际发送的字节数n。当缓冲区不够大,函数也不会等待,而是把一部分数据放入缓冲区后马上返回,这时返回值是实际发送的字节数m。程序可以这样写。
while(n > 0)
{
m = send(s, pBuf, n, 0);
if(m == SOCKET_ERROR)
{
printf(“send error”);
break;
}
pBuf += m; //移动指针,指向剩余的数据
n -= m; //剩余的长度
delay(10);
}
4 接收数据
使用recv函数:
int recv(int s, char far *pBuf, int len, int flags); s是套接字句柄;pBuf是用来返回数据的缓冲区;len是要接受的数据长度;flags是接收选项,一般也指定为0。如果接收失败,返回SOCKET_ERROR(-1),否则返回实际接收的字节数。
在阻塞模式下,函数等待直到有数据到达为止(接收缓冲区不为空),有多少数据到达就返回多少数据。要接收n字节长度的数据,程序如下:
while(n > 0)
{
m = recv(s, pBuf, n, 0);
if(m == SOCKET_ERROR)
{
printf(“recv error”);
break;
}
pBuf += m; //移动指针,指向剩余的数据
n -= m; //剩余的长度
}
在非阻塞模式下,如果接收缓冲区中已经有数据,recv的表现方式和阻塞模式相同,函数会马上返回,并视缓冲区中的数据数量返回1到n之间的数据。如果接收缓冲区空,函数不会等待,而是马上返回SOCKET_ERROR(-1)。
5 TCP服务端的介绍
TCP服务端一般应用在Windows系统下,所以下面描述的函数都来自MSDN,并且可以在VC++6.0中使用。
服务端在创建了socket后需要绑定到本地的一个端口上,等待客户端连接到这个端口。使用bind函数:
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(6000); //本机上的一个端口
addr.sin_addr.s_addr = INADDR_ANY; //表示本机
bind(s, (struct sockaddr far *)(&addr), sizeof(addr));
在绑定之后使用listen函数使TCP套接字进入监听状态:
listen(s, 5); 能够可以同时发现5个客户端连接而不遗漏。然后套接字就处于等待连接进入的状态了。
当有客户端向监听中的套接字发起连接后,必须对监听中的套接字调用accept函数,连接才最后被确认。accept函数将新建一个套接字并返回它的句柄,这个新套接字还是和客户端连接的,程序以后可以使用它来和客户端之间收发数据了。
while(1)
{
SOCKET newSocket = accept(s, 0, 0);
if(newSocket != INVALID_SOCKET)
{
//创建一个新线程
AfxBeginThread(newThread, LPVOID(newSocket));
}
}
DWORD WINAPI newThread(LPVOID lParam)
{
SOCKET hSocket = (SOCKET)lParam;
// recv(hSocket, pBuf, len, 0);
// send(hSocket, pBuf, len, 0);
}
总结
文章详细介绍了在MS-DOS操作系统下开发网络客户端程序,也介绍了Windows中的服务端。具体代码请参考董琐英编写的过电压下位机程序。有了上面这些知识,然后仿照过电压程序将会很容易开发出自己的网络应用。
2016-01-19
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人