开封景区网站建设项目方案烟台高端品牌网站建设
目录
第一章:select基础
1.1 select概述
1.2 select的使用场景
1.3 select的函数原型
1.4 fd_set和timeval结构体
1.5 select的基本流程
1.6 select的局限性
1.7 小结
第二章:select的高级使用
2.1 处理select的超时
2.1.1 设置永久等待
2.1.2 设置非阻塞等待
2.2 避免select的局限性
2.2.1 使用poll代替select
2.2.2 使用epoll(仅限于Linux)
2.3 select的错误处理
2.4 select在多线程环境中的应用
2.5 小结
第三章:select的应用实例
3.1 简单的echo服务器
3.1.1 echo服务器的实现
3.2 并发聊天服务器
3.3 小结
第一章:select基础
1.1 select概述
select是C语言中用于IO多路复用的一个系统调用,它允许程序监视多个文件描述符(通常是网络套接字)的状态,以便在某个文件描述符准备好进行IO操作时获得通知。select的这种能力使得单个线程能够管理多个并发连接,从而实现高效的并发处理。
1.2 select的使用场景
select通常用于以下场景:
- 当需要同时处理多个套接字时,例如在网络服务器中。
- 当需要同时监听标准输入和其他套接字时。
- 当需要等待多个套接字事件(如读就绪、写就绪、异常)时。
1.3 select的函数原型
select的函数原型如下:
#include <sys/select.h>int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
- nfds:监视的文件描述符集合中最大文件描述符加1。
- readfds:指向fd_set结构的指针,用于监视读就绪的文件描述符。
- writefds:指向fd_set结构的指针,用于监视写就绪的文件描述符。
- exceptfds:指向fd_set结构的指针,用于监视异常的文件描述符。
- timeout:指向timeval结构的指针,用于设置select的超时时间。
1.4 fd_set和timeval结构体
fd_set是一个位掩码,用于表示文件描述符集合。在select中,它用来指定需要监视的文件描述符。timeval结构体用于指定select函数的超时时间。
struct timeval {long tv_sec; // 秒long tv_usec; // 微秒
};
1.5 select的基本流程
使用select的基本流程如下:
- 初始化fd_set集合,将需要监视的文件描述符加入到相应的集合中。
- 调用select函数,等待文件描述符就绪或超时。
- 检查select返回值,处理就绪的文件描述符。
- 根据需要重复以上步骤。
1.6 select的局限性
select虽然是一个强大的工具,但它也有局限性:
- fd_set集合的大小受限于系统定义的FD_SETSIZE,通常为1024,这意味着select不能有效地处理超过1024个并发连接。
- 每次调用select都需要重新设置文件描述符集合,这在有大量文件描述符时效率较低。
- select返回后,需要遍历整个文件描述符集合来确定哪些文件描述符就绪,这在高并发场景下可能成为性能瓶颈。
1.7 小结
本章介绍了select的基础知识,包括select的使用场景、函数原型、基本流程以及局限性。通过本章的学习,读者应该对select有了初步的了解,为后续深入学习select的高级使用和应用实例打下了基础。在下一章中,我们将探讨select的高级使用技巧,包括如何处理select的超时、如何避免select的局限性等。
第二章:select的高级使用
2.1 处理select的超时
在select中,超时参数timeout
可以用来指定select函数等待的最大时间。如果在这个时间内没有任何文件描述符就绪,select将返回0。正确处理select的超时对于实现高效的网络程序至关重要。
2.1.1 设置永久等待
如果要使select永久等待,直到至少一个文件描述符就绪,可以将timeout
设置为NULL:
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;int ret = select(nfds, &readfds, &writefds, &exceptfds, NULL);
2.1.2 设置非阻塞等待
如果要使select在指定的时间后超时,可以设置timeout
结构体的tv_sec
和tv_usec
字段:
struct timeval timeout;
timeout.tv_sec = 5; // 等待5秒
timeout.tv_usec = 0; // 微秒int ret = select(nfds, &readfds, &writefds, &exceptfds, &timeout);
2.2 避免select的局限性
虽然select有局限性,但可以通过一些技巧来避免或减轻这些问题。
2.2.1 使用poll代替select
poll是另一个IO多路复用函数,它没有select的文件描述符数量限制,并且提供了更高效的接口来处理大量的文件描述符。
2.2.2 使用epoll(仅限于Linux)
epoll是Linux特有的IO多路复用机制,它解决了select和poll的一些问题,如文件描述符数量限制和效率问题。epoll使用事件驱动的机制,可以高效地处理大量的文件描述符。
2.3 select的错误处理
select在调用过程中可能会遇到错误,常见的错误包括:
- EBADF:文件描述符集合中有无效的文件描述符。
- EINTR:select被信号中断。
- EINVAL:select的参数无效,如
nfds
小于0或timeout
的值非法。
正确处理这些错误对于确保程序的健壮性非常重要。
2.4 select在多线程环境中的应用
在多线程环境中,select可以与线程结合起来使用,以提高程序的性能和并发处理能力。例如,可以在一个线程中使用select来监听多个套接字,然后在其他线程中处理就绪的套接字。
2.5 小结
本章介绍了select的高级使用,包括处理select的超时、避免select的局限性、错误处理以及在多线程环境中的应用。通过本章的学习,读者应该能够更加熟练地使用select,并能够应对一些复杂的网络编程场景。在下一章中,我们将通过一些实际的应用实例来进一步巩固对select的理解。
第三章:select的应用实例
3.1 简单的echo服务器
echo服务器是一个基本的网络编程实例,它接收客户端发送的数据并将其原样返回。使用select可以轻松实现一个多客户端的echo服务器。
3.1.1 echo服务器的实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024int main() {int server_fd, client_fd, max_fd;struct sockaddr_in server_addr, client_addr;socklen_t client_addr_len;fd_set readfds, temp_readfds;char buffer[BUFFER_SIZE];int client_sockets[MAX_CLIENTS] = {0};// 创建套接字server_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 绑定地址和端口memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = INADDR_ANY;server_addr.sin_port = htons(12345);if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// 监听if (listen(server_fd, MAX_CLIENTS) < 0) {perror("listen failed");exit(EXIT_FAILURE);}// 初始化fd_setFD_ZERO(&readfds);FD_SET(server_fd, &readfds);max_fd = server_fd;while (1) {temp_readfds = readfds;// select等待就绪文件描述符int activity = select(max_fd + 1, &temp_readfds, NULL, NULL, NULL);if (activity < 0) {perror("select failed");exit(EXIT_FAILURE);}// 检查服务器套接字if (FD_ISSET(server_fd, &temp_readfds)) {client_addr_len = sizeof(client_addr);client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);if (client_fd < 0) {perror("accept failed");exit(EXIT_FAILURE);}printf("New client connected, socket fd is %d\n", client_fd);// 添加到客户端套接字数组for (int i = 0; i < MAX_CLIENTS; i++) {if (client_sockets[i] == 0) {client_sockets[i] = client_fd;break;}}// 更新最大文件描述符if (client_fd > max_fd) {max_fd = client_fd;}}// 检查客户端套接字for (int i = 0; i < MAX_CLIENTS; i++) {client_fd = client_sockets[i];if (client_fd > 0 && FD_ISSET(client_fd, &temp_readfds)) {memset(buffer, 0, BUFFER_SIZE);int bytes_received = recv(client_fd, buffer, BUFFER_SIZE, 0);if (bytes_received <= 0) {// 客户端断开连接close(client_fd);client_sockets[i] = 0;} else {// 发送回显数据send(client_fd, buffer, bytes_received, 0);}}}}return 0;
}
3.2 并发聊天服务器
聊天服务器是一个稍微复杂的应用实例,它允许多个客户端连接并相互发送消息。使用select可以同时处理多个客户端的连接和消息传递。
3.2.1 聊天服务器的实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <pthread.h>#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024typedef struct {int client_fd;struct sockaddr_in client_addr;
} client_info;client_info clients[MAX_CLIENTS];
int num_clients = 0;
pthread_mutex_t lock;void *client_handler(void *arg) {client_info *client = (client_info *)arg;char buffer[BUFFER_SIZE];int bytes_received;while (1) {memset(buffer, 0, BUFFER_SIZE);bytes_received = recv(client->client_fd, buffer, BUFFER_SIZE, 0); if (bytes_received <= 0) { // 客户端断开连接 break; }// 广播消息给所有客户端for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i].client_fd > 0) {send(clients[i].client_fd, buffer, bytes_received, 0);}}}// 清理客户端信息pthread_mutex_lock(&lock);for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i].client_fd == client->client_fd) {clients[i].client_fd = 0;num_clients--;break;}}pthread_mutex_unlock(&lock);close(client->client_fd);free(client);return NULL;
}int main() { int server_fd, client_fd; struct sockaddr_in server_addr, client_addr; socklen_t client_addr_len; fd_set readfds, temp_readfds; int max_fd; pthread_t thread_id;// 初始化互斥锁pthread_mutex_init(&lock, NULL);// 创建套接字server_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 绑定地址和端口memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = INADDR_ANY;server_addr.sin_port = htons(12345);if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// 监听if (listen(server_fd, MAX_CLIENTS) < 0) {perror("listen failed");exit(EXIT_FAILURE);}// 初始化fd_setFD_ZERO(&readfds);FD_SET(server_fd, &readfds);max_fd = server_fd;while (1) {temp_readfds = readfds;// select等待就绪文件描述符int activity = select(max_fd + 1, &temp_readfds, NULL, NULL, NULL);if (activity < 0) {perror("select failed");exit(EXIT_FAILURE);}// 检查服务器套接字if (FD_ISSET(server_fd, &temp_readfds)) {client_addr_len = sizeof(client_addr);client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);if (client_fd < 0) {perror("accept failed");exit(EXIT_FAILURE);}printf("New client connected, socket fd is %d\n", client_fd);// 添加客户端信息pthread_mutex_lock(&lock);for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i].client_fd == 0) {clients[i].client_fd = client_fd;clients[i].client_addr = client_addr;num_clients++;break;}}pthread_mutex_unlock(&lock);// 创建线程处理新客户端client_info *new_client = malloc(sizeof(client_info));*new_client = clients[num_clients - 1];pthread_create(&thread_id, NULL, client_handler, (void *)new_client);}}// 清理资源close(server_fd);pthread_mutex_destroy(&lock);return 0;
}
3.3 小结
本章通过两个应用实例展示了select在实际网络编程中的应用。第一个实例是一个简单的echo服务器,它使用select来同时处理多个客户端的连接,并将客户端发送的数据原样返回。第二个实例是一个并发聊天服务器,它不仅使用select来处理多个客户端的连接,还使用多线程来同时处理客户端的消息,实现了广播功能。这些实例展示了select在多客户端网络应用中的强大功能,并为进一步探索网络编程提供了基础。