立即学习:https://edu.csdn.net/course/play/6082/113754?utm_source=blogtoedu
多线程并发的解决方案:
主服务线程accept,每监听到一个新的连接,就为它创建一个线程,在子服务线程里recv,send数据,但是一旦客户端连接数增多,线程的开销将非常大。
利用select函数实现IO管理,通过对selet函数的调用,应用程序可以判断套接字是否存在数据,能否向该套接字写入数据,使用该模式的好处是可以等待多个套接字。
select函数:检查当前各个套接字状态
int selelct(IN int nfds,IN OUT fd-set *readfds,IN OUT fd_set * writefds,IN OUT fd_set*exceptfds , IN const struct timeval*timeout);
参数:
第一个参数:0,无意义
第二个参数:检查可读性集合
第三个参数:检查可写性集合
第四个参数:例外数据集合
第五个参数:函数的返回时间
返回值:
成功返回处于就绪状态并且已经包含在fd_set结构中的描述字总数,超时则返回0 , 否则返回socket_error错误,通过WSAGetLastError()获取错误代码。
timeval结构体
structure timeval
{
long tv_sec ;//秒
long tv_usec;//毫秒
}
当timeval为空指针时,select会一直等待,直到有符合条件的套接字才返回
当t_sec和tv_usec之和为0时,无论是否有符合条件的套接字,select立即返回。
当tv_set和tv_usec之和为非0时,如果在等待时间内有套接字满足条件,函数返回符合条件的套接字,如果在等待时间内没有套接字满足设置的条件,则select会在时间用完时返回0 。
typefef stuct fd_set
{
u_int fd_count ;
socket fd_arra[FD_SETSIZE];
}fd_set ;
fd_cout 表示该集合套接字数量,最大为64
fd_array套接字组。
在select函数返回时,会在fd_set结构填入相应的套接字
readfds数组将包括满足一下条件的套接字
1、有数据可读,此时在此套接字上调用recv,立即收到对方的数据。
2、连接已经关闭,终止或终止
3、正在请求建立连接的套接字,此时调用accept函数会成功
writefds数据满足下列条件的套接字
1、有数据可以发出,此时在此套接字上调用send,可以向对方发送数据
2、调用connect函数,并连接成功的套接字。
exceptfds数据包括下列条件的套接字
1、调用connection函数,但连接失败的套接字
2、有带外(out of band)数据可读。
select 判断套接字是否可读的步骤
1、将该套接字加入到readfds集合。
2、以readfds作为第二个参数调用select函数
3、当select函数返回时,应用程序判断该套接字是否仍然存在readfds集合
4、如果该套接字存在readfds集合,则表明该套接字可读,此时就可以调用recv函数接收数据,否则该套接字不可读。
在调用select函数时,readfds,writefds和exceptfds三个参数至少有一个为非空,并且在该非空的参数中,必须至少包含一个套接字,否则select函数将没有任何套接字可以等待。
Windows Socket 提供了下列宏来简化对fd_set的操作
FD_CLF(s ,*set) 从set集合中删除s套接字
FD_ISSET(s ,*set) 检查s是否为set集合的成员
FD_SET(s,*set) 将套接字加入到set集合中
FD_ZERO(*set)将set集合初始化为空集合。
服务端代码
#include <iostream>
#include<WinSock2.h>
#include<cstdlib>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
WSADATA wd;
if (WSAStartup(MAKEWORD(2, 2), &wd) != 0)
{
cout << "wsastartup error " << WSAGetLastError() << endl;
exit(EXIT_FAILURE);
}
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET)
{
cout << "socket error " << WSAGetLastError() << endl;
exit(EXIT_FAILURE);
}
sockaddr_in addr;
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addr.sin_family = AF_INET;
addr.sin_port = htons(8000);
int bindRet = bind(s, (sockaddr*)&addr, sizeof sockaddr);
if (bindRet == SOCKET_ERROR)
{
cout << "bind error " << WSAGetLastError() << endl;
exit(EXIT_FAILURE);
}
int listenRet = listen(s, 0);
if (listenRet == SOCKET_ERROR)
{
cout << "listen error " << WSAGetLastError() << endl;
exit(EXIT_FAILURE);
}
//定义一个可读的集合
fd_set readSet;
//初始化集合,
FD_ZERO(&readSet);
//将套接字s加入到集合中
FD_SET(s, &readSet);
//while循环
while (true)
{
//定义一个临时的集合
fd_set tempRead;
//初始化临时集合
FD_ZERO(&tempRead);
//将套接字赋值给临时集合
tempRead = readSet;
//利用select选择出集合中可读写的套接字
int selectRet = select(0, &tempRead, NULL, NULL, NULL);
if (selectRet == SOCKET_ERROR)
{
continue;
}
int ret;
//成功筛选出来可发送或接受的socket
for (int i = 0; i < tempRead.fd_count; i++)
{
SOCKET tempS = tempRead.fd_array[i];
if (tempS == s)
{
sockaddr_in ClienAddr;
int addrLen = sizeof sockaddr;
SOCKET c = accept(s, (sockaddr*)&ClienAddr, &addrLen);
if (readSet.fd_count < FD_SETSIZE)
{
FD_SET(c, &readSet);
cout << "欢迎" << c << "进入聊天室" << endl;
char buf[100] = { '\0' };
sprintf_s(buf, "欢迎%d进入聊天室", c);
send(c, buf, 100, 0);
}
else
{
cout << "达到客户端上限" << endl;
}
}
else
{
char buf[100] = { '\0' };
ret = recv(tempS, buf, 100, 0);
if (ret == SOCKET_ERROR || ret == 0)
{
closesocket(tempS);
FD_CLR(tempS, &readSet);
cout << tempS << "离开聊天室" << endl;
}
else
{
cout << tempS << "说:" << buf << endl;
}
}
}
}
closesocket(s);
WSACleanup();
}