需要的头文件包含c库、Unix系统库和envent事件库
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/* Required by event.h. */
#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>
#include "queue.h"
/* Libevent. */
#include <event2/event.h>
#include <event2/event_struct.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
主要功能函数:
/* 监听端口. */
#define SERVER_PORT 5555
/* The libevent event base. In libevent 1 you didn't need to worry
* about this for simple programs, but its used more in the libevent 2
* API. */
static struct event_base *evbase;
/**
* 用于客户端特定数据的结构体.
*
* This also includes the tailq entry item so this struct can become a
* member of a tailq - 所有连接的客户端的链表.
*/
struct client {
/* 客户端套接字. */
int fd;
/* 客户端的缓冲区事件. */
struct bufferevent *buf_ev;
/*
* 它保存了中下一个和前一个条目的指针
*尾部队列
*/
TAILQ_ENTRY(client) entries;
};
/**
* 所有客户的头--就是迭代发送接收到的消息到所有连接的客户端
*/
TAILQ_HEAD(, client) client_tailq_head;
/**
* 将套接字设置为非阻塞模式。
*/
int setnonblock(int fd)
{
int flags;
flags = fcntl(fd, F_GETFL);
if (flags < 0)
return flags;
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0)
return -1;
return 0;
}
/**
* 当有数据要读时由libevent调用.
*/
void buffered_on_read(struct bufferevent *bev, void *arg)
{
struct client *this_client = arg;
struct client *client;
uint8_t data[8192];
size_t n;
/*每次读取,并将其发送到所有连接的客户端. */
for (;;) {
n = bufferevent_read(bev, data, sizeof(data));
if (n <= 0) {
printf("read:%s\n",data);
/* Done. */
break;
}
/* 发送数据到所有连接的客户端,除了发送数据的客户端 */
TAILQ_FOREACH(client, &client_tailq_head, entries) {
if (client != this_client) {
bufferevent_write(client->buf_ev, data, n);
}
}
}
}
/**
* 当底层套接字出现错误时由libevent指定描述符。
*/
void buffered_on_error(struct bufferevent *bev, short what, void *arg)
{
struct client *client = (struct client *)arg;
if (what & BEV_EVENT_EOF) {
/* 客户端断开连接,删除读取事件和释放客户端结构。 */
printf("Client disconnected.\n");
}
else {
warn("Client socket error, disconnecting.\n");
}
/* 将客户端从尾部移开. */
TAILQ_REMOVE(&client_tailq_head, client, entries);
bufferevent_free(client->buf_ev);
close(client->fd);
free(client);
}
/**
* 当有连接时,libevent将调用这个函数准备好被接受。
*/
void on_accept(int fd, short ev, void *arg)
{
int client_fd;
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
struct client *client;
client_fd = accept(fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0) {
warn("accept failed");
return;
}
/* 将客户端套接字设置为非阻塞模式. */
if (setnonblock(client_fd) < 0)
warn("failed to set client socket non-blocking");
/* 已经连接了一个客户端,创建一个新的客户端对象. */
client = calloc(1, sizeof(*client));
if (client == NULL)
err(1, "malloc failed");
client->fd = client_fd;
client->buf_ev = bufferevent_socket_new(evbase, client_fd, 0);
bufferevent_setcb(client->buf_ev, buffered_on_read, NULL,
buffered_on_error, client);
/* 必须在回调之前启用它 */
bufferevent_enable(client->buf_ev, EV_READ);
/*将新客户端添加到tailq中. */
TAILQ_INSERT_TAIL(&client_tailq_head, client, entries);
printf("Accepted connection from %s\n",
inet_ntoa(client_addr.sin_addr));
}
main函数:
int main(int argc, char **argv)
{
int listen_fd;
struct sockaddr_in listen_addr;
struct event ev_accept;
int reuseaddr_on;
/*初始化libevent. */
evbase = event_base_new();
/* 初始化tailq. */
TAILQ_INIT(&client_tailq_head);
/* 创建监听套接字. */
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0)
err(1, "listen failed");
memset(&listen_addr, 0, sizeof(listen_addr));
listen_addr.sin_family = AF_INET;
listen_addr.sin_addr.s_addr = INADDR_ANY;
listen_addr.sin_port = htons(SERVER_PORT);
if (bind(listen_fd, (struct sockaddr *)&listen_addr,
sizeof(listen_addr)) < 0)
err(1, "bind failed");
if (listen(listen_fd, 5) < 0)
err(1, "listen failed");
reuseaddr_on = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on,
sizeof(reuseaddr_on));
/* 将套接字设置为非阻塞,这在事件中很重要基于libevent编程. */
if (setnonblock(listen_fd) < 0)
err(1, "failed to set server socket to non-blocking");
/* 创建一个读事件当客户端连接时被通知 */
event_assign(&ev_accept, evbase, listen_fd, EV_READ|EV_PERSIST,
on_accept, NULL);
event_add(&ev_accept, NULL);
/* 启动事件循环. */
event_base_dispatch(evbase);
return 0;
}
为了编译方便可以使用Makefile
CFLAGS = -I$(LIBEVENT)/include -Wall
LIBS = -levent -lrt
#$(LIBEVENT)/.libs/libevent.a
#有使用enent_use_pthread()时要链接 -levent_pthreads 库
chat-server: chat-server.c
$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
clean:
rm -f chat-server *~
其中使用的队列是自己编的函数如果要用系统的队列函数需要更改一点代码。