IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:
(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
服务端:
#include <iostream>
#include <WinSock2.h>
#include <ws2tcpip.h>
using namespace std;
int main()
{
DWORD dwVersionRequest = MAKEWORD(2, 2);
WSADATA wsadata;
if (0 != WSAStartup(dwVersionRequest, &wsadata))
{
cout << "WSAStartup failed" << endl;
}
SOCKADDR_IN serv_addr;
serv_addr.sin_family = AF_INET; //IPV4
serv_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //①#define s_addr S_un.S_addr ②本机所有ip
serv_addr.sin_port = htons(8080); //端口号
SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == INVALID_SOCKET)
{
cout << "socket failed errorCode = " << WSAGetLastError() << endl;
}
if (bind(sockfd, (SOCKADDR*)&serv_addr, sizeof(SOCKADDR)) == SOCKET_ERROR)
{
cout << "bind failed errorCode = " << WSAGetLastError() << endl;
WSACleanup();
}
listen(sockfd, 5);
//select准备
fd_set readfds;
fd_set readfds_backup; //因为select后会修改readfds,所以再定义一个readfds_backup保存accept的套接字
struct timeval timeout;
int maxfd = sockfd;
FD_ZERO(&readfds); //清空集合
FD_ZERO(&readfds_backup);
FD_SET(sockfd, &readfds_backup); //套接字放到集合里
//accept准备
SOCKET sock_accept;
SOCKADDR_IN accept_addr;
socklen_t client_addr_len;
char client_ip_str[INET_ADDRSTRLEN];
//收发数据数组
char send_buf[100];
char recv_buf[100];
int send_len = 0;
int recv_len = 0;
while (1)
{
readfds = readfds_backup;
timeout.tv_sec = 50;
timeout.tv_usec = 0;
int selectRet = select(maxfd + 1, &readfds, NULL, NULL, &timeout);
if (selectRet < 0)
{
cout << "select failed" << endl;
}
else if (selectRet == 0)
{
cout << "select timeout" << endl;
continue;
}
//遍历select后的readfds
for (int i = 0; i <= maxfd; i++)
{
if (FD_ISSET(i, &readfds))
{
//分两种情况,1.有新的客户端连接,就去accept 2.已连接的客户端发信息过来
if (i == sockfd)
{
client_addr_len = sizeof(accept_addr);
sock_accept = accept(sockfd, (struct sockaddr*)&accept_addr, &client_addr_len);
inet_ntop(AF_INET, &(accept_addr.sin_addr), client_ip_str, sizeof(client_ip_str));
printf("----------------------------------------------\n");
printf("socket=%d, client ip=%s, port=%d 连接上来啦\n", sock_accept, client_ip_str, ntohs(accept_addr.sin_port));
FD_SET(sock_accept, &readfds_backup);
if (maxfd < sock_accept)
{
maxfd = sock_accept;
}
}
else
{
//这里就是已连接的Client发送信息进来啦
memset(recv_buf, 0, sizeof(recv_buf));
recv_len = recv(i, recv_buf, sizeof(recv_buf), 0);
if (recv_len < 0)
{
cout << "recv failed\n";
}
else
{
printf("socket= %d 对你说:%s\n", i, recv_buf);
}
send_len = send(i, recv_buf, recv_len, 0);
if (send_len < 0)
{
cout << "send failed\n";
}
}
}
}
}
WSACleanup();
system("pause");
return 0;
}
客户端:
#include <iostream>
#include <WinSock2.h>
#include <ws2tcpip.h>
#include <thread>
using namespace std;
int main()
{
DWORD dwVersionRequest = MAKEWORD(2, 2);
WSADATA wsadata;
if (0 != WSAStartup(dwVersionRequest, &wsadata))
{
cout << "WSAStartup failed" << endl;
}
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(8080);
SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0);
while (1)
{
if (connect(sockfd, (struct sockaddr*)& serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
{
cout << "connect failed continue " << "errorCode = " << WSAGetLastError() << endl;;
Sleep(1000);
continue;
}
cout << "connect success";
break;
}
//定义长度变量
int send_len = 0;
int recv_len = 0;
//定义发送缓冲区和接受缓冲区
char send_buf[100];
char recv_buf[100];
thread recvThread = thread([&]
{
while (1)
{
recv_len = recv(sockfd, recv_buf, 100, 0);
if (recv_len < 0) {
cout << "接受信息失败" << endl;
break;
}
else {
cout << "服务端对你说:" << recv_buf << endl;
}
}
});
while (1)
{
cout << "请输入发给服务端的信息:\n";
cin >> send_buf;
send_len = send(sockfd, send_buf, 100, 0);
if (send_len < 0) {
cout << "发送失败!" << endl;
break;
}
}
WSACleanup();
system("pause");
return 0;
}
运行结果截图:
1.
2.
3.
4.
------------------------------------------------end-----------------------------------------------------------------
备注:写的不够细节,例如:select函数的用法,使用时需注意的地方,实现原理。以及与poll,epoll的区别,后续会进行补充