在C语言中,实现异步Socket服务器主要涉及到非阻塞套接字(non-blocking sockets)、I/O多路复用技术(如select、poll或epoll)以及线程或事件驱动编程,以下是关于如何设计和实现一个基本的异步Socket服务器的详细步骤和示例代码。
1、非阻塞套接字:与阻塞套接字相对,非阻塞套接字在执行I/O操作(如read、write)时,如果操作不能立即完成,不会使调用进程进入阻塞状态,而是立即返回一个错误码(通常是EAGAIN或EWOULDBLOCK),表示操作未完成,需要稍后重试。
2、I/O多路复用:通过使用select、poll或epoll等系统调用,可以同时监控多个文件描述符(包括套接字)的状态变化,从而在一个线程或进程中高效地处理多个并发连接,这些系统调用可以检测哪些套接字有数据可读、可写或异常情况发生。
3、事件驱动编程:基于事件循环的编程模型,当某个事件发生时(如新连接到来、数据可读等),触发相应的回调函数或处理逻辑,这种模型通常与I/O多路复用技术结合使用,以实现高效的异步I/O操作。
1、创建套接字:使用socket()函数创建一个TCP套接字。
2、设置为非阻塞模式:使用fcntl()函数或ioctl()函数将套接字设置为非阻塞模式。
3、绑定端口:使用bind()函数将套接字绑定到一个本地地址和端口号上。
4、开始监听:使用listen()函数开始监听来自客户端的连接请求。
5、I/O多路复用:使用select()、poll()或epoll()函数监控套接字的状态变化,包括新的连接请求、数据可读或可写等。
6、接受连接:当检测到有新的连接请求时,使用accept()函数接受连接,并创建一个新的套接字用于与该客户端通信。
7、读写数据:对于已连接的套接字,使用read()或recv()函数读取客户端发送的数据,并使用write()或send()函数向客户端发送数据,由于套接字是非阻塞的,因此在读写操作时需要检查返回值,以确定操作是否成功或是否需要稍后重试。
8、关闭连接:当通信结束时,使用close()函数关闭套接字连接。
以下是一个使用select()实现的简单异步Socket服务器示例:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/time.h> #include <fcntl.h> #include <errno.h> #define PORT 8080 #define BACKLOG 5 #define BUFFER_SIZE 1024 int make_socket_non_blocking(int sockfd) { int flags = fcntl(sockfd, F_GETFL, 0); if (flags == -1) { perror("fcntl"); return -1; } flags |= O_NONBLOCK; if (fcntl(sockfd, F_SETFL, flags) == -1) { perror("fcntl"); return -1; } return 0; } int main() { int server_fd, new_socket, client_socket[BACKLOG], max_sd, activity, i, valread, sd; int max_clients = BACKLOG; struct sockaddr_in address; char buffer[BUFFER_SIZE]; fd_set readfds; // Initialize all client_socket[] to 0 so not checked for (i = 0; i < max_clients; i++) { client_socket[i] = 0; } // Create a master socket if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // Set master socket to non-blocking mode if (make_socket_non_blocking(server_fd) == -1) { exit(EXIT_FAILURE); } // Type of socket created address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); // Bind the socket to the port 8080 if (bind(server_fd, (struct sockaddr )&address, sizeof(address))<0) { perror("bind failed"); exit(EXIT_FAILURE); } printf("Listener on port %d ", PORT); // Try to specify maximum of 5 pending connections for the master socket if (listen(server_fd, 5) < 0) { perror("listen"); exit(EXIT_FAILURE); } // Accept the incoming connection using select() and non-blocking sockets while (1) { // Clear the socket set FD_ZERO(&readfds); // Add master socket to set FD_SET(server_fd, &readfds); max_sd = server_fd; // Add child sockets to set for (i = 0 ; i < max_clients ; i++) { // Socket descriptor sd = client_socket[i]; // If valid socket descriptor then add to read list if (sd > 0) FD_SET(sd, &readfds); // Highest file descriptor number, need it for the select function if (sd > max_sd) max_sd = sd; } // Wait for an activity on one of the sockets, timeout is NULL, wait indefinitely activity = select(max_sd + 1, &readfds, NULL, NULL, NULL); if ((activity < 0) && (errno != EINTR)) { printf("select error"); } // If something happened on the master socket, then its an incoming connection if (FD_ISSET(server_fd, &readfds)) { if ((new_socket = accept(server_fd, (struct sockaddr )&address, (socklen_t)&address))<0) { perror("accept"); exit(EXIT_FAILURE); } // Inform user of socket number used in send and receive commands printf("New connection, socket fd is %d, ip is: %s, port: %d ", new_socket, inet_ntoa(address.sin_addr), ntohs(address.sin_port)); // Send new connection greeting message if (send(new_socket, "Welcome to the server! ", 22, 0) != 22) { perror("send"); } // Add new socket to array of sockets for (i = 0; i < max_clients; i++) { // If position is empty if (client_socket[i] == 0) { client_socket[i] = new_socket; printf("Adding to list of sockets at index %d ", i); break; } } } // Else it's some IO operation on some other socket for (i = 0; i < max_clients; i++) { sd = client_socket[i]; if (FD_ISSET(sd, &readfds)) { // Check if it was for closing, and also read the incoming message if ((valread = read(sd, buffer, BUFFER_SIZE)) == 0) { // Somebody disconnected, get details and print getpeername(sd, (struct sockaddr)&address, (socklen_t)&address); printf("Host disconnected, ip %s, port %d ", inet_ntoa(address.sin_addr), ntohs(address.sin_port)); // Close the socket and mark as 0 in list for reuse close(sd); client_socket[i] = 0; } else { // Set the string terminating NULL byte on the end of the data read buffer[valread] = '