VC++对网络编程的支持有socket支持,WinInet支持,MAPI和ISAPI支持等。其中,Windows Sockets API是TCP/IP网络环境里,也是Internet上进行开发最为通用的API。最早美国加州大学Berkeley分校在UNIX下为TCP/IP协议开发了一个API,这个API就是著名的Berkeley Socket接口(套接字)。
在桌面操作系统进入Windows时代后,仍然继承了Socket方法。在TCP/IP网络通信环境下,Socket数据传输是一种特殊的I/O,它也相当于一种文件描述符,具有一个类似于打开文件的函数调用-socket()。
2.1一些基本的Socket系统调用 主要的系统调用包括:socket()-创建Socket;bind()-将创建的Socket与本地端口绑定;connect()与accept()- 建立Socket连接;listen()-服务器监听是否有连接请求;send()-数据的可控缓冲发送;recv()-可控缓冲接收;closesocket()-关闭Socket。
2.2Windows Socket的启动与终止 启动函数WSAStartup()建立与Windows Sockets DLL的连接,终止函数WSAClearup()终止使用该DLL,这两个函数必须成对使用。
2.4异步数据传输机制 WSAAsyncSelect()设定了Socket上的须响应通信事件后,每发生一个这样的事件就会产生一个WM_SOCKET消息传给窗口。而在窗口的回调函数中就应该添加相应的数据传输处理代码。
WinSock是Microsoft Windows Socket的简称,WinSock为软件开发人员提供一套Windows操作系统上的开放的、支持多种协议的网络编程接口,已成为在TCP/IP网络编程和Internet应用开发中通用的解决方案。 WinSock对一些重要的数据类型和结构做了如下定义。
1、套接字的定义
typedef unsigned int u_int;
typedef u_int Socket;
2、基本数据类型的定义
typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;
网络地址用无符号长整数unsigned long 表示。例如IP地址127.0.0.1可以表示为:
#define INADDR_LOOPBACK 0x7f000001
用inet_addr()函数可以将分16位IP地址转换为unsigned long 类型,其定义为:
unsigned long inet_addr(const char FAR* cp)
Cp为点分16位IP地址,如“192.1.8.84”。
4、套接字地址结构
sockaddr结构——通用Socket地址结构,其定义为:
struct sockaddr{u_short sa_family;char sa_da
其中sa_family为网络地址类型,随协议的不同而不同,一般为AF_INET,表示该socket在Internet域中通信。
sockaddr_in结构——专门针对Internet域的Socket地址结构,其定义为:
{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
其中sin_family必须设定为AF_INET;sin_port为服务器端口,如果端口设置为0,则系统会自动分配一个唯一的端口;sin_addr为一个unsigned long 的IP地址,若sin_addr为INADDR_ANY,则表示所有的IP地址;sin_zero为填充字段,用来保证结构的大小;
5、主机地址
{
char FAR* h_name; //主机名字
char FAR*FAR* h_aliases; //主机别名列表
short h_addrtype; //地址类型
short h_length; //地址长度
char FAR*FAR* h_addr_list; //IP地址
#define h_addr h_addr_list[0];
};
6、常见TCP/IP协议的定义
#define IPPROTO_IP 0
#define IPPROTO_ICMP 1
#define IPPROTO_IGMP 2
#define IPPROTO_TCP 6
#define IPPROTO_UDP 17
#define IPPROTO_RAW 255
以下介绍几个最基本的WinSock函数,用这些函数就可以完成从客户端到服务器端的套接字编程。其基本流程如下图所示:
基于(TCP/IP面向连接)的socket编程:
服务器端程序: 1.创建套接字(socket)。 2.将套接字绑定到一个本地地址和端口上(bind) 3.将套接字设为监听模式,准备接受客户请求(listen) 4.等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)。 5.用返回的套接字和客户端进行通信(send/recv)。 6.返回,等待另外客户请求。 7.关闭套接字。
客户端程序: 1.创建套接字(socket)。 2.向服务器发出连接请求(connect)。 3.和服务器端进行通信(send/recv)。 4.关闭套接字。
服务器端(接收端)程序: 1.创建套接字(socket)。 2.将套接字绑定到一个本地地址和端口上(bind)。 3.等待接受数据(recvfrom)。 4.关闭套接字。
客户端(发送端)程序: 1.创建套接字(socket)。 2.向服务器发送数据(sendto)。 3.关闭套接字。
1、socket()
功能:创建一个新的套接字。
原形:Socket PASCAL FAR socket(int af,int type,int protocol);
参数:af——通信发生的区域;
type——要建立的套接字类型;
procotol——使用的协议类型。
2、Bind()
功能:为一个新创建未绑定的Socket分配端口和local地址。对客户机/服务器构架的程序,服务器必须使用此函数为Socket分配端口号,客户机不用绑定,而是由系统自动分配。
原形:int PASCAL FAR Bind(Socket s,
const struct sockaddr FAR* name,int namelen);
参数:s——由socket()调用返回的并且未作连接的Socket识 别符。Name——WinSock地址结构,因协议类型而不同,对应TCP/IP协议的结构为 sockaddr_in。namelen——表示name的长度。
3、connect()和accept()
功能:用于在客户机/服务器构架的程序中建立套接字连接,客户端用connect()要求连接某一流式套接字到指定服务器,服务器用accept()完成套接字中服务器端Socket,接受客户端的连接请求。
const struct sockaddr FAR* name,int namelen);
Socket PASCAL FAR accept(Socket s,
struct sockaddr FAR* addr,int FAR *addrlen);
参数:name——服务器端Socket将要建立连接的客户端地址; namelen——表示name的长度。addr——要求与服务器端Socket建立连接的客户端地址;addrlen——addr的长度。
返回值:connect若成功返回0,否则返回SOCKET_ERROR;
accept返回的Socket是与客户端通信的Socket。
4、listen()
功能:设定Socket状态为监听,监听客户端的请求,准备被连接。
原形: int PASCAL FAR listen(Socket s,int backlog);
参数:backlog——未完成连接之前,客户端连接请求的最大数。
返回值:若成功则返回0,否则返回SOCKET_ERROR。
功能:在已建立连接的流式套接字之间收发数据, send()发送数据, recv()接收数据。
原形:int PASCAL FAR send(Socket s,
const char FAR* buf,int len,int flags);
int PASCAL FAR recv(Socket s,
const char FAR* buf,int len,int flags);
参数:s——发送或接受数据的套接字。
buf——发送或接受数据的缓冲区。
len——buf的长度;
flag——函数被调用的方式,其值为0,MSG_PEEK
或MSG_OOB.
6、 sendto()与recvfrom()
功能:若前图建立的不是流式套接字的话,就要用sendto()与recvfrom()代替send()与recv()来读写数据。功能与send()与recv()相同。
int flags,const struct sockaddr FAR* to,int tolen);
int recvfrom(Socket s, char FAR* buf,int len,
int flags, struct sockaddr FAR* from,int FAR* fromlen);
参数:to——指向目标套接字地址的指针(可选);
tolen——to的长度;
from——指向保存着返回的源地址的缓冲区的指针
(可选);
fromlen——指向from缓冲区的指针(可选)。
返回值:若成功返回发送或接受到的数据长度,否则返回
SOCKET_ERROR。
7、closesocket()
功能:关闭套接字;
原形: BOOL PASCAL FAR closesocket(Socket s);
参数:s——要关闭的套接字
返回值:若成功则返回0,否则返回SOCKET_ERROR。
WinSock对最早的Socket进行了一些扩展,主要是增加了符合Windows消息驱动特性的网络事件异步选择机制,扩充了一些异步函数。主要异步函数如下:
1、异步选择函数
功能:用来注册应用程序相关的网络事件,当事件发生时,应用程序窗口函数将接收到一个消息。
原形:int PASCAL FAR WSAAsyncSelect(
Socket s,
HWND hWnd, //窗口句柄
Unsigned int wMsg, //需要发送的消息
Long lEvent ); //事件类型
事件类型的值可以是:
FD_READ:当套接字收到数据时接到通知;
FD_WRITE:当套接字可发送数据时接到通知;
FD_OOB:当套接字有外来数据到达时接到通知;
FD_ACCEPT:当套接字有外来连接时接到通知;
FD_CLOSE:当套接字关闭时接到通知;
2、异步请求函数WSAAsyncGetXByY()
3、阻塞处理方法
当一个应用程序的套接字调用处于阻塞时,应能够使套接字放弃CPU让其他应用程序运行。WinSock的阻塞处理为:DLL初始化->循环操作。在循环中发送任何Windows消息,并检查此WinSock调用是否完成,必要时可以放弃CPU让其他应用程序执行。可以调用 WSACancelBlockingCall()函数取消此阻塞处理。
在WinSock中,默认的阻塞处理例程BlockingHook()能够获取并发送Windows消息。如果要对复杂的程序进行处理, WinSock中的WSASetBlockingHook()使得用户能够安装自己的阻塞处理例程;与该函数相对应的则是 WSAUnhookBlockingHook(),用于删除先前安装的任何阻塞处理例程,并重新安装默认的处理例程。应注意,设计自己的阻塞处理例程时,除了WSACancelBlockingCall()以外不能使用其他的WinSock API函数。调用WSACancelBlockingCall()函数将取消处于阻塞的操作,结束阻塞循环。
WinSock为了和多线程环境兼容,提供了WSAGetLastError()和WSASetLastError()两个出错处理函数来获取和设置当前线程的最近错误代码。
WSAGetLastError()函数的功能是返回故障类型,其定义为:int WSAGetLastError(void);
5、启动与终止
使用WSAStartup()和WSACleanup()启动和终止套接字。
WSAStartup()完成与Winsock.DLL的连接;
WSACleanup()结束对Winsock.DLL的调用。
WinSock包括开发组件和运行组件两大部分,开发组件包括WinSock实现文档、应用程序接口(API)引入库和一些头文件。运行组件包括 WinSock应用程序接口的动态连接库。在VC++6.0中使用WinSock编程时,需要将以下三个文件包含到项目中(以WinSock V2.0为例)
头文件: Winsock2.h
库文件:WS2_32.LIB
动态库: WS2_32.DLL
在TCP/IP网络中两个主机进行进程间异步通信时采用客户端/服务器模式。其中服务器工作过程如下:
1、服务器方先开启,打开一通信通道,公布接收客户请求的端口地址;
2、等待客户端发送请求到该端口;
3、接收到客户端服务请求,处理该请求并发送应答信号;
4、重复2、3步,处理多个客户请求;
5、关闭服务器。
1、打开一通信通道,连接到服务器所在主机的开放端口;
2、向服务器发送服务请求,等待应答信号;
3、收到应答信号后,关闭通信通道。
下面看一看WinSock网络应用程序的详细步骤。
1、启动与终止:
必须首先使用启动函数WSAStartup(),它指定WinSock API的版本,并获得Socket技术细节。调用方式为:
WORD wVersionRequested; //定义版本信息变量
WSADATA wsaData; //定义数据信息变量
int err; //定义错误号变量
wVersionRequested=MAKEWORD(1,1);
//给版本信息赋值
err= WSAStartup(wVersionRequested,&wsaData);
//获取错误信息
if(err!=0) return;
2、创建套接字:
Socket sock=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
//流式套接字
if(sock==INVALID_Socket) //错误处理
…..
3、套接字的绑定:
sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(0); //保证字节顺序
addr.sin_addr.s_addr=addr(“192.168.12.84”); //指定地址
int nResult=bind(s,(sockaddr*)&addr,sizeof(sockaddr));
4、服务器端套接字的监听:
int nResult=listen(s,5);
//s是已绑定但未连接的套接字,最多监听5个连接
sockaddr_in addr;
Socket s_d=accept(s,(sockaddr*)&addr,sizeof(sockaddr));
6、客户端将两个套接字连接起来准备通信:
sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(0); //保证字节顺序
addr.sin_addr.s_addr=htonl(INADDR_ANY);
int nResult=connect(s, (sockaddr*)&addr,sizeof(sockaddr));
7、服务器端向客户端套接字发送数据:
char buf[ ]=“einsun”; //指定缓冲区
int nResult=send(s,buf,strlen(buf));
8、客户端套接字接收数据:
char mess[1000];
int nResult=recv(s,mess,1000,0);
9、服务器端和客户端中断套接字连接,通知服务器端或客户端停止接收和发送数据:
int nResult=shutdown(s,SD_BOTH);
int nResult=closesocket(s);//s为要关闭的套接字
关闭套接字时,所有已经打开并连接的流式套接字将被复位,但那些已经由closesocket()函数关闭,但仍有未发送数据的套接字不受影响,仍然可发送未发完的数据。
11、出错处理
出错处理函数WSAGetLastError()的调用方式如下:
len=send(s,lpBuffer,len,0);
if((len==socket_ERROR)
&&(WSAGetLastError()==WSAWOULDBLOCK))
{
…….
}