- 2019-11-19
- 00:19:02
参考大佬: https://github.com/LinHaoo/chat
- Makefile:
- all:server client
- server:server.cpp
- g++ $^ -o [email protected]
- client:client.cpp
- g++ $^ -o [email protected]
- clean:
- rm server client
- utility.h
- #ifndef CHAT_UTILITY_H
- #define CHAT_UTILITY_H
- #include <iostream>
- #include <list>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/.NET.h>
- #include <sys/epoll.h>
- #include <fcntl.h>
- #include <errno.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- using namespace std;
- //clients_list save all the clients's socket
- list<int> clients_list;
- /***** macro defintion *****/
- //server ip
- #define SERVER_IP "127.0.0.1"
- //server port
- #define SERVER_PORT 8888
- //epoll size
- #define EPOLL_SIZE 5000
- //message buffer size
- #define BUF_SIZE 0xFFFF
- #define SERVER_WELCOME "Welcome you join to the chat room! Your chat ID is: Client #%d"
- #define SERVER_MESSAGE "ClientID %d say>> %s"
- //exit
- #define EXIT "EXIT"
- #define CAUTION "There is only ont int the char root!"
- /****** some function *****/
- /**
- * 设置非阻塞
- */
- int setnonblockint(int sockfd) {
- fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0) | O_NONBLOCK);
- return 0;
- }
- /**
- * 将文件描述符 fd 添加到 epollfd 标示的内核事件表中,
- * 并注册 EPOLLIN 和 EPOOLET 事件,
- * EPOLLIN 是数据可读事件; EPOOLET 表明是 ET 工作方式.
- * 最后将文件描述符设置非阻塞方式
- * @param epollfd:epoll 句柄
- * @param fd: 文件描述符
- * @param enable_et:enable_et = true,
- * 是否采用 epoll 的 ET(边缘触发)工作方式; 否则采用 LT(水平触发)工作方式
- */
- void addfd(int epollfd, int fd, bool enable_et) {
- struct epoll_event ev;
- ev.data.fd = fd;
- ev.events = EPOLLIN;
- if (enable_et) {
- ev.events = EPOLLIN | EPOLLET;
- }
- epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
- setnonblockint(fd);
- printf("fd added to epoll!\n\n");
- }
- // 发送广播
- int sendBroadcastmessage(int clientfd) {
- char buf[BUF_SIZE];
- char message[BUF_SIZE];
- bzero(buf, BUF_SIZE);
- bzero(buf, BUF_SIZE);
- printf("read from client(clientID = %d)\n", clientfd);
- int len = recv(clientfd, buf, BUF_SIZE, 0);
- if (0 == len) {
- close(clientfd);
- clients_list.remove(clientfd);
- printf("ClientID = %d closed.\n now there are %d client in the char room\n",
- clientfd, (int)clients_list.size());
- } else {
- if (1 == clients_list.size()) {
- send(clientfd, CAUTION, strlen(CAUTION), 0);
- return 0;
- }
- sprintf(message, SERVER_MESSAGE, clientfd, buf);
- list<int>::iterator it;
- for (it = clients_list.begin(); it != clients_list.end(); ++it) {
- if (*it != clientfd) {
- if (send(*it, message, BUF_SIZE, 0) <0) {
- perror("error");
- exit(-1);
- }
- }
- }
- }
- return len;
- }
- #endif //CHAT_UTILITY_H
- client.cpp
- #include "utility.h"
- #define error(msg) do {perror(msg); exit(EXIT_FAILURE); } while (0)
- int main(int argc, char *argv[]) {
- /**
- * TCP 客户端通信
- * 1. 创建套接字(socket)
- * 2. 使用 connect() 建立到达服务器的连接(connect)
- * 3. 客户端进行通信(使用 write()/send() 或 send()/recv() )
- * 4. 使用 close() 关闭客户连接
- */
- /**
- * 1: 创建套接字 socket
- * param1: 指定地址族为 IPv4;param2: 指定传输协议为流式套接字; param3: 指定传输协议为 TCP, 可设为 0, 由系统推导
- */
- int clientfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- if (clientfd < 0) { error("socket error"); }
- // 填充 sockadd 结构, 指定 ip 与端口
- struct sockaddr_in serverAddr;
- serverAddr.sin_family = AF_INET;
- serverAddr.sin_port = htons(SERVER_PORT);
- serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
- // 2: 连接服务端
- if (connect(clientfd, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0) {
- error("connect error");
- }
- // 创建管道, 其中 fd[0]用于父进程读, fd[1]用于子进程写
- int pipefd[2];
- if (pipe(pipefd) < 0) { error("pipe error"); }
- /**
- * epoll 使用
- * 1: 调用 epoll_create 函数在 Linux 内核中创建一个事件表;
- * 2: 然后将文件描述符添加到所创建的事件表中 (epoll_ctl);
- * 3: 在主循环中, 调用 epoll_wait 等待返回就绪的文件描述符集合;
- * 4: 分别处理就绪的事件集合
- */
- // 创建 epoll
- int epfd = epoll_create(EPOLL_SIZE);
- if (epfd < 0) { error("epfd error"); }
- static struct epoll_event events[2];
- // 将 sock 和管道读端描述符都添加到内核事件表中
- addfd(epfd, clientfd, true);
- addfd(epfd, pipefd[0], true);
- // 表示客户端是否正常工作
- bool isClientwork = true;
- // 聊天信息缓冲区
- char message[BUF_SIZE];
- // Fork
- int pid = fork();
- if (pid < 0) {
- error("fork error");
- } else if (pid == 0) { // 子进程
- // 子进程负责写入管道, 因此先关闭读端
- close(pipefd[0]);
- printf("Please input'exit'to exit the chat room\n");
- while (isClientwork) {
- bzero(&message, BUF_SIZE);
- fgets(message, BUF_SIZE, stdin);
- // 客户输出 exit, 退出
- if (strncasecmp(message, EXIT, strlen(EXIT)) == 0) {
- isClientwork = 0;
- } else { // 子进程将信息写入管道
- if (write(pipefd[1], message, strlen(message) - 1) < 0) {
- error("fork error");
- }
- }
- }
- } else { //pid> 0 父进程
- // 父进程负责读管道数据, 因此先关闭写端
- close(pipefd[1]);
- // 主循环(epoll_wait)
- while (isClientwork) {
- // 等待事件的产生, 函数返回需要处理的事件数目
- int epoll_events_count = epoll_wait(epfd, events, 2, -1);
- // 处理就绪事件
- for (int i = 0; i < epoll_events_count; ++i) {
- bzero(&message, BUF_SIZE);
- // 服务端发来消息
- if (events[i].data.fd == clientfd) {
- // 接受服务端消息
- int ret = recv(clientfd, message, BUF_SIZE, 0);
- // ret= 0 服务端关闭
- if (ret == 0) {
- printf("Server closed connection: %d\n", clientfd);
- close(clientfd);
- isClientwork = 0;
- } else printf("%s\n", message);
- }
- // 子进程写入事件发生, 父进程处理并发送服务端
- else {
- // 父进程从管道中读取数据
- int ret = read(events[i].data.fd, message, BUF_SIZE);
- // ret = 0
- if (ret == 0) {
- isClientwork = 0;
- } else { // 将信息发送给服务端
- send(clientfd, message, BUF_SIZE, 0);
- }
- }
- }//for
- }//while
- }
- if (pid) {
- // 关闭父进程和 sock
- close(pipefd[0]);
- close(clientfd);
- } else {
- // 关闭子进程
- close(pipefd[1]);
- }
- return 0;
- }
- server.cpp
- #include "utility.h"
- #define error(msg) do {perror(msg); exit(EXIT_FAILURE); } while (0)
- int main(int argc, char *argv[]) {
- /**
- * TCP 服务端通信
- * 1: 使用 socket()创建 TCP 套接字(socket)
- * 2: 将创建的套接字绑定到一个本地地址和端口上(bind)
- * 3: 将套接字设为监听模式, 准备接收客户端请求(listen)
- * 4: 等待客户请求到来: 当请求到来后, 接受连接请求, 返回一个对应于此次连接的新的套接字(accept)
- * 5: 用 accept 返回的套接字和客户端进行通信 (使用 write()/send() 或 send()/recv())
- * 6: 返回, 等待另一个客户请求
- * 7: 关闭套接字
- */
- /**
- * 1: 创建套接字 socket
- * param1: 指定地址族为 IPv4;param2: 指定传输协议为流式套接字; param3: 指定传输协议为 TCP, 可设为 0, 由系统推导
- */
- int listener = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- if (listener < 0) {
- error("socket error");
- }
- printf("listen socket created \n");
- // 地址复用
- int on = 1;
- if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
- error("setsockopt");
- }
- struct sockaddr_in serverAddr;
- serverAddr.sin_family = PF_INET;
- serverAddr.sin_port = htons(SERVER_PORT);
- serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
- // 绑定地址
- if (bind(listener, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0) {
- error("bind error");
- }
- // 监听
- if (listen(listener, SOMAXCONN) < 0) {
- error("listen error");
- }
- printf("Start to listen: %s\n", SERVER_IP);
- // 在内核中创建事件表
- int epfd = epoll_create(EPOLL_SIZE);
- if (epfd < 0) {
- error("epfd error");
- }
- printf("epoll created, epollfd = %d\n", epfd);
- static struct epoll_event events[EPOLL_SIZE];
- // 往内核事件表里添加事件
- addfd(epfd, listener, true);
- // 主循环
- while (1) {
- //epoll_events_count 表示就绪事件的数目
- int epoll_events_count = epoll_wait(epfd, events, EPOLL_SIZE, -1);
- if (epoll_events_count < 0) {
- perror("epoll failure");
- break;
- }
- printf("epoll_events_count = %d\n", epoll_events_count);
- // 处理这 epoll_events_count 个就绪事件
- for (int i = 0; i < epoll_events_count; ++i) {
- int sockfd = events[i].data.fd;
- // 新用户连接
- if (sockfd == listener) {
- struct sockaddr_in client_address;
- socklen_t client_addrLength = sizeof(struct sockaddr_in);
- int clientfd = accept(listener, (struct sockaddr *) &client_address, &client_addrLength);
- printf("client connection from: %s : % d(IP : port), clientfd = %d \n",
- inet_ntoa(client_address.sin_addr),
- ntohs(client_address.sin_port),
- clientfd);
- addfd(epfd, clientfd, true);
- // 服务端用 list 保存用户连接
- clients_list.push_back(clientfd);
- printf("Add new clientfd = %d to epoll\n", clientfd);
- printf("Now there are %d clients int the chat room\n", (int) clients_list.size());
- // 服务端发送欢迎信息
- printf("welcome message\n");
- char message[BUF_SIZE];
- bzero(message, BUF_SIZE);
- sprintf(message, SERVER_WELCOME, clientfd);
- int ret = send(clientfd, message, BUF_SIZE, 0);
- if (ret < 0) {
- error("send error");
- }
- } else { // 处理用户发来的消息, 并广播, 使其他用户收到信息
- int ret = sendBroadcastmessage(sockfd);
- if (ret < 0) {
- error("error");
- }
- }
- }
- }
- close(listener); // 关闭 socket
- close(epfd); // 关闭内核
- return 0;
- }
来源: http://www.bubuko.com/infodetail-3294303.html