说在前面的话:视频实时群聊天有三种架构

Mesh架构:终端之间互相连接,没有中心服务器,产生的问题,每个终端都要连接n-1个终端,每个终端的编码和网络压力都很大。群聊人数N不可能太大。

Router架构:终端之间引入中心服务器,学名MCU(Multi Point Control Unit),每个终端的视频流都发布到MCU服务器上,然后服务器负责编码发布多视频流的工作,减轻客户端的压力。

Mix架构:在Router架构基础上,多个视频流在服务器端被合为一个视频流,减轻网络压力。

下面讲我们的选择,在MCU方面有licode、kurento等解决方案。kurento在视频群聊领域有专门的kurento Room解决方案,官方还提供一个kurento room server的样例实现。

首先可以考虑不是一个Kurento Room Demo作为搭建方案原型的MCU组件。

Room Demo的部署可见:http://doc-kurento-room.readthedocs.io/en/stable/demo_deployment.html

其中碰到一些Maven编译问题:

  1. Unable to initialise extensions Component descriptor role: 'com.jcraft.jsch.UIKeyboardInteractive', implementation: 'org.apache.maven.wagon.providers.ssh.jsch.interactive.PrompterUIKeyboardInteractive', role hint: 'default' has a hint, but there are other implementations that don't

Maven的安装版本需要时3.0以上

还有碰到找不到bower命令行问题。bower是Node.js下面的一个包管理工具,安装node.js以后用npm安装即可

最后按照部署指南网页中的命令启动服务器即可。

Demo服务器有两部分,一部分是Demo Web服务器,二是把官方的kurento room server也集成到了这个demo中。不用再架设独立的kurento room server

说说Android段的实施:再说一个公司:http://www.nubomedia.eu/,这家公司提供实时媒体通信开源云服务,核心组件可能是kurento media server,它的官网和kurento官网用一个模板,about里面显示两家组织有联系,kurento官方提供的JavaClient因为底层API原因在android上不肯用,这个nubomedia组织提供了一个kurento android client的实现,同时还提供了一个kurento room client的实现以及room使用案例:https://github.com/nubomedia-vtt/nubo-test,这家公司对其开发的开源方案管理非常及时,早晨提个接口的issue,下午已经commit了代码修改。

这个案例虽然支持room沟通,但视频沟通是基于room发布订阅机制做的双人聊天。略改一下代码应该就可以实现多人聊天不过这家组织提供的两个client实现和官方的接口高度相似。主要改的是PeerVideoActivity这个类,下面我share一个基本走通多端通信的这个类的代码,供大家参考:

  1. package fi.vtt.nubotest;
  2. import android.app.ListActivity;
  3. import android.content.SharedPreferences;
  4. import android.graphics.PixelFormat;
  5. import android.opengl.GLSurfaceView;
  6. import android.os.Bundle;
  7. import android.os.Handler;
  8. import android.util.Log;
  9. import android.view.Menu;
  10. import android.view.MenuItem;
  11. import android.view.View;
  12. import android.view.WindowManager;
  13. import android.widget.TextView;
  14. import android.widget.Toast;
  15. import org.webrtc.IceCandidate;
  16. import org.webrtc.MediaStream;
  17. import org.webrtc.PeerConnection;
  18. import org.webrtc.RendererCommon;
  19. import org.webrtc.SessionDescription;
  20. import org.webrtc.VideoRenderer;
  21. import org.webrtc.VideoRendererGui;
  22. import java.util.Map;
  23. import fi.vtt.nubomedia.kurentoroomclientandroid.RoomError;
  24. import fi.vtt.nubomedia.kurentoroomclientandroid.RoomListener;
  25. import fi.vtt.nubomedia.kurentoroomclientandroid.RoomNotification;
  26. import fi.vtt.nubomedia.kurentoroomclientandroid.RoomResponse;
  27. import fi.vtt.nubomedia.webrtcpeerandroid.NBMMediaConfiguration;
  28. import fi.vtt.nubomedia.webrtcpeerandroid.NBMPeerConnection;
  29. import fi.vtt.nubomedia.webrtcpeerandroid.NBMWebRTCPeer;
  30. import fi.vtt.nubotest.util.Constants;
  31. /**
  32. * Activity for receiving the video stream of a peer
  33. * (based on PeerVideoActivity of Pubnub's video chat tutorial example.
  34. */
  35. public class PeerVideoActivity extends ListActivity implements NBMWebRTCPeer.Observer, RoomListener {
  36. private static final String TAG = "PeerVideoActivity";
  37. private NBMMediaConfiguration peerConnectionParameters;
  38. private NBMWebRTCPeer nbmWebRTCPeer;
  39. private SessionDescription localSdp;
  40. private SessionDescription remoteSdp;
  41. private String PaticipantID;
  42. private VideoRenderer.Callbacks localRender;
  43. private VideoRenderer.Callbacks remoteRender;
  44. private GLSurfaceView videoView;
  45. private SharedPreferences mSharedPreferences;
  46. private int publishVideoRequestId;
  47. private int sendIceCandidateRequestId;
  48. private TextView mCallStatus;
  49. private String  username, calluser;
  50. private boolean backPressed = false;
  51. private Thread  backPressedThread = null;
  52. private static final int LOCAL_X_CONNECTED = 72;
  53. private static final int LOCAL_Y_CONNECTED = 72;
  54. private static final int LOCAL_WIDTH_CONNECTED = 25;
  55. private static final int LOCAL_HEIGHT_CONNECTED = 25;
  56. // Remote video screen position
  57. private static final int REMOTE_X = 0;
  58. private static  int REMOTE_Y = 0;
  59. private static final int REMOTE_WIDTH = 25;
  60. private static final int REMOTE_HEIGHT = 25;
  61. private Handler mHandler;
  62. private CallState callState;
  63. private enum CallState{
  64. IDLE, PUBLISHING, PUBLISHED, WAITING_REMOTE_USER, RECEIVING_REMOTE_USER,PATICIPANT_JOINED,RECEIVING_PATICIPANT,
  65. }
  66. @Override
  67. public void onCreate(Bundle savedInstanceState) {
  68. super.onCreate(savedInstanceState);
  69. callState = CallState.IDLE;
  70. setContentView(R.layout.activity_video_chat);
  71. getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  72. mHandler = new Handler();
  73. Bundle extras = getIntent().getExtras();
  74. if (extras == null || !extras.containsKey(Constants.USER_NAME)) {
  75. ;
  76. Toast.makeText(this, "Need to pass username to PeerVideoActivity in intent extras (Constants.USER_NAME).",
  77. Toast.LENGTH_SHORT).show();
  78. finish();
  79. return;
  80. }
  81. this.username      = extras.getString(Constants.USER_NAME, "");
  82. Log.i(TAG, "username: " + username);
  83. if (extras.containsKey(Constants.CALL_USER)) {
  84. this.calluser      = extras.getString(Constants.CALL_USER, "");
  85. Log.i(TAG, "callUser: " + calluser);
  86. }
  87. this.mCallStatus   = (TextView) findViewById(R.id.call_status);
  88. TextView prompt   = (TextView) findViewById(R.id.receive_prompt);
  89. prompt.setText("Receive from " + calluser);
  90. this.videoView = (GLSurfaceView) findViewById(R.id.gl_surface);
  91. // Set up the List View for chatting
  92. RendererCommon.ScalingType scalingType = RendererCommon.ScalingType.SCALE_ASPECT_FILL;
  93. VideoRendererGui.setView(videoView, null);
  94. localRender = VideoRendererGui.create(  LOCAL_X_CONNECTED, LOCAL_Y_CONNECTED,
  95. LOCAL_WIDTH_CONNECTED, LOCAL_HEIGHT_CONNECTED,
  96. scalingType, true);
  97. NBMMediaConfiguration.NBMVideoFormat receiverVideoFormat = new NBMMediaConfiguration.NBMVideoFormat(352, 288, PixelFormat.RGB_888, 20);
  98. peerConnectionParameters = new NBMMediaConfiguration(   NBMMediaConfiguration.NBMRendererType.OPENGLES,
  99. NBMMediaConfiguration.NBMAudioCodec.OPUS, 0,
  100. NBMMediaConfiguration.NBMVideoCodec.VP8, 0,
  101. receiverVideoFormat,
  102. NBMMediaConfiguration.NBMCameraPosition.FRONT);
  103. nbmWebRTCPeer = new NBMWebRTCPeer(peerConnectionParameters, this, localRender, this);
  104. nbmWebRTCPeer.initialize();
  105. Log.i(TAG, "PeerVideoActivity initialized");
  106. mHandler.postDelayed(publishDelayed, 4000);
  107. MainActivity.getKurentoRoomAPIInstance().addObserver(this);
  108. callState = CallState.PUBLISHING;
  109. mCallStatus.setText("Publishing...");
  110. }
  111. private Runnable publishDelayed = new Runnable() {
  112. @Override
  113. public void run() {
  114. nbmWebRTCPeer.generateOffer("derp", true);
  115. }
  116. };
  117. @Override
  118. public boolean onCreateOptionsMenu(Menu menu) {
  119. // Inflate the menu; this adds items to the action bar if it is present.
  120. getMenuInflater().inflate(R.menu.menu_video_chat, menu);
  121. return true;
  122. }
  123. @Override
  124. public boolean onOptionsItemSelected(MenuItem item) {
  125. // Handle action bar item clicks here. The action bar will
  126. // automatically handle clicks on the Home/Up button, so long
  127. // as you specify a parent activity in AndroidManifest.xml.
  128. int id = item.getItemId();
  129. //noinspection SimplifiableIfStatement
  130. if (id == R.id.action_settings) {
  131. return true;
  132. }
  133. return super.onOptionsItemSelected(item);
  134. }
  135. @Override
  136. protected void onStart() {
  137. super.onStart();
  138. }
  139. @Override
  140. protected void onPause() {
  141. nbmWebRTCPeer.stopLocalMedia();
  142. super.onPause();
  143. }
  144. @Override
  145. protected void onResume() {
  146. super.onResume();
  147. nbmWebRTCPeer.startLocalMedia();
  148. }
  149. @Override
  150. protected void onStop() {
  151. endCall();
  152. super.onStop();
  153. }
  154. @Override
  155. protected void onDestroy() {
  156. super.onDestroy();
  157. }
  158. @Override
  159. public void onBackPressed() {
  160. // If back button has not been pressed in a while then trigger thread and toast notification
  161. if (!this.backPressed){
  162. this.backPressed = true;
  163. Toast.makeText(this,"Press back again to end.",Toast.LENGTH_SHORT).show();
  164. this.backPressedThread = new Thread(new Runnable() {
  165. @Override
  166. public void run() {
  167. try {
  168. Thread.sleep(5000);
  169. backPressed = false;
  170. } catch (InterruptedException e){ Log.d("VCA-oBP","Successfully interrupted"); }
  171. }
  172. });
  173. this.backPressedThread.start();
  174. }
  175. // If button pressed the second time then call super back pressed
  176. // (eventually calls onDestroy)
  177. else {
  178. if (this.backPressedThread != null)
  179. this.backPressedThread.interrupt();
  180. super.onBackPressed();
  181. }
  182. }
  183. public void hangup(View view) {
  184. finish();
  185. }
  186. public void receiveFromRemote(View view){
  187. Log.e(TAG,"--->receiveFromRemote");
  188. if (callState == CallState.PUBLISHED){
  189. callState = CallState.WAITING_REMOTE_USER;
  190. nbmWebRTCPeer.generateOffer("remote", false);
  191. runOnUiThread(new Runnable() {
  192. @Override
  193. public void run() {
  194. mCallStatus.setText("Waiting remote stream...");
  195. }
  196. });
  197. }
  198. }
  199. /**
  200. * Terminates the current call and ends activity
  201. */
  202. private void endCall() {
  203. callState = CallState.IDLE;
  204. try
  205. {
  206. if (nbmWebRTCPeer != null) {
  207. nbmWebRTCPeer.close();
  208. nbmWebRTCPeer = null;
  209. }
  210. }
  211. catch (Exception e){e.printStackTrace();}
  212. }
  213. @Override
  214. public void onLocalSdpOfferGenerated(final SessionDescription sessionDescription, NBMPeerConnection nbmPeerConnection) {
  215. Log.e(TAG,"--->onLocalSdpOfferGenerated");
  216. if (callState == CallState.PUBLISHING || callState == CallState.PUBLISHED) {
  217. localSdp = sessionDescription;
  218. Log.e(TAG,"--->onLocalSdpOfferGenerated:publish");
  219. runOnUiThread(new Runnable() {
  220. @Override
  221. public void run() {
  222. if (MainActivity.getKurentoRoomAPIInstance() != null) {
  223. Log.d(TAG, "Sending " + sessionDescription.type);
  224. publishVideoRequestId = ++Constants.id;
  225. //                    String sender = calluser + "_webcam";
  226. //                    MainActivity.getKurentoRoomAPIInstance().sendReceiveVideoFrom(sender, localSdp.description, publishVideoRequestId);
  227. MainActivity.getKurentoRoomAPIInstance().sendPublishVideo(localSdp.description, false, publishVideoRequestId);
  228. }
  229. }
  230. });
  231. } else { // Asking for remote user video
  232. Log.e(TAG,"--->onLocalSdpOfferGenerated:remote");
  233. remoteSdp = sessionDescription;
  234. //            nbmWebRTCPeer.selectCameraPosition(NBMMediaConfiguration.NBMCameraPosition.BACK);
  235. runOnUiThread(new Runnable() {
  236. @Override
  237. public void run() {
  238. if (MainActivity.getKurentoRoomAPIInstance() != null) {
  239. Log.e(TAG, "Sending--> " +calluser+ sessionDescription.type);
  240. publishVideoRequestId = ++Constants.id;
  241. String sender = calluser + "_webcam";
  242. MainActivity.getKurentoRoomAPIInstance().sendReceiveVideoFrom(sender, remoteSdp.description, publishVideoRequestId);
  243. }
  244. }
  245. });
  246. }
  247. }
  248. @Override
  249. public void onLocalSdpAnswerGenerated(SessionDescription sessionDescription, NBMPeerConnection nbmPeerConnection) {
  250. }
  251. @Override
  252. public void onIceCandidate(IceCandidate iceCandidate, NBMPeerConnection nbmPeerConnection) {
  253. Log.e(TAG,"--->onIceCandidate");
  254. sendIceCandidateRequestId = ++Constants.id;
  255. if (callState == CallState.PUBLISHING || callState == CallState.PUBLISHED){
  256. Log.e(TAG,"--->onIceCandidate:publish");
  257. MainActivity.getKurentoRoomAPIInstance().sendOnIceCandidate(this.username, iceCandidate.sdp,
  258. iceCandidate.sdpMid, Integer.toString(iceCandidate.sdpMLineIndex), sendIceCandidateRequestId);
  259. } else{
  260. Log.e(TAG,"--->onIceCandidate:"+this.calluser);
  261. MainActivity.getKurentoRoomAPIInstance().sendOnIceCandidate(this.calluser, iceCandidate.sdp,
  262. iceCandidate.sdpMid, Integer.toString(iceCandidate.sdpMLineIndex), sendIceCandidateRequestId);
  263. }
  264. }
  265. @Override
  266. public void onIceStatusChanged(PeerConnection.IceConnectionState iceConnectionState, NBMPeerConnection nbmPeerConnection) {
  267. Log.i(TAG, "onIceStatusChanged");
  268. }
  269. @Override
  270. public void onRemoteStreamAdded(MediaStream mediaStream, NBMPeerConnection nbmPeerConnection) {
  271. if (callState == CallState.PUBLISHING || callState == CallState.PUBLISHED) {
  272. Log.e(TAG, "-->onRemoteStreamAdded-->no");
  273. return;
  274. }
  275. Log.e(TAG, "-->onRemoteStreamAdded");
  276. RendererCommon.ScalingType scalingType = RendererCommon.ScalingType.SCALE_ASPECT_FILL;
  277. remoteRender = VideoRendererGui.create( REMOTE_X, REMOTE_Y,
  278. REMOTE_WIDTH, REMOTE_HEIGHT,
  279. scalingType, false);
  280. REMOTE_Y = REMOTE_Y+25;
  281. nbmWebRTCPeer.attachRendererToRemoteStream(remoteRender, mediaStream);
  282. runOnUiThread(new Runnable() {
  283. @Override
  284. public void run() {
  285. mCallStatus.setText("");
  286. }
  287. });
  288. }
  289. @Override
  290. public void onRemoteStreamRemoved(MediaStream mediaStream, NBMPeerConnection nbmPeerConnection) {
  291. Log.i(TAG, "onRemoteStreamRemoved");
  292. }
  293. @Override
  294. public void onPeerConnectionError(String s) {
  295. Log.e(TAG, "onPeerConnectionError:" + s);
  296. }
  297. @Override
  298. public void onRoomResponse(RoomResponse response) {
  299. Log.e(TAG, "-->OnRoomResponse:" + response);
  300. if (Integer.valueOf(response.getId()) == publishVideoRequestId){
  301. SessionDescription sd = new SessionDescription(SessionDescription.Type.ANSWER,
  302. response.getValue("sdpAnswer").get(0));
  303. if (callState == CallState.PUBLISHING){
  304. callState = CallState.PUBLISHED;
  305. nbmWebRTCPeer.processAnswer(sd, "derp");
  306. } else if (callState == CallState.WAITING_REMOTE_USER){
  307. callState = CallState.RECEIVING_REMOTE_USER;
  308. nbmWebRTCPeer.processAnswer(sd, "remote");
  309. } else if (callState == CallState.PATICIPANT_JOINED){
  310. callState = CallState.RECEIVING_PATICIPANT;
  311. nbmWebRTCPeer.processAnswer(sd, this.PaticipantID);
  312. //NOP
  313. }
  314. }
  315. }
  316. @Override
  317. public void onRoomError(RoomError error) {
  318. Log.e(TAG, "OnRoomError:" + error);
  319. }
  320. @Override
  321. public void onRoomNotification(RoomNotification notification) {
  322. Log.e(TAG, "OnRoomNotification--> (state=" + callState.toString() + "):" + notification);
  323. if(notification.getMethod().equals("iceCandidate")) {
  324. Map<String, Object> map = notification.getParams();
  325. String sdpMid = map.get("sdpMid").toString();
  326. int sdpMLineIndex = Integer.valueOf(map.get("sdpMLineIndex").toString());
  327. String sdp = map.get("candidate").toString();
  328. IceCandidate ic = new IceCandidate(sdpMid, sdpMLineIndex, sdp);
  329. Log.e(TAG, "callState-->" + callState);
  330. if (callState == CallState.PUBLISHING || callState == CallState.PUBLISHED) {
  331. nbmWebRTCPeer.addRemoteIceCandidate(ic, "derp");
  332. }else if(callState==CallState.PATICIPANT_JOINED ||  callState== CallState.RECEIVING_PATICIPANT){
  333. nbmWebRTCPeer.addRemoteIceCandidate(ic,this.PaticipantID);
  334. }else {
  335. nbmWebRTCPeer.addRemoteIceCandidate(ic, "remote");
  336. }
  337. }
  338. if(notification.getMethod().equals("participantPublished"))
  339. {
  340. Map<String, Object> map = notification.getParams();
  341. final String user = map.get("id").toString();
  342. this.calluser = user;
  343. this.PaticipantID = "pt_"+this.calluser;
  344. PeerVideoActivity.this.runOnUiThread(new Runnable() {
  345. @Override
  346. public void run() {
  347. callState = CallState.PATICIPANT_JOINED;
  348. nbmWebRTCPeer.generateOffer(PaticipantID, false);
  349. }
  350. });
  351. }
  352. }
  353. @Override
  354. public void onRoomConnected() {
  355. }
  356. @Override
  357. public void onRoomDisconnected() {
  358. }
  359. }

再就是android room demo中的MainActivity的添加cert的代码要去掉注释,让这段代码生效,就可以连通服务器了。

iOS的实施方面,上面这家公司也提供了一个工具包:https://github.com/nubomediaTI/Kurento-ios ,工具包里面也有demo

Web方面,最上面官方的哪个demo就足够参考了

后记:很荣幸这篇博客获得了很多CSDN程序员的关注和询问,这只能证明我很荣幸有机会在去年的那个时间点(16年7月)在大家之前处理了一个后续大家都很关注的技术问题,而处理这个问题主要用到的服务器端room server项目和android端nubo test项目,官方在后续好像都做了一定的升级,反而是我自己搞完这个之后,因为产品设计的原因,后来再没有深入地去生产实施这个东西,甚至开发笔记本关于这个项目的源码项目好像都已经删除了,对于大家提出的问题,早期的我还能答一答,后面的我估计你们用到的源码和我用到的源码估计都不是一个版本了,再就是里面的代码细节也基本忘得差不多,在这儿我建议后续开发这个功能可以去深入阅读分析Kurento官方(https://github.com/Kurento)和欧洲媒体服务云服务商nubomedia官方(https://github.com/nubomedia-vtt)的代码示例和文档。我面给出的代码样例是基于nubomedia一对一视聊样例改的,官方原始代码样例在这段时间内都有了变更。在掌握大的基本WebRTC通信的原理的前提下,我觉得改新的代码估计也不会太难。

基于Kurento的WebRTC移动视频群聊技术方案的更多相关文章

  1. 网易云信技术分享:IM中的万人群聊技术方案实践总结

    本文来自网易云信团队的技术分享,原创发表于网易云信公众号,原文链接:mp.weixin.qq.com/s/LT2dASI7QVpcOVxDAsMeVg,收录时有改动. 1.引言 在不了解IM技术的人眼 ...

  2. android 开发,视频群聊引发短信异常

    说到 NDK 开发,其实是为了有些时候为了项目需求需要调用底层的一些 C/C++ 的一些东西:另外就是为了效率更加高些. 但是很多时候能不用就不用:这个是啥原因?个人感觉有些时候是觉得麻烦,首先要配置 ...

  3. 一套高可用、易伸缩、高并发的IM群聊架构方案设计实践

    本文原题为“一套高可用群聊消息系统实现”,由作者“于雨氏”授权整理和发布,内容有些许改动,作者博客地址:alexstocks.github.io.应作者要求,如需转载,请联系作者获得授权. 一.引言 ...

  4. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(四) 添加表情、群聊功能

    休息了两天,还是决定把这个尾巴给收了.本篇是最后一篇,也算是草草收尾吧.今天要加上表情功能和群聊.基本上就差不多了,其他功能,读者可以自行扩展或者优化.至于我写的代码方面,自己也没去重构.好的,我们开 ...

  5. 使用java做一个能赚钱的微信群聊机器人(2020年基于PC端协议最新可用版)

    前言 微信群机器人,主要用来管理群聊,提供类似天气查询.点歌.机器人聊天等用途. 由于微信将web端的协议封杀后,很多基于http协议的群聊机器人都失效了,所以这里使用基于PC端协议的插件来实现. 声 ...

  6. 基于itchat的微信群聊小助手基础开发(一)

    前段时间由于要管理微信群,基于itchat开发了一个简单的微信机器人 主要功能有: 图灵机器人功能 群聊昵称格式修改提示 消息防撤回功能 斗图功能 要开发一个基于itchat的最基本的聊天机器人,在g ...

  7. 基于ejabberd简单实现xmpp群聊离线消息

    首先,xmpp服务器是基于ejabberd.离线消息模块是mod_interact,原地址地址:https://github.com/adamvduke/mod_interact: 修改后实现群聊离线 ...

  8. Flask(4)- flask请求上下文源码解读、http聊天室单聊/群聊(基于gevent-websocket)

    一.flask请求上下文源码解读 通过上篇源码分析,我们知道了有请求发来的时候就执行了app(Flask的实例化对象)的__call__方法,而__call__方法返回了app的wsgi_app(en ...

  9. 基于websocket的单聊.群聊

    关于ai.baidu.com的 代码: #########################################核心代码################################### ...

随机推荐

  1. ROS_Kinetic_13 ROS数据录制与回放

    ROS_Kinetic_13 ROS数据录制与回放 官网教程:http://wiki.ros.org/cn/ROS/Tutorials/Recording%20and%20playing%20back ...

  2. 经典面试题:一张表区别DOM解析和SAX解析XML

                                                                                 ============DOM解析    vs ...

  3. 如何回滚请求<复制系统初始的数据>所处理的数据

    一.    问题提出 请求名称:复制系统初始的数据 参数:空 问题: 今天早上财务实施人员新配置了一个OU,然后在跑复制系统初始的数据报表的时候,不小心,不输入参数就直接跑. 报表先是报错. 接下来的 ...

  4. 【TCP/IP 协议】 TCP/IP 基础

    总结 : 通过学习 TCP/IP 基础, 并总结相关笔记 和 绘制思维导图 到博客上, 对 TCP/IP 框架有了大致了解, 之后开始详细学习数据链路层的各种细节协议, 并作出笔记; 博客地址 : h ...

  5. Android光线传感器-android学习之旅(65)

    主要讲解光线传感器的使用,其实所有的传感器用法类似 主要是定义一个TextView用来显示光线强度,用完了以后记得在OnDestory里面释放资源 代码如下 public class MainActi ...

  6. Java中的五种单例模式

    Java模式之单例模式: 单例模式确保一个类只有一个实例,自行提供这个实例并向整个系统提供这个实例. 特点: 1,一个类只能有一个实例 2 自己创建这个实例 3 整个系统都要使用这个实例 例: 在下面 ...

  7. UNIX环境高级编程——标准I/O库函数和Unbuffered I/O函数

    以写文件为例,C标准I/O库函数(printf(3) .putchar(3) .fputs(3) )与系统调用write(2) 的关 系如下图所示. 库函数与系统调用的层次关系 open .read ...

  8. Socket编程实践(5) --TCP粘包问题与解决

    TCP粘包问题 由于TCP协议是基于字节流且无边界的传输协议, 因此很有可能产生粘包问题, 问题描述如下 对于Host A 发送的M1与M2两个各10K的数据块, Host B 接收数据的方式不确定, ...

  9. Java之谜 —— 来自Neal Gafter的演讲

    翻译人员: 铁锚 翻译日期: 2013年11月20日 原文链接: A Puzzle from "A Brief History of the (Java) World and a Peek ...

  10. How to create DB2 user function easily by DB Query Analyzer 6.03

    How to create DB2user function easily by DB Query Analyzer 6.03 Ma Genfeng (Guangdong Unitoll Servic ...