专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

WebRTC实战P2P音视频通话解决方案

  1、简介
  本文将详细介绍如何利用WebRTC技术实现P2P音视频通话,并提供了一个跨平台的方案,包括:基于socket。io和Node。js实现的服务端,以及JavaScript和Android客户端。让我们一起来探讨如何搭建这个系统,以及如何编写代码吧。
  由于server、js、android代码还在整理中,预计还需要23天时间。地址:github。comyangkun1992
  下面是PC与IOS在不同网络环境下的效果图(WiFi移动网络):
  2、服务端2。1使用nodejs和socket。io实现信令服务器
  我们借助上一篇信令服务的流程图,来实现一个nodejs信令服务器
  我们先设计一个信令
  join:当前用户和远端用户加入到房间中的信令
  leave:当前用户和远端用户离开房间的信令
  message:交换双方的SDP、ICE信令
  首先,我们需要搭建一个Node。js服务端,用于处理信令交换。在这里,我们将使用socket。io库作为通信协议,借助http、https、fs等组件。实现一个简单的Node。js服务端实例:
  createserver。js下面就是信令服务的核心代码varlog4jsrequire(log4js);varhttprequire(http);varhttpsrequire(https);varfsrequire(fs);varsocketIorequire(socket。io);varexpressrequire(express);varserveIndexrequire(serveindex);varUSERCOUNT3;。。。httpservervarhttpserverhttp。createServer(app);httpserver。listen(80,0。0。0。0);varoptions{key:fs。readFileSync(。certxxx。key),cert:fs。readFileSync(。certxxx。pem)}httpsservervarhttpsserverhttps。createServer(options,app);variosocketIo。listen(httpsserver);io。sockets。on(connection,(socket){socket。on(message,(room,data){socket。to(room)。emit(message,room,data);发送给当前房间的其它客户端});socket。on(join,(room){socket。join(room);varmyRoomio。sockets。adapter。rooms〔room〕;varusers(myRoom)?Object。keys(myRoom。sockets)。length:0;logger。debug(theusernumberofroomis:users);if(usersUSERCOUNT){socket。emit(joined,room,socket。id);发送给自己,相当于回调if(users1){socket。to(room)。emit(otherjoin,room,socket。id);发送给当前房间的其它客户端}}else{socket。leave(room);socket。emit(full,room,socket。id);}});socket。on(leave,(room){varmyRoomio。sockets。adapter。rooms〔room〕;varusers(myRoom)?Object。keys(myRoom。sockets)。length:0;logger。debug(theusernumberofroomis:(users1));socket。to(room)。emit(bye,room,socket。id);socket。emit(leaved,room,socket。id);});});httpsserver。listen(443,0。0。0。0);
  要运行上面的server。js信令服务器,您需要按照以下步骤进行安装和运行:安装Node。js和npm:安装所需的依赖项npminstallexpresssocket。iofshttphttps启动servernodeserver。js2。2搭建sturnturn服务器
  由于网络环境的影响我们需要搭建一个sturnturn服务器,以便提升P2P的成功率,下面是一个粗略的搭建方式,但是也够用了。安装Coturn
  在终端中输入以下命令,使用yum包管理器安装Coturn:sudoyuminstallcoturn配置Coturn
  找到并编辑Coturn的配置文件etccoturnturnserver。conf,根据您的需求修改以下配置项:配置监听的端口号listeningport3478minport49152maxport65535配置域名realmxxx。com允许使用TURNSTUN服务的用户的凭据user123456:123456certpathtoxxx。pempkeypathtoxxx。pem配置日志文件路径logfilerootlogturnserver。log启动Coturn
  在终端中输入以下命令,启动Coturn服务:sudosystemctlstartcoturnsudosystemctlstopcoturnsudosystemctlrestartcoturnsudosystemctlstatuscoturn测试coturn我们可以去trickleice测试网站进行测试
  正如trickleice网站所说:如果你测试一个STUN服务器,你能收集到一个类型为srflx的候选者,它就可以工作。如果你测试一个TURN服务器,你能收集到一个类型为relay的候选人,它就会工作。
  由此上图sturn和turn候选者地址都能成功连接。
  C音视频学习资料免费获取方法:关注音视频开发T哥,点击链接即可免费获取2023年最新C音视频开发进阶独家免费学习大礼包!3、客户端
  WebRTC是一种基于Web技术的实时通信解决方案,可用于在浏览器中实现P2P音视频通话。当然,现在基本上所有上层平台都支持了。在WebRTC中,双方通信通过ICE协议进行连接,通过SDP协议交换媒体信息,通过DTLS协议进行加密,通过SRTP协议进行媒体传输。
  下面,我们将为你介绍如何使用WebRTC在浏览器和Android中实现P2P音视频通话。3。1Web
  我们按照上面信令的流程来实现:3。1。1获取媒体流
  WebRTC支持从设备摄像头和麦克风获取视频和音频流。使用JavaScript的getUserMediaAPI,您可以请求用户授权,从摄像头和麦克风获取本地媒体流,并将其添加到一个MediaStream对象中。functionstartCall(){if(!navigator。mediaDevices!navigator。mediaDevices。getUserMedia){console。error(thegetUserMediaisnotsupported!);return;}else{varconstraints{video:true,传输视频audio:true传输音频}navigator。mediaDevices。getUserMedia(constraints)。then(getMediaStream)打开成功的回调。catch(handleError);打开失败}}3。1。2连接信令服务器并加入到房间中functionconnect(){连接信令服务器socketio。connect();加入成功的通知socket。on(joined,(roomid,id){。。。});远端加入socket。on(otherjoin,(roomid){。。。});房间满了socket。on(full,(roomid,id){。。。});接收自己离开房间的回调socket。on(leaved,(roomid,id){。。。});收到对方挂断的消息socket。on(bye,(room,id){。。。});收到服务断开的消息socket。on(disconnect,(socket){。。。});收消息,用于交换SDP和ICE消息等socket。on(message,(roomid,data){。。。});发送join消息到信令服务器并加入到123456房间中socket。emit(join,123456);}3。1。3创建PeerConnection并添加媒体轨道
  当收到自己加入房间成功的消息后,连接到远程对等方,我们就需要创建一个RTCPeerConnection对象,并将本地媒体流添加到其中。然后,您需要创建一个RTCDataChannel对象,用于在对等方之间传输数据。varpcConfig{iceServers:〔{urls:turn:xxx:3478,credential:1234,username:1234}〕};pcnewRTCPeerConnection(pcConfig);当前icecandida数据pc。onicecandidate(e){。。。}datachannel传输通道pc。ondatachannele{。。。}添加远端的媒体流到videoelementpc。ontrackgetRemoteStream;最后添加媒体轨道到peerconnection对象中localStream。getTracks()。forEach((track){pc。addTrack(track,localStream);});创建一个非音视频的数据通道dcpc。createDataChannel(test);dc。onmessagereceivemsg;接收对端消息dc。onopendataChannelStateChange;当打开dc。onclosedataChannelStateChange;当关闭functiongetRemoteStream(e){remoteStreame。streams〔0〕;remoteVideo。srcObjecte。streams〔0〕;}3。1。4发送createOffer数据到远端
  当对方加入到房间中,我们需要把当前UserA的SDP信息告诉UserB用户,使用如下代码varofferOptions{同时接收远端的音、视频数据offerToRecieveAudio:1,offerToRecieveVideo:1}pc。createOffer(offerOptions)。then(getOffer)创建成功的回调。catch(handleOfferError);functiongetOffer(desc){设置UserASDP信息pc。setLocalDescription(desc);offerdescdesc;将usera的SDP发送到信令服务器,信令服务器再根据roomid进行转发sendMessage(roomid,offerdesc);}3。1。5发送answer消息到对方
  当UserB收到UserA发来的offer消息,我们需要设置UserA的SDP并且设置当前的SDP然后再讲自己的SDP发送给UserA,以进行媒体协商,如下代码:1。当收到UserAOFFER消息,设置SDPpc。setRemoteDescription(newRTCSessionDescription(data));2。然后创建answer消息pc。createAnswer()。then(getAnswer)。catch(handleAnswerError);3。当创建成功后,拿到UserB自己的SDP消息并设置当前的SDP信息,最后再讲SDP消息发给信令再转发给roomid房间中的客户端functiongetAnswer(desc){pc。setLocalDescription(desc);optBw。disabledfalse;sendanswersdpsendMessage(roomid,desc);}3。1。6接收answer消息,并设置UserB的SDP信息
  当我们收到UserB发来的answersdp消息后告诉底层pc。setRemoteDescription(newRTCSessionDescription(data));3。1。7交换ICE候选
  SDP协商完后,UserAUserB交换ice消息,用于nat和转发媒体数据,如果都在局域网其实可以省略这一步userAUserB收到onicecandidate回调然后将candidate发送给UserBpc。onicecandidate(e){if(e。candidate){sendMessage(roomid,{type:candidate,label:event。candidate。sdpMLineIndex,id:event。candidate。sdpMid,candidate:event。candidate。candidate});}else{console。log(thisistheendcandidate);}}当UserBUserA接收到UserAUserB的candidate后进行添加functionaddIcecandida(data){varcandidatenewRTCIceCandidate({sdpMLineIndex:data。label,candidate:data。candidate});pc。addIceCandidate(candidate)。then((){console。log(Successedtoaddicecandidate);})。catch(err{console。error(err);});}
  通过如上核心步骤代码,你已经完成了一个基于WebRTCJS版的跨平台P2P音视频通话系统。当然,这里展示的代码只是简化版示例,完整版的代码可以点击文末简介处有说明。3。2Android
  上面我们实现了服务端和跨平台的JS端,最后我们实现一个Android端,毕竟最开始我就是搞Android的。
  对于Android客户端,您可以使用Google提供的WebRTC库。如下,当前也可以直接依赖javac源码。当前我们就是直接依赖的javac源码
  依赖wertcsdk方式,在build。gradle文件中添加依赖项:implementationorg。webrtc:googlewebrtc:1。0。
  依赖wertc源码方式,在build。gradle文件中添加如下设置:externalNativeBuild{cmake{version3。10。2pathCMakeLists。txt}}
  没错,我们通过编写cmake直接依赖的c源码。好了,依赖方式就不再多说了,可以直接去看项目中的build。gradle文件即可。
  Android上的实现步骤流程与JS几乎一样,我们来看一下如何实现吧。3。2。1获取媒体流并初始化PeerConnectionFactory
  这里我们直接通过Camera2来实现相机数据的采集privateVideoCapturercreateVideoCapture(){finalVideoCapturervideoCapturer;videoCapturercreateCameraCapturer(newCamera2Enumerator(this));returnvideoCapturer;}设置本地预览窗口mLocalSurfaceView。init(mRootEglBase。getEglBaseContext(),null);mLocalSurfaceView。setScalingType(RendererCommon。ScalingType。SCALEASPECTFILL);mLocalSurfaceView。setMirror(true);mLocalSurfaceView。setEnableHardwareScaler(falseenabled);设置远端预览窗口mRemoteSurfaceView。init(mRootEglBase。getEglBaseContext(),null);mRemoteSurfaceView。setScalingType(RendererCommon。ScalingType。SCALEASPECTFILL);mRemoteSurfaceView。setMirror(true);mRemoteSurfaceView。setEnableHardwareScaler(trueenabled);mRemoteSurfaceView。setZOrderMediaOverlay(true);callStartedTimeMsSystem。currentTimeMillis();创建factory,pc是从factory里获得的createPeerConnectionFactory();privatevoidcreatePeerConnectionFactory(){finalStringfieldTrialsgetFieldTrials(mPeerConnectionParameters);executor。execute((){Log。d(Constants。P2PTAG,InitializeWebRTC。Fieldtrials:fieldTrials);PeerConnectionFactory。initialize(PeerConnectionFactory。InitializationOptions。builder(mContext)。setFieldTrials(fieldTrials)。setEnableInternalTracer(true)。createInitializationOptions());});executor。execute((){createPeerConnectionFactoryInternal();});}3。2。2连接信令服务器并加入到房间中publicvoidconnectToRoom(RoomConnectionParametersparameters,ISignalEventListenersignalEventListener){mRoomConnectParametersparameters;executor。execute((){if(mISignalClient!null){try{mISignalClient。connect(parameters。roomUrl,newISignalEventListener(){OverridepublicvoidOnConnecting(){Log。i(Constants。P2PTAG,OnConnecting);。。。}OverridepublicvoidOnConnected(){Log。i(Constants。P2PTAG,OnConnected);Log。i(Constants。P2PTAG,join:parameters。roomId);mISignalClient。join(parameters。roomId);。。。}OverridepublicvoidOnDisconnected(){if(signalEventListener!null){signalEventListener。OnConnecting();}}OverridepublicvoidOnUserJoined(StringroomName,StringuserId,booleanisInitiator){if(signalEventListener!null){signalEventListener。OnUserJoined(roomName,userId,isInitiator);}Log。i(Constants。P2PTAG,joined:roomNameuserIdisInitiator);Log。i(Constants。P2PTAG,createPeerConnection);。。。}OverridepublicvoidOnUserLeaved(StringroomName,StringuserId){。。。}OverridepublicvoidOnRemoteUserJoined(StringroomName,StringuserId){Log。i(Constants。P2PTAG,createOfferroomNameuserId);。。。}OverridepublicvoidOnRemoteUserLeaved(StringroomName,StringuserId){。。。}OverridepublicvoidOnRoomFull(StringroomName,StringuserId){。。。}OverridepublicvoidOnMessage(JSONObjectmessage){。。。}});}catch(Exceptione){Log。e(TAG,e。getMessage());}}});}3。2。3创建PeerConnection并添加媒体轨道
  当收到自己加入房间成功的消息后,连接到远程对等方,我们就需要创建一个PeerConnection对象,并将本地媒体流添加到其中。然后,您需要创建一个DataChannel对象,用于在对等方之间传输数据。
  简要代码如下:当连接成功并且进入到房间中执行privatevoidcreatePeerConnection(){executor。execute((){try{createMediaConstraintsInternal();createPeerConnectionInternal();Log。i(Constants。P2PTAG,createPeerConnectionSucceed);}catch(Exceptione){Log。e(TAG,Failedtocreatepeerconnection:e。getMessage());throwe;}});}privatevoidcreateMediaConstraintsInternal(){Createvideoconstraintsifvideocallisenabled。。。。Createaudioconstraints。mAudioConstraintsnewMediaConstraints();addedforaudioperformancemeasurementsif(mPeerConnectionParameters。noAudioProcessing){Log。d(TAG,Disablingaudioprocessing);mAudioConstraints。mandatory。add(newMediaConstraints。KeyValuePair(AUDIOECHOCANCELLATIONCONSTRAINT,false));mAudioConstraints。mandatory。add(newMediaConstraints。KeyValuePair(AUDIOAUTOGAINCONTROLCONSTRAINT,false));mAudioConstraints。mandatory。add(newMediaConstraints。KeyValuePair(AUDIOHIGHPASSFILTERCONSTRAINT,false));mAudioConstraints。mandatory。add(newMediaConstraints。KeyValuePair(AUDIONOISESUPPRESSIONCONSTRAINT,false));}CreateSDPconstraints。mSdpMediaConstraintsnewMediaConstraints();mSdpMediaConstraints。mandatory。add(newMediaConstraints。KeyValuePair(OfferToReceiveAudio,true));mSdpMediaConstraints。mandatory。add(newMediaConstraints。KeyValuePair(OfferToReceiveVideo,Boolean。toString(isVideoCallEnabled())));}privatevoidcreatePeerConnectionInternal(){if(mPeerConnectionFactorynull){Log。e(TAG,Peerconnectionfactoryisnotcreated);return;}Log。d(TAG,Createpeerconnection。);queuedRemoteCandidatesnewArrayList();ListPeerConnection。IceServericeServersnewArrayList();iceServers。add(PeerConnection。IceServer。builder(turn:xxx:3478)。setPassword(xxx)。setUsername(xxx)。createIceServer());PeerConnection。RTCConfigurationrtcConfignewPeerConnection。RTCConfiguration(iceServers);TCPcandidatesareonlyusefulwhenconnectingtoaserverthatsupportsICETCP。rtcConfig。tcpCandidatePolicyPeerConnection。TcpCandidatePolicy。DISABLED;rtcConfig。bundlePolicyPeerConnection。BundlePolicy。MAXBUNDLE;rtcConfig。rtcpMuxPolicyPeerConnection。RtcpMuxPolicy。REQUIRE;rtcConfig。continualGatheringPolicyPeerConnection。ContinualGatheringPolicy。GATHERCONTINUALLY;UseECDSAencryption。rtcConfig。keyTypePeerConnection。KeyType。ECDSA;rtcConfig。sdpSemanticsPeerConnection。SdpSemantics。UNIFIEDPLAN;mPeerConnectionmPeerConnectionFactory。createPeerConnection(rtcConfig,pcObserver);if(dataChannelEnabled){DataChannel。InitinitnewDataChannel。Init();init。orderedmPeerConnectionParameters。dataChannelParameters。ordered;init。negotiatedmPeerConnectionParameters。dataChannelParameters。negotiated;init。maxRetransmitsmPeerConnectionParameters。dataChannelParameters。maxRetransmits;init。maxRetransmitTimeMsmPeerConnectionParameters。dataChannelParameters。maxRetransmitTimeMs;init。idmPeerConnectionParameters。dataChannelParameters。id;init。protocolmPeerConnectionParameters。dataChannelParameters。protocol;mDataChannelmPeerConnection。createDataChannel(P2Pdata,init);}isInitiatorfalse;SetINFOlibjinglelogging。NOTE:thismusthappenwhilefactoryisalive!Logging。enableLogToDebugOutput(Logging。Severity。LSINFO);ListStringmediaStreamLabelsCollections。singletonList(ARDAMS);if(isVideoCallEnabled()){mPeerConnection。addTrack(createVideoTrack(mVideoCapture),mediaStreamLabels);Wecanaddtherenderersrightawaybecausewedontneedtowaitforananswertogettheremotetrack。remoteVideoTrackgetRemoteVideoTrack();remoteVideoTrack。setEnabled(renderVideo);目前就一个remoteVideoTrack。addSink(mRemoteSurfaceView);}mPeerConnection。addTrack(createAudioTrack(),mediaStreamLabels);if(isVideoCallEnabled()){findVideoSender();}}3。2。4发送createOffer数据到远端
  当对方加入到房间中,我们需要把当前UserA的SDP信息告诉UserB用户,使用如下代码publicvoidcreateOffer(){executor。execute((){if(mPeerConnection!null){Log。d(Constants。P2PTAG,PCCreateOFFER);isInitiatortrue;1。createoffermPeerConnection。createOffer(sdpObserver,mSdpMediaConstraints);}});}2。当createOffer成功我们会收到如下回调OverridepublicvoidonCreateSuccess(finalSessionDescriptiondesc){然后我们需要设置当前的SDPmPeerConnection。setLocalDescription(sdpObserver,newDesc);}3。当设置成功后,我们会收到onSetSuccess回调,然后将UserASDPoffer消息发送给对等方OverridepublicvoidonSetSuccess(){JSONObjectmessagenewJSONObject();try{Stringtypeoffer;if(sdp。typeSessionDescription。Type。ANSWER)typeanswer;message。put(type,type);message。put(sdp,sdp。description);sendMessage(message);}catch(JSONExceptione){e。printStackTrace();}}3。2。5发送answer消息到对方
  当UserB收到UserAoffer消息后的处理1。设置UserASDP描述符mPeerConnection。setRemoteDescription(sdpObserver,sdpRemote);if(desc。typeSessionDescription。Type。OFFER){Log。i(Constants。P2PTAG,CreatingANSWER。。。);2。创建answermPeerConnection。createAnswer(sdpObserver,mSdpMediaConstraints);}3。answer创建成功后的处理mPeerConnection。setLocalDescription(sdpObserver,newDesc);4。UserB设置成功后的处理,将sdp发给UserAJSONObjectmessagenewJSONObject();try{Stringtypeoffer;if(sdp。typeSessionDescription。Type。ANSWER)typeanswer;message。put(type,type);message。put(sdp,sdp。description);sendMessage(message);}catch(JSONExceptione){e。printStackTrace();}3。2。6接收answer消息,并设置UserB的SDP信息
  当我们收到UserB发来的answersdp消息后告诉底层mPeerConnection。setRemoteDescription(sdpObserver,sdpRemote);3。2。7交换ICE候选
  SDP协商完后,UserAUserB交换ice消息,用于nat和转发媒体数据,如果都在局域网其实可以省略这一步userAUserB收到onicecandidate回调然后将candidate发送给UserBOverridepublicvoidonIceCandidate(finalIceCandidateiceCandidate){executor。execute((){Log。i(Constants。P2PTAG,onIceCandidate:iceCandidate);try{JSONObjectmessagenewJSONObject();message。put(type,candidate);message。put(label,iceCandidate。sdpMLineIndex);message。put(id,iceCandidate。sdpMid);message。put(candidate,iceCandidate。sdp);mISignalClient。sendSignalMessage(mRoomConnectParameters。roomId,message);}catch(JSONExceptione){e。printStackTrace();}});}当UserBUserA接收到UserAUserB的candidate后进行添加mPeerConnection。addIceCandidate(candidate,newAddIceObserver(){。。。}}
  通过如上核心步骤代码,你已经完成了一个基于WebRTCAndroid版的P2P音视频通话系统。当然,这里展示的代码只是简化版示例,完整版的代码可以点击文末简介处有说明。
  到此,你已经可以JSJS、AndroidAndroid、JSAndroid平台下进行P2P的音视频通话了。4、总结
  本文为你介绍了如何基于WebRTC实现一个P2P音视频通话系统,和提供了一个跨平台的实现方案,主要包括以下三个部分:服务端:使用Node。js和socket。io构建的信令服务器,负责协调通信和传递ICE候选、SDP信息。客户端(跨平台):基于WebRTC的JavaScript客户端,实现浏览器端的音视频通话功能。客户端:Android客户端,使用Google提供的WebRTC库构建音视频通话应用。
  请注意,本文提供的代码是简化版示例,您可以根据项目需求进行扩展和优化。通过本教程,您应该对如何使用WebRTC构建P2P音视频通话系统有了更深入的了解,并能将其应用于实际项目中。
  到此,P2P音视频通话系统我们已经实现完了,下一篇我们会介绍视频会议的实现方案,尽请期待吧。
  原文链接:WebRTC实战:P2P音视频通话解决方案掘金

545亿颗,中国芯片砍单量再次突破纪录,高通主动抛来橄榄枝阅读下面文章之前,希望您能够在上方点个免费的关注!接下来您每天都能够收到免费的国际资讯哟!您的关注是我的动力多多支持下嘛!!自从芯片规则被修改后,国内企业想要进口美国芯片就变得难上T3出行拿到微信流量入口,有望冲击市场老大?滴滴别说了近些年T3出行的动静,可以说是不小,也是,人家作为国企也确实有跳脱的资本。从一开始,T3出行走的就是跟普通网约车平台不一样的道路。比如给司机设定底薪,给司机交社保,可以说是网约车行16点00分!中国女篮对决欧洲劲旅,开赛时间有变,保四争二有戏了14决赛对阵法国队,中国女篮终于在世界大赛的淘汰赛当中,享受了一次上上签待遇。当然了,能够闯进女篮世界杯的淘汰赛,没有一支球队是鱼腩。对于中国女篮来说,抽到了运气好的签位,更应该发中国歼15性能已超越俄原版苏33最近中国海军歼15双机编队飞越美国海军阿利伯克级导弹驱逐舰一事也引起了俄罗斯相关媒体的注意。俄罗斯军事评论网站认为,这件事证明了中国海军在相当短的时间内走到了相当远的距离。这件事对心存希翼目有繁星,追光而遇沐光而行秋风知劲草,秋叶化悲凉。又是硕果时,何处惹尘埃。时光飞逝又是一年秋风到,还未摆脱昨日悲伤,今又又添新伤,树不懂叶的落寞,夏不知秋的无助。01愿为草木低眉沉静,愿为自己善许光阴张爱玲延河宝塔中国梦(歌词)A巍巍的宝塔山,凄冽的西北风,硝烟弥漫延河哭,苍天泪洒动愁容,宝塔见证着苦难的路啊,千年的华夏穿百孔穿百孔。奔腾的延河水,激荡波汹涌,冲刷耻辱奋抗争,百折不挠向前冲,澎湃咆热度过后,浅谈隐入尘烟和马老四不在沉默中爆发,就在沉默中灭亡!哀莫大于心死,悲莫过于无声!当看到老四平静的去打印照片的时候,你就会感到某座大厦将倾,悲剧即将发生,一切都已无法挽回。世界上只有一种英雄主义,那就是努力要趁早甘孜的秋天很短!昨天还是炎炎夏日,今儿就突然想跳过秋天!似乎急于和白雪相拥。早上胡七八糟的吃点东西,看着蓝天白云,阳光明媚。双腿不自觉的往着山上走去。可像山上有什么东东拉着我,非去致中年人的一封信中年人是儿子的爸爸,爸爸的儿子。然而,社会对中年人是不够宽容的!中年人的生活里,是满满的不易。人到中年,已无路可退!如离弦之箭,如落叶之秋。中年人谈梦想太奢侈。谈骄傲,儿子还小!社山西这座花2。5亿修复的皇坟,中华第一陵,它究竟长什么样?雅伦在山西自驾旅行时,依然是慢悠悠地走国道,所以不期而遇的惊喜总是一个接着一个,而从长治到晋城时,我在高平市境内遇到了一座规模非常宏伟的建筑!其实,当天见到它时还完全不清楚它究竟是2022年香港回成都机票航班9101112月汇总2022年香港回成都机票航班9101112月合集2022年910月香港成都机票航班计划如下CX986香港成都9月234567910111213141617181920日9月2123
点亮童真!这场亲子DIY趣味十足为进一步丰富社区儿童的课余生活,提升儿童探索新事物的兴趣,长白新村街道未成年人保护工作站依托上海联劝基金会发起的宝藏小屋项目,联合上海市阳光善行公益事务中心开展造物星球成长教育课堂家有男孩,3岁用爱养,6岁要严养,12岁捧着养,那12岁后怎么养?头条创作挑战赛养育男孩的路上,总是充满了焦虑和挑战。只有了解男孩的成长特质,用更科学更适合的方式来教育男孩,才能事半功倍。作者枫子英国的一位作家曾说一个男孩比十二个女孩增添的麻烦还想要孩子一直当学霸,从小学起,这两个坑就不能踩作为父母,我们希望孩子学习好,将来会有更好的发展。所以,我们不遗余力地为孩子提供助力,希望能帮助孩子搞好学习。但有不少家长,本来是为了孩子好,一不小心却带着孩子踩坑了,导致孩子学习五折自助大餐吃到饱,吃喝玩乐各种优惠包你满意结束了繁忙的工作日,还不快安排起来周末活动!不管你是想吃喝玩乐还是看电影看演出,应有尽有,优惠力度大,购物福利多!给你一个赶走压力,彻底放松的周末!吃吃吃同乐寿司推出买一赠一活动不中国是世界第一罐头生产国,为何国人却光生产不吃,以出口为主头条创作挑战赛提起罐头,国人对它可谓是既熟悉又陌生。说熟悉,是因为在今天全国各地的大小商店超市中,都能看到各种罐头的身影说陌生,是因为在我们的日常饮食中,几乎没有罐头的一席之地。即华干林夜车杂记华干林文去北京赴一场飞行宴会,回扬州时决定乘一趟夕发朝至的直快。20年前,扬州还没通火车时,南京至北京也有一趟夕发朝至的直快。那时从扬州要去趟北京,必须到镇江或南京去坐火车,或去碌我80后江苏人,专职旅游,10年穷游25个国家没花一分钱,轻松自在这是我们讲述的第1608位真人故事大家好,我是周元芳周元芳向前冲,江苏南京人。我的工作很普通,没有底层的逆袭,在家人的庇护下慢慢长大。热爱旅行的我,只是想通过自己擅长的方式完成自我春游踏青,防火先行春季来临万物复苏相信不少小伙伴会选择春游踏青户外野炊烧烤但是,春季也是火灾频发的季节提醒大家游踏青时消防安全不能忘!我们一起来关注春季消防安全提示小常识春季踏青郊游忙防火安全不能忘沈阳这家洗浴中心哈根达斯雪糕不限量沈阳洗浴连载篇三元气汤泉149元团购了一个元气汤泉洗浴门票,里面的哈根达斯雪糕现磨咖啡三十几种国产进口水果及饮品不限量免费任吃。元气汤泉位于辽宁省沈阳市浑南区世纪路54号3门。16去贵州旅游在当地应该买上哪些特产?盘点贵州5大知名特产!贵州省是我国非常知名的旅游圣地,得益于云贵高原当地独特的地理环境,贵州省有这许多能展现大自然鬼斧神工的自然景点,同时也因为贵州也是我国知名的红色根据地,所以也有着非常丰富的红色旅游新疆巴州博湖县第二届沙漠文化旅游节开幕百余名选手齐聚博斯腾湖畔驰骋沙场享沙漠激情游塞上风景品特色美食2月18日,丝路山水壮美巴州巴州博湖县第二届沙漠文化旅游节暨豪帅杯沙漠越野拉力赛盛大开幕,百名赛车手齐聚在博湖县博斯腾湖乡艾勒逊乌拉沙漠,在沙漠赛道上
友情链接:快好知快生活快百科快传网中准网文好找聚热点快软网