《TCP/IP网络编程》第12章 I/O复用
理解复用
1个通信频道中传递多个数据(信号)的技术,用最少物理要素传递最多数据用于提高物理设备效率的技术。
可分为时(time)分复用技术和频(frequency)分复用技术。
select函数
select函数将多个文件描述符集中到一起统一监视:
- 是否存在套接字接收了数据?
- 无需阻塞传输数据的套接字有哪些?
- 哪些套接字发生了异常?
监视项称为事件,发送监视项对应情况称为发生了事件。
调用方法和顺序
- 设置文件描述符
需要监视的文件描述符(关联套接字),用fd_set数组变量进行设置。
//初始化
FD_ZERO(fd_set *fdset)
//注册
FD_SET(int fd, fd_set *fdset)
//清除
FD_CLR(int fd, fd_set *fdset)
//是否包含
FD_ISSET(int fd, fd_set *fdset)
- 指定监视范围及设置超时
#include <sys/select.h>
#include <sys/time.h>
//成功返回发生事件的文件描述符数,失败-1
int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
//maxfd,监视文件描述符数量,一般取监视的最大文件描述符的值。
//readset,用于监视存在待读取数据的文件描述符。
//writeset,用于监视存在可传输无阻塞数据的文件描述符。
//exceptset,用于监视发生异常的文件描述符。
//timeout,超时信息。
//select只有在监视的文件描述符发生变化时(否则阻塞)才返回。timeval设置最长等待时间(NULL阻塞),超时后返回0。
struct timeval {
long tv_sec;//seconds
long tv_usec;//microseconds
};
- 调用select函数后查看调用结果
select返回值大于0,必定存在监视文件描述符中发生了指定事件。
for(int i=0; i<=fd_max; i++) {
if(FD_ISSET(i, &readset)) {
} else if(FD_ISSET(i, &writeset)) {
} else if(FD_ISSET(i, &exceptset)) {
}
}
select示例
12.select_linux.c
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>
#define BUF_SIZE 30
int main(int argc, char *argv[])
{
fd_set reads;
FD_ZERO(&reads);
FD_SET(0, &reads); // 0 is standard input(console)
while (1)
{
fd_set temps = reads;
struct timeval timeout = {5, 0};
// 调用select后,timeout的tv_sec和tv_usec将被替换为超时前剩余时间
int result = select(1, &temps, 0, 0, &timeout);
if (result == -1)
{
puts("select() error!");
break;
}
else if (result == 0)
{
puts("Time-out!");
continue;
}
else
{
if (FD_ISSET(0, &temps))
{
char buf[BUF_SIZE];
int str_len = read(0, buf, BUF_SIZE - 1);
buf[str_len] = 0;
printf("message from console: %s", buf);
}
}
}
return 0;
}
// gcc 12.select_linux.c -o 12.select_linux && ./12.select_linux
select实现I/O复用服务器
12.echo_selectserver_linux.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>
#define PORT 9999
#define BUF_SIZE 100
void error_handling(const char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int main(int argc, char *argv[])
{
int serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if (serv_sock == -1)
error_handling("socket() error");
int opt = 1;
if (setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt)) == -1)
error_handling("setsockopt() error");
socklen_t addr_size = sizeof(struct sockaddr_in);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, addr_size);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(PORT);
if (bind(serv_sock, (struct sockaddr *)&serv_addr, addr_size) == -1)
error_handling("bind() error");
if (listen(serv_sock, 5) == -1)
error_handling("listen() error");
fd_set reads;
FD_ZERO(&reads);
FD_SET(serv_sock, &reads);
int fd_max = serv_sock;
while (1)
{
fd_set cpy_reads = reads;
struct timeval timeout = {5, 5000};
//调用select后,timeout的tv_sec和tv_usec将被替换为超时前剩余时间
int fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout);
if (fd_num == -1)
break;
else if (fd_num == 0)
continue;
for (int i = 0; i <= fd_max; i++)
{
if (FD_ISSET(i, &cpy_reads))
{
if (i == serv_sock)
{
struct sockaddr_in clnt_addr;
int clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &addr_size);
FD_SET(clnt_sock, &reads);
if (fd_max < clnt_sock)
fd_max = clnt_sock;
printf("connected client: %d\n", clnt_sock);
}
else
{
char buf[BUF_SIZE];
int str_len = read(i, buf, BUF_SIZE);
if (str_len == 0)
{
close(i);
FD_CLR(i, &reads);
printf("closed client: %d\n", i);
}
else
{
write(i, buf, str_len);
}
}
}
}
}
close(serv_sock);
return 0;
}
// gcc 12.echo_selectserver_linux.c -o 12.echo_selectserver_linux && ./12.echo_selectserver_linux
Windows实现
#include <winsock2.h>
//成功0,失败-1
//nfds无用
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *excepfds, const struct timeval *timeout);
typedef struct timeval {
long tv_set;
long tv_usec;
} TIMEVAL;
//Windows套接字句柄并非从0开始,且无规律
typedef struct fd_set {
u_int fd_count;
SOCKET fd_array[FD_SETSIZE];
} fd_set;
12.echo_selectserver_win.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#define PORT 9999
#define BUF_SIZE 1024
void ErrorHanding(const char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int main(int argc, char *argv[])
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHanding("WSAStartup() error!");
SOCKET hServSock = socket(PF_INET, SOCK_STREAM, 0);
if (hServSock == INVALID_SOCKET)
ErrorHanding("socket() error");
int opt = 1;
if (setsockopt(hServSock, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)) < 0)
ErrorHanding("setsockopt() error");
int szAddr = sizeof(SOCKADDR_IN);
SOCKADDR_IN servAddr;
memset(&servAddr, 0, szAddr);
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(PORT);
if (bind(hServSock, (SOCKADDR *)&servAddr, szAddr) == SOCKET_ERROR)
ErrorHanding("bind() error");
if (listen(hServSock, 5) == SOCKET_ERROR)
ErrorHanding("listen() error");
fd_set reads; // 记录所有文件描述符(套接字)
FD_ZERO(&reads);
FD_SET(hServSock, &reads); // 加入服务端套接字
while (1)
{
fd_set cpyReads = reads; // 记录可读文件描述符(套接字)
TIMEVAL timeout = {5, 5000};
int fdNum = select(0, &cpyReads, 0, 0, &timeout);
if (fdNum == SOCKET_ERROR)
break;
else if (fdNum == 0)
continue;
SOCKET hClntSock;
for (int i = 0; i < reads.fd_count; i++) // 遍历所有文件描述符
{
if (FD_ISSET(reads.fd_array[i], &cpyReads)) // 当前文件描述符位于cpyReads集合中,可读
{
if (reads.fd_array[i] == hServSock) // 服务端套接字可读,表示有新的客户端连接
{
SOCKADDR_IN clntAddr;
hClntSock = accept(hServSock, (SOCKADDR *)&clntAddr, &szAddr); // 新客户端套接字
FD_SET(hClntSock, &reads); // 加入客户端套接字
printf("connected client: %d\n", hClntSock);
}
else // 客户端套接字可读
{
char buf[BUF_SIZE];
int strLen = recv(reads.fd_array[i], buf, BUF_SIZE - 1, 0); // 从客户端套接字中读取缓冲区
if (strLen == 0)
{
closesocket(reads.fd_array[i]); // 关闭客户端套接字
printf("closed client: %d\n", reads.fd_array[i]);
FD_CLR(reads.fd_array[i], &reads); // 移除客户端套接字
}
else
{
send(reads.fd_array[i], buf, strLen, 0);
}
}
}
}
}
closesocket(hServSock);
WSACleanup();
return 0;
}
// gcc 12.echo_selectserver_win.c -o 12.echo_selectserver_win -lws2_32 && 12.echo_selectserver_win