前文有介绍,网络数据的传输是通过套接字来实现的,套接字共有3种类型:流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM)和原始套接字(RAW)。在本文中将蛀牙介绍流式套接字与数据包套接字、
一、流式套接字
流式套接字是面向连接的,提供双向,有序,无重复且无记录边界的数据流服务。
1、服务器端编程步骤:
(1)WSAStartup()
此函数在应用程序种初始化Windows Sockets DLL,只有此函数调用成功后,应用程序才可以再调用其他Windows Sockets DLL中的API函数。在程序中该函数调用形式如下:
int WSAStartup(
WORD wVersionRequested, // 所使用的WinSocket版本
LPWSADATA lpWSAData //存储系统返回的WinSockets信息
);
(2)、建立Socket
初始化WinSock的动态链接库后,需要在服务器端建立一个监听Socket,为此可以调用socket()函数来建立这个监听的Socket,并定义此Socket所使用的通信协议:
SOCKET socket (
int af, //目前只提供PF_INET(AF_INET)
int type, //Socket套接字的类型(流式套接字,数据报套接字)
int protocol //通讯协议,默认为0
);
调用成功返回Socket对象,失败则返回INVALID_SOCKET(调用WSAGetLastError()可得治原因,所以WinSocket函数都可以使用该函数获取失败的原因)。第二个参数type对应TCP/IP协议为SOCK_STREAM,对应UDP协议为SOCK_DGRAM
(3)、绑定端口
接下来要为服务器端定义的监听Socket指定一个地址及端口,这样客户端才知道将要连接哪一个地址的哪一个端口,为此调用bind()函数,该函数调用成功返回0,否则返回SOCKET_ERROR.
int bind (
SOCKET s, // Socket 对象名
const struct sockaddr FAR *name, //Socket的地址值,即所在机器的IP地址
int namelen //name的长度
);
如果使用者不在意地址或端口的值,那么可以设定地址为INADDR_ANY,及Port为0,Windows Sockets会自动将其设定为适当的地址及Port(1024-5000之间的值)。此后可以调用getsockname()函数来获知其被设定的值
(4).监听
当服务器端的Socket对象绑定完成之后,必须建立一个监听的队列来接收客户端的连接请求。listen()函数使服务器端的Socket进入监听状态,并设定可以建立的最大连接数。该函数调用成功返回0,否则返回SOCKET_ERROR.
int listen (
SOCKET s, //需要建立监听的Socket
int backlog //最大连接个数
);
服务器端的Socket调用完listen()后,如果此时客户端调用connect()函数提出连接申请,服务器端必须再调用acceot()函数,这样服务器和客户端才算正式完成通信程序的连接动作。为了知道什么时候客户端提出连接请求,从而服务器端的Socket在恰当的时候调用accept()函数完成连接的建立,我们就要使用WSAAsyncSelect()函数,让系统主动来通知我们又客户端提出连接请求了,该函数调用成功返回0,否则返回SOCKET_ERROR:
int WSAAsynSelect (
SOCKET s, //Socket对象
HWND hwnd, //接收消息的窗口句柄
unsigned int wMsg, //传给窗口的消息
long lEvent //被注册的网络事件
);
被注册的网络事件IEvent就是应用程序向窗口发送消息的网络事件,该值为下列值FD_READ、FD_WRITE、FD_OOB、FD_CONNECT、FD_CLOSE的组合,各个值具体含义如下。
FD_READ :希望在套接字s收到数据时收到消息
FD_WRITE : 希望在套接字s上可以发送数据时收到消息
FD_ACCEPT : 希望在套接字s上收到连接请求时收到消息
FD_CONNECT : 希望在套接字s上连接成功时收到消息
FD_CLOSE : 希望在套接字s上连接关闭时收到消息
FD_OOB : 希望在套接字s上收到OOB数据时收到消息
具体应用时,wMsg是在应用程序中定义的消息名称,而消息结构中的lParam则为以上各种网络事件名称。
(5)、服务器端接受客户端的连接请求
当Client提出连接请求时,Server端的hwnd窗口会受到Winsock Stack送来的我们自定义的一个消息,这时我们可以分析lPararm,然后调用相关的函数来处理事件。为了使服务器端接受客户端的连接请求,就要使用accept()函数,该函数新建一个Socket与客户端的Socket想通,原先舰艇的Socket继续进入监听状态,等待其他客户端的连接要求,该函数调用成功返回一个新产生的Socket对象,否则返回INVALID_SOCKET:
SOCKET accept (
SOCKET s, //Socket的识别码
strucet sockaddr FAR *addr. //存放连接的客户端地址
int FAR *addrlen //地址的长度
);
(6)、结束Socket连接
结束服务器和客户端的通信连接是很简单的,这一过程可以由服务器或客户机的任一端启动,只要调用closesocket()就可以了,而要关闭Serber端监听状态的Socket,同样也是利用次函数。另外,与程序启动时调用WSAStartup()函数相对于,需要调用WSACleanup()来通知Winsock Stack所占用的资源。这两个函数都是调用成功返回0,否则返回SOCKET_ERROR:
int closesocket (
SOCKET s, //Socket的识别码
);
(7)、最后调用WSACleanup()
int WSACleanup(void);
2、客户端编程步骤
(1) 建立客户端的Socket
客户端应用程序首先也是调用WSAStartup()函数来与Winsock的动态链接库建立关系,然后同样调用socket()来建立一个TCP/IP或者UDP Socket(相同协定的Sockets才能相遇,TCP对TCP,UDP对UDP)。与服务器端的Socket不同的是,客户端的Socket可以调用bind()函数。由自己来指定IP地址及port号码,但是也可以不调用bind(),而由Winsock来自动设定IP地址及port号码
(2)提出连接请求:
客户端的Socket使用connect()函数来提出与服务器端的Socket建立连接的申请,函数调用成功返回0,否则返回SOCKET_ERROR
int connect (
SOCKET s, //服务器端Socket的识别码
const struct sockaddr FAR *name, //Socket想要连接的对方地址
int namelen //地址长度
);
作为客户端的监控程序,其实现过程要比服务器简单许多。由于需要接收数据,因此在异步选择函数中需要设定待监测的网络事件为FD_CLOSE与FD_READ。在消息相应函数中可以通过对消息参数的低位字节进行判断而区分具体发生的是何种网络事件,并对其作出相应的反应。
3 、数据的传送
基于TCP/IP连接协议(流式套接字)的服务是设计客户机/服务器应用程序时的主流标准,但有些服务是可以通过无连接协议(数据报套接字)提供的一般情况下TCP Socket的数据发送和接受是调用send()及recv()这两个函数来达成的,而UDP Socket则是用sendto()及recvfrom()这两个函数。这两个函数调用成功返回发送或接受的资料的长度,否则返回SOCKET_ERROR.
int send (
SOCKET s, //Socket的识别码
const char FAR *buf, //存放要传送的资料的暂存区
int len, //buf的长度
int flags //此函数被调用的方式
);
对于Datagram Socket而言,若是Datagram的大小超过限制,则不会送出任何资料,对stream Socket而言,在Blocking模式下,若是传送系统内的存储空间不够存放这些要传送的资料,send()将会被block住,直到资料送完为止;如果该Socket被设定为Non-blocking模式,那么将视目前的output buffer空间有多少,就送出多少资料,并不会被block住。flags值可设为0或MSG_DONTROUTE及MSG_OOB的组合、
int recv (
SOCKET s, //Socket的识别码
char FAR *buf, //存放接收到资料的暂存区
int len, //buf的长度
int flags //此函数被调用的方式
);
二、数据报套接字编程
数据报套接字提供双向的通信,但是没有可靠/有序/不重复的保证,所以UDP传送数据可能会收到无次序、重复的信息,甚至信息在传输过程中出现遗漏,但是传输效率较高,在网络上仍然有很多应用。与流式套接字编程的主要区别在于,在传输过程中使用的是sendto()及recvfrom()这两个函数。
int sendto (
SOCKET s, //Socket的识别码
const char FAR *buf, //存放要传送资料的暂存区
int len, //buf的长度
const struct sockaddr FAR *to, //传送目标的地址
int tolen //传送目标的地址长度
);
recvfrom (
SOCKET s, //Socket的识别码
char FAR *buf, //存放要接收资料的暂存区
int len, //buf的长度
int flags, //此函数被调用的方式
struct sockaddr FAR *from, //发送方的地址
int FAR *fromlen //发送方地址长度
);