音视频同步!RTCP 协议解析及代码实现
RTCP 是实时控制协议(Real-Time Control Protocol)的缩写。RTCP 由 RFC 3550 定义(取代作废的 RFC 1889)。
实时传输协议(RTP)和实时控制协议(RTCP)结合使用,可以监视大型多播网络的数据传递。RTP 承载媒体流,而 RTCP 用于监视传输统计信息和服务质量。监视使接收器能够检测是否有任何丢包并补偿任何延迟抖动。
两种协议都独立于基础传输层协议和网络层协议工作。RTP 标头中的信息告诉接收器如何重建数据,并描述编解码器比特流的打包方式。
下面我们重点讲解 RTCP 功能、RTCP 信息包等。最后 RTCP 协议解析实现。
RTCP 有哪些功能
1、RTCP 主要功能是提供有关质量的反馈数据分发。这是 RTP 角色不可或缺的一部分,传输协议,与流量和拥塞有关其他传输协议的控制功能。
2、RTCP 带有 RTP 的持久性传输级别标识符源称为规范名称或 CNAME。自从如果发现冲突或程序重新启动,接收方要求 CNAME 跟踪每个参与者。
接收者也可能要求 CNAME 将来自给定参与者的多个数据流关联到集合中相关 RTP 会话的数量,例如同步音频和视频。媒体间同步还需要 NTP 和 RTP 数据发送方在 RTCP 数据包中包含的时间戳。
3、前两个功能要求所有参与者发送 RTCP 数据包,因此必须控制速率以使 RTP 能够扩大到大量参与者。通过让每个参与者将其控制包发送给所有其他人,每个人都可以独立观察参与者的数量。
4、这项功能对于参加者可以任意进入和离开的松散会话进程十分有用,参加者可以自由进入或离开,没有成员控制或参数协调。
功能 1-3 应该在所有环境中使用,尤其是在 IP 多播环境。RTP 应用程序设计师应避免只能在单播模式下工作且无法扩展到的机制更大的数字。RTCP 的传输可以单独控制对于发送者和接收者,适用于例如单向链接,而接收者没有反馈可能的。
RTCP 协议的端口
RTSP 通常使用 RTP 协议来传送实时流,RTP 一般使用偶数端口,而 RTCP 使用相邻的奇数端口,即 RTP 端口号+1。
RTP 端口
RTCP 端口
RTCP 信息包有哪些
在 RTCP 通信控制中,RTCP 协议的功能是通过不同类型的 RTCP 包来实现的。RTCP 也是基于 UDP 包来传送的,主要有五种类型的封包:
SR:发送端报告,由发送 RTP 数据报的应用程序或中端发出的。
RR:接收端报告,由接受但不发送 RTP 数据报的应用程序或中端发出。
SDES:源描述,传递与会话成员有关的标识信息的载体,如用户名、邮件、电话等。
BYE:通知离开,通知回话中的其他成员将退出会话。
APP:由应用程序自己定义,作为 RTCP 协议的扩展。
#define RTCP_SR 200
#define RTCP_RR 201
#define RTCP_SDES 202
#define RTCP_BYE 203
#define RTCP_APP 204
我们可以根据这五种类型包判断 RTCP 头部
static bool dissect_rtcp_heur(u_char *rtcp_info,int PayloadLen)
{
unsigned int offset = 0;
unsigned int first_byte = 0;
unsigned int packet_type = 0; /* 查看第一个字节 */
first_byte = rtcp_info[offset]; /* 版本位是否设置为2*/
//printf("version: %d\n",((first_byte & 0xC0) >> 6));
if (((first_byte & 0xC0) >> 6) != 2)
{
return false;
} /* 看包类型 */
offset += 1;
packet_type = rtcp_info[offset];
//printf("packet_type: %d\n",packet_type);
/* 复合数据包中的第一个数据包应该是发送方或接收者报告 */
if (!((packet_type == RTCP_SR) || (packet_type == RTCP_RR) ||
(packet_type == RTCP_BYE) || (packet_type == RTCP_APP) ||
(packet_type == RTCP_PSFB)))
{
return false;
} /*总长度必须是4个字节的倍数*/
//printf("PayloadLen: %d\n",PayloadLen);
if (PayloadLen % 4)
{
return false;
} /* OK, dissect as RTCP */
dissect_rtcp(rtcp_info,packet_type,offset,PayloadLen);
return true;
}
RTCP 协议报文格式
SR:发送端报告
版本(V):同 RTP 包头部
填充 ( P ) :同 RTP 包头部。
接收报告计数器(RC):5b 该 SR 包中接收的报告块的数目。
包类型(PT): 8bit SR 包类型为 200
长度(length):SR 包以 32bit 为 1 单位的长度减 1
同步源(SSRC):SR 包发送的同步源标识符。与对应 RTP 包中的 SSRC 一样。
NTP 时间戳(Network Time Protocol):SR 包发送时的绝对时间。用于同步不同的流。
RTP 时间戳:与 NTP 时间戳对应,与 RTP 包中的时间戳具有相同的初始值。
Send’s Packet count:从开始发包到产生这个 SR 包的这段时间内发送者发送的有效数据的总字节数,不包括头部和填充,发送者改变 SSRC 时,该域要清零。
同步源 n 的 SSRC 标识符:该报告中包含的是从该源接收到的包的统计信息。
丢失率:表明从上一个 SR 或 RR 包发出依来从同步源 n 发送的 RTP 包的丢失率。
累计丢失数据:从开始接受 SSRC_n 的包到发送 SR 这个时间段内 SSRC_n 发送的 RTP 丢失的总数目。
收到的扩展最大序列号:从 SSRC_n 收到的从 RTP 数据包中的最大序列号。
接收抖动(Interarrival jitter):RTP 数据包接收时间的统计方差估计。
上次 SR 时间戳(Last SR):取最近从 SSRC_n 收到的 SR 包中的 NTP 时间戳中的中间 32bit。如果还未收到 SR 包,则为 0。
上次依赖 SR 延迟(Delay since Last SR):从上次 SSRC_n 收到 SR 包到发送本包的延迟
Wireshark 抓包:
活动会话的参与者在发送和接收 RTP 分组时使用 SR。SR 有三个不同的部分:报头信息、发送方信息和许多接收方报告块。SR 也可以有一个与大纲相关的扩展域。
Wireshark 抓包:
SDES:源描述
SDES 提供了传递与会话成员有关的标识信息的载体,如用户名、邮件、电话等。每个 RTCP 混合分组中必须有 SDES 分组。
报头包含一个长度域、一个净荷类型域(PT=202)和一个源计数(RC)域。RC 域 5 个 bit,表示分组中信息块的数量。
每个信息块包含一个 SSRC 或 CSRC 值,其后跟着一个或多个的标识符和一些可用于 SSRC 或 CSRC 的信息。
CNAME 项的 SDES 包必须包含在每个组合 RTCP 包中。SDES 包可能包括其他源描述项,这要根据特别的应用需要,并同时考虑带宽限制。
Wireshark 抓包:
SDES 源描述包提供了直观的文本信息来描述会话的参加者,包括 CNAME、NAME、EMAIL、PHONE、LOC 等源描述项。
这些为接收方获取发送方的有关信息提供了方便。SDES 包由包头与数据块组成,数据块可以没有,也可有多个。包头由版本(V)、填充(P)、长度指示、包类型(PT)和源计数(SC)组成。
PT 占 8 位,用于识别 RTCP 的 SDES 包,SC 占 5 位,指示包含在 SDES 包中的 SSRC/CSRC 块数量,零值有效,但没有意义。
BYE: 通知离开
BYE 分组用于表示一个或多个媒体源不再是处于激活状态。
Wireshark 抓包:
作为可选项,BYE 包可包括一个 8 位八进制计数,后跟文本信息,表示离开原因。
最后,组合包中每个 RTCP 包可独立处理,而不需要按照包组合的先后顺序处理。
在组合包中有以下几条强制约束。
只要带宽允许,在 SR 包或 RR 包中的接收统计应该经常发送,因此每个周期发送的组合 RTCP 包中应包含报告包。
每个组合包中都应该包含 SDES CNAME,因为新接收者需要通过接收 CNAME 来识别源,并与媒体联系进行同步。
组合包前面是包类型数量,其增长应该受到限制。
RTCP 协议如何实现媒体流的同步
通过抓包分析 RTCP 发送端报告,RTP 的同步其实就靠这三个域:
sender SSRC :SR 包发送的同步源标识符。与对应 RTP 包中的 SSRC 一样。
NTP timestamp:SR 包发送时的绝对时间。用于同步不同的流。
RTP timestamp:与 NTP 时间戳对应,与 RTP 包中的时间戳具有相同的初始值。
那怎么计算 NTP 时间呢?在 RTCP 中 NTP 时间存放在 8 个字节中,分为:MSW 和 LSW,分别占用 4 个字节。
const char *tvb_ntp_fmt_ts_sec(u_char *rtcp_info, int offset)
{
uint32_t tempstmp = 0;
time_t temptime = 0;
struct tm *bd;
char *buff = NULL; tempstmp = ntohl(*(uint32_t*)(rtcp_info + offset));
if (tempstmp == 0){
return "NULL";
} /* We need a temporary variable here so the unsigned math
* works correctly (for years > 2036 according to RFC 2030
* chapter 3).
*/
temptime = (time_t)(tempstmp - NTP_BASETIME);
bd = gmtime(&temptime);
if (!bd){
return "Not representable";
} buff = (char *)malloc(NTP_TS_SIZE);
snprintf(buff, NTP_TS_SIZE,
"%s %2d, %d %02d:%02d:%02d UTC",
mon_names[bd->tm_mon],
bd->tm_mday,
bd->tm_year + 1900,
bd->tm_hour,
bd->tm_min,
bd->tm_sec);
return buff;
}
NTP timestamp
/* NTP timestamp */
ts_msw = ntohl(*(uint32_t*)(rtcp_info + offset));
printf("ts_msw: 0x%x\n",ts_msw);
ts_lsw = ntohl(*(uint32_t*)(rtcp_info + offset + 4));
printf("ts_lsw: 0x%x\n",ts_lsw);
printf("MSW: %s\n",tvb_ntp_fmt_ts_sec(rtcp_info,offset));
offset += 8;
RTCP 协议实现
下面我给出对 RTCP 协议解析实现的代码,根据回放报文,解析字段信息。
/* 接收者/发送者计数是最后5位 */
#define RTCP_COUNT(octet) ((octet) & 0x1F) #define RTCP_PT_MIN 192
/* Supplemental H.261 specific RTCP packet types according to Section C.3.5 */
#define RTCP_FIR 192
#define RTCP_NACK 193
#define RTCP_SMPTETC 194
#define RTCP_IJ 195
/* RTCP packet types according to Section A.11.1 */
/* And https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml */
#define RTCP_SR 200
#define RTCP_RR 201
#define RTCP_SDES 202
#define RTCP_BYE 203
#define RTCP_APP 204
#define RTCP_RTPFB 205
#define RTCP_PSFB 206
#define RTCP_XR 207
#define RTCP_AVB 208
#define RTCP_RSI 209
#define RTCP_TOKEN 210 #define RTCP_PT_MAX 210 static const char mon_names[12][4] = {
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
}; /** data structure to hold time values with nanosecond resolution*/
typedef struct {
time_t secs;
int nsecs;
} nstime_t; /*
* 1900-01-01 00:00:00 (proleptic?) UTC.
* Used by a number of time formats.
*/
#define EPOCH_DELTA_1900_01_01_00_00_00_UTC 2208988800U /*
* NTP_BASETIME is in fact epoch - ntp_start_time; ntp_start_time
* is January 1, 2036, 00:00:00 UTC.
*/
#define NTP_BASETIME EPOCH_DELTA_1900_01_01_00_00_00_UTC
#define NTP_FLOAT_DENOM 4294967296.0
#define NTP_TS_SIZE 100 /* 解剖长度字段。附加到此字段的文字表示转换为的实际字节数 (即 (原始值 + 1) * 4) */
static int dissect_rtcp_length_field(u_char *rtcp_info, int offset)
{ uint16_t raw_length = ntohs(*(uint16_t*)(rtcp_info + offset));
printf("(%u bytes)\n", (raw_length+1)*4);
offset += 2;
return offset;
}
static int dissect_rtcp_rr(u_char *rtcp_info, int offset,int count, int packet_length )
{
int counter = 0;
uint8_t rr_flt = 0;
int rr_offset = offset; counter = 1;
while ( counter <= count ) {
uint32_t lsr = 0, dlsr = 0; /* Create a new subtree for a length of 24 bytes */ /* SSRC_n source identifier, 32 bits */ offset += 4; /* Fraction lost, 8bits */
rr_flt = rtcp_info[offset]; offset++; /* Cumulative number of packets lost, 24 bits */
offset += 3; /* Sequence number cycles */ offset += 2;
/* highest sequence number received */ offset += 2; /* Interarrival jitter */ offset += 4; /* Last SR timestamp */
lsr = ntohl(*(uint32_t*)(rtcp_info + offset));
printf("Last SR timestamp: 0x%x\n",lsr);
offset += 4; /* Delay since last SR timestamp */
dlsr = ntohl(*(uint32_t*)(rtcp_info + offset)); printf("(%d milliseconds)\n",(int)(((double)dlsr/(double)65536) * 1000.0));
offset += 4; counter++;
} return offset;
} const char *tvb_ntp_fmt_ts_sec(u_char *rtcp_info, int offset)
{
uint32_t tempstmp = 0;
time_t temptime = 0;
struct tm *bd;
char *buff = NULL; tempstmp = ntohl(*(uint32_t*)(rtcp_info + offset));
if (tempstmp == 0){
return "NULL";
} /* We need a temporary variable here so the unsigned math
* works correctly (for years > 2036 according to RFC 2030
* chapter 3).
*/
temptime = (time_t)(tempstmp - NTP_BASETIME);
bd = gmtime(&temptime);
if (!bd){
return "Not representable";
} buff = (char *)malloc(NTP_TS_SIZE);
snprintf(buff, NTP_TS_SIZE,
"%s %2d, %d %02d:%02d:%02d UTC",
mon_names[bd->tm_mon],
bd->tm_mday,
bd->tm_year + 1900,
bd->tm_hour,
bd->tm_min,
bd->tm_sec);
return buff;
} static int dissect_rtcp_sr(u_char *rtcp_info, int offset,int count, int packet_length)
{ uint32_t ts_msw = 0, ts_lsw = 0;
int sr_offset = offset; /* NTP timestamp */
ts_msw = ntohl(*(uint32_t*)(rtcp_info + offset));
printf("ts_msw: 0x%x\n",ts_msw);
ts_lsw = ntohl(*(uint32_t*)(rtcp_info + offset + 4));
printf("ts_lsw: 0x%x\n",ts_lsw); //printf("offset: 0x%x 0x%x 0x%x 0x%x\n",rtcp_info[offset],rtcp_info[offset + 1],rtcp_info[offset + 2],rtcp_info[offset + 3]);
printf("MSW: %s\n",tvb_ntp_fmt_ts_sec(rtcp_info,offset));
offset += 8; /* RTP timestamp, 32 bits */ offset += 4;
/* Sender's packet count, 32 bits */ offset += 4;
/* Sender's octet count, 32 bits */ offset += 4; /* The rest of the packet is equal to the RR packet */
if ( count != 0 )
offset = dissect_rtcp_rr(rtcp_info, offset, count, packet_length-(offset-sr_offset));
else
{
/* If length remaining, assume profile-specific extension bytes */
if ((offset-sr_offset) < packet_length)
{ offset = sr_offset + packet_length;
}
} return offset;
} static int dissect_rtcp_sdes(u_char *rtcp_info, int offset, int count)
{
int chunk = 0; int start_offset = 0;
int items_start_offset = 0;
uint32_t ssrc = 0;
unsigned int item_len = 0;
unsigned int sdes_type = 0;
unsigned int prefix_len = 0; chunk = 1;
while ( chunk <= count )
{
/* Create a subtree for this chunk; we don't yet know
the length. */
start_offset = offset; ssrc = ntohl(*(uint32_t*)(rtcp_info + offset));
printf("Chunk %u, SSRC/CSRC 0x%X\n", chunk, ssrc); /* SSRC_n source identifier, 32 bits */
offset += 4; /* Create a subtree for the SDES items; we don't yet know
the length */ /*
* Not every message is ended with "null" bytes, so check for
* end of frame as well.
*/ /* ID, 8 bits */
sdes_type = rtcp_info[offset];
printf("Type: %d\n",sdes_type);
if (sdes_type == 0)
break;
offset++;
/* Item length, 8 bits */
item_len = rtcp_info[offset];
printf("Length: %d\n",item_len);
offset++; char *pszText = (char*)malloc(item_len);
if (pszText != 0)
{
memcpy(pszText, rtcp_info + offset,item_len);
pszText[item_len] = '\0';
printf("Text = %s\n",pszText);
} chunk++;
} return offset;
} static void dissect_rtcp(u_char *rtcp_info,int packet_type, int offset,int PayloadLen)
{
unsigned int temp_byte = 0;
int elem_count = 0;
int packet_length = 0;
int total_packet_length = 0;
int loop = 2;
bool flag_rtcp = false; /*检查是否为有效类型*/
if ( ( packet_type < RTCP_PT_MIN ) || ( packet_type > RTCP_PT_MAX ) )
exit(-1); /*
* 获取完整的RTCP数据包的长度
*/ packet_length = (ntohs(*(uint16_t*)(rtcp_info + offset + 1)) + 1) * 4 ;
//printf("packet_length: %d\n",packet_length); temp_byte = rtcp_info[offset-1];
elem_count = RTCP_COUNT( temp_byte );/* Source count, 5 bits */
printf("Reception report count: %d\n",elem_count); switch ( packet_type )
{ case RTCP_SR:
case RTCP_RR:
/*
Real-time Transport Control Protocol (Receiver Report)
10.. .... = Version: RFC 1889 Version (2)
..0. .... = Padding: False
...0 0001 = Reception report count: 1
Packet type: Receiver Report (201)
Length: 7 (32 bytes)
Sender SSRC: 0xb584b03e (3045371966)
Source 1
*/ /* Packet type, 8 bits */
offset++;
/* Packet length in 32 bit words MINUS one, 16 bits */
offset = dissect_rtcp_length_field(rtcp_info, offset);
/* Sender Synchronization source, 32 bits */
offset += 4; if ( packet_type == RTCP_SR )
{
offset = dissect_rtcp_sr(rtcp_info, offset, elem_count, packet_length-8 );
printf("dissect_rtcp_sr\n");
}
else
{
offset = dissect_rtcp_rr(rtcp_info, offset, elem_count, packet_length-8 );
} //uint16_t second_packet_type = ntohs(*(uint16_t*)(rtcp_info + offset)); //printf("111offset: 0x%x 0x%x 0x%x 0x%x\n",rtcp_info[offset],rtcp_info[offset + 1],rtcp_info[offset + 2],rtcp_info[offset + 3]); if (rtcp_info[offset + 1] == RTCP_SDES)
{ /* Source count, 5 bits */
offset++;
/* Packet type, 8 bits */
offset++;
/* Packet length in 32 bit words MINUS one, 16 bits */
offset = dissect_rtcp_length_field(rtcp_info, offset);
offset = dissect_rtcp_sdes(rtcp_info,offset,elem_count); } break; default:
/*
* To prevent endless loops in case of an unknown message type
* increase offset. Some time the while will end :-)
*/
offset++;
break; } } static bool dissect_rtcp_heur(u_char *rtcp_info,int PayloadLen)
{
unsigned int offset = 0;
unsigned int first_byte = 0;
unsigned int packet_type = 0; /* 查看第一个字节 */
first_byte = rtcp_info[offset]; /* 版本位是否设置为2*/
//printf("version: %d\n",((first_byte & 0xC0) >> 6));
if (((first_byte & 0xC0) >> 6) != 2)
{
return false;
} /* 看包类型 */
offset += 1;
packet_type = rtcp_info[offset];
//printf("packet_type: %d\n",packet_type);
/* 复合数据包中的第一个数据包应该是发送方或接收者报告 */
if (!((packet_type == RTCP_SR) || (packet_type == RTCP_RR) ||
(packet_type == RTCP_BYE) || (packet_type == RTCP_APP) ||
(packet_type == RTCP_PSFB)))
{
return false;
} /*总长度必须是4个字节的倍数*/
//printf("PayloadLen: %d\n",PayloadLen);
if (PayloadLen % 4)
{
return false;
} /* OK, dissect as RTCP */
dissect_rtcp(rtcp_info,packet_type,offset,PayloadLen);
return true;
} static void confirm_rtcp_packet(struct ip *pIp)
{
int iIpTotalLen = ntohs(pIp->ip_len);
int offset = 0;
int nFragSeq = 0;
struct udphdr* pUdpHdr = (struct udphdr*)((char*)pIp + (pIp->ip_hl<<2));
if (pIp->ip_p == IPPROTO_UDP)
{
printf("\n"); int iPayloadLen = iIpTotalLen - (pIp->ip_hl<<2) - 8;
printf("UDP Payload Len %d\n", iPayloadLen); u_char *pDnsHdr = (u_char*)(pUdpHdr+1);
dissect_rtcp_heur(pDnsHdr,iPayloadLen); }
}
编译运行:
总结
RTCP 协议是流媒体通信的基石。RTCP 协议则负责可靠传输、流量控制和拥塞控制等服务质量保证。上面讲解了 RTCP 功能、RTCP 数据包格式及代码实现。最后,学习一个新的协议,最好还是研究学习官方文档,因为这是最权威的资料。
音视频同步!RTCP 协议解析及代码实现的更多相关文章
- FFmpeg简易播放器的实现-音视频同步
本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10284653.html 基于FFmpeg和SDL实现的简易视频播放器,主要分为读取视频文 ...
- vlc源码分析(五) 流媒体的音视频同步
vlc播放流媒体时实现音视频同步,简单来说就是发送方发送的RTP包带有时间戳,接收方根据此时间戳不断校正本地时钟,播放音视频时根据本地时钟进行同步播放.首先了解两个概念:stream clock和sy ...
- WebRTC 音视频同步原理与实现
所有的基于网络传输的音视频采集播放系统都会存在音视频同步的问题,作为现代互联网实时音视频通信系统的代表,WebRTC 也不例外.本文将对音视频同步的原理以及 WebRTC 的实现做深入分析. 时间戳 ...
- Android 音视频同步机制
一.概述 音视频同步(avsync),是影响多媒体应用体验质量的一个重要因素.而我们在看到音视频同步的时候,最先想到的就是对齐两者的pts,但是实际使用中的各类播放器,其音视频同步机制都比这些复杂的多 ...
- ffmpeg 2.3版本号, 关于ffplay音视频同步的分析
近期学习播放器的一些东西.所以接触了ffmpeg,看源代码的过程中.就想了解一下ffplay是怎么处理音视频同步的,之前仅仅大概知道通过pts来进行同步,但对于怎样实现却不甚了解,所以想借助这个机会, ...
- ffplay的音视频同步分析
以前工作中参与了一些音视频程序的开发,不过使用的都是芯片公司的SDK,没有研究到更深入一层,比如说音视频同步是怎么回事.只好自己抽点时间出来分析开源代码了,做音视频编解码的人都知道ffmpeg,他在各 ...
- ffplay(2.0.1)中的音视频同步
最近在看ffmpeg相关的一些东西,以及一些播放器相关资料和代码. 然后对于ffmpeg-2.0.1版本下的ffplay进行了大概的代码阅读,其中这里把里面的音视频同步,按个人的理解,暂时在这里作个笔 ...
- (转)ffplay的音视频同步分析之视频同步到音频
以前工作中参与了一些音视频程序的开发,不过使用的都是芯片公司的SDK,没有研究到更深入一层,比如说音视频同步是怎么回事.只好自己抽点时间出来分析开源代码了,做音视频编解码的人都知道ffmp ...
- [SimplePlayer] 8. 音视频同步
音频与视频在播放当中可能会由于种种原因(如:音视频并非在同一时间开始播放,或视频由于解码任务繁重导致输出图像延迟等)导致音频与视频的播放时间出现偏差,这种就是音视频的同步问题,本文会对音视频同步进行讨 ...
- Android 音视频同步(A/V Sync)
1. 音视频同步原理 1)时间戳 音视频同步主要用于在音视频流的播放过程中,让同一时刻录制的声音和图像在播放的时候尽可能的在同一个时间输出. 解决音视频同步问题的最佳方案就是时间戳:首先选择一个参考 ...
随机推荐
- Mysql-------查询各科成绩前三名的记录
查询各科成绩前三名的记录 分析如下: 1 select s.* from (select distinct Student.*,a.c_id, a.s_score, count( distinct b ...
- <三>JMeter逻辑控制器简介
一.添加HTTP请求线程 1.运行jmeter.bat进入jmeter界面 2.修改语言为简体中文 3.右键测试计划添加一个线程组 4.右键线程组添加一个HTTP请求 5.填写HTTP请求服务名称和路 ...
- bzoj 3106
好久没写oi系列的题解了 要不是为了大作业我才不会回来学这些奇怪的东西呢 本题对抗搜索就好啦 首先要分析一点,就是由于我们的黑棋每次走两步,白棋只走一步而且是白棋先走,所以除非白棋第一步吃掉黑棋,否则 ...
- 前端element ui 文件base64加密字符串 上传
<el-form-item label="附件" prop="attachment"> <el-upload :multiple=" ...
- AX2012 使用HTML自定义popup内样式
在Class Box下新增方法如下: public client static DialogButton yesNoHTML( str _text, DialogButton _defaultButt ...
- 学Java的第5天,今天做了个双色球系统
今天是学JAVA的第5天,刚刚把方法学完,然后就在这做黑马的题. 用了一个多小时时间,把他的 这些题都做完了 但是最后一道题,这个双色球系统我感觉挺有意思的 我看到这个题,分析后感觉需要4种方法: 1 ...
- Leetcode457
A very absurd description for this problem, but people can get the idea by looking at the examples.. ...
- C# POST GET请求方式汇总
/// <summary> /// POST方式提交 application/json /// </summary> /// <param name="post ...
- OSIDP-文件管理-12(end)
概述 文件特性:可长期存储:可在进程间共享:有特定结构. 文件系统提供对文件操作的功能接口:创建.删除.打开.关闭.读和写. 域(field):基本数据单元,一个域包含一个值. 记录(record): ...
- PHP中获取时间的下一周下个月的方法
PHP中获取时间的下一周,下个月等通常用于定制服务的时候使用,比如包月会员,包年等等 //通常用于定制服务的时候使用,比如包月会员,包年等等 //获取当前时间过一个月的时间,以DATETIME格式显示 ...