1、 两种I/O模式
* 阻塞模式: 执行I/O操作完成前会一直进行等待,不会将控制权交给程序。套接字默认为阻塞模式。可以通过多线程技术进行处理。
* 非阻塞模式:执行I/O操作时,Winsock函数会返回并交出控制权。这种模式使用起来比较复杂,因为函数在没有运行完成就进行返回,会不断地返回 WSAEWOULDBLOCK错误。但功能强大。
为了解决这个问题,提出了进行I/O操作的一些I/O模型,下面介绍最常见的三种:
◆、select模型:
通过调用select函数可以确定一个或多个套接字的状态,判断套接字上是否有数据,或者能否向一个套接字写入数据。
int select(
int nfds,
fd_set FAR *readfds,
fd_set FAR *writefds,
fd_set FAR *exceptfds,
const struct timeval FAR *timeout
);
nfds
[in] Ignored. The nfds parameter is included only for compatibility with Berkeley sockets.
readfds
[in, out] Optional pointer to a set of sockets to be checked for readability.
writefds
[in, out] Optional pointer to a set of sockets to be checked for writability
exceptfds
[in, out] Optional pointer to a set of sockets to be checked for errors.
timeout
[in] Maximum time for select to wait, provided in the form of a TIMEVAL structure. Set the timeout parameter to NULL for blocking operation.
readfds:
If listen has been called and a connection is pending, accept will succeed.
Data is available for reading (includes OOB data if SO_OOBINLINE is enabled).
Connection has been closed/reset/terminated.
writefds:
If processing a connect call (nonblocking), connection has succeeded.
Data can be sent.
exceptfds:
If processing a connect call (nonblocking), connection attempt failed.
OOB data is available for reading (only if SO_OOBINLINE is disabled).
The select function returns the total number of socket handles that are ready and contained in the fd_set structures,
zero if the time limit expired, or SOCKET_ERROR if an error occurred.
The fd_set structure is used by various Windows Sockets functions and service providers, such as the select function,
to place sockets into a "set" for various purposes, such as testing a given socket for readability using the readfds parameter of the select function.
typedef struct fd_set {
u_int fd_count; // how many are SET?
SOCKET fd_array[FD_SETSIZE]; // an array of SOCKETs
} fd_set;
Members:
fd_count
Number of sockets in the set.
fd_array
Array of sockets that are in the set.
The macros to manipulate and check fd_set contents are:
FD_CLR(s, *set)
Removes the descriptor s from set.
FD_ISSET(s, *set)
Nonzero if s is a member of the set. Otherwise, zero.
FD_SET(s, *set)
Adds descriptor s to set.
FD_ZERO(*set)
Initializes the set to the NULL set.
readfds、writefds、exceptfds三个变量至少有一个不为空,同时这个不为空的套接字组种至少有一个socket,道理很简单,否则要select干什么呢。
举例:测试一个套接字是否可读:
fd_set fdread;
FD_ZERO(&fdread);
FD_SET(s,&fdread); //加入套接字
if(select(0,&fdread,NULL,NULL,NULL)>0
{
//成功
if(FD_ISSET(s,&fread) //是否存在fread中,详细定义请看winsock2.h
{
//是可读的
}
}
◆I/O操作函数:主要用于获取与套接字相关的操作参数。
The ioctlsocket function controls the I/O mode of a socket.
int ioctlsocket(
SOCKET s,
long cmd,
u_long FAR *argp
);
s为I/O操作的套接字。
cmd为对套接字的操作命令。
argp为命令所带参数的指针。
The ioctlsocket function can be used on any socket in any state. It is used to set or retrieve operating parameters associated with the socket
,independent of the protocol and communications subsystem. Here are the supported commands to use in the cmd parameter and their semantics:
FIONBIO
Use with a nonzero argp parameter to enable the nonblocking mode of socket s.
The argp parameter is zero if nonblocking is to be disabled.
When a socket is created, it operates in blocking mode by default (nonblocking mode is disabled).
To set the socket back to blocking mode, an application must first disable WSAAsyncSelect by calling WSAAsyncSelect with the lEvent parameter equal to zero,
or disable WSAEventSelect by calling WSAEventSelect with the lNetworkEvents parameter equal to zero.
FIONREAD
Use to determine the amount of data pending in the network's input buffer that can be read from socket s.
The argp parameter points to an unsigned long value in which ioctlsocket stores the result.
FIONREAD returns the amount of data that can be read in a single call to the recv function,
which may not be the same as the total amount of data queued on the socket.
If s is message oriented (for example, type SOCK_DGRAM), FIONREAD still returns the amount of pending data in the network buffer
, however, the amount that can actually be read in a single call to the recv function is limited to the data size written in the send or sendto function call.
SIOCATMARK
Use to determine whether or not all OOB data has been read. (See section Windows Sockets 1.1 Blocking Routines and EINPROGRESS for a discussion on out of band (OOB) data.)
This applies only to a stream oriented socket (for example, type SOCK_STREAM) that has been configured for in-line reception of any OOB data (SO_OOBINLINE). If no OOB data is waiting to be read, the operation returns TRUE. Otherwise, it returns FALSE, and the next recv or recvfrom performed on the socket will retrieve some or all of the data preceding the mark. The application should use the SIOCATMARK operation to determine whether any data remains. If there is any normal data preceding the urgent (out of band) data, it will be received in order. (A recv or recvfrom will never mix OOB and normal data in the same call.)
The argp parameter points to an unsigned long value in which ioctlsocket stores the Boolean result.
◆、WSAAsynSelect模型:
WSAAsynSelect模型也是一个常用的异步I/O模型。应用程序可以在一个套接字上接收以
WINDOWS消息为基础的网络事件通知。该模型的实现方法是通过调用WSAAsynSelect函
数 自动将套接字设置为非阻塞模式,并向WINDOWS注册一个或多个网络时间,并提供一
个通知时使用的窗口句柄。当注册的事件发生时,对应的窗口将收到一个基于消息的通知。
int WSAAsyncSelect( SOCKET s, HWND hWnd, u_int wMsg, long lEvent);
s为需要事件通知的套接字
hWnd为接收消息的窗口句柄
wMsg为要接收的消息
lEvent为掩码,指定应用程序感兴趣的网络事件组合,主要如下:
#define FD_READ_BIT 0
#define FD_READ (1 << FD_READ_BIT)
#define FD_WRITE_BIT 1
#define FD_WRITE (1 << FD_WRITE_BIT)
#define FD_OOB_BIT 2
#define FD_OOB (1 << FD_OOB_BIT)
#define FD_ACCEPT_BIT 3
#define FD_ACCEPT (1 << FD_ACCEPT_BIT)
#define FD_CONNECT_BIT 4
#define FD_CONNECT (1 << FD_CONNECT_BIT)
#define FD_CLOSE_BIT 5
#define FD_CLOSE (1 << FD_CLOSE_BIT)
用法:要接收读写通知:
int nResult= WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE);
if(nResult==SOCKET_ERROR)
{
//错误处理
}
取消通知:
int nResult= WSAAsyncSelect(s,hWnd,0,0);
当应用程序窗口hWnd收到消息时,wMsg.wParam参数标识了套接字,lParam的低字标明
了网络事件,高字则包含错误代码。
◆、WSAEventSelect模型
WSAEventSelect模型类似WSAAsynSelect模型,但最主要的区别是网络事件发生时会被发
送到一个事件对象句柄,而不是发送到一个窗口。
使用步骤如下:
a、 创建事件对象来接收网络事件:
#define WSAEVENT HANDLE
#define LPWSAEVENT LPHANDLE
WSAEVENT WSACreateEvent( void );
该函数的返回值为一个事件对象句柄,它具有两种工作状态:已传信(signaled)和未传信
(nonsignaled)以及两种工作模式:人工重设(manual reset)和自动重设(auto reset)。默认未
未传信的工作状态和人工重设模式。
b、将事件对象与套接字关联,同时注册事件,使事件对象的工作状态从未传信转变未
已传信。
int WSAEventSelect( SOCKET s,WSAEVENT hEventObject,long lNetworkEvents );
s为套接字
hEventObject为刚才创建的事件对象句柄
lNetworkEvents为掩码,定义如上面所述
c、I/O处理后,设置事件对象为未传信
BOOL WSAResetEvent( WSAEVENT hEvent );
Hevent为事件对象
成功返回TRUE,失败返回FALSE。
d、等待网络事件来触发事件句柄的工作状态:
DWORD WSAWaitForMultipleEvents( DWORD cEvents,
const WSAEVENT FAR * lphEvents, BOOL fWaitAll,
DWORD dwTimeout, BOOL fAlertable );
lpEvent为事件句柄数组的指针
cEvent为为事件句柄的数目,其最大值为WSA_MAXIMUM_WAIT_EVENTS
fWaitAll指定等待类型:TRUE:当lphEvent数组重所有事件对象同时有信号时返回;
FALSE:任一事件有信号就返回。
dwTimeout为等待超时(毫秒)
fAlertable为指定函数返回时是否执行完成例程
对事件数组中的事件进行引用时,应该用WSAWaitForMultipleEvents的返回值,减去
预声明值WSA_WAIT_EVENT_0,得到具体的引用值。例如:
nIndex=WSAWaitForMultipleEvents(…);
MyEvent=EventArray[Index- WSA_WAIT_EVENT_0];
e、判断网络事件类型:
int WSAEnumNetworkEvents( SOCKET s,
WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );
s为套接字
hEventObject为需要重设的事件对象
lpNetworkEvents为记录网络事件和错误代码,其结构定义如下:
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
f、关闭事件对象句柄:
BOOL WSACloseEvent(WSAEVENT hEvent);
调用成功返回TRUE,否则返回FALSE
使用阻塞套接字。阻塞套接字可能会对应用程序的性能造成影响,在阻塞套接字时,程序必须等待SOCKET处理完毕或者出错后才返回,这段时间CPU就无聊的等待,对于非阻塞可以在这段时间做一些数据准备工作,一旦SOCKET可以用就立即发送数据,并行性更好
非阻塞在单socket的情况下操作起来比阻塞要复杂点,但是对于多个socket并且要求实时性比较高的场合下基本上阻塞就不能用。譬如,socket A先进行了一个阻塞操作,那么不超时/不结束调用的话,代码的控制权不会返回,那么B socket上面就算有数据需要被接受并且处理,由于已经阻塞在A上面了,此时没有办法去处理B上面的数据。而非阻塞的时候调用立即返回,B就可以被处理。
非阴塞的服务器程序:
#include <winsock2.h>
#include <stdio.h>
#define MYPORT 5550 // 定义默认通信端口
void main(void)
{
WSADATA wsaData;
SOCKET ListeningSocket; // 服务器套接字
SOCKET NewConnection; // 客户机套接字
SOCKADDR_IN ServerAddr;
SOCKADDR_IN ClientAddr;
int Ret, ClientAddrLen;
char DataBuffer[1024]; // 接收数据的缓冲区
fd_set readfds; // 等待可读性检查的套接口结构体
unsigned long ul = 1;
struct timeval timeout; // 最多等待时间
printf("\n------------------非阻塞模式的套接字(服务器)------------------\n\n");
// 初始化 Winsock 2.2 版本
if ((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0)
{
printf("WSAStartup failed with error %d\n", Ret);
return;
}
// 创建一个新套接字来监听客户机的连接
if ((ListeningSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
printf("socket failed with error %d\n", WSAGetLastError());
WSACleanup();
return;
}
// 初始化一个 SOCKADDR_IN 结构
ServerAddr.sin_family = AF_INET; // 使用IP地址族
ServerAddr.sin_port = htons(MYPORT); // 通信端口(5550)
ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 使用自己的IP地址,实际默认为0
// 将这个地址信息和套接字关联起来
if (bind(ListeningSocket, (SOCKADDR *)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR)
{
printf("bind failed with error %d\n", WSAGetLastError());
closesocket(ListeningSocket);
WSACleanup();
return;
}
// 将套接字转入监听模式,监听客户机的连接,指定最大队列长度为5
if (listen(ListeningSocket, 5) == SOCKET_ERROR)
{
printf("listen failed with error %d\n", WSAGetLastError());
closesocket(ListeningSocket);
WSACleanup();
return;
}
// 设置套接字为非阻塞模式
Ret = ioctlsocket(ListeningSocket, FIONBIO, (unsigned long *) &ul);
if (Ret == SOCKET_ERROR)
{
printf("ioctlsocket failed with error %d\n", WSAGetLastError());
}
else
{
printf("set nonblock mode successed, return value %d\n", Ret);
}
printf("waiting for connection on port %d....\n", MYPORT);
// 定义Select() 的最多等待时间
timeout.tv_sec = 0;
timeout.tv_usec = 500;
while (1)
{
FD_ZERO(&readfds); // 对结构体进行初始化
FD_SET(ListeningSocket, &readfds); // 把套接字加入集合
// 查询套接口的可读性
Ret = select(0, &readfds, NULL, NULL, &timeout);
if (Ret > 0)
{
if (FD_ISSET(ListeningSocket, &readfds))
{
ClientAddrLen = sizeof(ClientAddr);
// 当有连接请求到达时,接受一个新连接
if ((NewConnection = accept(ListeningSocket, (SOCKADDR *) &ClientAddr,
&ClientAddrLen)) == INVALID_SOCKET)
{
printf("accept failed with error %d\n", WSAGetLastError());
closesocket(ListeningSocket);
WSACleanup();
exit(1);
}
printf("got a connection from %s .\n", inet_ntoa(ClientAddr.sin_addr));
// 循环
while(1)
{
// FD_ZERO(&readfds);
FD_SET(NewConnection, &readfds);
// 第二次检查套接口的可读性,等待时间设为空,否则会出现超时错误
Ret = select(0, &readfds, NULL, NULL, NULL);
if (FD_ISSET(NewConnection, &readfds))
{
// 接收数据
Ret = recv(NewConnection, DataBuffer, sizeof(DataBuffer)-1, 0);
if (Ret == SOCKET_ERROR || Ret == 0)
{
// 接收错误
printf("recv failed with error %d\n", WSAGetLastError());
closesocket(NewConnection);
WSACleanup();
return ;
}
else
{
DataBuffer[Ret] = '\0';
printf("received %d byte: %s", (Ret-1), DataBuffer);
}
}
}
}
}
}
// 关闭套接字
closesocket(NewConnection);
// 释放由 winsock 分配的资源
WSACleanup();
}
非阻塞的客户机程序:
#include <winsock2.h>
#include <stdio.h>
#define MYPORT 5550 // 定义默认通信端口
#define LINELEN 128 // 定义一行数据的最大长度
void main(int argc, char **argv)
{
WSADATA wsaData;
SOCKET s;
SOCKADDR_IN ServerAddr;
int Ret, length;
char buf[LINELEN];
fd_set writefds; // 等待可写性检查的套接口结构体
unsigned long ul = 1;
struct timeval timeout; // 最多等待时间
// 对主函数的参数进行处理
switch(argc)
{
case 1:
argv[1] = "127.0.0.1"; // 定义一个默认的IP地址
break;
case 2:
argv[1]
argv[2]
break;
default:
printf("argument error!\n");
exit(1);
}
printf("\n------------------非阻塞模式的套接字(客户机)------------------\n\n");
// 初始化 Winsock 2.2 版本
if ((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0)
{
printf("WSAStartup failed with error %d\n", Ret);
return;
}
// 创建一个新套接字来建立客户机连接
if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
printf("socket failed with error %d\n", WSAGetLastError());
WSACleanup();
return;
}
// 设置套接字为非阻塞模式
Ret = ioctlsocket(s, FIONBIO, (unsigned long *) &ul);
if (Ret == SOCKET_ERROR)
{
printf("ioctlsocket failed with error %d\n", WSAGetLastError());
}
else
{
printf("set nonblock mode successed, return value %d\n", Ret);
}
// 初始化一个 SOCKADDR_IN 结构
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(MYPORT);
ServerAddr.sin_addr.s_addr = inet_addr(argv[1]); // 定义服务器地址
// 用套接字 s 来创建一个到服务器的连接
Ret = connect(s, (SOCKADDR *) &ServerAddr, sizeof(ServerAddr));
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
printf("connect failed with error %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return;
}
else
{
// 和服务器的连接成功
printf("connect to %s on %d succeeded.\n", argv[1], MYPORT);
printf("please input send data....\n");
// 定义Select() 的最多等待时间
timeout.tv_sec = 0;
timeout.tv_usec = 500;
while (1)
{
FD_ZERO(&writefds);
FD_SET(s, &writefds);
// 查询套接口的可写性
Ret = select( 0, NULL, &writefds, NULL, &timeout);
if (Ret > 0)
{
if (FD_ISSET(s, &writefds))
{
// 循环:从输入流中取数据
while (fgets(buf, sizeof(buf), stdin))
{
buf[LINELEN] = '\0'; // 在字符串最后加终止符
length = strlen(buf); // 实际发送字节数
// 如果输入是回车,则结束程序
if (buf[0] == '\n')
{
closesocket(s);
WSACleanup();
return;
}
// 发送数据
if ((Ret = send(s, buf, length, 0)) == SOCKET_ERROR)
{
printf("send failed with error %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return;
}
}
}
}
}
}
// 关闭套接字
closesocket(s);
// 释放由 winsock 分配的资源
WSACleanup();
}
* 阻塞模式: 执行I/O操作完成前会一直进行等待,不会将控制权交给程序。套接字默认为阻塞模式。可以通过多线程技术进行处理。
* 非阻塞模式:执行I/O操作时,Winsock函数会返回并交出控制权。这种模式使用起来比较复杂,因为函数在没有运行完成就进行返回,会不断地返回 WSAEWOULDBLOCK错误。但功能强大。
为了解决这个问题,提出了进行I/O操作的一些I/O模型,下面介绍最常见的三种:
◆、select模型:
通过调用select函数可以确定一个或多个套接字的状态,判断套接字上是否有数据,或者能否向一个套接字写入数据。
int select(
int nfds,
fd_set FAR *readfds,
fd_set FAR *writefds,
fd_set FAR *exceptfds,
const struct timeval FAR *timeout
);
nfds
[in] Ignored. The nfds parameter is included only for compatibility with Berkeley sockets.
readfds
[in, out] Optional pointer to a set of sockets to be checked for readability.
writefds
[in, out] Optional pointer to a set of sockets to be checked for writability
exceptfds
[in, out] Optional pointer to a set of sockets to be checked for errors.
timeout
[in] Maximum time for select to wait, provided in the form of a TIMEVAL structure. Set the timeout parameter to NULL for blocking operation.
readfds:
If listen has been called and a connection is pending, accept will succeed.
Data is available for reading (includes OOB data if SO_OOBINLINE is enabled).
Connection has been closed/reset/terminated.
writefds:
If processing a connect call (nonblocking), connection has succeeded.
Data can be sent.
exceptfds:
If processing a connect call (nonblocking), connection attempt failed.
OOB data is available for reading (only if SO_OOBINLINE is disabled).
The select function returns the total number of socket handles that are ready and contained in the fd_set structures,
zero if the time limit expired, or SOCKET_ERROR if an error occurred.
The fd_set structure is used by various Windows Sockets functions and service providers, such as the select function,
to place sockets into a "set" for various purposes, such as testing a given socket for readability using the readfds parameter of the select function.
typedef struct fd_set {
u_int fd_count; // how many are SET?
SOCKET fd_array[FD_SETSIZE]; // an array of SOCKETs
} fd_set;
Members:
fd_count
Number of sockets in the set.
fd_array
Array of sockets that are in the set.
The macros to manipulate and check fd_set contents are:
FD_CLR(s, *set)
Removes the descriptor s from set.
FD_ISSET(s, *set)
Nonzero if s is a member of the set. Otherwise, zero.
FD_SET(s, *set)
Adds descriptor s to set.
FD_ZERO(*set)
Initializes the set to the NULL set.
readfds、writefds、exceptfds三个变量至少有一个不为空,同时这个不为空的套接字组种至少有一个socket,道理很简单,否则要select干什么呢。
举例:测试一个套接字是否可读:
fd_set fdread;
FD_ZERO(&fdread);
FD_SET(s,&fdread); //加入套接字
if(select(0,&fdread,NULL,NULL,NULL)>0
{
//成功
if(FD_ISSET(s,&fread) //是否存在fread中,详细定义请看winsock2.h
{
//是可读的
}
}
◆I/O操作函数:主要用于获取与套接字相关的操作参数。
The ioctlsocket function controls the I/O mode of a socket.
int ioctlsocket(
SOCKET s,
long cmd,
u_long FAR *argp
);
s为I/O操作的套接字。
cmd为对套接字的操作命令。
argp为命令所带参数的指针。
The ioctlsocket function can be used on any socket in any state. It is used to set or retrieve operating parameters associated with the socket
,independent of the protocol and communications subsystem. Here are the supported commands to use in the cmd parameter and their semantics:
FIONBIO
Use with a nonzero argp parameter to enable the nonblocking mode of socket s.
The argp parameter is zero if nonblocking is to be disabled.
When a socket is created, it operates in blocking mode by default (nonblocking mode is disabled).
To set the socket back to blocking mode, an application must first disable WSAAsyncSelect by calling WSAAsyncSelect with the lEvent parameter equal to zero,
or disable WSAEventSelect by calling WSAEventSelect with the lNetworkEvents parameter equal to zero.
FIONREAD
Use to determine the amount of data pending in the network's input buffer that can be read from socket s.
The argp parameter points to an unsigned long value in which ioctlsocket stores the result.
FIONREAD returns the amount of data that can be read in a single call to the recv function,
which may not be the same as the total amount of data queued on the socket.
If s is message oriented (for example, type SOCK_DGRAM), FIONREAD still returns the amount of pending data in the network buffer
, however, the amount that can actually be read in a single call to the recv function is limited to the data size written in the send or sendto function call.
SIOCATMARK
Use to determine whether or not all OOB data has been read. (See section Windows Sockets 1.1 Blocking Routines and EINPROGRESS for a discussion on out of band (OOB) data.)
This applies only to a stream oriented socket (for example, type SOCK_STREAM) that has been configured for in-line reception of any OOB data (SO_OOBINLINE). If no OOB data is waiting to be read, the operation returns TRUE. Otherwise, it returns FALSE, and the next recv or recvfrom performed on the socket will retrieve some or all of the data preceding the mark. The application should use the SIOCATMARK operation to determine whether any data remains. If there is any normal data preceding the urgent (out of band) data, it will be received in order. (A recv or recvfrom will never mix OOB and normal data in the same call.)
The argp parameter points to an unsigned long value in which ioctlsocket stores the Boolean result.
◆、WSAAsynSelect模型:
WSAAsynSelect模型也是一个常用的异步I/O模型。应用程序可以在一个套接字上接收以
WINDOWS消息为基础的网络事件通知。该模型的实现方法是通过调用WSAAsynSelect函
数 自动将套接字设置为非阻塞模式,并向WINDOWS注册一个或多个网络时间,并提供一
个通知时使用的窗口句柄。当注册的事件发生时,对应的窗口将收到一个基于消息的通知。
int WSAAsyncSelect( SOCKET s, HWND hWnd, u_int wMsg, long lEvent);
s为需要事件通知的套接字
hWnd为接收消息的窗口句柄
wMsg为要接收的消息
lEvent为掩码,指定应用程序感兴趣的网络事件组合,主要如下:
#define FD_READ_BIT 0
#define FD_READ (1 << FD_READ_BIT)
#define FD_WRITE_BIT 1
#define FD_WRITE (1 << FD_WRITE_BIT)
#define FD_OOB_BIT 2
#define FD_OOB (1 << FD_OOB_BIT)
#define FD_ACCEPT_BIT 3
#define FD_ACCEPT (1 << FD_ACCEPT_BIT)
#define FD_CONNECT_BIT 4
#define FD_CONNECT (1 << FD_CONNECT_BIT)
#define FD_CLOSE_BIT 5
#define FD_CLOSE (1 << FD_CLOSE_BIT)
用法:要接收读写通知:
int nResult= WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE);
if(nResult==SOCKET_ERROR)
{
//错误处理
}
取消通知:
int nResult= WSAAsyncSelect(s,hWnd,0,0);
当应用程序窗口hWnd收到消息时,wMsg.wParam参数标识了套接字,lParam的低字标明
了网络事件,高字则包含错误代码。
◆、WSAEventSelect模型
WSAEventSelect模型类似WSAAsynSelect模型,但最主要的区别是网络事件发生时会被发
送到一个事件对象句柄,而不是发送到一个窗口。
使用步骤如下:
a、 创建事件对象来接收网络事件:
#define WSAEVENT HANDLE
#define LPWSAEVENT LPHANDLE
WSAEVENT WSACreateEvent( void );
该函数的返回值为一个事件对象句柄,它具有两种工作状态:已传信(signaled)和未传信
(nonsignaled)以及两种工作模式:人工重设(manual reset)和自动重设(auto reset)。默认未
未传信的工作状态和人工重设模式。
b、将事件对象与套接字关联,同时注册事件,使事件对象的工作状态从未传信转变未
已传信。
int WSAEventSelect( SOCKET s,WSAEVENT hEventObject,long lNetworkEvents );
s为套接字
hEventObject为刚才创建的事件对象句柄
lNetworkEvents为掩码,定义如上面所述
c、I/O处理后,设置事件对象为未传信
BOOL WSAResetEvent( WSAEVENT hEvent );
Hevent为事件对象
成功返回TRUE,失败返回FALSE。
d、等待网络事件来触发事件句柄的工作状态:
DWORD WSAWaitForMultipleEvents( DWORD cEvents,
const WSAEVENT FAR * lphEvents, BOOL fWaitAll,
DWORD dwTimeout, BOOL fAlertable );
lpEvent为事件句柄数组的指针
cEvent为为事件句柄的数目,其最大值为WSA_MAXIMUM_WAIT_EVENTS
fWaitAll指定等待类型:TRUE:当lphEvent数组重所有事件对象同时有信号时返回;
FALSE:任一事件有信号就返回。
dwTimeout为等待超时(毫秒)
fAlertable为指定函数返回时是否执行完成例程
对事件数组中的事件进行引用时,应该用WSAWaitForMultipleEvents的返回值,减去
预声明值WSA_WAIT_EVENT_0,得到具体的引用值。例如:
nIndex=WSAWaitForMultipleEvents(…);
MyEvent=EventArray[Index- WSA_WAIT_EVENT_0];
e、判断网络事件类型:
int WSAEnumNetworkEvents( SOCKET s,
WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );
s为套接字
hEventObject为需要重设的事件对象
lpNetworkEvents为记录网络事件和错误代码,其结构定义如下:
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
f、关闭事件对象句柄:
BOOL WSACloseEvent(WSAEVENT hEvent);
调用成功返回TRUE,否则返回FALSE
使用阻塞套接字。阻塞套接字可能会对应用程序的性能造成影响,在阻塞套接字时,程序必须等待SOCKET处理完毕或者出错后才返回,这段时间CPU就无聊的等待,对于非阻塞可以在这段时间做一些数据准备工作,一旦SOCKET可以用就立即发送数据,并行性更好
非阻塞在单socket的情况下操作起来比阻塞要复杂点,但是对于多个socket并且要求实时性比较高的场合下基本上阻塞就不能用。譬如,socket A先进行了一个阻塞操作,那么不超时/不结束调用的话,代码的控制权不会返回,那么B socket上面就算有数据需要被接受并且处理,由于已经阻塞在A上面了,此时没有办法去处理B上面的数据。而非阻塞的时候调用立即返回,B就可以被处理。
非阴塞的服务器程序:
#include <winsock2.h>
#include <stdio.h>
#define MYPORT 5550 // 定义默认通信端口
void main(void)
{
WSADATA wsaData;
SOCKET ListeningSocket; // 服务器套接字
SOCKET NewConnection; // 客户机套接字
SOCKADDR_IN ServerAddr;
SOCKADDR_IN ClientAddr;
int Ret, ClientAddrLen;
char DataBuffer[1024]; // 接收数据的缓冲区
fd_set readfds; // 等待可读性检查的套接口结构体
unsigned long ul = 1;
struct timeval timeout; // 最多等待时间
printf("\n------------------非阻塞模式的套接字(服务器)------------------\n\n");
// 初始化 Winsock 2.2 版本
if ((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0)
{
printf("WSAStartup failed with error %d\n", Ret);
return;
}
// 创建一个新套接字来监听客户机的连接
if ((ListeningSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
printf("socket failed with error %d\n", WSAGetLastError());
WSACleanup();
return;
}
// 初始化一个 SOCKADDR_IN 结构
ServerAddr.sin_family = AF_INET; // 使用IP地址族
ServerAddr.sin_port = htons(MYPORT); // 通信端口(5550)
ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 使用自己的IP地址,实际默认为0
// 将这个地址信息和套接字关联起来
if (bind(ListeningSocket, (SOCKADDR *)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR)
{
printf("bind failed with error %d\n", WSAGetLastError());
closesocket(ListeningSocket);
WSACleanup();
return;
}
// 将套接字转入监听模式,监听客户机的连接,指定最大队列长度为5
if (listen(ListeningSocket, 5) == SOCKET_ERROR)
{
printf("listen failed with error %d\n", WSAGetLastError());
closesocket(ListeningSocket);
WSACleanup();
return;
}
// 设置套接字为非阻塞模式
Ret = ioctlsocket(ListeningSocket, FIONBIO, (unsigned long *) &ul);
if (Ret == SOCKET_ERROR)
{
printf("ioctlsocket failed with error %d\n", WSAGetLastError());
}
else
{
printf("set nonblock mode successed, return value %d\n", Ret);
}
printf("waiting for connection on port %d....\n", MYPORT);
// 定义Select() 的最多等待时间
timeout.tv_sec = 0;
timeout.tv_usec = 500;
while (1)
{
FD_ZERO(&readfds); // 对结构体进行初始化
FD_SET(ListeningSocket, &readfds); // 把套接字加入集合
// 查询套接口的可读性
Ret = select(0, &readfds, NULL, NULL, &timeout);
if (Ret > 0)
{
if (FD_ISSET(ListeningSocket, &readfds))
{
ClientAddrLen = sizeof(ClientAddr);
// 当有连接请求到达时,接受一个新连接
if ((NewConnection = accept(ListeningSocket, (SOCKADDR *) &ClientAddr,
&ClientAddrLen)) == INVALID_SOCKET)
{
printf("accept failed with error %d\n", WSAGetLastError());
closesocket(ListeningSocket);
WSACleanup();
exit(1);
}
printf("got a connection from %s .\n", inet_ntoa(ClientAddr.sin_addr));
// 循环
while(1)
{
// FD_ZERO(&readfds);
FD_SET(NewConnection, &readfds);
// 第二次检查套接口的可读性,等待时间设为空,否则会出现超时错误
Ret = select(0, &readfds, NULL, NULL, NULL);
if (FD_ISSET(NewConnection, &readfds))
{
// 接收数据
Ret = recv(NewConnection, DataBuffer, sizeof(DataBuffer)-1, 0);
if (Ret == SOCKET_ERROR || Ret == 0)
{
// 接收错误
printf("recv failed with error %d\n", WSAGetLastError());
closesocket(NewConnection);
WSACleanup();
return ;
}
else
{
DataBuffer[Ret] = '\0';
printf("received %d byte: %s", (Ret-1), DataBuffer);
}
}
}
}
}
}
// 关闭套接字
closesocket(NewConnection);
// 释放由 winsock 分配的资源
WSACleanup();
}
非阻塞的客户机程序:
#include <winsock2.h>
#include <stdio.h>
#define MYPORT 5550 // 定义默认通信端口
#define LINELEN 128 // 定义一行数据的最大长度
void main(int argc, char **argv)
{
WSADATA wsaData;
SOCKET s;
SOCKADDR_IN ServerAddr;
int Ret, length;
char buf[LINELEN];
fd_set writefds; // 等待可写性检查的套接口结构体
unsigned long ul = 1;
struct timeval timeout; // 最多等待时间
// 对主函数的参数进行处理
switch(argc)
{
case 1:
argv[1] = "127.0.0.1"; // 定义一个默认的IP地址
break;
case 2:
argv[1]
argv[2]
break;
default:
printf("argument error!\n");
exit(1);
}
printf("\n------------------非阻塞模式的套接字(客户机)------------------\n\n");
// 初始化 Winsock 2.2 版本
if ((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0)
{
printf("WSAStartup failed with error %d\n", Ret);
return;
}
// 创建一个新套接字来建立客户机连接
if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
printf("socket failed with error %d\n", WSAGetLastError());
WSACleanup();
return;
}
// 设置套接字为非阻塞模式
Ret = ioctlsocket(s, FIONBIO, (unsigned long *) &ul);
if (Ret == SOCKET_ERROR)
{
printf("ioctlsocket failed with error %d\n", WSAGetLastError());
}
else
{
printf("set nonblock mode successed, return value %d\n", Ret);
}
// 初始化一个 SOCKADDR_IN 结构
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(MYPORT);
ServerAddr.sin_addr.s_addr = inet_addr(argv[1]); // 定义服务器地址
// 用套接字 s 来创建一个到服务器的连接
Ret = connect(s, (SOCKADDR *) &ServerAddr, sizeof(ServerAddr));
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
printf("connect failed with error %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return;
}
else
{
// 和服务器的连接成功
printf("connect to %s on %d succeeded.\n", argv[1], MYPORT);
printf("please input send data....\n");
// 定义Select() 的最多等待时间
timeout.tv_sec = 0;
timeout.tv_usec = 500;
while (1)
{
FD_ZERO(&writefds);
FD_SET(s, &writefds);
// 查询套接口的可写性
Ret = select( 0, NULL, &writefds, NULL, &timeout);
if (Ret > 0)
{
if (FD_ISSET(s, &writefds))
{
// 循环:从输入流中取数据
while (fgets(buf, sizeof(buf), stdin))
{
buf[LINELEN] = '\0'; // 在字符串最后加终止符
length = strlen(buf); // 实际发送字节数
// 如果输入是回车,则结束程序
if (buf[0] == '\n')
{
closesocket(s);
WSACleanup();
return;
}
// 发送数据
if ((Ret = send(s, buf, length, 0)) == SOCKET_ERROR)
{
printf("send failed with error %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return;
}
}
}
}
}
}
// 关闭套接字
closesocket(s);
// 释放由 winsock 分配的资源
WSACleanup();
}