Bluedroid:音频数据传输过程
一. UIPC:
Audio Flinger获取到a2dp的hw module,然后蓝牙协议栈有专用于发送和接收media数据的线程,
名称:btif_media_task.
蓝牙与Audio的通信则采用了socket的方式,管理socket的中间文件为:UIPC。主要的作用就是,接收Audio的控制命令和音频数据发送给BT,即 Audio_a2dp_hw -> UIPC -> btif_media_task
UIPC建立了两条socket,分别为:
#define A2DP_CTRL_PATH "/data/misc/bluedroid/.a2dp_ctrl"
#define A2DP_DATA_PATH "/data/misc/bluedroid/.a2dp_data"
1.control socket在btif media task启动的时创建:
bool btif_a2dp_start_media_task(void) { ... thread_post(worker_thread, btif_media_thread_init, NULL); APPL_TRACE_EVENT("## A2DP MEDIA THREAD STARTED ##"); return true; ... } static void btif_media_thread_init(UNUSED_ATTR void *context) { memset(&btif_media_cb, 0, sizeof(btif_media_cb)); UIPC_Init(NULL); #if (BTA_AV_INCLUDED == TRUE) UIPC_Open(UIPC_CH_ID_AV_CTRL , btif_a2dp_ctrl_cb); #endif raise_priority_a2dp(TASK_HIGH_MEDIA); media_task_running = MEDIA_TASK_STATE_ON; }
2.data socket在收到audio cmd start时创建:
case A2DP_CTRL_CMD_START: /* Don't sent START request to stack while we are in call. Some headsets like the Sony MW600, don't allow AVDTP START in call and respond BAD_STATE. */ if (!btif_hf_is_call_idle()) { a2dp_cmd_acknowledge(A2DP_CTRL_ACK_INCALL_FAILURE); break; } if (btif_av_stream_ready() == TRUE) { /* setup audio data channel listener */ UIPC_Open(UIPC_CH_ID_AV_AUDIO, btif_a2dp_data_cb); /* post start event and wait for audio path to open */ btif_dispatch_sm_event(BTIF_AV_START_STREAM_REQ_EVT, NULL, 0); #if (BTA_AV_SINK_INCLUDED == TRUE) if (btif_media_cb.peer_sep == AVDT_TSEP_SRC) a2dp_cmd_acknowledge(A2DP_CTRL_ACK_SUCCESS);//发送回执 #endif } else if (btif_av_stream_started_ready()) { /* already started, setup audio data channel listener and ack back immediately */ UIPC_Open(UIPC_CH_ID_AV_AUDIO, btif_a2dp_data_cb); a2dp_cmd_acknowledge(A2DP_CTRL_ACK_SUCCESS);//发送回执 } else { a2dp_cmd_acknowledge(A2DP_CTRL_ACK_FAILURE); break; } break;
3.UIPC接收command机制:
(1) 启动时,创建一个thread,接收command:
void UIPC_Init(void *p_data) { UNUSED(p_data); BTIF_TRACE_DEBUG("UIPC_Init"); memset(&uipc_main, 0, sizeof(tUIPC_MAIN)); uipc_main_init(); uipc_start_main_server_thread(); } int uipc_start_main_server_thread(void) { uipc_main.running = 1; if (pthread_create(&uipc_main.tid, (const pthread_attr_t *) NULL, (void*)uipc_read_task, NULL) < 0) { BTIF_TRACE_ERROR("uipc_thread_create pthread_create failed:%d", errno); return -1; } return 0; }
(2)监听每一个socket,发现数据后,优先判断是不是音频,再判断是不是命令:
static void uipc_read_task(void *arg) { while (uipc_main.running) { result = select(uipc_main.max_fd+1, &uipc_main.read_set, NULL, NULL, NULL); //发现有数据 /* make sure we service audio channel first */ uipc_check_fd_locked(UIPC_CH_ID_AV_AUDIO); //先确定是不是音频 /* check for other connections */ for (ch_id = 0; ch_id < UIPC_CH_NUM; ch_id++) { if (ch_id != UIPC_CH_ID_AV_AUDIO) uipc_check_fd_locked(ch_id); //再确定是不是command } } static int uipc_check_fd_locked(tUIPC_CH_ID ch_id) { if (uipc_main.ch[ch_id].cback) //通知btif,有command过来 uipc_main.ch[ch_id].cback(ch_id, UIPC_RX_DATA_READY_EVT); }
(3).通知btif去读取command并处理:
static void btif_a2dp_ctrl_cb(tUIPC_CH_ID ch_id, tUIPC_EVENT event) { switch(event) { case UIPC_RX_DATA_READY_EVT: btif_recv_ctrl_data(); //去获取command break; } } static void btif_recv_ctrl_data(void) { UINT8 cmd = 0; int n; n = UIPC_Read(UIPC_CH_ID_AV_CTRL, NULL, &cmd, 1); //读取该socket上的命令 }
二、btif_media_task:
1.btif_media_task是蓝牙协议栈接收HAL层音频数据的主处理线程,数据流程框图如下:
- BTIF reads PCM data from audio flinger via Audio HAL.(Step 6)
- BTIF calls SBC encoder to encode PCM data to SBC frames which are put in a queue.(Step 7 and 8)
- BTIF notifies BTA that the source data is ready in the queue.(Step 9~13)
- BTA gets the SBC frames from the queue, then adds SBC Header. Media PL is constructed now.(Step 15~17)
- BTA writes Media PL to AVDTP.(Step 18)
- AVDTP adds Media Packet Header.(Step 19)
2.主要代码框架:
- btif/src/btif_av.c Bluedroid AV HAL implementation which implements the interface defined in AOSP/hardware/bt_av.h.
- btif/src/btif_media_task.c This is the multimedia module for the BTIF system. It contains task implementations AV, HS and HF profiles' audio&video processing.
- btif/co/bta_av_co.c This is the advanced audio/video call-out function implementation for BTIF.
- bta/av/bta_av_ci.c This is the implementation for advanced audio/video call-in functions which are called from BTIF.
- bta/av/bta_av_api.c This is the implementation of the API for the advanced audio/video(AV) subsystem of BTA. This interface is called from btif_av.c.
- bta/av/bta_av_mian.c This is the main implementation file for BTA advanced audio/video.
- bta/av/bta_av_ssm.c This is the stream state machine for the BTA advanced audio/video.
- bta/av/bta_av_aact.c This file contains action functions for advanced audio/video stream.
- bta/av/bta_av_sbc.c This module contains utility functions for dealing with SBC data frames and codec capabilities.
- stack/a2dp/a2d_api.c This is the implementation of the API for the Advanced Audio Distribution Profile(A2DP)
- stack/a2dp/a2d_sbc.c This file contains utility functions to help build and parse SBC codec information element and media payload.
- embdrv/sbc/sbc/encoder This folder contains the files which implement SBC decoder.
三、A2DP 数据处理流程:
1.ACL 包发送流程:
由前面所述,HAL层socket发送的音频数据通过a2dp继续下发到AVDTP:
而在 AVDTP 中,所有的功能想发送 Data,必须调用 avdt_ad_write_req 这个函数:
1 //当CCB或SCB给l2cap的 Channel 发送数据时,他们最终都会使用到L2CAP的 API:L2CA_Data_Write 2 UINT8 avdt_ad_write_req(UINT8 type, tAVDT_CCB *p_ccb, tAVDT_SCB *p_scb, BT_HDR *p_buf) 3 { 4 UINT8 tcid; 5 6 /* get tcid from type, scb */ 7 tcid = avdt_ad_type_to_tcid(type, p_scb); 8 9 10 return L2CA_DataWrite(avdt_cb.ad.rt_tbl[avdt_ccb_to_idx(p_ccb)][tcid].lcid, p_buf); 11 } 12 //L2CA_DataWrite的返回形式有三种,分别是: 13 //1. L2CAP_DW_SUCCESS:此数据写成功 14 //2.L2CAP_DW_CONGESTED:写数据成功,但是当前信道拥堵 15 //3.L2CAP_DW_FAILED:写数据失败 16 UINT8 L2CA_DataWrite (UINT16 cid, BT_HDR *p_data) 17 { 18 L2CAP_TRACE_API2 ("L2CA_DataWrite() CID: 0x%04x Len: %d", cid, p_data->len); 19 return l2c_data_write (cid, p_data, L2CAP_FLUSHABLE_CH_BASED); 20 }
当音频数据流到达 l2c_data_write 这个函数时,标志数据流正式进入到L2CAP层。
l2c_data_write 这个函数做的事情主要有:
- 根据参数 cid(Channel ID) 找到 对应的 ccb(Channel Control Block), 找不到返回 L2CAP_DW_FAILED
- 如果测试者 打开 TESTER 这个宏,发送任意数据,当数据大小 大于 MTU 最大值,也会返回L2CAP_DW_FAILED
- 通过检查 p_ccb->cong_sent 字段,TRUE,则说明当前 Channel 已经拥挤,此时L2CAP的这个Channel不在接收数据,返回 L2CAP_DW_FAILED
- 以上三个条件都通过,说明数据可发送,将数据通过 l2c_csm_execute 继续处理。进入 l2c_csm_execute 函数,标志着这笔数据已经成功交给 l2CAP 来处理,与上层已经没有关系了。
- l2c_csm_execute 函数执行结束后,再次检查 p_ccb->cong_sent 字段,看看当前的 Channel 是否拥挤,如果拥挤则告诉上层 L2CAP_DW_CONGESTED,否则返回 L2CAP_DW_SUCCESS,表示数据已经成功发送。
1 //返回的数据跟上面的 L2CA_DataWrite 作用相同 2 UINT8 l2c_data_write (UINT16 cid, BT_HDR *p_data, UINT16 flags) 3 { 4 tL2C_CCB *p_ccb; 5 6 //遍历l2cb.ccb_pool,通过Channel ID找到对应的Channel Control Block 7 //l2cu_find_ccb_by_cid 见下面源码注释 8 if ((p_ccb = l2cu_find_ccb_by_cid (NULL, cid)) == NULL) 9 { 10 L2CAP_TRACE_WARNING1 ("L2CAP - no CCB for L2CA_DataWrite, CID: %d", cid); 11 GKI_freebuf (p_data); 12 return (L2CAP_DW_FAILED); 13 } 14 15 #ifndef TESTER /* Tester may send any amount of data. otherwise sending message 16 bigger than mtu size of peer is a violation of protocol */ 17 if (p_data->len > p_ccb->peer_cfg.mtu) 18 { 19 L2CAP_TRACE_WARNING1 ("L2CAP - CID: 0x%04x cannot send message bigger than peer's mtu size", cid); 20 GKI_freebuf (p_data); 21 return (L2CAP_DW_FAILED); 22 } 23 #endif 24 25 /* channel based, packet based flushable or non-flushable */ 26 //Bluedroid中默认的是 L2CAP_FLUSHABLE_CH_BASED 27 //这个 layer_specific 在 数据发送的 l2c_link_send_to_lower 中表示 ACL包分包 个数 28 p_data->layer_specific = flags; 29 30 //发现本 Channel 已经拥堵,直接返回L2CAP_DW_FAILED 告诉上层等会再发数据 31 //当几个应用 共用 此 Channel 可能会出现这种情况 32 if (p_ccb->cong_sent) 33 { 34 L2CAP_TRACE_ERROR3 ("L2CAP - CID: 0x%04x cannot send, already congested xmit_hold_q.count: %u buff_quota: %u", 35 p_ccb->local_cid, p_ccb->xmit_hold_q.count, p_ccb->buff_quota); 36 37 GKI_freebuf (p_data); 38 return (L2CAP_DW_FAILED); 39 } 40 //毫无疑问啦,这个函数就是我们继续需要分析的函数 41 l2c_csm_execute (p_ccb, L2CEVT_L2CA_DATA_WRITE, p_data); 42 43 //已经将上层的这笔数据发送完,如果此 Channel 拥挤了(之前发送这笔包还没拥挤) 44 //返回 L2CAP_DW_CONGESTED 告诉上层当前信道拥挤,你要给我L2CAP层发数据,是不发下来的 45 if (p_ccb->cong_sent) 46 return (L2CAP_DW_CONGESTED); 47 48 //成功发送,并且此时 Channel 并不拥挤 49 return (L2CAP_DW_SUCCESS); 50 } 51 52 //通过 Channel ID 找到 Channel Control Block 53 tL2C_CCB *l2cu_find_ccb_by_cid (tL2C_LCB *p_lcb, UINT16 local_cid) 54 { 55 tL2C_CCB *p_ccb = NULL; 56 #if (L2CAP_UCD_INCLUDED == TRUE) 57 UINT8 xx; 58 #endif 59 60 if (local_cid >= L2CAP_BASE_APPL_CID) //大于或等于 0x0040 说明不是 Fixed Channel 61 { 62 /* find the associated CCB by "index" */ 63 local_cid -= L2CAP_BASE_APPL_CID; 64 65 if (local_cid >= MAX_L2CAP_CHANNELS) 66 return NULL; 67 68 p_ccb = l2cb.ccb_pool + local_cid; //直接通过地址偏移找到 69 70 /* make sure the CCB is in use */ 71 if (!p_ccb->in_use) 72 { 73 p_ccb = NULL; 74 } 75 /* make sure it's for the same LCB */ 76 else if (p_lcb && p_lcb != p_ccb->p_lcb) 77 { 78 p_ccb = NULL; 79 } 80 } 81 #if (L2CAP_UCD_INCLUDED == TRUE) //默认是关闭的,既然从上层来的都是 数据包了,我认为不会用到 Fixed Channel 82 else 83 { 84 /* searching fixed channel */ 85 p_ccb = l2cb.ccb_pool; 86 for ( xx = 0; xx < MAX_L2CAP_CHANNELS; xx++ ) 87 { 88 if ((p_ccb->local_cid == local_cid) 89 &&(p_ccb->in_use) 90 &&(p_lcb == p_ccb->p_lcb)) 91 break; 92 else 93 p_ccb++; 94 } 95 if ( xx >= MAX_L2CAP_CHANNELS ) 96 return NULL; 97 } 98 #endif 99 100 return (p_ccb); 101 }
首先进入了 L2CAP 层的状态机:
1 void l2c_csm_execute (tL2C_CCB *p_ccb, UINT16 event, void *p_data) 2 { 3 switch (p_ccb->chnl_state) 4 { 5 case CST_CLOSED: 6 l2c_csm_closed (p_ccb, event, p_data); 7 break; 8 9 case CST_ORIG_W4_SEC_COMP: 10 l2c_csm_orig_w4_sec_comp (p_ccb, event, p_data); 11 break; 12 13 case CST_TERM_W4_SEC_COMP: 14 l2c_csm_term_w4_sec_comp (p_ccb, event, p_data); 15 break; 16 17 case CST_W4_L2CAP_CONNECT_RSP: 18 l2c_csm_w4_l2cap_connect_rsp (p_ccb, event, p_data); 19 break; 20 21 case CST_W4_L2CA_CONNECT_RSP: 22 l2c_csm_w4_l2ca_connect_rsp (p_ccb, event, p_data); 23 break; 24 25 case CST_CONFIG: 26 l2c_csm_config (p_ccb, event, p_data); 27 break; 28 29 case CST_OPEN: 30 l2c_csm_open (p_ccb, event, p_data); 31 break; 32 33 case CST_W4_L2CAP_DISCONNECT_RSP: 34 l2c_csm_w4_l2cap_disconnect_rsp (p_ccb, event, p_data); 35 break; 36 37 case CST_W4_L2CA_DISCONNECT_RSP: 38 l2c_csm_w4_l2ca_disconnect_rsp (p_ccb, event, p_data); 39 break; 40 41 default: 42 break; 43 } 44 } 具体的 Channel 状态信息如下 1 typedef enum 2 { 3 CST_CLOSED, /* Channel is in clodes state */ 4 CST_ORIG_W4_SEC_COMP, /* Originator waits security clearence */ 5 CST_TERM_W4_SEC_COMP, /* Acceptor waits security clearence */ 6 CST_W4_L2CAP_CONNECT_RSP, /* Waiting for peer conenct response */ 7 CST_W4_L2CA_CONNECT_RSP, /* Waiting for upper layer connect rsp */ 8 CST_CONFIG, /* Negotiating configuration */ 9 CST_OPEN, /* Data transfer state */ 10 CST_W4_L2CAP_DISCONNECT_RSP, /* Waiting for peer disconnect rsp */ 11 CST_W4_L2CA_DISCONNECT_RSP /* Waiting for upper layer disc rsp */ 12 } tL2C_CHNL_STATE;
音频数据包在函数 l2c_csm_open 中流转,经过各种选择和判断,最后走的是 L2CEVT_L2CA_DATA_WRITE 这个 case。这个 case 调用了 l2c_enqueue_peer_data 让数据进入到当前 ccb 的 xmit_hold_q 队列中,暂存此数据包。l2c_link_check_send_pkts 这个函数发送数据包:
1 //l2c_csm_open 处理 Channel 处于 OPEN 状态下的各种 Event 2 static void l2c_csm_open (tL2C_CCB *p_ccb, UINT16 event, void *p_data) 3 { 4 UINT16 local_cid = p_ccb->local_cid; 5 tL2CAP_CFG_INFO *p_cfg; 6 tL2C_CHNL_STATE tempstate; 7 UINT8 tempcfgdone; 8 UINT8 cfg_result; 9 10 #if (BT_TRACE_VERBOSE == TRUE) 11 L2CAP_TRACE_EVENT2 ("L2CAP - LCID: 0x%04x st: OPEN evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); 12 #else 13 L2CAP_TRACE_EVENT1 ("L2CAP - st: OPEN evt: %d", event); 14 #endif 15 16 #if (L2CAP_UCD_INCLUDED == TRUE) //默认 UCD 是关闭的 17 if ( local_cid == L2CAP_CONNECTIONLESS_CID ) 18 { 19 /* check if this event can be processed by UCD */ 20 if ( l2c_ucd_process_event (p_ccb, event, p_data) ) 21 { 22 /* The event is processed by UCD state machine */ 23 return; 24 } 25 } 26 #endif 27 28 switch (event) 29 { 30 case L2CEVT_LP_DISCONNECT_IND: //Link 都断开连接了,自然 Channel也没有存在的必要了,各种清除 CCB 的工作 31 L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); 32 l2cu_release_ccb (p_ccb);//释放 当前的 CCB 33 if (p_ccb->p_rcb) 34 (*p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(local_cid, FALSE); 35 break; 36 37 case L2CEVT_LP_QOS_VIOLATION_IND: /* QOS violation */ 38 /* Tell upper layer. If service guaranteed, then clear the channel */ 39 if (p_ccb->p_rcb->api.pL2CA_QoSViolationInd_Cb) 40 (*p_ccb->p_rcb->api.pL2CA_QoSViolationInd_Cb)(p_ccb->p_lcb->remote_bd_addr); 41 break; 42 43 case L2CEVT_L2CAP_CONFIG_REQ: /* Peer config request */ 44 p_cfg = (tL2CAP_CFG_INFO *)p_data; 45 46 tempstate = p_ccb->chnl_state; 47 tempcfgdone = p_ccb->config_done; 48 p_ccb->chnl_state = CST_CONFIG; //如果数据流中的数据是 L2CEVT_L2CAP_CONFIG_REQ,当然要转到 CST_CONFIG中继续处理 49 p_ccb->config_done &= ~CFG_DONE_MASK; 50 //启动一个 timer ,一段时间后,查看 cfg 的状态 51 //如果配置处于 L2CAP_PEER_CFG_UNACCEPTABLE,继续尝试配置 52 //如果配置处于断开状态,那当前 Channel 直接断开连接。 53 btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT); 54 55 if ((cfg_result = l2cu_process_peer_cfg_req (p_ccb, p_cfg)) == L2CAP_PEER_CFG_OK) 56 { 57 (*p_ccb->p_rcb->api.pL2CA_ConfigInd_Cb)(p_ccb->local_cid, p_cfg); 58 } 59 60 /* Error in config parameters: reset state and config flag */ 61 else if (cfg_result == L2CAP_PEER_CFG_UNACCEPTABLE) 62 { 63 btu_stop_timer(&p_ccb->timer_entry); 64 p_ccb->chnl_state = tempstate; 65 p_ccb->config_done = tempcfgdone; 66 l2cu_send_peer_config_rsp (p_ccb, p_cfg); 67 } 68 else /* L2CAP_PEER_CFG_DISCONNECT */ 69 { 70 /* Disconnect if channels are incompatible 71 * Note this should not occur if reconfigure 72 * since this should have never passed original config. 73 */ 74 l2cu_disconnect_chnl (p_ccb); 75 } 76 break; 77 78 case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnected request */ 79 // btla-specific ++ 80 /* Make sure we are not in sniff mode */ 81 #if BTM_PWR_MGR_INCLUDED == TRUE 82 { 83 tBTM_PM_PWR_MD settings; 84 settings.mode = BTM_PM_MD_ACTIVE; 85 BTM_SetPowerMode (BTM_PM_SET_ONLY_ID, p_ccb->p_lcb->remote_bd_addr, &settings); 86 } 87 #else 88 BTM_CancelSniffMode (p_ccb->p_lcb->remote_bd_addr); 89 #endif 90 // btla-specific -- 91 92 p_ccb->chnl_state = CST_W4_L2CA_DISCONNECT_RSP; //Peer 发送 Disconnect,我们要对此发 Response 93 btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_DISCONNECT_TOUT); 94 L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x Conf Needed", p_ccb->local_cid); 95 (*p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(p_ccb->推荐阅读
音频和视频的整体解码过程和同步过程-2.音频和视频同步的整体过程
在 ffmpeg 转码过程中复制音频和视频元数据(元数据、标签
go语言Socket编程-Socket编程 什么是Socket Socket,英文含义是插座、插孔,一般称之为套接字,用于描述IP地址和端口。可以实现不同程序间的数据通信。 Socket起源于Unix,而Unix基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用:Socket,该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。 套接字的内核实现较为复杂,不宜在学习初期深入学习,了解到如下结构足矣。 套接字通讯原理示意 在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。 常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。 网络应用程序设计模式 C/S模式 传统的网络应用设计模式,客户机(client)/服务器(server)模式。需要在通讯两端各自部署客户机和服务器来完成数据通信。 B/S模式 浏览器(Browser)/服务器(Server)模式。只需在一端部署服务器,而另外一端使用每台PC都默认配置的浏览器即可完成数据的传输。 优缺点 对于C/S模式来说,其优点明显。客户端位于目标主机上可以保证性能,将数据缓存至客户端本地,从而提高数据传输效率。且,一般来说客户端和服务器程序由一个开发团队创作,所以他们之间所采用的协议相对灵活。可以在标准协议的基础上根据需求裁剪及定制。例如,腾讯所采用的通信协议,即为ftp协议的修改剪裁版。 因此,传统的网络应用程序及较大型的网络应用程序都首选C/S模式进行开发。如,知名的网络游戏魔兽世界。3D画面,数据量庞大,使用C/S模式可以提前在本地进行大量数据的缓存处理,从而提高观感。 C/S模式的缺点也较突出。由于客户端和服务器都需要有一个开发团队来完成开发。工作量将成倍提升,开发周期较长。另外,从用户角度出发,需要将客户端安插至用户主机上,对用户主机的安全性构成威胁。这也是很多用户不愿使用C/S模式应用程序的重要原因。 B/S模式相比C/S模式而言,由于它没有独立的客户端,使用标准浏览器作为客户端,其工作开发量较小。只需开发服务器端即可。另外由于其采用浏览器显示数据,因此移植性非常好,不受平台限制。如早期的偷菜游戏,在各个平台上都可以完美运行。 B/S模式的缺点也较明显。由于使用第三方浏览器,因此网络应用支持受限。另外,没有客户端放到对方主机上,缓存数据不尽如人意,从而传输数据量受到限制。应用的观感大打折扣。第三,必须与浏览器一样,采用标准http协议进行通信,协议选择不灵活。 因此在开发过程中,模式的选择由上述各自的特点决定。根据实际需求选择应用程序设计模式。 简单的C/S模型通信 Server端:Listen函数 func Listen(network, address string) (Listener, error) network:选用的协议:TCP、UDP, 如:“tcp”或 “udp” address:IP地址+端口号, 如:“127.0.0.1:8000”或 “:8000” Listener 接口: type Listener interface { Accept (Conn, error) Close error Addr Addr } Conn 接口: type Conn interface { Read(b byte) (n int, err error) Write(b byte) (n int, err error) Close error LocalAddr Addr RemoteAddr Addr SetDeadline(t time.Time) error SetReadDeadline(t time.Time) error SetWriteDeadline(t time.Time) error } 参看 [<u>https://studygolang.com/pkgdoc</u>](https://studygolang.com/pkgdoc) 中文帮助文档中的demo: 示例代码:TCP服务器.go package main import ( "net" "fmt" ) func main { // 创建监听 listener, err:= net.Listen("tcp", ":8000") if err != nil { fmt.Println("listen err:", err) return } defer listener.Close // 主协程结束时,关闭listener fmt.Println("服务器等待客户端建立连接...") // 等待客户端连接请求 conn, err := listener.Accept if err != nil { fmt.Println("accept err:", err) return } defer conn.Close // 使用结束,断开与客户端链接 fmt.Println("客户端与服务器连接建立成功...") // 接收客户端数据 buf := make(byte, 1024) // 创建1024大小的缓冲区,用于read n, err := conn.Read(buf) if err != nil { fmt.Println("read err:", err) return } fmt.Println("服务器读到:", string(buf[:n])) // 读多少,打印多少。 }
Bluedroid:音频数据传输过程
了解公钥和私钥 - 公钥加密算法又称非对称加密算法,使用不同的密码进行加密和解密,其中一个用于公钥,另一个用于私钥: 公钥和私钥成对使用 公钥称为公钥,私钥称为私钥。 用公钥加密的数据只能用相应的私钥解密 用私钥加密的数据只能用相应的公钥解密。 如果数据可以用公钥解密,则必须用相应的私钥加密。 如果数据可以用私钥解密,则必须用相应的公钥加密。 公钥和私钥是相对的,没有规定哪一个必须是公钥或私钥。 第二,实现数据的安全传输 要实现数据的安全传输,当然要对数据进行加密。 如果使用对称加密算法,加密和解密使用同一个密钥,除了自己要保存外,对方也必须知道密钥才能解密数据。如果把密钥传给对方,就有可能泄露密码。所以我们使用非对称算法,过程如下: 首先,接收方生成一对密钥,即私钥和公钥; 然后,接收方将公钥发送给发送方; 发送方用收到的公开密钥加密数据并发送给接收方; 接收方收到数据后使用自己的私钥解密。 由于在非对称算法中,用公钥加密的数据必须用相应的私钥解密,而私钥只有接收方知道,这就确保了数据传输的安全性。 第三,信息的数字签名 除了确保数据的安全传输,公钥系统的另一个用途是对数据进行签名。通常,"数字签名 "用于验证发送者的身份,帮助保护数据的完整性。 例如,发送者 A 想向所有人发送一些信息,他用自己的私人密钥对信息进行了加密,即签名。这样,每个收到数据的人都能用发送者的公开密钥验证数据,并确认数据是由 A 发送的(因为只有 A 用他的私人密钥签署了数据,所以无法验证发送者的身份)。(因为只有用 A 的私钥签名的信息才能用公钥解密)。使用数字签名可以确认两件事: 保证信息是由签名者本人签名发送的,签名者无法否认或难以否认。 保证信息从发出到收到都没有被以任何方式修改过。 之所以能确认这两点,是因为公钥的解密必然要有相应的私钥加密,而私钥只有签名者持有。 四、公钥算法的缺陷 在现实中,公钥机制也有其缺点,那就是效率很低,比常用的私钥算法(如 DES 和 AES)慢上一两个数量级都有可能。因此,它不适合对大量原始信息进行加密。为了兼顾安全性和效率,我们通常会将公钥算法和私钥算法结合起来使用: 首先,发送方使用对称算法加密原始信息。 接收方使用公钥机制生成一对密钥,一个是公钥,一个是私钥。 接收方将公钥发送给发送方。 发送方用公钥加密对称算法的密钥,然后发送给接收方。 接收方用私人密钥解密对称算法的密钥。 发送方将加密后的原始信息发送给接收方。 接收方使用对称算法的密钥解密信息。 摘要
后端常用技巧:解决java项目中数据传输前后中文出现乱码、问号的问题-2.解决过程
[音频]耳机插拔在线控制按钮识别过程[传输]。
AAC 文件解析和解码过程(描述音频术语 aac he lc 等及其功能)
音频/视频 | OggOgg 封装格式 - 包括 Ogg 封装过程、包(packet)、页(page)、段(segment)等。
Type-C 转音频(USB2.0 数据传输)+PD 充电芯片 LOTUS LDR6500/LDR6023