实践–聊天室
使用poll函数实现IO多路复用,实现一个聊天室。
目录结构
代码实现
- Makefile
CC = gcc
CFLAGS = -Iinclude -Wall -g
SRC_DIR = src
INC_DIR = include
OBJ_DIR = obj
SRCS = $(wildcard $(SRC_DIR)/*.c)
OBJS = $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(SRCS))
TARGET = chat_server
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
$(CC) $(CFLAGS) -c -o $@ $^
$(OBJ_DIR):
mkdir -p $(OBJ_DIR)
clean:
rm -rf $(OBJ_DIR) chat_server
.PHONY: all clean
server.h
#ifndef SERVER_H
#define SERVER_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <errno.h>
#include <arpa/inet.h>
#include <string.h>
#define PORT 8099
#define MAXCLIENTS 100
int create_server_socket();
#endif // SERVER_H
client_handler.h
#ifndef CLIENT_HANDLER_H
#define CLIENT_HANDLER_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#define BUFFER_SIZE 1024
void handle_new_connection(int server_socket, struct pollfd *fds, int *nfds);
void handle_client_message(struct pollfd *fds, int *nfds, int i);
void broadcast_message(struct pollfd *fds, int nfds, int sender_fd, const char *message, ssize_t message_len);
#endif
server.c
#include "server.h"
#include "client_handler.h"
int create_server_socket()
{
int server_socket = socket(PF_INET, SOCK_STREAM, 0);
if (server_socket == -1)
{
perror("socket");
exit(EXIT_FAILURE);
}
struct sockaddr_in server_addr;
server_addr.sin_family = PF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
if(bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr)) == -1) {
perror("bind");
close(server_socket);
exit(EXIT_FAILURE);
}
if(listen(server_socket,10) == -1) {
perror("listen");
close(server_socket);
exit(EXIT_FAILURE);
}
return server_socket;
}
client_handler.c
#include "client_handler.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
void handle_new_connection(int server_socket, struct pollfd *fds, int *nfds)
{
struct sockaddr_in client_addr;
socklen_t clientaddr_len = sizeof(client_addr);
int client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &clientaddr_len);
if (client_socket == -1)
{
perror("accept");
exit(EXIT_FAILURE);
}
else
{
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
printf("New connection accepted from %s:%d\n", client_ip, ntohs(client_addr.sin_port));
char newbuffer[BUFFER_SIZE];
sprintf(newbuffer,"Welcome,Friends,You are login in my server now,your ip is:%s\n", client_ip);
send(client_socket,newbuffer,strlen(newbuffer),0);
fds[*nfds].fd = client_socket;
fds[*nfds].events = POLLIN;
(*nfds)++;
}
}
void handle_client_message(struct pollfd *fds, int *nfds, int i)
{
char buffer[BUFFER_SIZE];
ssize_t bytes_received = recv(fds[i].fd, buffer, BUFFER_SIZE - 1, 0);
if (bytes_received > 0)
{
buffer[bytes_received] = '\0';
printf("Received message: %s\n", buffer);
// 获取发送者的 IP 和端口
struct sockaddr_in sender_addr;
socklen_t addr_len = sizeof(sender_addr);
getpeername(fds[i].fd, (struct sockaddr *)&sender_addr, &addr_len);
char sender_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &sender_addr.sin_addr, sender_ip, sizeof(sender_ip));
int sender_port = ntohs(sender_addr.sin_port);
// 计算前缀长度和剩余空间
const char *prefix_format = "From %s:%d: ";
int prefix_len = snprintf(NULL, 0, prefix_format, sender_ip, sender_port);
int remaining_space = BUFFER_SIZE - prefix_len - 1;
// 确保 buffer 不会超过 remaining_space
if (bytes_received > remaining_space)
{
bytes_received = remaining_space;
buffer[bytes_received] = '\0'; // 确保 buffer 以空字符结尾
}
// 创建新的消息缓冲区并添加发送者的 IP 和端口信息
char newbuffer[BUFFER_SIZE];
snprintf(newbuffer, sizeof(newbuffer), prefix_format, sender_ip, sender_port);
strncat(newbuffer, buffer, remaining_space);
broadcast_message(fds, *nfds, fds[i].fd, newbuffer, strlen(newbuffer));
}
else if (bytes_received == 0)
{
printf("Client disconnected\n");
close(fds[i].fd);
fds[i] = fds[*nfds - 1];
(*nfds)--;
}
else
{
perror("recv");
close(fds[i].fd);
fds[i] = fds[*nfds - 1];
(*nfds)--;
}
}
void broadcast_message(struct pollfd *fds, int nfds, int sender_fd, const char *message, ssize_t message_len)
{
for (int i = 1; i < nfds; i++)
{
if (fds[i].fd != sender_fd)
{
send(fds[i].fd, message, message_len, 0);
}
}
}
main.c
#include "server.h"
#include "client_handler.h"
#define MAX_CLIENTS 100
int main() {
int server_socket = create_server_socket(PORT);
struct pollfd fds[MAX_CLIENTS];
int nfds = 1;
fds[0].fd = server_socket;
fds[0].events = POLLIN;
printf("Server listening on port %d\n", PORT);
while (1) {
int poll_count = poll(fds, nfds, -1);
if (poll_count == -1) {
perror("poll");
close(server_socket);
exit(EXIT_FAILURE);
}
for (int i = 0; i < nfds; i++) {
if (fds[i].revents & POLLIN) {
if (fds[i].fd == server_socket) {
handle_new_connection(server_socket, fds, &nfds);
} else {
handle_client_message(fds, &nfds, i);
}
}
}
}
close(server_socket);
return 0;
}
演示