I/O复用技术主要就是select函数的使用。
使用select函数可以同时监视多个文件描述符。当然,监视文件描述符可以视为监视套接字。此时首先需要将要监视的文件描述符集中到一起。集中时也要按照监视项(接收、传输、异常)进行区分,即按照上述3种监视项分成3类。
使用fd_set数组变量执行此项操作,如图。该数组时存有0和1的位数组。(liux的是维数组,windows的是句柄数组,这并没有影响,用法和下面宏的用法都是一样的,前面说的linux文件描述符脑海中自动替换成套接字句柄即可)。
如果该位置位1,则表示该文件描述符时监视对象。
在fd_set变量中注册或更改值得操作都由下列宏完成:
代码:
客户端
#include<iostream>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#define bufsize 1024
using namespace std;
void main() {
WSADATA wsadata;
SOCKET clientSocket;
SOCKADDR_IN serverAddr;
int recvCnt;
char message[bufsize] = "\0";
if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
cout << "WSAStartup() error" << endl;
if ((clientSocket = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
cout << "socket() error" << endl;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
serverAddr.sin_port = htons(9999);
if(connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr))==SOCKET_ERROR)
cout<<"connect() error"<<endl;
while (1) {
cout << "输入Q或q退出:";
cin >> message;
if (!strcmp(message, "Q") || !strcmp(message, "q")) break;
send(clientSocket, message, strlen(message), 0);
memset(message, 0, sizeof(message));
recv(clientSocket, message, bufsize, 0);
cout << "服务器结果:" << message << endl;
}
closesocket(clientSocket);
WSACleanup();
}
服务器端
#include<iostream>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#define bufsize 1024
using namespace std;
void main() {
WSADATA wsadata;
SOCKET serverSocket,clientSocket;
int szClientAddr,fdnum,str_len;
SOCKADDR_IN serverAddr, clientAddr;
fd_set reads, cpyReads;
TIMEVAL timeout;
char message[bufsize] = "\0";
if(WSAStartup(MAKEWORD(2, 2), &wsadata)!=0)
cout<<"WSAStartup() error"<<endl;
serverSocket = socket(PF_INET, SOCK_STREAM, 0);
if(serverSocket == INVALID_SOCKET)
cout<<"socket() error"<<endl;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(9999);
if (bind(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
cout << "bind () error" << endl;
listen(serverSocket, 5);
cout << "服务器启动成功!" << endl;
FD_ZERO(&reads); //所有初始化为0
FD_SET(serverSocket, &reads); //将服务器套接字存入
while (1) {
cpyReads = reads;
timeout.tv_sec = 5; //5秒
timeout.tv_usec = 5000; //5000毫秒
//找出监听中发出请求的套接字
if ((fdnum = select(0, &cpyReads, 0, 0, &timeout)) == SOCKET_ERROR)
break;
if (fdnum == 0) {
cout << "time out!" << endl;
continue;
}
for (unsigned int i = 0; i < reads.fd_count; i++) {
if (FD_ISSET(reads.fd_array[i], &cpyReads)) { //判断是否为发出请求的套接字
if (reads.fd_array[i] == serverSocket) { //是否为服务器套接字
szClientAddr = sizeof(clientAddr);
clientSocket = accept(serverSocket, (SOCKADDR*)&clientAddr, &szClientAddr);
if (clientSocket == INVALID_SOCKET) cout << "accept() error" << endl;
FD_SET(clientSocket, &reads);
cout << "连接的客户端是:" << clientSocket << endl;
}
else {//否 就是客户端
str_len = recv(reads.fd_array[i], message, bufsize - 1, 0);
if (str_len == 0) {//根据接受数据的大小 判断是否是关闭
FD_CLR(reads.fd_array[i], &reads); //清除数组中该套接字
closesocket(cpyReads.fd_array[i]);
cout << "关闭的客户端是:" << cpyReads.fd_array[i] << endl;
}
else {
send(reads.fd_array[i], message, str_len, 0);
}
}
}
}
}
closesocket(clientSocket);
closesocket(serverSocket);
WSACleanup();
}