select(
_In_ int nfds,//为了兼容的参数,忽略填0
_Inout_opt_ fd_set FAR * exceptfds,//错误套接字集合指针
_In_opt_ const struct timeval FAR * timeout//select等待时间
_In_ int nfds,//为了兼容的参数,忽略填0
_Inout_opt_ fd_set FAR * readfds,//可读套接字集合指针
_Inout_opt_ fd_set FAR * writefds,//可写套接字集合指针_Inout_opt_ fd_set FAR * exceptfds,//错误套接字集合指针
_In_opt_ const struct timeval FAR * timeout//select等待时间
);
readfds数组将包括满足以下条件的套接字:
1有数据可读
2连接已经关闭、重设或终止
3正在请求建立连接的套接字(listfd),此时调用accept函数会直接成功,accept相当于非阻塞的
writefds数组包含满足下列条件的套接字:
1有数据可以发送,此时在此sockfd上调用send,可以向对方发送数据。
2调用connect函数,并连接成功的sockfd
windows sockets提供了下列宏,用来对fd_set进行一系列操作
FD_CLR(fd,*set);从set集合中删除sockfd套接字
FD_ISSET(fd,*set);检查sockfd是否为set集合的成员
FD_SET(fd,*set);将sockfd加入到set集合中
FD_ZERO(*set);将set集合初始化为空集合
代码编程如下
server
#include <stdio.h>
#include<WinSock2.h>
#include <WS2tcpip.h>
#pragma comment(lib,"ws2_32.lib") //加载ws2_32.lib库
int main()
{
WSADATA wsadata;
WORD SockVersion = MAKEWORD(2,2);//初始化socker的版本
if (WSAStartup(SockVersion,&wsadata)!=0)//WSAStartup是绑定socket版本号,指定操作系统调用那个版本的方法
{
printf("WSAStartup fail\n");
return -1;
}
//创建scoket
SOCKET ScoketFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ScoketFd == INVALID_SOCKET)
{
printf("socket error !");
return -1;
}
//绑定IP和端口
struct sockaddr_in SockAddr;
SockAddr.sin_family = AF_INET;
SockAddr.sin_port = htons(1111);
SockAddr.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(ScoketFd, (SOCKADDR*)&SockAddr, sizeof(SOCKADDR)) == SOCKET_ERROR)
{
printf("bind error !\n");
return -1;
}
//开始监听
if (listen(ScoketFd, 10) == SOCKET_ERROR)
{
printf("listen error !\n");
return -1;
}
// select模型
fd_set AllSocketSet;
FD_ZERO(&AllSocketSet);
FD_SET(ScoketFd,&AllSocketSet);//将ScoketFd加入sock集合
int ret = 0;
//接收数据
SOCKET ClientConFd;
struct sockaddr_in ClientAddr;
int nSize = sizeof(ClientAddr);
char RevData[1024] = { 0 };
while (1)
{
//设置监听fd可读事件
fd_set ReadFd;
fd_set WriteFd;
FD_ZERO(&ReadFd);
FD_ZERO(&WriteFd);
ReadFd = AllSocketSet;
WriteFd = AllSocketSet;
ret = select(0, &ReadFd, &WriteFd, NULL, NULL);//第一个参数nfds被忽略,是为了兼容版本
if (ret == SOCKET_ERROR)
{
printf("select error\n");
return -1;
}
if (FD_ISSET(ScoketFd, &ReadFd)) //检查ScoketFd是否在这个集合里面,
{ //select将更新这个集合,把其中不可读的套节字去掉
//只保留符合条件的套节字在这个集合里面
printf("start accept new connect\n");
ClientConFd = accept(ScoketFd, (SOCKADDR*)&ClientAddr, &nSize);//等待连接,连接后会产生一个行的fd
if (ClientConFd == INVALID_SOCKET)
{
printf("accept error !\n");
return -1;
}
else
{
FD_SET(ClientConFd, &AllSocketSet);//将新产生的fd加入到原先的集合中
}
}
for (u_int i = 1; i < AllSocketSet.fd_count; ++i)//第一个fd是监听连接的fd,第二个开始是连接上的fd
{
if (FD_ISSET(AllSocketSet.fd_array[i], &ReadFd))//通过轮询原来的集合,检查ScoketFd是否在ReadFd集合中
{
printf("接收一个连接:%s \n", inet_ntoa(ClientAddr.sin_addr));
//接收数据
ret = recv(AllSocketSet.fd_array[i], RevData, 1024, 0);
if (ret == SOCKET_ERROR)
{
DWORD err = WSAGetLastError();
if (err == WSAECONNRESET)// 客户端的socket没有被正常关闭,即没有调用closesocket
{
printf("Client is forced to close\n");
closesocket(AllSocketSet.fd_array[i]);
FD_CLR(AllSocketSet.fd_array[i], &AllSocketSet);//把AllSocketSet.fd_array[i]从AllSocketSet集合中删除
break;
}
else
{
printf("recv fail\n");
}
}
else if (ret == 0)//客户端的socket正常关闭
{
closesocket(AllSocketSet.fd_array[i]);
FD_CLR(AllSocketSet.fd_array[i], &AllSocketSet);
printf("Client closes normally");
}
else
{
printf("%s\n", RevData);//打印读取的数据
}
}
if (FD_ISSET(AllSocketSet.fd_array[i], &WriteFd))
{
//发送数据
char * SendData = "hello,TcpClient";
send(AllSocketSet.fd_array[i], SendData, strlen(SendData), 0);
}
}
}
for (u_int j = 0; j < AllSocketSet.fd_count; ++j)
{
SOCKET socket = AllSocketSet.fd_array[j];
closesocket(socket);
}
WSACleanup();//终止 DLL 的使用
return 0;
}
client:测试的时候我用同样的客户端代码,生成多个客户端访问服务端
#include <WINSOCK2.H>
#include <STDIO.H>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WSADATA wsadata;
WORD SockVersion = MAKEWORD(2, 2);//初始化socker的版本
if (WSAStartup(SockVersion, &wsadata) != 0)//WSAStartup是绑定socket版本号,指定操作系统调用那个版本的方法
{
printf("WSAStartup fail\n");
return -1;
}
//创建scoket
SOCKET ScoketFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ScoketFd == INVALID_SOCKET)
{
printf("socket error !");
return -1;
}
//连接服务端
struct sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(1111);
ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(ScoketFd, (SOCKADDR *)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR)
{
printf("connect error !\n");
closesocket(ScoketFd);
return 0;
}
//发送数据到服务端
char * sendData = "hello,Tcp Server,i'm client";
send(ScoketFd, sendData, strlen(sendData), 0);
//读取服务端数据
char revData[1024] = {0};
int ret = recv(ScoketFd, revData, 1024, 0);
if (ret > 0)
{
printf("%s\n", revData);
}
getchar();
closesocket(ScoketFd);
WSACleanup();
return 0;
}
结果如图