服务于win10的telnet的简易聊天服务器(单进程)

用来练手熟悉Linux C/C++编程的小服务器,只有一个文件。由于重心在熟悉Linux相关函数上,所以写的很简陋。

开发环境:Ubuntu 18.04.1 LTS 个人的腾讯云服务器
运行方法:直接将cpp文件编译运行即可,运行时需要提供服务器ip和port

主要涉及信号、Epoll等基础知识点
最重要的是:win10的telnet是每次实时发送字符(这与ubuntu是不同的),所以我在服务器端添加了string同时如果检测到telnet发送了长度为2的"\r\n"(这是win系统中换行字符),就从对应string里拿出相应积累的字符串并将其输出到其他用户终端上。所以这个程序的客户端telnet不能是linux系统下的telnet服务。

源代码如下

//code1.cpp

//用于一对一聊天的程序
//Ubuntu 18.04.1 LTS
//客户端要求:Win10原装telnet程序即可
//win10 cmd 命令: telnet 服务器ip 服务器指定端口

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/sendfile.h>
// #include<sys/uio.h>
// #include<sys/stat.h>
// #include<sys/types.h>
#include<fcntl.h>
// #include<pthread.h>
#include<sys/epoll.h>
// #include<sys/wait.h>
// #include<sys/mman.h>
#include<signal.h>
#include<string>
using namespace std;

/* 通过缓存消息依次分发的方式进行管理 */
/* epoll进行IO复用 采用统一信号管道 */
/* 发送方采用win10 telnet单字节发送 单行消息的结束符号识别为"\r\n" */

class UserInfo{
public:
    //构造函数
    UserInfo():fd(-1){};
    //初始化
    void init(int fd){this->fd=fd;this->buffer="";};
    //清除
    void clear(){this->fd=-1;this->buffer="";};
    //用户文件描述符
    int fd;
    //用户缓冲区
    string buffer;
};

/* 全局变量 */

//缓存区大小
const int BUFFER_SIZE = 1024;
//epoll列表大小
const int MAX_EPOLL_NUMBER = 10;
//backlog值
const int BACKLOG = 5;
//最大接入用户数量
const int MAX_USER_NUM = 2;

//当前在线用户数量 <=2
int cur_user_num = 0;
//当前在线用户的信息
UserInfo users[MAX_USER_NUM];

//统一信号管道 所有信号 => sig_pipefd[1] ==> sig_pipefd[0]
int sig_pipefd[2];


/* 预备函数 */

//自定义信号处理函数
void handler(int sig){
    int save_errno = errno;
    int msg = sig;
    send(sig_pipefd[1],(void*)&msg,1,0);
    errno = save_errno;
}

//添加信号
void addsig(int sig,void(handler)(int)){
    struct sigaction sa;
    memset(&sa,0,sizeof(sa));
    sa.sa_flags |= SA_RESTART;
    sigfillset(&sa.sa_mask);
    sa.sa_handler = handler;
    assert( sigaction(sig,&sa,NULL) != -1 );
}

//记录客户端文件描述符 成功0 超量失败-1
int adduserfd(int fd){
    //使用前最好先检查当前在线用户数量
    if(cur_user_num == MAX_USER_NUM)return -1;
    for(int i=0;i<MAX_USER_NUM;++i){
        if(users[i].fd == -1){
            users[i].init(fd);
            cur_user_num++;
            return 0;
        }
    }
    return -1;
}

//从客户端文件描述符记录表中删除指定的fd 成功0 没有找到失败-1
int deluserfd(int fd){
    //使用前最好先确保fd是有效的在线用户的文件描述符
    for(int i=0;i<MAX_USER_NUM;++i){
        if(users[i].fd == fd){
            users[i].clear();
            cur_user_num--;
            return 0;
        }
    }
    return -1;
}

//向fd指定的客户链接添加char*t指定的长度为len的字符串
int addCharsTofd(int fd,char* t,int len){
    for(int i=0;i<MAX_USER_NUM;++i){
        if(users[i].fd == fd){
            for(int k=0;k<len;++k)users[i].buffer+=t[k];
            return 0;
        }
    }
    return -1;
}

//获取并销毁fd中目前存储的数据
int getCharsfromfd(int fd,string& Chars){
    for(int i=0;i<MAX_USER_NUM;++i){
        if(users[i].fd == fd){
            Chars = users[i].buffer;
            users[i].buffer = "";
            return 0;
        }
    }
    return -1;
}

//设置文件描述符非阻塞
int setnonblocking(int fd){
    int old_option = fcntl(fd,F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd,F_SETFL,new_option);
    return old_option;
}

//向epollfd中注册文件描述符fd(非阻塞)
void addfd(int epollfd,int fd){
    epoll_event event;
    bzero(&event,sizeof(event));
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
    setnonblocking(fd);
}

//向fd客户端发送来自服务器的系统消息
void sendSysMsg(int fd,const char* msg){
    string line = string("ServerMsg:[") + string(msg) + string("]\r\n");
    int len = line.size();
    char* buf = new char[len+10];
    memcpy(buf,line.c_str(),line.size());
    buf[line.size()] = '\0';
    printf("SendSysMsgtoUserfd[%d]: %s",fd,buf);
    send(fd,buf,strlen(buf),0);
    delete[] buf;
}

//向在线的所有用户广播系统消息
void allSendSysMsg(const char* msg){
    string line = string("ServerMsg:[") + string(msg) + string("]\r\n");
    int len = line.size();
    char* buf = new char[len+10];
    memcpy(buf,line.c_str(),line.size());
    buf[line.size()] = '\0';
    int msgLenth = strlen(buf);
    printf("SendSysMsgforAllUsers: %s",buf);
    //向全员发送系统消息
    for(int i=0;i<MAX_USER_NUM;++i){
        if(users[i].fd != -1){
            send(users[i].fd,buf,msgLenth,0);
        }
    }
    delete[] buf;
}

//删除epollfd中对fd文件描述符的监听
void delfd(int epollfd,int fd){
    epoll_event event;
    bzero(&event,sizeof(event));
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&event);
}

//从fd获取消息并发送到其他在线用户中
int sendMsgFromfd(int msgfd){
    string msg = "";
    if(getCharsfromfd(msgfd,msg) == -1)return -1;
    //构建缓冲消息
    string beifen = msg;
    msg += "\r\n";//补齐win版本换行符
    char* buf = new char[msg.size() + 4];
    memset(buf,0,sizeof(buf));
    memcpy(buf,msg.c_str(),msg.size());
    //分发消息
    for(int k=0;k<MAX_USER_NUM;++k){
        if( users[k].fd != -1 && users[k].fd != msgfd ){
            send(users[k].fd,buf,strlen(buf),0);
        }
    }
    delete[] buf;
    printf("[sockfd]:%d send [MSG]:%s\n",msgfd,beifen.c_str());
    return 0;
}

//核心程序
int main(int argc,char* argv[]){
    if(argc <= 2){
        printf("usage: %s ip_address port\n",basename(argv[0]));
        return 1;
    }
    //初始化服务器参数
    printf("Init Server System...\n");
    const char* ip = argv[1];
    int port = atoi(argv[2]);
    int ret = -1;

    sockaddr_in address;
    bzero(&address,sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&address.sin_addr);
    address.sin_port = htons(port);

    int listenfd = socket(AF_INET,SOCK_STREAM,0);
    assert(listenfd >= 0);

    ret = bind(listenfd,(struct sockaddr*)&address,sizeof(address));
    assert(ret != -1);

    ret = listen(listenfd,BACKLOG);
    assert(ret != -1);

    //初始化全双工管道
    printf("Init Pipe...\n");
    ret = socketpair(PF_UNIX,SOCK_STREAM,0,sig_pipefd);
    assert(ret != -1);
    setnonblocking(sig_pipefd[1]);//设置管道非阻塞

    //初始化epoll内核表
    printf("Init Epoll...\n");
    int epollfd = epoll_create(MAX_EPOLL_NUMBER);
    assert(epollfd >= 0);

    //epoll内核监听注册
    addfd(epollfd,listenfd);//注册epoll对listenfd的监听
    addfd(epollfd,sig_pipefd[0]);//注册epoll对信号管道0端的监听

    //设置信号处理
    printf("Init Signals...\n");
    addsig(SIGTERM,handler);
    addsig(SIGINT,handler);
    addsig(SIGCHLD,handler);
    addsig(SIGPIPE,SIG_IGN);

    //核心循环
    bool m_stop = false;
    epoll_event events[MAX_EPOLL_NUMBER];
    printf("Server Running..\n");

    while(!m_stop){
        int number = epoll_wait(epollfd,events,MAX_EPOLL_NUMBER,-1);
        printf("epoll number == %d\n",number);
        if( (number < 0) && (errno != EINTR) ){
            printf("epoll error\n");
            break;
        }

        //循环检测事件
        for(int i=0;i<number;++i){
            int sockfd = events[i].data.fd;
            if( (sockfd == listenfd) && (events[i].events & EPOLLIN) ){
                //新的客户链接需要接入
                sockaddr_in client_address;
                socklen_t client_length = sizeof(client_address);
                int client_fd = accept(sockfd,(struct sockaddr*)&client_address,&client_length);
                if(client_fd < 0){
                    printf("accept error [fd %d] [errno %d]\n",client_fd,errno);
                    continue;
                }
                //注册新用户信息
                ret = adduserfd(client_fd);
                if(ret == -1){
                    //用户已满无法注册登录
                    const char* nolink = "Sorry Full in Chating.\r\n";
                    send(client_fd,nolink,strlen(nolink),0);
                    close(client_fd);
                    printf("One User Get into Link but Failed for Full\n");
                    continue;
                }
                printf("One User Get into Link\n");
                //注册关于client_fd的监听
                addfd(epollfd,client_fd);
                //发送系统消息
                sendSysMsg(client_fd,"Welcome to ChatServer Based on TELNET");
                //向其他用户发送新用户消息
                for(int k=0;k<MAX_USER_NUM;++k){
                    int aim = users[k].fd;
                    if(aim != -1 && aim != client_fd){
                        sendSysMsg(aim,"One User Get into Server");
                    }
                }
            }else if( (sockfd == sig_pipefd[0]) && (events[i].events & EPOLLIN) ){
                //存在信号通知
                char signals[100];
                memset(signals,0,sizeof(signals));
                ret = recv(sockfd,signals,100,0);
                printf("Get Signals Num is %d\n",ret);
                //处理信号
                for(int k=0;k<ret;++k){
                    switch(signals[k]){
                    case SIGCHLD:{
                        printf("SIG:[SIGCHLD]\n");
                        break;
                    }
                    case SIGTERM:
                    case SIGINT:{
                        if(signals[k] == SIGTERM) printf("sig:[SIGTERM]\n");
                        else printf("sig:[SIGINT]\n");
                        m_stop = true;
                        break;
                    }
                    default:break;
                    }
                }
            }else if( events[i].events & EPOLLIN ){
                //存在输入事件
                char buf[BUFFER_SIZE];
                memset(buf,0,sizeof(buf));
                ret = recv(sockfd,buf,BUFFER_SIZE-1,0);
                printf("msg recv() len == %d\n",ret);
                if(ret < 0){
                    if(errno != EAGAIN){
                        //断开客户端sockfd的连接
                        deluserfd(sockfd);
                        delfd(epollfd,sockfd);
                        close(sockfd);
                        allSendSysMsg("One User Left Just Now");
                        printf("link break for recv()<0\n");
                    }
                }else if(ret == 0){
                    //断开客户端sockfd的连接
                    deluserfd(sockfd);
                    delfd(epollfd,sockfd);
                    close(sockfd);
                    allSendSysMsg("One User Left Just Now");
                    printf("link break for recv()==0\n");
                }else{//主业务逻辑
                    if(ret == 2 && buf[0] == '\r' && buf[1] == '\n'){
                        //向其他用户发送消息
                        sendMsgFromfd(sockfd);
                    }else{
                        //添加字符串到缓存
                        addCharsTofd(sockfd,buf,ret);
                    }
                }
            }else{
                continue;
            }
        }//for
    }//while

    //关闭所有仍在线的客户端链接
    for(int i=0;i<MAX_USER_NUM;++i){
        if(users[i].fd != -1){
            int fd = users[i].fd;
            sendSysMsg(fd,"Chat Server's System Exit");
            users[i].clear();
            delfd(epollfd,fd);
            close(fd);
        }
    }

    close(listenfd);
    close(epollfd);
    close(sig_pipefd[0]);
    close(sig_pipefd[1]);
    printf("System Exit\n");
    return 0;
}


// g++ code1.cpp -o code1
// ./code1 172.17.0.6 12345
// g++ code1.cpp -o code1 && ./code1 172.17.0.6 12345

头文件有冗余,可以筛选一下,已经注释掉了一部分没用的头文件。注意Linux与Win在换行上的差异。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值