项目---基于TCP的高并发聊天系统
目录
服务端
服务端视角下的流程图
一、数据库管理模块
1.1 数据库表的创建
1.2 .对于数据库的操作
1.2.1首先得连接数据库
1.2.2执行数据库语句
1.2.3 返回数据库中存放的所有用户的信息
1.2.4返回数据库中存放的所有用户的好友信息
二、用户管理模块
2.1、UserInfo类:描述单个用户的信息
2.2UserMana类:组织用户的信息
2.2.1初始化
剩下的就是各类业务的接口
三、自定义消息格式
3.1了解Json对象
3.2、Json序列化和反序列化
3.3、模拟注册请求和发送请求
四、网络通信模块&业务模块
4.1、InitChatSvr函数
4.2 StartChatSvr函数
客户端
客户端视角下的流程图
1、注册
2、登录
3、发送消息
4、添加好友
服务端
服务端视角下的流程图
一、数据库管理模块
数据库模块是项目的最底层,主要就是负责与数据库进行打交道,
具体功能有:连接数据库、将用户信息存入数据库中、从数据库中获取信息、操作数据库中的表
1.1 数据库表的创建
这里创建了两张表:user,friendinfo。分别用来存放用户信息,用户好友信息。
1.2 .对于数据库的操作
1.2.1首先得连接数据库
1.2.2执行数据库语句
1.2.3 返回数据库中存放的所有用户的信息
用户管理模块刚初始化的时候就需要一个工作:从数据库当中获取所有用户的信息,还有每个用户的好友信息
1.2.4返回数据库中存放的所有用户的好友信息
二、用户管理模块
数据库模块是整个项目的最低层,而用户管理模块是倒数第二层,是建立在数据库模块之上的
2.1、UserInfo类:描述单个用户的信息
2.2UserMana类:组织用户的信息
2.2.1初始化
用户管理模块刚初始化的时候就需要一个工作:从数据库当中获取所有用户的信息,还有每个用户的好友信息,并将这些信息放于user_map当中去,方便后续的查询
剩下的就是各类业务的接口
三、自定义消息格式
3.1了解Json对象
只有当服务端和客户端发送的消息格式统一的时候,才能正常的进行通信,就像网络当中的各种协议一样,只有双方都遵守的时候,才能正常的进行数据交换。
我们这里采用的是Json数据格式
Json就是一个key :value的东西,他可以包含多动类型
简单介绍:
示例:
这就是一个Json对象,Json还支持嵌套,可以有无数种格式
我们来测试一下:
能够看到,json对象用起来是非常方便的。
我们再来测试看一下Json的NB之处:
我们来运行一下看能打印出什么:
3.2、Json序列化和反序列化
为什么需要序列化呢?
在tcpsocket编程的时候,我们学习过,内存不连续的结构体是不能直接用send发送的,需要使用序列化,将内存组合起来,放到一块连续的内存当中去,然后再发送。反序列化就是反过来,具体可以去看我之前写的博客:网络基础2--HTTP协议详解_Flying clouds的博客-CSDN博客
序列化:
反序列化:
3.3、模拟注册请求和发送请求
注册请求:
注册响应:
四、网络通信模块&业务模块
1、该模块负责TCP的socket编程,来负责网络通信。用epoll来监控多个文件描述符,从而实现具有高并发的基础
2、该模块还负责处理各种业务如:注册、登录、添加好友、聊天的具体实现
4.1、InitChatSvr函数
这个函数的作用是初始化资源,获取到需要的所有资源,比如用户管理模块的实例化指针、发送队列和接收队列、提供TCP服务的实例化指针、epoll操作句柄。
bool InitChatSvr(int wtc = 4){/* 1. 初始化用户管理类的指针 */um_ = UserMana::GetInstance();if(um_ == NULL){std::cout << "初始化用户管理失败了, 请检查..." << std::endl;return false;}/* 2. 初始化队列资源 */recv_que_ = new MsgPool<ChatMsg>();if(recv_que_ == NULL){std::cout << "init MsgPool failed" << std::endl;return false;}send_que_ = new MsgPool<ChatMsg>();if(send_que_ == NULL){std::cout << "init MsgPool failed" << std::endl;return false;}work_thread_count_ = wtc;/* 3. 初始化tcp通信的内容 */sockfd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if(sockfd_ < 0){perror("socket");return false;}int opt = 1;// sockfd为需要端口复用的套接字setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt));struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port_);addr.sin_addr.s_addr = inet_addr("0.0.0.0");int ret = bind(sockfd_, (struct sockaddr*)&addr, sizeof(addr));if(ret < 0){perror("bind");return false;}ret = listen(sockfd_, 10);if(ret < 0){perror("listen");return false;}/* 4. 初始化epoll */ep_fd_ = epoll_create(5);if(ep_fd_ < 0){perror("epoll_create");return false;}return true;}
4.2 StartChatSvr函数
这个函数相当于是开始工作的函数,调用这个函数之后,主线程就会 启动接收线程、发送线程、工作线程。
启动完别的线程之后,我们给主线程也找了一个活干:一直在accept,并将接收到的新连接套接字放到epoll当中去,让epoll帮我们去监控这些个文件描述符的状态
bool StartChatSvr(){/* 1. 启动接收线程 */pthread_t tid;int ret = pthread_create(&tid, NULL, RecvStart, (void*)this);if(ret < 0){std::cout << "create thread failed\n";return false;}/* 2. 启动发送线程 */ret = pthread_create(&tid, NULL, SendStart, (void*)this);if(ret < 0){std::cout << "create thread failed\n";return false;}/* 3. 启动工作线程 */for(int i = 0; i < work_thread_count_; i++){ret = pthread_create(&tid, NULL, WorkerStart, (void*)this);if(ret < 0){std::cout << "create thread failed\n";return false;}}/* 4. 主线程accept *//* 5. 将新连接套接字放到epoll当中 */while(1){int new_sockfd = accept(sockfd_, NULL, NULL);if(new_sockfd < 0){continue;}/* 添加到epoll */struct epoll_event ee;ee.events = EPOLLIN;ee.data.fd = new_sockfd;epoll_ctl(ep_fd_, EPOLL_CTL_ADD, new_sockfd, &ee);}
客户端
客户端视角下的流程图
客户端我们这里选取了MFC框架来进行,我们这里只是用到了MFC最基本的使用方法,想要熟练掌握MFC的同学可以去看b站上面的视频,上面的讲解是非常清晰的,我们这里只是简单来用。
我们在客户端主要是实现了四个基本的业务:注册,登录,添加好友,给好友发送消息
我们先来看看四个窗口:
1、注册
我们首先要插入一个Dialog,然后去对其进行操作,具体做法就是:插入一个Dialog,然后点击这个Dialog,我们就能看到一个窗口,然后再对窗口添加button。。。啥的各种内容。
在窗口当中对 : 姓名、学校、电话、密码添加变量,用来保存之后输入的值。
我们右键去对其添加变量、控件什么的,双击就是会给我们自动生成代码。
我们这个注册窗口是需要点击提交之后,成功的话就直接跳转到登录的界面,我们这里选择的是双击提交,生成代码,然后在这个函数里面实现我们想要的功能
void CRegisterdlg::OnBnClickedCommit()
{// TODO: 在此添加控件通知处理程序代码/*1.获取输入框的内容*/UpdateData(true);if (m_nickname_.IsEmpty()||m_school_.IsEmpty()||m_tel_.IsEmpty()||m_passwd_.IsEmpty()) {MessageBox(TEXT("请输入完整的注册内容!"));return;}std::string nickname = CT2A(m_nickname_.GetString());std::string school = CT2A(m_school_.GetString());std::string telnum = CT2A(m_tel_.GetString());std::string passwd = CT2A(m_passwd_.GetString());/*2.组织ChatMSg数据*/ChatMsg cm;cm.msg_type_ = Register;cm.json_msg_["nickname"] = nickname.c_str();cm.json_msg_["school"] = school.c_str();cm.json_msg_["telnum"] = telnum.c_str();cm.json_msg_["passwd"] = passwd.c_str();/*3.获取TCP实例化指针*/TcpSvr* ts = TcpSvr::GetInstance();/*4.发送消息*/std::string msg;cm.GetMsg(&msg);ts->Send(msg);/*5.获取消息队列的实例化指针*/MsgQueue* mq = MsgQueue::GetInstance();if (mq == NULL) {exit(1);}/*6.按照消息类型获取注册应答*/msg.clear();mq->Pop(Register_Resp,&msg);/*7.解析应答,判断应答结果注册成功 ==》跳转到登 录界面注册失败 ==》情空注册框,提示失败了(电话号码重复了)*/cm.clear();cm.PraseMsg(-1,msg);if (cm.reply_status_ == REGISTER_FAILED) {MessageBox(TEXT("电话号码重复了,请检查输入。。。"));}else {MessageBox(TEXT("注册成功!"));}}
2、登录
登录的时候,需要做到是点击登录按钮之后能够跳转到我们的聊天界面,需要注意的是,在跳转到聊天界面之前,我们需要提前吧聊天界面的内容给加载好,这样一条转到界面我们就能进行操作的,跳转界面我们已经知道了是调用domodel,用模态的方式打开界面,但是怎么加载数据呢?
我们这里可以找到方法:在调用domodel函数的时候,会调用一个函数,这个函数是重写父类的虚函数
BOOL CChatDlg::OnInitDialog()
在这个函数里面,我们可以能够实现获取聊天界面内容的功能,我们要获取的内容就两个:好友列表和好友的消息记录。
知道了这些之后,我就只剩下开工了:
如何展示:
保存好友信息到本地
因为好友信息是动态变化的,我们如果只是在获取好友信息的时候就在列表当中展示一次的话,那么当添加好友或者删除好友的时候,我们只能获取到新的好友信息,原来的好友信息就不知道从哪里再去获取了,方便起见,我们就直接保存在客户端,用vector来进行保存。
3、发送消息
我们想要发送消息,第一步肯定是要知道消息到底要发送给谁,就像微信一样,我们在好友列表当中点击谁,就代表要给哪个好友发送消息了,我们为了能实现这个功能:需要做到的就是能够时刻识别出来我们点击了好友列表,并且要知道到底点击了谁。
这个功能我们用一个MFC的函数来实现 : GetText,这个函数能够知道我们点击了哪个好友
/*当用户点击了 frilist,1. 要及时的更新 recv_userid2. 更新对话框*//* 1. 获取点击的内容 */CString str;m_frilist_.GetText(m_frilist_.GetCurSel(), str);/* 2. 根据点击的内容进行比对*//* 3. 更新 recv_userid_ */int i = 0;for (; i < fris_info_.size(); i++) {// eg: zs-bite zs-bite:10// zs lsstd::string tmp = CT2A(str.GetString());if (strstr((tmp.c_str()), fris_info_[i].nickname_.c_str()) != NULL) {recv_userid_ = fris_info_[i].user_id_;break;}}
当我们切换了好友之后,我们也要同步更新聊天框的内容
我们这里的做法也很简单粗暴:将现在所有的消息全部清空,然后再将当前好友的历史记录依次展示上去
/* 4. 刷新聊天界面 */for (int i = m_output_.GetCount(); i >= 0; i--) {m_output_.DeleteString(i);}std::vector<std::string> h_m = fris_info_[i].history_msg_;for (int i = 0; i < h_m.size(); i++) {m_output_.InsertString(m_output_.GetCount(), h_m[i].c_str());}fris_info_[i].unread_msg_ = 0;/* 5. 清空输入框 */m_input_.Empty();m_input_edit_.SetWindowTextA(0);/* 6. 刷新UserList */ReferFriList();
4、添加好友
我们想要做到的是输入一个好友的电话号码,然后给服务端发送一个添加好友的请求,在服务端这里进行判断,如果有这个电话号码是一个注册用户的话,我们就给这个用户推送一个添加好友的请求,如果不是的话服务端就忽略这条消息。
当用户收到添加好友请求的时候,可以选择同意或者拒绝,如果拒绝,就当作无事发生就行;如果点击了同意,就需要给服务端回一个同意添加好友的请求,然后服务端进行处理,将两个人的关系设置为好友,并再次给客户端推送应答,让客户端的好友列表进行更新,将新添加的好友展示上去。
UpdateData(true);if (m_fritel.IsEmpty()) {MessageBox(TEXT("输入内容不能为空..."));return;}std::string input = CT2A(m_fritel.GetString());ChatMsg cm;cm.msg_type_ = AddFriend;cm.user_id_ = userid_;cm.json_msg_["fri_telnum"] = input.c_str();/* 3. 获取TCP的实例化指针 */TcpSvr* ts = TcpSvr::GetInstance();/* 4. 发送消息 */std::string msg;cm.GetMsg(&msg);ts->Send(msg);CDialog::OnCancel();
相关文章:
项目---基于TCP的高并发聊天系统
目录 服务端 服务端视角下的流程图 一、数据库管理模块 1.1 数据库表的创建 1.2 .对于数据库的操作 1.2.1首先得连接数据库 1.2.2执行数据库语句 1.2.3 返回数据库中存放的所有用户的信息 1.2.4返回数据库中存放的所有用户的好友信息 二、用户管理模块 2.1、UserInfo类&…...
iOS热更新-8种实现方式
一、JSPatch 热更新时,从服务器拉去js脚本。理论上可以修改和新建所有的模块,但是不建议这样做。 建议 用来做紧急的小需求和 修复严重的线上bug。 二、lua脚本 比如: wax。热更新时,从服务器拉去lua脚本。游戏开发经常用到。…...
R语言 | 编写自己的函数
目录 一、正式编写程序 二、设计第一个函数 三、函数也是一个对象 四、程序代码的简化 五、return()函数的功能 六、省略函数的大括号 七、传递多个参数函数的应用 7.1 设计可传递2个参数的函数 7.2 函数参数的默认值 7.3 3点参数“…”的使用 八、函数也可以作为参数 …...
【Java校招面试】基础知识(七)——数据库
目录 前言一、数据库索引二、数据库锁三、数据库事务四、数据库连接池后记 前言 本篇主要介绍数据库的相关内容。 “基础知识”是本专栏的第一个部分,本篇博文是第六篇博文,如有需要,可: 点击这里,返回本专栏的索引文…...
MySQL高级--锁
一、锁 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题…...
Maven(六):Maven的使用——继承与聚合
Maven(六):Maven的使用——继承与聚合 前言一、实验九:继承1、概念2、作用3、举例4、操作4.1 创建父工程4.2 创建模块工程4.3 查看被添加新内容的父工程 pom.xml4.4 解读子工程的pom.xml4.5 在父工程中配置依赖的统一管理4.6 子工…...
Java ---System类
System 类位于 java.lang 包,代表当前 Java 程序的运行平台,系统级的很多属性和控制方法都放置在该类的内部。由于该类的构造方法是 private 的,所以无法创建该类的对象,也就是无法实例化该类。 System 类提供了一些类变量和类方…...
代码随想录_贪心_leetcode 406 452
leetcode 406. 根据身高重建队列 406. 根据身高重建队列 假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高…...
C++类的静态成员详解:成员函数非静态成员函数的非法调用
在C中,静态成员是属于整个类的而不是某个对象,静态成员变量只存储一份供所有对象共用。所以在所有对象中都可以共享它。使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内存。 静态成员的定义或声明要…...
Qt之滑动条和进度条(QSlider、QProgressBar)
文章目录 前言一、QSliderQSlider的常用API信号与槽 二、QProgressBar滑动条和滚动条的常用API 总结 前言 在用户界面设计中,滑动条和进度条是常见的控件。Qt中提供了QProgressBar和QSlider两个类来实现滚动条和滑动条。 一、QSlider 在Qt中,QSlider是…...
Flutter之插件开发plugin
目的:适用于独立业务模块,或者与原生页面交互频繁的地方。 基于flutter3.x , IDE :androidStudio demo:https://download.csdn.net/download/SHTLoveXX/87751845 步骤: 1.新建flutter project 【New flutter project】. 2. 在新建工程面板记得切换 …...
asp.net基于web的音乐管理网站dzkf17A9程序
本系统主要包含了等系统用户管理、公告信息管理、音乐资讯管理、音乐类型管理多个功能模块。下面分别简单阐述一下这几个功能模块需求。 管理员的登录模块:管理员登录系统对本系统其他管理模块进行管理。 用户的登录模块:用户登录本系统,对个…...
itop-3568开发板驱动学习笔记(25)设备树(四)GPIO 实例分析
《【北京迅为】itop-3568开发板驱动开发指南.pdf》 学习笔记 文章目录 GPIO 控制器必要属性其他属性 指定 GPIO 引脚 和时钟类似,GPIO 在设备树中也存在两层定义,首先是 GPIO 控制器,这部分由芯片原厂工程师编写,相当于 GPIO 底层…...
函数(定义、返回值、调用、参数)
目录 ❤ 无参函数 ❤ 有参函数 ❤ 空函数 ❤ 什么是返回值? ❤ 为什么要有返回值? ❤ 什么是函数调用? ❤ 为何用调用函数? ❤ 函数调用的三种形式 ❤ 形参和实参 形参 实参 ❤ 位置参数 位置形参 位置实…...
28. Kubernetes 核心组件讲解——API Server
本章讲解知识点 Kubernetes API Server 概述etcd 简介API Server 架构解析API Server 的 List-Watch 机制独特的 Kubernetes Proxy API 接口集群功能模板之间的通信1. Kubernetes API Server 概述 1.1 基本概念 Kubernetes API Server(API Server)是 Kubernetes 的核心组件…...
springboot框架开发医院云HIS 住院医生站、住院护士站功能实现
住院医生站主模块:包括医嘱管理、病案首页、分配入科、住院清单、我的质控等子模块 (1)医嘱管理功能简介 ①住院患者开立医嘱、支持医嘱复制、停止、作废等操作; ②医嘱类型含药品、项目、材料、嘱托; ③支持住院各…...
高性能定时器介绍及代码逐行解析--时间堆
简介 在《Linux高性能服务器编程》中,介绍了三种定时方法: socket选项SO_RCVTIMEO和SO_SNDTIMEOSIGALRM信号I/O复用系统调用的超时参数 基础知识 非活跃,是指客户端(这里是浏览器)与服务器端建立连接后,…...
汇编语言学习笔记五
div指令 除法, 被除数:默认是放在ax或者dx中,其位数为16位,则在ax中,如位数为32位,则高位在dx中,低位在ax中 除数:放在寄存器或者内存单元中,有8位和16位两种。 结果&am…...
Linux下的epf 是什么?
EPF (Extended Page Frame) 是 Linux 内核中的一个功能,它用于管理大内存系统中的物理页框。具体来说,当系统中的物理内存超过 1TB 时,传统的页表结构会变得非常庞大和复杂,给内存管理带来很大的困难。 EPF 架构通过将物理地址分…...
如何在广告形式选择上化解用户厌恶和变现瓶颈?
用户讨厌广告,这似乎是一个共识。在日复一日的使用中,用户会遇到各种各样的广告形式,从搜索结果中的广告链接,到视频中不间断的广告,再到流行应用中的推广内容。 无处不在的广告已经让用户不胜其烦,这也…...
【Android入门到项目实战-- 9.2】—— 传感器实战使用教程(靠近黑屏和计步器)
上篇文章介绍了传感器的基础用法(如有需要,可先移步),下面将通过两个实战案例学习具体如何使用。 一、靠近黑屏 这是距离传感器的简单应用。 –检测手机是否贴在耳朵上正在打电话,以便自动熄灭屏幕达到省电的目的。也…...
软件项目生命周期模型
目录 瀑布模型 快速原型模型 敏捷模型 迭代模型(增量模型) 螺旋模型 瀑布模型 定义:早就计划好了,按计划顺序(计划、设计、开发、测试、维护)线性执行 适用于:需求明确、变化少的项目 缺…...
linux系统TP-ti,tsc2046外设调试
一、整体调试思路 tp外设属于比较常见且比较简单的外设,今天以ti,tsc2046这款为例简述下tp外设的调试。 整体思路 1、配置设备树----驱动调试的device部分 2、tp驱动编译及匹配—driver部分 3、驱动整体调试 二、配置设备树 对于ti,tsc2046我们可以参考内核Docum…...
ChatGPT指令大全
1. 写报告:我现在正在 [报告的情境与目的]。我的简报主题是 [主题],请提供 [数字] 种开头方式,要简单到 [目标族群] 能听懂,同时要足够能吸引人,让他们愿意专心听下去。 2. 研究报告:写出一篇有关 [知识] …...
【Vue面试题】Vue2.x生命周期?
文章目录 1.有哪些生命周期(系统自带)?beforeCreate( 创建前 )created ( 创建后)beforeMount (挂载前)mount (挂载后)beforeUpdate (更新前)updated (更新后)beforeDestroy(销毁前)destroy(销毁后…...
运算放大器 - 笔记 02 -恒流源
恒流源 / 电流源 一、方案一二、方案二三、方案三四、方案四 前言:最近在学习运放,三极管,二极管,场效应管等器件的组合电路。捡起了以前的模电知识,写下笔记,以防再度忘记。 本文使用Multisim仿真软件进行…...
Python:Python进阶:Python字符串驻留技术
Python字符串驻留技术 1.什么是字符串驻留2. 为什么要驻留字符串3. Python的字符串驻留4. Python 字符驻留原理4.1 如何驻留字符串4.2 如何清理驻留的字符串 5. 字符串驻留的实现5.1. 变量、常量与函数名5.2 字典的键5.3 任何对象的属性5.4 显式地驻留 6 字符串驻留的其他发现 …...
2022年 全国职业院校技能大赛(中职组)网络安全赛项 正式赛卷 A模块 做题记录
评分标准文件及环境 评分标准:ZZ-2022029 网络安全赛项正式赛卷.zip 自己做的Linux靶机: 自己做的Windows靶机: 文章目录 评分标准文件及环境A-1 任务一 登录安全加固1. 密码策略(Windows,Linux)a. 最小密…...
华为OD机试 - 优选核酸检测点(Python)
题目描述 张三要去外地出差,需要做核酸,需要在指定时间点前做完核酸,请帮他找到满足条件的核酸检测点。 给出一组核酸检测点的距离和每个核酸检测点当前的人数给出张三要去做核酸的出发时间 出发时间是10分钟的倍数,同时给出张三做核酸的最晚结束时间题目中给出的距离是整…...
windows怎么把包含某个关键词的文件移动到一个文件夹中
文章目录 windows怎么把包含某个关键词的文件移动到一个文件夹中问题来源省流版本操作过程具体问题方法一:使用cmd终端解决方法二:使用python脚本 总结 windows怎么把包含某个关键词的文件移动到一个文件夹中 问题来源 今天想移动window文件࿰…...
潍坊 网站建设/各大引擎搜索入口
上半年流畅度的新机排名,哇!第一名居然是这款2019-05-11 22:26:4510点赞6收藏29评论要了解更多流行资讯、玩机技巧、数码体验、科普深扒,点击右上角关注我-----------2019年到现在已经过半了,各大手机厂商的新机发布热潮也慢慢降了…...
东莞营销商城网站建设/服装品牌策划方案
实际操作步骤: 输入brew install nginx-full --with-rtmp-module命令出现以下报错: 需要先安装nginx服务器,运行命令brew tap homebrew/nginx,出现报错: 换一个github项目,即运行命令brew tap denji/nginx …...
自己做的网站根目录哪里找到/seo是如何优化
有谁需要阿里云一键安装包吗?https://market.aliyun.com/products/56014009/cmgj000262.html 可以到这里去下载使用https://github.com/drinkboyyu/opt_shell 大家如果对于使用有问题,或者以前使用过,现在想升级nginx、php、mysql等版本&…...
网站前置审批专项/国际婚恋网站排名
环境: 电脑:联想E14 系统:Windows 10 专业版 64位 VMware 16.0 :Ubuntu20.04 问题描述: 如何更新国内源,原因是安装ssh没成功 解决方案: 1.备份原来的源 sudo cp /etc/apt/sources.list…...
wordpress电影主题公园/网络营销活动策划方案
1、被连接件接合面设计要注意的问题1)接合面应有合理的形状和足够大的尺寸。为使两零件可靠的连接起来,它们的接合面必须紧密贴合。因此两零件的接合面形状应简单,容易得到高精度和紧密的配合,最常见的接合面是平面和圆柱面。图1a中两个零件用…...
网站镜像上传到域名空间/注册城乡规划师含金量
前言 XML现已成为一种通用的数据交流方式,它的平台无关性,语言无关性,系统无关性,给数据集成与交互带来了极大的方便,对于XML的解析有四种方式:DOM生成和解析XML文档,SAX生成和解析XML文件&…...