漫画C语言 做个聊天软件你不懂也得懂

学完C语言做不出东西?不存在的,咱们做一个最“隐私”的聊天器,就俩人,你和我。咱们聊天的信息你知我知没别人知。

对了,本文评论区点赞、收藏抽奖。
社区也有抽奖,本周社区抽奖帖子 :https://bbs.csdn.net/topics/603458199
以上两个抽奖都是周日开奖,名额十个,然后书、日历等奖品都有。.
没学过C语言的,觉得难的看这里:https://blog.csdn.net/a757291228/category_11468001.html

我们直接开始写代码,只要你会基础的C语言,不要担心看不懂,不懂的我帮你刨根问底,把根都挖出来嚼烂,绝对懂。
在这里插入图片描述

一、一个聊天软件的基础模型是怎么样的?

你是个新手的话你可能就会问,什么是模型?!听不懂,我在骗你学习。放心,我现在就告诉你什么是基础“模型”。

我们可以简单的理解“模型”指这个聊天软件基本是怎么进行通信的,常规形式是怎样的,只要清楚了这个形式流程,然后在这个流程中添加一些代码就ok了,啥都不用想。如果你还是不懂什么是“流程”,那我就跟你说这个是一个步骤,只需要懂这个步骤,我们使用代码编写这个步骤就可以完成了。

好了,现在没啥问题了吧?现在开始,第一步在一个通信中,一般有一个服务端。那什么是服务端?

1.1 什么是服务端

服务端就简单了,曾经…曾经…你去例如移动或者联通的营业挺,客服小姐姐就会对你提供服务,例如业务办理,办个卡,销个号等,那我们的服务端是用来通信的,所以这个服务端就是指等待跟我聊天的人,只要你上线了,开电脑打开软件了,连接上我的服务端了,咱们就可以聊天了。

服务端一般就是一直在这里等你上线的那个,风里雨里我在这里等你。

1.2 又不懂什么是客户端了?

不懂没关系,打游戏懂吧?你下载到你电脑你手机的就是客户端,你打个游戏如果没有服务端就不能跟人匹配,这个懂了吧?

1.3 基本的工具要拿过来吧?

还知道头文件吧?
头文件就等于是一个工具箱,需要干啥就可以使用拿头文件过来,这样就可以用里面的工具了。
那咱们做一个聊天的软件就需要一个工具箱吧,这个工具箱叫做“winsock2.h”,那怎么拿呢?都知道#include<> 吧?
那就直接把这个头文件拿过来就好了,代码就可以写成:#include<winsock2.h>
常规的输入输出工具箱也要拿吧?所以就第一步把 stdio.h 也拿过来,所以这个服务端的第一行第二行代码就写成:

#include<stdio.h>
#include<WinSock2.h>

1.4 开始 socket 编程

不会了不会了!是不是一说 socket 你就说这是个什么鬼?
我先说一句让你懵逼的定义“socket 就是应用之间通信的端点”。懂不懂?
不懂呀,那我继续说。
socket 就是两个通信软件之间的接口,你可以当成服务端是“插座”,客户端是“插头”,一插,欧了!这样不就通电了,这样说你明白了吧?
当然这样解释比较片面,但用“抽象”的方式讲又不一定能让大家听得懂,所以你就理解成插头肯定没问题。
在这里插入图片描述

1.5 开始抬杠我拿三座插两座插不进!


咱们用的插头都是有标准的,你想想,没有标准怎么那么多电器都可以用常规的插头?
像这个 socket 这个通信端口,是有基于一些标准的。例如 TCP/IP这些通信协议。
好了,我说了TCP/IP可能就会有同学问,这又是什么鬼!没关系,你只需要知道这个是一个通信协议,咱们现在是用 socket 进行通信就好,知道 socket 怎么用就行,协议咱们可不需要现在搞懂,咱们只需要知道 socket 如何运用即可。

二、开始敲服务端代码

2.1 搞清楚使用 socket 进行通信的步骤

编写C语言Windows下的socket需要经过几个步骤:首先对WSAStartup 进行初始化,初始化对socket 套接字(socket也叫套接字)进行创建,随后配合绑定信息,接着进行配置信息的bind 绑定;绑定了信息后,通过该信息进行isten 监听,监听后若有链接则connect 连接,再接下来开始使用accept 接收请求,得到请求后可以选择接受recv或者send发送数据,最后closesocket 关闭 socket,WSACleanup 最终关闭。

简单点就是下面的这个流程:

不懂了?不懂就慢慢来嘛。

这是进行 socket 编程的步骤,如果你要问为什么要这样做…我只能回答你规定的流程就这样,因为你要进行通信,那肯定需要创建一个 socket ,创建完毕后那么肯定要绑定你要通信的信息,如果你不绑定你怎么知道你要跟谁说话呢?急着我收到了一个信息后就等于跟我请求通话,我同意了,咱们就开始通信了,通信肯定要发送信息,那就用send这些方法发送了,最后面说完话我就关闭这个 socket了,那你说不是吗?

还不懂?那你看下面。

请添加图片描述

2.1 第一步初始化

既然第一步是初始化,那我要初始化什么东西?
我们需要初始化一个 WSADATA 类型数据的对象。
什么鬼?又是 WSADATA 又是对象的,听不懂啊!
没关系的拉,WSADATA 其实就是一个结构体,咱们在把使用socket的工具箱 WinSock2 拿过来的时候这个 WSADATA 结构体就已经创建好了,直接使用这个结构体创建一个结构体变量就好了。

WSADATA 的作用就是用来存储初始化信息的,就像你打个游戏初始化创建一个人,这个人总得有信息吧,光头、小眼睛、腿短…对吧?

那么我们的代码就可以写成以下:

#include<stdio.h>
#include<WinSock2.h>
#include <stdlib.h>
int main(){WSADATA wsaData;
}

接下来就可以开始初始化了,初始化 socket 有一个函数叫做 WSAStartup,既然是函数一般都有参数吧,参数有哪些呢?
这个 WSAStartup 方法需要传入一个 版本号,还有一个用于存储信息的 WSADATA 结构体。现在我们已经知道 WSADATA 的结构体就是上面这个代码创建的 wsaData 结构体变量,那么版本号又是什么?

这个版本号是说明我们使用哪个 Winsock 版本,Winsock 有一个 1.1 版本还有一个 2.2 版本。两个版本有不同,1.1 版本只支持 TCP/IP 协议,还有一个版本 2.2 支持多个协议,这个时候你懂用哪个了吧?

什么?! 还不懂? 那肯定是全都要呀!
2.2 版本兼容性之类的更好,兼容啥我们不管,反正用多的。
那直接写成 WSAStartup(2, 2, &wsaData)

不不不,我们写法有一些不同,需要用一个函数 MAKEWORD 对版本进行生成,就像这样 WSAStartup(MAKEWORD(2, 2), &wsadata);,规定咱们使用 MAKEWORD 告诉 WSAStartup 初始化调用什么版本。

那么整个初始化的代码就如下所示咯:

#include<stdio.h>
#include<WinSock2.h>
#include <stdlib.h>
int main(){WSADATA wsaData;WSAStartup(MAKEWORD(2, 2), &wsadata);
}

什么?不懂 &wsadata ?来来来,我们的漫画同学告诉你是啥意思:


懂了吧?传个地址方便信息存储。

2.2 第二步创建 socket

这一步超级简单,代码就是这个:

SOCKET serverSock = socket(PF_INET, SOCK_STREAM, 0);

我知道你要骂我,写什么是什么鬼。
好了好了,首先 SOCKET 是一个socket的类型,还记得 int a 吧?int 是一个类型,那么 SOCKET 肯定就是一个类型了,说明创建一个 SOCKET 类型的变量,然后 socket() 是创建 socket 的函数,这个没毛病吧?

你说是里面的参数不懂?

小问题了,第一个 PF_INET 就表示指定 IPV4 ,也就是说先给个网络协议,那么多的网络协议你总要选一个吧。那为什么要用 IPv4 呢?我只能说用这个东西计算更快,毕竟咱们做个聊天软件是局域网通信,你就理解为,咱们做的东西是个“小东西”,没必要那么大“体量”,迷你更好用,那就用那个 IPV4 了,你想不开你也可以用 IPV6 试试。

那 SOCK_STREAM 是什么?SOCK_STREAM 表示咱们进行的通信是 TCP 通信,稳定可靠。在这里使用 SOCK_STREAM 也表示向我们的系统,或者你理解成“计算机”申请一个通信的端口,不然系统不给你“开个口子”,我的数据怎么传出去对吧,不然就是叫破喉咙都没人理我。

那最后一个参数 0 又是什么呢?
这里就是一个编号,说仔细点这个是 socket 所使用的传输协议编号,是不是不明白?其实这就是一个编号,不做设置,但是要给一个值,所以就给一个 0 咯。

2.3 第三步绑定信息

绑定信息这一步就有点玄了。在这里咱们要了解两个结构体,一个是 sockaddr_in,还有一个是 SOCKADDR。需要注意的是,这两个结构体包含的数据都是一样的,是一样的…

主要是使用上有区别。有啥区别?
sockaddr 是个系统用,而 sockaddr 是用来强制转换 sockaddr_in 结构体给系统调用的函数用。是不是迷茫?不要迷茫,一般都是这样做,那就这样做吧。你只需要记住,sockaddr 保存信息然后就别管了,而sockaddr 咱们就用来给参数给函数用。

在 socket 中,咱们使用 sockaddr_in 结构体绑定监听的 IP 信息,首先需要创建这个结构体:

struct sockaddr_in sockAddr;

接下来始绑定端口、IP类型,其中 127.0.0.1 表示本机、1234 表示监听端口:

sockAddr.sin_family = PF_INET; //IPv4
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //服务器的IP
sockAddr.sin_port = htons(1234); //端口

这个懂没懂?
sockAddr.sin_family 是表示这个结构体中用于存储IP协议的结构体变量,PF_INET 之前说了是 ipV4,表示在这里设置 ipV4类型。

sockAddr.sin_addr.s_addr 这里是表示需要绑定的 ip 地址,在这里使用 inet_addr(“127.0.0.1”) 进行指定。那为什么指定个 ip 还需要 inet_addr?
inet_addr 的作用是将一个字符串格式的ip地址转换成一个uint32_t数字格式。为什么要转换?那肯定是因为 sockAddr.sin_addr.s_addr 是一个 uint32_t 这个类型了。

最后的 sockAddr.sin_port 是表示要指定某一个端口,在这里指定 1234 这个端口。

所以该部分的代码就写成这样了:

#include<stdio.h>
#include<WinSock2.h>
#include <stdlib.h>
int main(){WSADATA wsaData;WSAStartup(MAKEWORD(2, 2), &wsadata);SOCKET serverSock = socket(PF_INET, SOCK_STREAM, 0);struct sockaddr_in sockAddr;sockAddr.sin_family = PF_INET; //IPv4sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //服务器的IPsockAddr.sin_port = htons(1234); //端口
}

最后就是绑定一下了:

bind(serverSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));

在这里 bind() 方法就是表示绑定信息了,第一个参数是 serverSock 就是表示要绑定的 socket,然后 (SOCKADDR*)&sockAddr 就是需要绑定的地址,最后一个就是一个地址长度。

(SOCKADDR*)&sockAddr 我们讲过,SOCKADDR 就是给函数使用的,sockAddr 就是给系统使用的,所以就这样写就没毛病了。

2.4 监听端口

先让你懵一下,下面是代码:

listen(serverSock, 20);

简单吧?listen 就是表示监听,第一个参数就是要监听的 socket 第二个就是表示 同时能处理的最大连接。终于简单了这一步,你爽我也爽,还不懂就看下面漫画。

2.5 有人请求聊天?设置个接待员

接下来就是有人请求给你聊天了,那怎么办呢?一个人忙不过来呢,那就设置个接待员。

SOCKADDR cIntAddr; 
int nSize = sizeof(SOCKADDR);
SOCKET cIntSock = accept(serverSock, (SOCKADDR*)&cIntAddr, &nSize);

accept 函数就是一个接待员,有人连接来敲门了,就需要去接待,换句比较专业的话就是 accept 接收一个套接字中已建立的连接。

传入的参数第一个 serverSock 就是一个已连接的套接字,(SOCKADDR*)&cIntAddr 是一个按照规定的指向struct sockaddr的指针,所以我猜在前面创建,最后一个就是所指向这个指针的长度咯。

设置完后就等于创建了一个接待员 cIntSock 。
不过要注意,accept 没有连接的时候就会一直在等待,不然不会执行下面的代码的。


这一部分的代码如下:

#include<stdio.h>
#include<WinSock2.h>
#include <stdlib.h>
int main(){WSADATA wsaData;WSAStartup(MAKEWORD(2, 2), &wsadata);SOCKET serverSock = socket(PF_INET, SOCK_STREAM, 0);struct sockaddr_in sockAddr;sockAddr.sin_family = PF_INET; //IPv4sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //服务器的IPsockAddr.sin_port = htons(1234); //端口listen(serverSock, 20);SOCKADDR cIntAddr; int nSize = sizeof(SOCKADDR);SOCKET cIntSock = accept(serverSock, (SOCKADDR*)&cIntAddr, &nSize);
}

2.6 开始循环聊天

在聊天的时候肯定是需要一个循环,不用循环只能发一次信息就完成了,所以肯定有一个 while:

while (1) {


那循环里面写啥?
当然是写你接收信息和发送信息的代码了,我一次性贴上,简简单单:

while (1) {char sendBuf[50]={"Hello client"};char recvBuf[50];recv(cIntSock, recvBuf, 50, 0);printf("来自客户端:");printf("%s\n", recvBuf);printf_s("请输入内容:");scanf("%s",sendBuf);//sendBuf="s";//gets_s(sendBuf);send(cIntSock, sendBuf, strlen(sendBuf) + 1, 0);}

sendBuf就是一个字符数组,用来输入自己的要输入的内容。

主要看recv,recv 接收4个参数,第一个参数是建立的通信、第二个参数是是一个数组,接收数据存放的地方、之后会缓存大小,最后一个参数是指定调用方式,不用管一般设置为0。

cIntSock 就是刚刚从套接字里接受的那个接待员,现在就用接待员和他说话了。

接着就使用printf显示接待员听到的话,简简单单。

然后就到我们输入信息,使用scanf够简单了吧?
接着使用 send函数发送信息就可以了,第一个就是告诉接待员 cIntSock 要传达话了,sendBuf 就是咱们要说的话,第三个参数就是咱们说的话的长度,最后一个依旧是0,不用管。

这样就还差最后一步就完成服务端了,此时咱们只需要关闭套接字就可以了,最后还需要清理一下,完整代码如下了:

#include<stdio.h>
#include<WinSock2.h>
#include <stdlib.h>int main()
{WSADATA wsadata;WSAStartup(MAKEWORD(2, 2), &wsadata);SOCKET serverSock = socket(PF_INET, SOCK_STREAM, 0);struct sockaddr_in sockAddr;sockAddr.sin_family = PF_INET;sockAddr.sin_addr.s_addr = htons(INADDR_ANY);sockAddr.sin_port = htons(1234);bind(serverSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));listen(serverSock, 20);SOCKADDR cIntAddr; int nSize = sizeof(SOCKADDR);SOCKET cIntSock = accept(serverSock, (SOCKADDR*)&cIntAddr, &nSize);while (1) {char sendBuf[50]={"Hello client"};char recvBuf[50];recv(cIntSock, recvBuf, 50, 0);printf("来自客户端:");printf("%s\n", recvBuf);printf_s("请输入内容:");scanf("%s",sendBuf);send(cIntSock, sendBuf, strlen(sendBuf) + 1, 0);}//关闭closesocket(cIntSock);closesocket(serverSock);WSACleanup();return 0;
}

三、客户端编写

客户端和服务端是一样的你信吗?
下面是代码:

#include<stdio.h>
#include<winsock2.h>int main()
{WSADATA wsadata;int nRes = WSAStartup(MAKEWORD(2, 2), &wsadata);SOCKET sock = socket(PF_INET, SOCK_STREAM, 0);struct sockaddr_in sockAddr;sockAddr.sin_family = PF_INET; sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //只需要在这里指向服务器 ip 就可以了 sockAddr.sin_port = htons(1234);//连接服务器connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));while (1) {char recvBuf[50];char sendBuf[50]={"Hello server"};printf("跟服务端说: ");scanf("%s",sendBuf);send(sock, sendBuf, strlen(sendBuf) + 1, 0);recv(sock, recvBuf, 50, 0);printf("服务端跟你说: ");printf("%s\n", recvBuf);}closesocket(sock);WSACleanup();system("pause");
}

不同的几个点只有使用了 connect 连接服务器就没了,难道你说不是吗?
简简单单对吧?那就行,解决。

下面是演示示例:

注意 若使用devc复制代码都报错,则点击编译->编译选项:
在这里插入图片描述
随后在出现的窗口中添加如下参数:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/286695.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Microstation】第一章:Microstation三维模型构建概述

MicroStation 是国际上和AutoCAD齐名的二维和三维CAD设计软件&#xff0c;第一个版本由Bentley兄弟在1986年开发完成。其专用格式是DGN&#xff0c;并兼容AutoCAD的DWG/DXF等格式。 MicroStation是Bentley 工程软件系统有限公司在建筑、土木工程、交通运输、加工工厂、离散制造…

libgdx游戏引擎开发笔记(十)SuperJumper游戏例子的讲解(篇四)---- 主游戏界面内部框架编写...

上一讲&#xff0c;我们已经实现了点击play进入游戏界面但仅仅是个黑屏 今天&#xff0c;我们就试着编写代码让它出现游戏的一些简单场景。还是在上一讲的代码基础上&#xff0c;我们创建两个类&#xff1a;World 和 WorldRenderer 1.Word类&#xff1a; 12345678910111213141…

看看《System.CommandLine》

记得之前出过几篇.net tool工具的文章&#xff0c;当时的做法是所有工具的语法分析全部自己解释&#xff0c;自己执行&#xff0c;语法的解释占了大部分时间&#xff0c;反而工具的功能被弱化了。其实微软有一个CommandLine框架在缓慢的发展着&#xff0c;至今都没有正式发布&a…

Sublime Text 3 import Anaconda 无法正常补全模块名解决办法

Sublime Text 3 Anaconda配置 在安装Sublime Text3之后我们总会安装一些插件&#xff0c;比如Python的Anaconda自动补全插件。但是&#xff0c;装好之后发现import 时无法像别的IDE里面那样自动补全模块名&#xff0c;就像图中一样&#xff1a; 解决办法在Sublime Text的git…

Android之添加固定图标到桌面

我的QQ群 1 需求 Android之添加固定图标到桌面 2 部分实现 在AndroidManifest.xml里面添加如下权限 <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" /><uses-permission android:name="com.android.launcher.perm…

想做程序员?不同方向入门路线全解

学习计算机编程有很多方向如果你没有一个正确的路线&#xff0c;那么就会&#xff1a; 就会跟上面所说的那样&#xff0c;被迫成为一个全栈&#xff0c;这是比较尴尬的。 若你想比较准确的针对某个方向学习&#xff0c;那就继续往下看吧。 一、程序员分为哪几个方向 随着…

【转】OpenGL超级宝典笔记——纹理映射Mipmap

原文地址 http://my.oschina.net/sweetdark/blog/177812 , 感谢作者&#xff0c;若非法转载请联系本人。 目录[-] MipmappingMipmap过滤构建Mip层Mipmaps 硬件生成LOD&#xff08;多细节层次&#xff09;偏好纹理对象管理多个纹理常驻纹理纹理优先级回顾Mipmapping Mipmap是一个…

【Microstation】第二章:Microstation三维建模基础知识

本章的主要内容包括模型的显示样式(线框、光滑)、三维定位(V、T、S、F)、Microstation常见的坐标系统(世界坐标系、ACS辅助坐标系、精确绘图坐标系、)和Microstation的工作区域(2D和3D)。 一、显示样式 二、三维定位 三维定位在Microstation中显得尤为重要,常见…

实现 EF Core 6 自定义查询标记

前言在《EF Core使用Simple Logging输出日志》中&#xff0c;我们介绍了查询标记 TagWith&#xff0c;它可以帮助我们快速定位到需要的日志&#xff1a;而在 .NET 6 中&#xff0c;新增了另外一个查询标记 TagWithCallSite&#xff0c;它可以标记出代码的位置&#xff1a;var u…

0运维?微信小程序云开发增删查改【05】

在创建小程序时&#xff0c;选择云开发&#xff1a; 随后进入项目之后&#xff0c;此时整个目录如下&#xff1a; 此时我们如图目录即可找到首页位置&#xff1a; 接着咱们清除 index.wxml 代码内容&#xff1a; 在 index.wxml 中加入如下代码&#xff1a; <view> …

【Microstation】第三章:Microstation三维模型构建与编辑

本章主要讲述三维基本实体绘制、三维构造元素绘制、三维模型编辑。 一、三维基本体素绘制 对于立方体、圆柱、球、圆锥等这些基本立体单位,MS提供了专门的绘图工具。 基本体素绘制有两种方式: (1)精确绘图工具 (2&

文件系统管理相关命令

查看文件系统相关属性的命令&#xff1a;blkidblkid是一个查看磁盘设备属性相关信息的命令行工具blkid -L LABEL | UUID :根据UUID查看对应的设备是哪个blkid [-ghlv] [-c file] [-w file] [-o format][-s tag] [-t NAMEvalue] device [device ...]-i&#xff1a;显示io限制lsb…

Android之实现夸克浏览器书签和历史页面滑动时候右上角图标切换效果

1 需求 实现夸克浏览器书签和历史页面滑动时候右上角图标切换效果,页面滑动的时候,图标也左右滑动,但是只是显示其中的一个 https://www.captainai.net/st/ 2 代码实现 xml布局实现 <LinearLayoutandroid:id="@+id/mainLl"android:layout_width="24d…

你都用 Python 来做什么?

你们都用python做些什么呢&#xff1f; 在开发中 python 这一个语言就像是小叮当&#xff0c;而 python 的第三方库则是“百宝箱”&#xff0c;你只要想着对某一个方向进行开发&#xff0c;那么这个“百宝箱”就会给你想要的东西。 由于我是在开发多年后接触到的 python&#…

Android之tint图片着色器

1、爆照 上面是原图,下面是点击效果。 2、介绍 设置着色模式用的。这个模式共有6种,分别为: multiply screen src_in(默认) src_over src_atop add android:tint 属性可以改变图片颜色 3 源代码 colors.xml <?xml version="1.0" encoding="utf-8&qu…

关于 Oracle分页数据重复的问题

2019独角兽企业重金招聘Python工程师标准>>> 先说问题吧。最近在测试一个新的模块&#xff0c;发现列表数据的前三页数据竟然是一样的。第一反应是 pageNo 的问题&#xff0c;debug一看&#xff0c;pageNo是正确的&#xff0c;然后一层层debug下去&#xff0c;所有参…

10分钟做一个新闻问答web站点[iVX低代码实战]

一、创建首页 进入到iVX线上编辑器后&#xff0c;选择相对定位。 点击创建后进入到 IDE 之中&#xff1a; 我们在第一步中首先创建一个首页。点击左侧组件栏中的页面组件&#xff0c;创建一个页面&#xff1a; 接着重命名该页面为Home&#xff0c;在页面中创建一个行&#x…

iOS开发网络篇—网络编程基础

一、为什么要学习网络编程 1.简单说明 在移动互联网时代&#xff0c;移动应用的特征有&#xff1a; &#xff08;1&#xff09;几乎所有应用都需要用到网络&#xff0c;比如QQ、微博、网易新闻、优酷、百度地图 &#xff08;2&#xff09;只有通过网络跟外界进行数据交互、数据…

深入理解Three.js(WebGL)贴图(纹理映射)和UV映射

本文将详细描述如何使用Three.js给3D对象添加贴图&#xff08;Texture Map&#xff0c;也译作纹理映射&#xff0c;“贴图”的翻译要更直观&#xff0c;而“纹理映射”更准确。&#xff09;。为了能够查看在线演示效果&#xff0c;你需要有一个兼容WebGL的现代浏览器&#xff0…

在兰州吃牛肉面时亲眼目睹的一幕,我感动了

今天中午去吃占国牛肉面&#xff0c;人比较多&#xff0c;很热闹。旁边坐了一群身穿校服的小学生&#xff0c;身后坐着一位小学老师&#xff0c;当孩子们发现老师在后面的时候&#xff0c;都异口同声地叫老师过去跟他们一起吃&#xff0c;老师嘿的一笑&#xff0c;没同意&#…