基于阿里云用C/C++做了一个http协议与TCP协议的web聊天室的服务器——《干饭聊天室》
基于阿里云用C/C++做了一个http协议与TCP协议的web聊天室的服务器——《干饭聊天室》
在这里首先感谢前端小伙伴飞鸟
前端技术请看一款基于React、C++,使用TCP/HTTP协议的多人聊天室小应用
如有错误,欢迎各位批评指正
本文一切输出内容都是调试用的
一、用cpp封装socket套接字
包含成员有位于in.h下的sockaddr_in结构体:
struct sockaddr_in {__kernel_sa_family_t sin_family; /* Address family 地址结构类型*/__be16 sin_port; /* Port number 端口号*/struct in_addr sin_addr; /* Internet address IP地址*//* Pad to size of `struct sockaddr'. */unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -sizeof(unsigned short int) - sizeof(struct in_addr)];
};
以及成员unistd.h下的socklen_t。
封装函数socket(),bind(),listen(),accept(),connect()以及对结构体sockaddr_in成员赋值的函数
class net{public:sockaddr_in server;socklen_t len;public:net();~net();int Socket(int Domain,int Type,int protocol);void Bind(int Socketfd,const struct sockaddr *addr,socklen_t addrlen);void Listen(int Socketfd,int backlog);int Accept(int Socketfd,struct sockaddr *addr,socklen_t *addrlen);void Connect(int Socketfd,const struct sockaddr *addr,socklen_t addrlen);void set_sockaddr_in(int Domain,char *ip,int port);
};
二、封装epoll函数实现并发服务器——多任务IO服务器
包含epoll_create(),epoll_ctl(),epoll_wait()
class Epoll{public:struct epoll_event e_event[MAXSIZE];public:Epoll();~Epoll();int Epoll_create(int size);void Epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);int Epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
};
三、封装http协议
这里首先要先了解http协议,这里参考的几篇文章:
HTTP详解
菜鸟的HTTP 教程
遇到的坑:
"Access-Control-Allow-Origin:*\r\n" //解决前后端分离时浏览器跨域问题
"Connection:keep-alive\r\n" //前后端保持长链接,不总是很稳定,TCP有时会自动断开
struct _client{char name[10];char pwd[18];char _hash[32];int client_fd;std::vector<std::string> chat_record;}Client;
上述结构体用来存储处理的这次http报文中的姓名,密码,socket文件描述符还有该用户在聊天中未收到的聊天记录。每次用户登陆都会在std::unordered_map<std::string,_client> data_client;//客户数据
创建出以姓名为建值的哈希表,value为_client类型用来存储token值及hash还有聊天记录。
"Content-Length:"; //响应头通知浏览器接收多少个字节。
响应头在这里并未写全因为每次响应数据的长度即大小不知道需要发送之前计算。
#ifndef __HTTP_H_
#define __HTTP_H_#include<iostream>
#include<string.h>
#include<unordered_map>
#include<string>
#include"NetBYdw.h"
#include<fcntl.h>
#include<stdlib.h>
#include <sys/stat.h>
#include<vector>
//响应头
const char success[]="HTTP/1.1 200 ok\r\n""Connection:keep-alive\r\n""Access-Control-Allow-Origin:*\r\n" //解决前后端分离时浏览器跨域问题//"Access-Control-Allow-Origin:http://47.110.144.145:80\r\n""Access-Control-Allow-Methods:POST,GET,OPTIONS,DELETE\r\n""Access-Control-Max-Age:3600\r\n""Access-Control-Allow-Headers:x-requested-with,content-type\r\n""Access-Control-Expose-Headers:serve-header\r\n""Content-Length:"; //响应头通知浏览器接收多少个字节。
class http{private:char login_status[16];struct _client{char name[10];char pwd[18];char _hash[32];int client_fd;std::vector<std::string> chat_record;}Client;public:char Request_method[8];//请求方法char Request_data[1024];//报文整体,相当于in_strchar send_data[1024];//需要发送的报文数据char Newspaper_data[1024];//报文体std::unordered_map<std::string,std::string> data_map;//报文真实数据std::unordered_map<std::string,_client> data_client;//客户数据public:http();~http();char * http_Request_method(char in_str[],char out_str[]);//char * http_get_Newspaper(char in_str[],char out_str[]);//int http_send(char out_str[]);//int http_put(int client_fd);//处理put请求int http_post(int client_fd);//处理post请求int http_get(int client_fd);//处理get请求int http_get_data(char in_str[]);//int http_delete(char in_str[]);int http_get_original_news(char in_str[]);
};#endif
http协议的处理
前面介绍到响应头不全,下面就介绍了为什么不全itoa(strlen(str),c_size,10);
把字节大小改为字符串,再通过write(client_fd,"\r\n\r\n",strlen("\r\n\r\n"));
实现整的http响应头。
char str[]=R"({"delete":0})";
char c_size[1024];
memset(c_size,0,sizeof(c_size));
itoa(strlen(str),c_size,10);
printf("%s\r\n\r\n",c_size);
write(client_fd,c_size,strlen(c_size));
write(client_fd,"\r\n\r\n",strlen("\r\n\r\n"));
write(client_fd,str,strlen(str));
无序哈希表问题
this->data_map.insert(std::make_pair(first.c_str(),second.c_str()));//必须这么存才能在哈希表里找到,不能存对象``在这里存数据时开始使用的是char *类型的字符串,但发现虽然是C语言字符串,但键值类型还是地址,比如char *first=“name“,查找时查找data_map["name"]是找不到的判断该键值不存在。后来改为:unordered_map\<std::string,std::string\>类型,又发现把string类型当键值也找不到,最后发现采用string.c_str()类型可以成功存储。欢迎大神解答这个问题。
int http::http_get_data(char in_str[]){this->data_map.clear();for(int i=0;in_str[i]!='\0';++i){ //把报文数据改变成哈希表,=号前为键值,=号后为valuestd::string first;for(int j=0;j<1024;++j){if(in_str[i]=='\0'){return -1;}if(in_str[i]!='='){first.push_back(in_str[i]);++i;}else {first.push_back('\0');++i;break;}}std::string second;for(int j=0;j<1024;++j){if(in_str[i]!='&'&&in_str[i]!='\0'){second.push_back(in_str[i]);++i;}else {second.push_back('\0');break;}}this->data_map.insert(std::make_pair(first.c_str(),second.c_str()));//必须这么存才能在哈希表里找到,不能存对象}return 0;
}
四、书写server服务端即main程序
如果服务器先关闭,虽然server的应用程序终止了,但TCP协议层的连接并没有完全断开,因此不能再次监听同样的server端口。server终止时,socket描述符会自动关闭并发FIN段给client,client收到FIN后处于CLOSE_WAIT状态,但是client并没有终止,也没有关闭socket描述符,因此不会发FIN给server,因此server的TCP连接处于FIN_WAIT2状态。client终止时自动关闭socket描述符,server的TCP连接收到client发的FIN段后处于TIME_WAIT状态。TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态,因为我们先Ctrl-C终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口。
解决服务端先关闭办法:
const int reuse = 1;
setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));//解决服务器先关闭
为了在服务器端的后台长期运行,创建守护进程,代码如下:
int r_ret=fork();
if(r_ret==0){setsid();
}else{sleep(1);exit(1);
}
守护进程的简单创建:
守护进程的简单创建: |
---|
1、创建子进程,父进程退出所有工作在子进程中进行形式上脱离了控制终端 |
2、在子进程中创建新会话setsid()函数使子进程完全独立出来,脱离控制 |
setsid函数
创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID。 pid_t setsid(void); 成功:返回调用进程的会话ID;失败:-1,设置errno 调用了setsid函数的进程,既是新的会长,也是新的组长。
接下来需要了解TCP的连接方式,三次握手四次挥手,可以参考HTTP详解的评论区,接下来我也会写。tcp连接的建立。
int server_fd,client_fd,epfd;
int client[MAXSIZE];
int flag1=0,flag2=0;
std::unordered_map<int,_client> client_map;
socklen_t server_len,client_len;
net Server;
sockaddr_in Client;
struct epoll_event tev;
Epoll _epoll;
http _http;Server.set_sockaddr_in(AF_INET,ip,htons(port));server_fd=Server.Socket(AF_INET,SOCK_STREAM,0);
const int reuse = 1;
setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));//解决服务器先关闭
Server.Bind(server_fd,(struct sockaddr *)&Server.server,sizeof(Server.server));Server.Listen(server_fd,128);
client_fd=Server.Accept(server_fd,(struct sockaddr *)&client,&client_len);
采用epoll实现高并发
不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件
。epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,
只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
http的处理
char __str[1024];
memset(__str,0,sizeof(__str));
int __ret=read(client_fd,__str,sizeof(__str));//读客户端文件描述符client_fd里的内容
if(__ret<0){perror("post read error\n");
}
printf("%s\n",__str);
_http.http_get_original_news(__str);//获取原始报文数据
_http.http_Request_method(_http.Request_data,_http.Request_method);//获取请求方法
if(strcmp(_http.Request_method,"GET")==0){//请求方法
_http.http_get(client_fd);//具体方法处理
memset(_http.Request_method,0,sizeof(_http.Request_method));//清除本次请求方法
memset(_http.Request_data,0,sizeof(_http.Request_data));//清除本次原始报文}else if(strcmp(_http.Request_method,"PUT")==0){_http.http_put(client_fd);
memset(_http.Request_method,0,sizeof(_http.Request_method));
memset(_http.Request_data,0,sizeof(_http.Request_data));}else if(strcmp(_http.Request_method,"POST")==0){_http.http_post(client_fd);
memset(_http.Request_method,0,sizeof(_http.Request_method));
memset(_http.Request_data,0,sizeof(_http.Request_data));}else if(strcmp(_http.Request_method,"Delete")==0){}else{}
#include"NetBYdw.h"
#include"Net_epoll.h"
#include "sign_in.h"
#include <unordered_map>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "http.h"
#include<sys/sendfile.h>#define port 4567
char ip[]="***.***.***.***"; //服务器内网ip
//char ip[]="172.16.10.66";//本地调试接口
struct _client{ int fd_id;int flag;char name[1024];
};
int main(int argc,char *argv[]){int r_ret=fork();if(r_ret==0){setsid();}else{sleep(1);exit(1);}int server_fd,client_fd,epfd;int client[MAXSIZE];int flag1=0,flag2=0;std::unordered_map<int,_client> client_map;socklen_t server_len,client_len;net Server;sockaddr_in Client;struct epoll_event tev;Epoll _epoll;http _http;Server.set_sockaddr_in(AF_INET,ip,htons(port));server_fd=Server.Socket(AF_INET,SOCK_STREAM,0);const int reuse = 1; setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));//解决服务器先关闭Server.Bind(server_fd,(struct sockaddr *)&Server.server,sizeof(Server.server));Server.Listen(server_fd,128);// client_fd=Server.Accept(server_fd,(struct sockaddr *)&client,&client_len);int i=0;for(i=0;i<MAXSIZE;++i)client[i]=-1;int _addr=-1;epfd=_epoll.Epoll_create(MAXSIZE);tev.events=EPOLLIN;tev.data.fd=server_fd;_epoll.Epoll_ctl(epfd,EPOLL_CTL_ADD,server_fd,&tev);while (1){int ret;ret=_epoll.Epoll_wait(epfd,_epoll.e_event,MAXSIZE,0);for(i=0;i<ret;++i){if((EPOLLIN&_epoll.e_event[i].events)==0)continue;if(_epoll.e_event[i].data.fd==server_fd){client_fd=Server.Accept(server_fd,(struct sockaddr*)&Client,&client_len);char str[64];std::cout<<"received from "<<inet_ntop(AF_INET,&Client.sin_addr.s_addr,str,sizeof(str))<<" at PORT"<<ntohs(Client.sin_port)<<std::endl;//把建立连接的客户端文件描述符写入clinet数组int j;for(j=0;i<MAXSIZE;++j){if(client[j]<0){client[j]=client_fd;break;}}//判断是否达到连接上限if(j==MAXSIZE){std::cout<<"client is full"<<std::endl;break;}client_map.insert(std::pair<int,_client>(client_fd,{client_fd,0,0}));//把指向栈顶的指针_addr移位if(j>_addr) _addr=j;//把得到的客户端文件描述符写入epfd中tev.events=EPOLLIN;tev.data.fd=client_fd;_epoll.Epoll_ctl(epfd,EPOLL_CTL_ADD,client_fd,&tev);//登陆界面char __str[1024];memset(__str,0,sizeof(__str));int __ret=read(client_fd,__str,sizeof(__str));if(__ret<0){perror("post read error\n");}printf("%s\n",__str);_http.http_get_original_news(__str);_http.http_Request_method(_http.Request_data,_http.Request_method);if(strcmp(_http.Request_method,"GET")==0){_http.http_get(client_fd);memset(_http.Request_method,0,sizeof(_http.Request_method));memset(_http.Request_data,0,sizeof(_http.Request_data));}else if(strcmp(_http.Request_method,"PUT")==0){_http.http_put(client_fd);memset(_http.Request_method,0,sizeof(_http.Request_method));memset(_http.Request_data,0,sizeof(_http.Request_data));}else if(strcmp(_http.Request_method,"POST")==0){_http.http_post(client_fd);memset(_http.Request_method,0,sizeof(_http.Request_method));memset(_http.Request_data,0,sizeof(_http.Request_data));}else if(strcmp(_http.Request_method,"Delete")==0){}else{}}else{int read_fd=_epoll.e_event[i].data.fd;int ret_sum;char buf[1024];memset(buf,0,sizeof(buf));ret_sum=read(read_fd,buf,sizeof(buf));if(ret_sum<0){perror("read error"); }else if(ret_sum==0){//判断TCP是否断开连接char str[64];std::cout<<inet_ntop(AF_INET,&Client.sin_addr,str,sizeof(str))<<" is closed"<<std::endl;int j=0;for(j=0;j<MAXSIZE;++j){if(client[j]==read_fd){client[j]=-1;break;}}client_map.erase(read_fd);_epoll.Epoll_ctl(epfd,EPOLL_CTL_DEL,read_fd,_epoll.e_event);close(read_fd);}else{printf("%s\n",buf);_http.http_get_original_news(buf);_http.http_Request_method(_http.Request_data,_http.Request_method);memset(buf,0,sizeof(buf));if(strcmp(_http.Request_method,"GET")==0){_http.http_get(read_fd);memset(_http.Request_method,0,sizeof(_http.Request_method));memset(_http.Request_data,0,sizeof(_http.Request_data));}else if(strcmp(_http.Request_method,"PUT")==0){_http.http_put(read_fd);memset(_http.Request_method,0,sizeof(_http.Request_method));memset(_http.Request_data,0,sizeof(_http.Request_data));}else if(strcmp(_http.Request_method,"POST")==0){_http.http_post(read_fd);memset(_http.Request_method,0,sizeof(_http.Request_method));memset(_http.Request_data,0,sizeof(_http.Request_data));}else if(strcmp(_http.Request_method,"Delete")==0){}else{}}}}}close(server_fd);close(epfd);return 0;
}
五、数据存储方式
目前采用的文件存储的方式。包括聊天记录,用户,密码。
六、未来计划
打算加入线程池实现高并发,数据存储打算采用Redis数据库。
七、服务器重启shell
#!/bin/bash
id=`ps aux|awk '{print $2 $11}'|grep ./server|awk 'BEGIN{FS="."}{print $1}'`
kill -9 ${id}
if [ $? -eq 0 ]; thenecho "success!"
elseecho "failed!"
fi
./server
八、源码连接
https://github.com/dwnb/chat_web
https://github.com/lzxjack/chat-room
基于阿里云用C/C++做了一个http协议与TCP协议的web聊天室的服务器——《干饭聊天室》相关推荐
- 基于阿里云IOT Studio和STM32的电机远程监测设计
今天来总结一下用阿里云的IOT Studio做的一个电机远程监控的小系统吧! 说来话长,在去年九月份的时候,我踏入了研究生的行列.我的导师是搞电机方向的,但我本科是测控的,考虑我的基础,导师给我推荐了 ...
- 基于阿里云搭建的适合初创企业的轻量级架构--架构总结
----基于阿里云搭建的适合初创企业的轻量级架构 前言 在项目的初期往往存在很多变数,业务逻辑时刻在变,而且还要保证快速及时,所以,一个灵活多变.快速部署.持续集成并可以适应多种情况的架构便显得尤为重 ...
- 小打卡基于阿里云构建企业级数仓的实践及总结
简介:本次分享主要有4块内容,小打卡介绍,小打卡数仓场景简介,小打卡数仓选型思路以及代表性案例分享. 小打卡架构师 申羡 本次分享主要有4块内容,小打卡介绍,小打卡数仓场景简介,小打卡数仓选型思路以及 ...
- 基于阿里云 MaxCompute 构建企业云数据仓库CDW
在本文中阿里云资深产品专家云郎分享了基于阿里云 MaxCompute 构建企业云数据仓库CDW的最佳实践建议. 本文内容根据演讲视频以及PPT整理而成. 大家下午好,我是云郎,之前在甲骨文做企业架构师 ...
- 基于阿里云的 Node.js 稳定性实践
前言 如果你看过 2018 Node.js 的用户报告,你会发现 Node.js 的使用有了进一步的增长,同时也出现了一些新的趋势. Node.js 的开发者更多的开始使用容器并积极的拥抱 Serve ...
- 如何基于阿里云搭建适合初创企业的轻量级架构?
----基于阿里云搭建的适合初创企业的轻量级架构 前言 在项目的初期往往存在很多变数,业务逻辑时刻在变,而且还要保证快速及时,所以,一个灵活多变.快速部署.持续集成并可以适应多种情况的架构便显得尤为重 ...
- 基于阿里云数加MaxCompute的企业大数据仓库架构建设思路
摘要: 数加大数据直播系列课程主要以基于阿里云数加MaxCompute的企业大数据仓库架构建设思路为主题分享阿里巴巴的大数据是怎么演变以及怎样利用大数据技术构建企业级大数据平台. 本次分享嘉宾是来自阿 ...
- 基于阿里云实现游戏数据运营(附Demo)
摘要: 原作者:阿里云解决方案架构师,陆宝.通过阅读本文,您可以学会怎样使用阿里云的maxcompute搭建一套数据分析系统. 一.总览 一个游戏/系统的业务数据分析,总体可以分为图示的几个关键步骤: ...
- 基于阿里云MaxCompute实现游戏数据运营
摘要: 一.总览 一个游戏/系统的业务数据分析,总体可以分为图示的几个关键步骤: 1.数据采集:通过SDK埋点或者服务端的方式获取业务数据,并通过分布式日志收集系统,将各个服务器中的数据收集起来并 ...
最新文章
- 布Sendmail之网,安全则不漏(上)
- oracle 00966,oracle错误代码一览表
- 大道至简 23种模式一点就通
- 将 Firefox 浏览器的书签导出
- 11gpath失败 oracle_Oracle 11gR2执行DBCA报错误
- 准备写个delphi 代码生成器或者说是一个记事本
- poj2976 Dropping tests
- Python+Pandas读取Excel文件分析关系最好的两个演员
- javaweb项目静态资源被拦截的解决方法
- Win10访问Linux分区
- 微信的商业价值有哪些?
- uc android 面试题,一道新浪UC部门软件测试面试题
- twitteR Unauthorized
- rails select下拉框
- 什么是数据库中的一对一关系?
- 火车运输java_基于jsp的火车售票-JavaEE实现火车售票 - java项目源码
- 【vue下载】vue 点击下载图片直接打开问题解决
- 吴恩达新动向揭晓:加入精神健康领域的人工智能Woebot
- Atitti 住房部建设指南
- 学生狗租房舒舍给你详解
热门文章
- Microbiome: 再论扩增子功能预测分析(Picrust)的效果
- 一些你可能忽略的护牙小技巧
- custom的短语_custom的短语_custom的用法总结大全
- R语言禁止数值表示为科学计数法实战(Turn Off Scientific Notation ):全局设置或者单变量设置
- R语言vtreat包自动处理dataframe的缺失值、计算数据列的均值和方差并基于均值和方差信息对数据列进行标准化缩放、计算所有数据列的均值和方差对所有数据列进行标准化缩放
- 什么是牛顿法(Newton methods)?什么是拟牛顿法(Quasi Newton methods)?牛顿法和梯度下降法的区别是什么?
- 随机森林(Random Forest)和梯度提升树(GBDT)有什么区别?
- K-means聚类K值的选择、Calinski-Harabasz准则
- 什么是词向量?word2vec、Glove、FastText分别是什么?
- android8.0 go 机型,安卓8.0良心!还开发了安卓GO, 适配给低配手机,魅族很尴尬!...