使用select端口复用技术,实现一个聊天程序的服务器端的C++代码
时间: 2024-05-14 18:19:46 浏览: 104
一、select 端口复用技术介绍
select 函数可以实现多路复用输入/输出,即一个进程可以同时监听多个文件句柄,以便在任意一个文件句柄上完成输入输出操作。
select 函数使用一个 fd_set 类型的数组来检测一组文件描述符的状态。fd_set 变量的具体定义如下:
```
typedef struct fd_set {
u_int fds_bits[howmany(FD_SETSIZE, NFDBITS)];
} fd_set;
```
其中 howmany 和 NFDBITS 定义在头文件 sys/param.h 中,其功能主要是做一些与文件描述符相关的计算。
select 函数的原型如下:
```
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
```
参数 nfds: 所有描述符的最大值加 1.
参数 readfds, writefds, exceptfds 分别指向可读、可写和异常事件的 fd_set 集合。
参数 timeout 为等待该文件描述符集合变化的最大超时时间。如果为 NULL,则函数会一直阻塞直到有描述符集合中的事件发生。
成功时,该函数会返回待处理事件的文件数量。如果在超时时间内没有检测到任何文件,则返回 0,如果函数发生错误,返回 -1。
二、聊天程序服务器端C代码实现
下面是一个基于 select 的服务器端聊天程序的 C 代码实现,其中包含了接收和转发客户端发送消息的功能:
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/time.h>
#define MAX_CLIENTS 5
#define BUFFER_SIZE 1024
int server_socket;
void die(char *msg) {
perror(msg);
exit(EXIT_FAILURE);
}
int init_server(char *ip, int port) {
int sockfd;
struct sockaddr_in servaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
die("socket");
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
if (ip == NULL) {
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
} else {
if (inet_pton(AF_INET, ip, &servaddr.sin_addr) <= 0) {
die("inet_pton");
}
}
if (bind(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
die("bind");
}
if (listen(sockfd, MAX_CLIENTS) < 0) {
die("listen");
}
return sockfd;
}
int main(int argc, char **argv) {
struct sockaddr_in client_addr;
int maxfd = -1, max_i = -1;
char buffer[BUFFER_SIZE];
fd_set rset, all_set;
int clients[MAX_CLIENTS];
int i, maxi = -1;
for (i = 0; i < MAX_CLIENTS; i++) {
clients[i] = -1;
}
server_socket = init_server(NULL, 12345);
FD_ZERO(&all_set);
FD_SET(server_socket, &all_set);
maxfd = server_socket;
while (1) {
rset = all_set;
int nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
if (nready < 0) {
die("select");
}
if (FD_ISSET(server_socket, &rset)) {
socklen_t len = sizeof(client_addr);
int connfd;
if ((connfd = accept(server_socket, (struct sockaddr*) &client_addr, &len)) < 0) {
die("accept");
}
for (i = 0; i < MAX_CLIENTS; i++) {
if (clients[i] < 0) {
clients[i] = connfd;
break;
}
}
if (i == MAX_CLIENTS) {
fprintf(stderr, "too many clients\n");
close(connfd);
continue;
}
printf("New client connected from %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
FD_SET(connfd, &all_set);
if (connfd > maxfd) {
maxfd = connfd;
}
if (i > maxi) {
maxi = i;
}
if (--nready <=0) {
continue;
}
}
for (i = 0; i <= maxi; i++) {
int sockfd = clients[i];
if (sockfd < 0) {
continue;
}
if (FD_ISSET(sockfd, &rset)) {
ssize_t n = recv(sockfd, buffer, BUFFER_SIZE-1, 0);
if (n < 0) {
die("recv");
}
if (n == 0) {
FD_CLR(sockfd, &all_set);
close(sockfd);
clients[i] = -1;
} else {
buffer[n] = '\0';
printf("Received message: %s", buffer);
for (int j = 0; j <= maxi; j++) {
int dest_sockfd = clients[j];
if (dest_sockfd < 0 || dest_sockfd == sockfd) {
continue;
}
if (send(dest_sockfd, buffer, strlen(buffer), 0) < 0) {
die("send");
}
}
}
if (--nready <= 0) {
break;
}
}
}
}
return EXIT_SUCCESS;
}
```
该程序使用了 MAX_CLIENTS 个 TCP 连接,对于每个连接在缓冲区的最大长度为 BUFFER_SIZE。程序使用了一个循环来监听所有连接,如果有连接上来,程序将把它加入到待监听文件句柄集合中。程序每次循环都需要调用 select 函数来检测每个连接上事件的发生情况,接着程序根据事件类型来进行相应的处理。如果是有连接到来,程序就将该连接加入到待监听的文件句柄集合中,并记录该连接的描述符,在最大描述符的范围内更新相应的值;如果是有客户端发来消息,程序就遍历所有连接并将消息转发到其他客户端上。
阅读全文