用来练手熟悉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在换行上的差异。

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

  1. win10系统找不到telnet服务器,win10找不到telnet服务怎么办_win10没有telnet服务如何找回...

    我们要知道,Telnet协议是win10系统中TCP/IP协议族中的一员,同时也是Internet远程登录服务的标准协议和主要方式,有的用户想要通过telnet服务来进行远程连接操作时总是找不到tel ...

  2. 计算机开启telnet服务,win10打开telnet服务如何操作_win10怎么开启电脑telnet服务-win7之家...

    在win10系统的众多功能服务中,telnet服务是TCP/IP协议族中的一员,是Internet远程登录服务的标准协议和主要方式,为用户提供了在本地计算机上完成远程主机工作的能力,那么win10怎么 ...

  3. win10系统telnet服务器,win10 开启telnet服务

    2017-11-13 11:52:05 浏览量:29646 Telnet协议想必大家都不陌生,其是Internet远程登陆服务的标准协议和主要方式.最近,一位用户反馈自己想要在windows10系统中 ...

  4. 连夜撸了一个简易聊天室

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 分不清轮询.长轮询?不知道什么时候该用websocket还 ...

  5. 撸一个简易聊天室,不信你学不会实时消息推送(附源码)

    点击上方 好好学java ,选择 星标 公众号重磅资讯,干货,第一时间送达 今日推荐:推荐 19 个 github 超牛逼项目!个人原创100W +访问量博客:点击前往,查看更多 分不清轮询.长轮询? ...

  6. Socket编程实现简易聊天室

    1.Socket基础知识 Socket(套接字)用于描述IP地址和端口,是通信链的句柄,应用程序可以通过Socket向网络发出请求或者应答网络请求. Socket是支持TCP/IP协议的网络通信的基本 ...

  7. RDIFramework.NET ━ .NET快速信息化系统开发框架 V2.8 版本━新增企业通(内部简易聊天工具)...

    RDIFramework.NET ━ .NET快速信息化系统开发框架 V2.8 版本 新增企业通(内部简易聊天工具) RDIFramework.NET,基于.NET的快速信息化系统开发.整合框架,给用 ...

  8. python实现简易聊天需要登录博客园zip下载_Python基于Socket实现简易多人聊天室的示例代码...

    前言 套接字(Sockets)是双向通信信道的端点. 套接字可以在一个进程内,在同一机器上的进程之间,或者在不同主机的进程之间进行通信,主机可以是任何一台有连接互联网的机器. 套接字可以通过多种不同的 ...

  9. Express+Socket.IO 实现简易聊天室

    代码地址如下: http://www.demodashi.com/demo/12477.html 闲暇之余研究了一下 Socket.io,搭建了一个简易版的聊天室,如有不对之处还望指正,先上效果图: ...

  10. java socket 工具_java Socket简易聊天工具

    本文实例为大家分享了一款Socket简易聊天工具,希望大家喜欢. 代码运行如图,看起来还不错 服务端 package qiu; import java.awt.BorderLayout; import ...

最新文章

  1. c语言编程显示单月日历,任意年月日历输出-题解(C语言代码)
  2. Bootstrap的下拉菜单
  3. 【ASP.NET】服务器控件大演练与实例分析
  4. STL 之adjacent_find, merge,inplace_merge
  5. 使用Java将数据流式传输到HPCC
  6. Finally 与 return
  7. Collection的使用 字符串保存 java
  8. linux-bash的基本-自动补全-快捷键-历史-命令的别名
  9. 通用职责分配软件原则之3-低耦合原则
  10. C# Iterators
  11. 【绘图】Origin关闭加速模式(speed mode)
  12. 国外兼职网站列举 79个
  13. python PDF解密打印文件
  14. php 360 检测,检测某个链接是否被360搜索引擎收录
  15. android 锁屏应用,推荐几款好用的安卓(Android)手机锁屏软件
  16. 在云服务器上(Windows)手动搭建FTP站点
  17. 视频编辑转换 ViscomSoft SDK ActiveX 19.0
  18. 地图编辑器开发(一)
  19. 华三s3100v3时区配置_H3C S3100V3-SI交换机设置NTP时间
  20. SQL数据库无法附加

热门文章

  1. Python用pyecharts绘制中国各地级市gdp分布点图
  2. Spark机器学习解析
  3. spark 机器学习一 聚类算法案例小结
  4. 微信小程序语音播放功能的实现
  5. 删除win10易升更新的办法
  6. 拼多多商品采集、商品数据解析详解
  7. 2021年PMP考试模拟题1(含答案)
  8. 免费易用的Web版OFD阅读器
  9. 免费OFD在线阅读器,可以二次开发
  10. 给已有表添加字段sql