正在显示
10 个修改的文件
包含
484 行增加
和
43 行删除
| @@ -60,29 +60,32 @@ int SrsClient::do_cycle() | @@ -60,29 +60,32 @@ int SrsClient::do_cycle() | ||
| 60 | int ret = ERROR_SUCCESS; | 60 | int ret = ERROR_SUCCESS; |
| 61 | 61 | ||
| 62 | if ((ret = get_peer_ip()) != ERROR_SUCCESS) { | 62 | if ((ret = get_peer_ip()) != ERROR_SUCCESS) { |
| 63 | - srs_warn("get peer ip failed. ret=%d", ret); | 63 | + srs_error("get peer ip failed. ret=%d", ret); |
| 64 | return ret; | 64 | return ret; |
| 65 | } | 65 | } |
| 66 | srs_verbose("get peer ip success. ip=%s", ip); | 66 | srs_verbose("get peer ip success. ip=%s", ip); |
| 67 | 67 | ||
| 68 | if ((ret = rtmp->handshake()) != ERROR_SUCCESS) { | 68 | if ((ret = rtmp->handshake()) != ERROR_SUCCESS) { |
| 69 | - srs_warn("rtmp handshake failed. ret=%d", ret); | 69 | + srs_error("rtmp handshake failed. ret=%d", ret); |
| 70 | return ret; | 70 | return ret; |
| 71 | } | 71 | } |
| 72 | srs_verbose("rtmp handshake success"); | 72 | srs_verbose("rtmp handshake success"); |
| 73 | 73 | ||
| 74 | if ((ret = rtmp->connect_app(req)) != ERROR_SUCCESS) { | 74 | if ((ret = rtmp->connect_app(req)) != ERROR_SUCCESS) { |
| 75 | - srs_warn("rtmp connect vhost/app failed. ret=%d", ret); | 75 | + srs_error("rtmp connect vhost/app failed. ret=%d", ret); |
| 76 | return ret; | 76 | return ret; |
| 77 | } | 77 | } |
| 78 | - srs_info("rtmp connect success. tcUrl=%s, pageUrl=%s, swfUrl=%s", | ||
| 79 | - req->tcUrl.c_str(), req->pageUrl.c_str(), req->swfUrl.c_str()); | ||
| 80 | - | ||
| 81 | - srs_trace("rtmp connect success. " | 78 | + srs_trace("rtmp connect app success. " |
| 82 | "tcUrl=%s, pageUrl=%s, swfUrl=%s, schema=%s, vhost=%s, port=%s, app=%s", | 79 | "tcUrl=%s, pageUrl=%s, swfUrl=%s, schema=%s, vhost=%s, port=%s, app=%s", |
| 83 | req->tcUrl.c_str(), req->pageUrl.c_str(), req->swfUrl.c_str(), | 80 | req->tcUrl.c_str(), req->pageUrl.c_str(), req->swfUrl.c_str(), |
| 84 | req->schema.c_str(), req->vhost.c_str(), req->port.c_str(), | 81 | req->schema.c_str(), req->vhost.c_str(), req->port.c_str(), |
| 85 | req->app.c_str()); | 82 | req->app.c_str()); |
| 83 | + | ||
| 84 | + if ((ret = rtmp->set_window_ack_size(2.5 * 1000 * 1000)) != ERROR_SUCCESS) { | ||
| 85 | + srs_error("set window acknowledgement size failed. ret=%d", ret); | ||
| 86 | + return ret; | ||
| 87 | + } | ||
| 88 | + srs_verbose("set window acknowledgement size success"); | ||
| 86 | 89 | ||
| 87 | return ret; | 90 | return ret; |
| 88 | } | 91 | } |
| @@ -55,6 +55,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | @@ -55,6 +55,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| 55 | #define ERROR_RTMP_AMF0_DECODE 303 | 55 | #define ERROR_RTMP_AMF0_DECODE 303 |
| 56 | #define ERROR_RTMP_AMF0_INVALID 304 | 56 | #define ERROR_RTMP_AMF0_INVALID 304 |
| 57 | #define ERROR_RTMP_REQ_CONNECT 305 | 57 | #define ERROR_RTMP_REQ_CONNECT 305 |
| 58 | +#define ERROR_RTMP_REQ_TCURL 306 | ||
| 59 | +#define ERROR_RTMP_MESSAGE_DECODE 307 | ||
| 58 | 60 | ||
| 59 | #define ERROR_SYSTEM_STREAM_INIT 400 | 61 | #define ERROR_SYSTEM_STREAM_INIT 400 |
| 60 | 62 |
| @@ -30,6 +30,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | @@ -30,6 +30,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| 30 | #include <srs_core_buffer.hpp> | 30 | #include <srs_core_buffer.hpp> |
| 31 | #include <srs_core_stream.hpp> | 31 | #include <srs_core_stream.hpp> |
| 32 | 32 | ||
| 33 | +/**************************************************************************** | ||
| 34 | +***************************************************************************** | ||
| 35 | +****************************************************************************/ | ||
| 33 | /** | 36 | /** |
| 34 | 5. Protocol Control Messages | 37 | 5. Protocol Control Messages |
| 35 | RTMP reserves message type IDs 1-7 for protocol control messages. | 38 | RTMP reserves message type IDs 1-7 for protocol control messages. |
| @@ -124,6 +127,9 @@ messages. | @@ -124,6 +127,9 @@ messages. | ||
| 124 | */ | 127 | */ |
| 125 | #define RTMP_MSG_AggregateMessage 22 // 0x16 | 128 | #define RTMP_MSG_AggregateMessage 22 // 0x16 |
| 126 | 129 | ||
| 130 | +/**************************************************************************** | ||
| 131 | +***************************************************************************** | ||
| 132 | +****************************************************************************/ | ||
| 127 | /** | 133 | /** |
| 128 | * 6.1.2. Chunk Message Header | 134 | * 6.1.2. Chunk Message Header |
| 129 | * There are four different formats for the chunk message header, | 135 | * There are four different formats for the chunk message header, |
| @@ -164,6 +170,9 @@ messages. | @@ -164,6 +170,9 @@ messages. | ||
| 164 | // the same as the timestamp of Type 0 chunk. | 170 | // the same as the timestamp of Type 0 chunk. |
| 165 | #define RTMP_FMT_TYPE3 3 | 171 | #define RTMP_FMT_TYPE3 3 |
| 166 | 172 | ||
| 173 | +/**************************************************************************** | ||
| 174 | +***************************************************************************** | ||
| 175 | +****************************************************************************/ | ||
| 167 | /** | 176 | /** |
| 168 | * 6. Chunking | 177 | * 6. Chunking |
| 169 | * The chunk size is configurable. It can be set using a control | 178 | * The chunk size is configurable. It can be set using a control |
| @@ -189,11 +198,58 @@ messages. | @@ -189,11 +198,58 @@ messages. | ||
| 189 | */ | 198 | */ |
| 190 | #define RTMP_EXTENDED_TIMESTAMP 0xFFFFFF | 199 | #define RTMP_EXTENDED_TIMESTAMP 0xFFFFFF |
| 191 | 200 | ||
| 201 | +/**************************************************************************** | ||
| 202 | +***************************************************************************** | ||
| 203 | +****************************************************************************/ | ||
| 192 | /** | 204 | /** |
| 193 | * amf0 command message, command name: "connect" | 205 | * amf0 command message, command name: "connect" |
| 194 | */ | 206 | */ |
| 195 | #define RTMP_AMF0_COMMAND_CONNECT "connect" | 207 | #define RTMP_AMF0_COMMAND_CONNECT "connect" |
| 196 | 208 | ||
| 209 | +/**************************************************************************** | ||
| 210 | +***************************************************************************** | ||
| 211 | +****************************************************************************/ | ||
| 212 | +/** | ||
| 213 | +* the chunk stream id used for some under-layer message, | ||
| 214 | +* for example, the PC(protocol control) message. | ||
| 215 | +*/ | ||
| 216 | +#define RTMP_CID_ProtocolControl 0x02 | ||
| 217 | +/** | ||
| 218 | +* the AMF0/AMF3 command message, invoke method and return the result, over NetConnection. | ||
| 219 | +* generally use 0x03. | ||
| 220 | +*/ | ||
| 221 | +#define RTMP_CID_OverConnection 0x03 | ||
| 222 | +/** | ||
| 223 | +* the AMF0/AMF3 command message, invoke method and return the result, over NetConnection, | ||
| 224 | +* the midst state(we guess). | ||
| 225 | +* rarely used, e.g. onStatus(NetStream.Play.Reset). | ||
| 226 | +*/ | ||
| 227 | +#define RTMP_CID_OverConnection2 0x04 | ||
| 228 | +/** | ||
| 229 | +* the stream message(amf0/amf3), over NetStream. | ||
| 230 | +* generally use 0x05. | ||
| 231 | +*/ | ||
| 232 | +#define RTMP_CID_OverStream 0x05 | ||
| 233 | +/** | ||
| 234 | +* the stream message(amf0/amf3), over NetStream, the midst state(we guess). | ||
| 235 | +* rarely used, e.g. play("mp4:mystram.f4v") | ||
| 236 | +*/ | ||
| 237 | +#define RTMP_CID_OverStream2 0x08 | ||
| 238 | +/** | ||
| 239 | +* the stream message(video), over NetStream | ||
| 240 | +* generally use 0x06. | ||
| 241 | +*/ | ||
| 242 | +#define RTMP_CID_Video 0x06 | ||
| 243 | +/** | ||
| 244 | +* the stream message(audio), over NetStream. | ||
| 245 | +* generally use 0x07. | ||
| 246 | +*/ | ||
| 247 | +#define RTMP_CID_Audio 0x07 | ||
| 248 | + | ||
| 249 | +/**************************************************************************** | ||
| 250 | +***************************************************************************** | ||
| 251 | +****************************************************************************/ | ||
| 252 | + | ||
| 197 | SrsProtocol::SrsProtocol(st_netfd_t client_stfd) | 253 | SrsProtocol::SrsProtocol(st_netfd_t client_stfd) |
| 198 | { | 254 | { |
| 199 | stfd = client_stfd; | 255 | stfd = client_stfd; |
| @@ -263,6 +319,118 @@ int SrsProtocol::recv_message(SrsMessage** pmsg) | @@ -263,6 +319,118 @@ int SrsProtocol::recv_message(SrsMessage** pmsg) | ||
| 263 | return ret; | 319 | return ret; |
| 264 | } | 320 | } |
| 265 | 321 | ||
| 322 | +int SrsProtocol::send_message(SrsMessage* msg) | ||
| 323 | +{ | ||
| 324 | + int ret = ERROR_SUCCESS; | ||
| 325 | + | ||
| 326 | + if ((ret = msg->encode_packet()) != ERROR_SUCCESS) { | ||
| 327 | + srs_error("encode packet to message payload failed. ret=%d", ret); | ||
| 328 | + return ret; | ||
| 329 | + } | ||
| 330 | + srs_info("encode packet to message payload success"); | ||
| 331 | + | ||
| 332 | + // p set to current write position, | ||
| 333 | + // it's ok when payload is NULL and size is 0. | ||
| 334 | + char* p = (char*)msg->payload; | ||
| 335 | + | ||
| 336 | + // always write the header event payload is empty. | ||
| 337 | + do { | ||
| 338 | + // generate the header. | ||
| 339 | + char* pheader = NULL; | ||
| 340 | + int header_size = 0; | ||
| 341 | + | ||
| 342 | + if (p == (char*)msg->payload) { | ||
| 343 | + // write new chunk stream header, fmt is 0 | ||
| 344 | + pheader = out_header_fmt0; | ||
| 345 | + *pheader++ = 0x00 | (msg->get_perfer_cid() & 0x3F); | ||
| 346 | + | ||
| 347 | + // chunk message header, 11 bytes | ||
| 348 | + // timestamp, 3bytes, big-endian | ||
| 349 | + if (msg->header.timestamp >= RTMP_EXTENDED_TIMESTAMP) { | ||
| 350 | + *pheader++ = 0xFF; | ||
| 351 | + *pheader++ = 0xFF; | ||
| 352 | + *pheader++ = 0xFF; | ||
| 353 | + } else { | ||
| 354 | + pp = (char*)&msg->header.timestamp; | ||
| 355 | + *pheader++ = pp[2]; | ||
| 356 | + *pheader++ = pp[1]; | ||
| 357 | + *pheader++ = pp[0]; | ||
| 358 | + } | ||
| 359 | + | ||
| 360 | + // message_length, 3bytes, big-endian | ||
| 361 | + pp = (char*)&msg->header.payload_length; | ||
| 362 | + *pheader++ = pp[2]; | ||
| 363 | + *pheader++ = pp[1]; | ||
| 364 | + *pheader++ = pp[0]; | ||
| 365 | + | ||
| 366 | + // message_type, 1bytes | ||
| 367 | + *pheader++ = msg->header.message_type; | ||
| 368 | + | ||
| 369 | + // message_length, 3bytes, little-endian | ||
| 370 | + pp = (char*)&msg->header.stream_id; | ||
| 371 | + *pheader++ = pp[0]; | ||
| 372 | + *pheader++ = pp[1]; | ||
| 373 | + *pheader++ = pp[2]; | ||
| 374 | + *pheader++ = pp[3]; | ||
| 375 | + | ||
| 376 | + // chunk extended timestamp header, 0 or 4 bytes, big-endian | ||
| 377 | + if(msg->header.timestamp >= RTMP_EXTENDED_TIMESTAMP){ | ||
| 378 | + pp = (char*)&msg->header.timestamp; | ||
| 379 | + *pheader++ = pp[3]; | ||
| 380 | + *pheader++ = pp[2]; | ||
| 381 | + *pheader++ = pp[1]; | ||
| 382 | + *pheader++ = pp[0]; | ||
| 383 | + } | ||
| 384 | + | ||
| 385 | + header_size = pheader - out_header_fmt0; | ||
| 386 | + pheader = out_header_fmt0; | ||
| 387 | + } else { | ||
| 388 | + // write no message header chunk stream, fmt is 3 | ||
| 389 | + pheader = out_header_fmt3; | ||
| 390 | + *pheader++ = 0xC0 | (msg->get_perfer_cid() & 0x3F); | ||
| 391 | + | ||
| 392 | + // chunk extended timestamp header, 0 or 4 bytes, big-endian | ||
| 393 | + if(msg->header.timestamp >= RTMP_EXTENDED_TIMESTAMP){ | ||
| 394 | + pp = (char*)&msg->header.timestamp; | ||
| 395 | + *pheader++ = pp[3]; | ||
| 396 | + *pheader++ = pp[2]; | ||
| 397 | + *pheader++ = pp[1]; | ||
| 398 | + *pheader++ = pp[0]; | ||
| 399 | + } | ||
| 400 | + | ||
| 401 | + header_size = pheader - out_header_fmt3; | ||
| 402 | + pheader = out_header_fmt3; | ||
| 403 | + } | ||
| 404 | + | ||
| 405 | + // sendout header and payload by writev. | ||
| 406 | + // decrease the sys invoke count to get higher performance. | ||
| 407 | + int payload_size = msg->size - ((char*)msg->payload - p); | ||
| 408 | + if (payload_size > out_chunk_size) { | ||
| 409 | + payload_size = out_chunk_size; | ||
| 410 | + } | ||
| 411 | + | ||
| 412 | + // send by writev | ||
| 413 | + iovec iov[2]; | ||
| 414 | + iov[0].iov_base = pheader; | ||
| 415 | + iov[0].iov_len = header_size; | ||
| 416 | + iov[1].iov_base = p; | ||
| 417 | + iov[1].iov_len = payload_size; | ||
| 418 | + | ||
| 419 | + ssize_t nwrite; | ||
| 420 | + if ((ret = skt->writev(iov, 2, &nwrite)) != ERROR_SUCCESS) { | ||
| 421 | + srs_error("send with writev failed. ret=%d", ret); | ||
| 422 | + return ret; | ||
| 423 | + } | ||
| 424 | + | ||
| 425 | + // consume sendout bytes when not empty packet. | ||
| 426 | + if (msg->payload && msg->size > 0) { | ||
| 427 | + p += payload_size; | ||
| 428 | + } | ||
| 429 | + } while (p < (char*)msg->payload + msg->size); | ||
| 430 | + | ||
| 431 | + return ret; | ||
| 432 | +} | ||
| 433 | + | ||
| 266 | int SrsProtocol::recv_interlaced_message(SrsMessage** pmsg) | 434 | int SrsProtocol::recv_interlaced_message(SrsMessage** pmsg) |
| 267 | { | 435 | { |
| 268 | int ret = ERROR_SUCCESS; | 436 | int ret = ERROR_SUCCESS; |
| @@ -325,7 +493,7 @@ int SrsProtocol::recv_interlaced_message(SrsMessage** pmsg) | @@ -325,7 +493,7 @@ int SrsProtocol::recv_interlaced_message(SrsMessage** pmsg) | ||
| 325 | return ret; | 493 | return ret; |
| 326 | } | 494 | } |
| 327 | 495 | ||
| 328 | -int SrsProtocol::read_basic_header(char& fmt, int& cid, int& size) | 496 | +int SrsProtocol::read_basic_header(char& fmt, int& cid, int& bh_size) |
| 329 | { | 497 | { |
| 330 | int ret = ERROR_SUCCESS; | 498 | int ret = ERROR_SUCCESS; |
| 331 | 499 | ||
| @@ -339,10 +507,10 @@ int SrsProtocol::read_basic_header(char& fmt, int& cid, int& size) | @@ -339,10 +507,10 @@ int SrsProtocol::read_basic_header(char& fmt, int& cid, int& size) | ||
| 339 | 507 | ||
| 340 | fmt = (*p >> 6) & 0x03; | 508 | fmt = (*p >> 6) & 0x03; |
| 341 | cid = *p & 0x3f; | 509 | cid = *p & 0x3f; |
| 342 | - size = 1; | 510 | + bh_size = 1; |
| 343 | 511 | ||
| 344 | if (cid > 1) { | 512 | if (cid > 1) { |
| 345 | - srs_verbose("%dbytes basic header parsed. fmt=%d, cid=%d", size, fmt, cid); | 513 | + srs_verbose("%dbytes basic header parsed. fmt=%d, cid=%d", bh_size, fmt, cid); |
| 346 | return ret; | 514 | return ret; |
| 347 | } | 515 | } |
| 348 | 516 | ||
| @@ -355,8 +523,8 @@ int SrsProtocol::read_basic_header(char& fmt, int& cid, int& size) | @@ -355,8 +523,8 @@ int SrsProtocol::read_basic_header(char& fmt, int& cid, int& size) | ||
| 355 | 523 | ||
| 356 | cid = 64; | 524 | cid = 64; |
| 357 | cid += *(++p); | 525 | cid += *(++p); |
| 358 | - size = 2; | ||
| 359 | - srs_verbose("%dbytes basic header parsed. fmt=%d, cid=%d", size, fmt, cid); | 526 | + bh_size = 2; |
| 527 | + srs_verbose("%dbytes basic header parsed. fmt=%d, cid=%d", bh_size, fmt, cid); | ||
| 360 | } else if (cid == 1) { | 528 | } else if (cid == 1) { |
| 361 | required_size = 3; | 529 | required_size = 3; |
| 362 | if ((ret = buffer->ensure_buffer_bytes(skt, 3)) != ERROR_SUCCESS) { | 530 | if ((ret = buffer->ensure_buffer_bytes(skt, 3)) != ERROR_SUCCESS) { |
| @@ -367,8 +535,8 @@ int SrsProtocol::read_basic_header(char& fmt, int& cid, int& size) | @@ -367,8 +535,8 @@ int SrsProtocol::read_basic_header(char& fmt, int& cid, int& size) | ||
| 367 | cid = 64; | 535 | cid = 64; |
| 368 | cid += *(++p); | 536 | cid += *(++p); |
| 369 | cid += *(++p) * 256; | 537 | cid += *(++p) * 256; |
| 370 | - size = 3; | ||
| 371 | - srs_verbose("%dbytes basic header parsed. fmt=%d, cid=%d", size, fmt, cid); | 538 | + bh_size = 3; |
| 539 | + srs_verbose("%dbytes basic header parsed. fmt=%d, cid=%d", bh_size, fmt, cid); | ||
| 372 | } else { | 540 | } else { |
| 373 | srs_error("invalid path, impossible basic header."); | 541 | srs_error("invalid path, impossible basic header."); |
| 374 | srs_assert(false); | 542 | srs_assert(false); |
| @@ -620,7 +788,7 @@ SrsMessage::SrsMessage() | @@ -620,7 +788,7 @@ SrsMessage::SrsMessage() | ||
| 620 | size = 0; | 788 | size = 0; |
| 621 | stream = NULL; | 789 | stream = NULL; |
| 622 | payload = NULL; | 790 | payload = NULL; |
| 623 | - decoded_payload = NULL; | 791 | + packet = NULL; |
| 624 | } | 792 | } |
| 625 | 793 | ||
| 626 | SrsMessage::~SrsMessage() | 794 | SrsMessage::~SrsMessage() |
| @@ -630,9 +798,9 @@ SrsMessage::~SrsMessage() | @@ -630,9 +798,9 @@ SrsMessage::~SrsMessage() | ||
| 630 | payload = NULL; | 798 | payload = NULL; |
| 631 | } | 799 | } |
| 632 | 800 | ||
| 633 | - if (decoded_payload) { | ||
| 634 | - delete decoded_payload; | ||
| 635 | - decoded_payload = NULL; | 801 | + if (packet) { |
| 802 | + delete packet; | ||
| 803 | + packet = NULL; | ||
| 636 | } | 804 | } |
| 637 | 805 | ||
| 638 | if (stream) { | 806 | if (stream) { |
| @@ -641,16 +809,6 @@ SrsMessage::~SrsMessage() | @@ -641,16 +809,6 @@ SrsMessage::~SrsMessage() | ||
| 641 | } | 809 | } |
| 642 | } | 810 | } |
| 643 | 811 | ||
| 644 | -SrsPacket* SrsMessage::get_packet() | ||
| 645 | -{ | ||
| 646 | - if (!decoded_payload) { | ||
| 647 | - srs_error("the payload is raw/undecoded, invoke decode_packet to decode it."); | ||
| 648 | - } | ||
| 649 | - srs_assert(decoded_payload != NULL); | ||
| 650 | - | ||
| 651 | - return decoded_payload; | ||
| 652 | -} | ||
| 653 | - | ||
| 654 | int SrsMessage::decode_packet() | 812 | int SrsMessage::decode_packet() |
| 655 | { | 813 | { |
| 656 | int ret = ERROR_SUCCESS; | 814 | int ret = ERROR_SUCCESS; |
| @@ -684,19 +842,64 @@ int SrsMessage::decode_packet() | @@ -684,19 +842,64 @@ int SrsMessage::decode_packet() | ||
| 684 | stream->reset(); | 842 | stream->reset(); |
| 685 | if (command == RTMP_AMF0_COMMAND_CONNECT) { | 843 | if (command == RTMP_AMF0_COMMAND_CONNECT) { |
| 686 | srs_info("decode the AMF0 command(connect vhost/app message)."); | 844 | srs_info("decode the AMF0 command(connect vhost/app message)."); |
| 687 | - decoded_payload = new SrsConnectAppPacket(); | ||
| 688 | - return decoded_payload->decode(stream); | 845 | + packet = new SrsConnectAppPacket(); |
| 846 | + return packet->decode(stream); | ||
| 689 | } | 847 | } |
| 690 | 848 | ||
| 691 | // default packet to drop message. | 849 | // default packet to drop message. |
| 692 | srs_trace("drop the AMF0 command message, command_name=%s", command.c_str()); | 850 | srs_trace("drop the AMF0 command message, command_name=%s", command.c_str()); |
| 693 | - decoded_payload = new SrsPacket(); | 851 | + packet = new SrsPacket(); |
| 694 | return ret; | 852 | return ret; |
| 695 | } | 853 | } |
| 696 | 854 | ||
| 697 | // default packet to drop message. | 855 | // default packet to drop message. |
| 698 | srs_trace("drop the unknown message, type=%d", header.message_type); | 856 | srs_trace("drop the unknown message, type=%d", header.message_type); |
| 699 | - decoded_payload = new SrsPacket(); | 857 | + packet = new SrsPacket(); |
| 858 | + | ||
| 859 | + return ret; | ||
| 860 | +} | ||
| 861 | + | ||
| 862 | +SrsPacket* SrsMessage::get_packet() | ||
| 863 | +{ | ||
| 864 | + if (!packet) { | ||
| 865 | + srs_error("the payload is raw/undecoded, invoke decode_packet to decode it."); | ||
| 866 | + } | ||
| 867 | + srs_assert(packet != NULL); | ||
| 868 | + | ||
| 869 | + return packet; | ||
| 870 | +} | ||
| 871 | + | ||
| 872 | +int SrsMessage::get_perfer_cid() | ||
| 873 | +{ | ||
| 874 | + if (!packet) { | ||
| 875 | + return RTMP_CID_ProtocolControl; | ||
| 876 | + } | ||
| 877 | + | ||
| 878 | + // we donot use the complex basic header, | ||
| 879 | + // ensure the basic header is 1bytes. | ||
| 880 | + if (packet->get_perfer_cid() < 2) { | ||
| 881 | + return packet->get_perfer_cid(); | ||
| 882 | + } | ||
| 883 | + | ||
| 884 | + return packet->get_perfer_cid(); | ||
| 885 | +} | ||
| 886 | + | ||
| 887 | +void SrsMessage::set_packet(SrsPacket* pkt) | ||
| 888 | +{ | ||
| 889 | + if (packet) { | ||
| 890 | + delete packet; | ||
| 891 | + } | ||
| 892 | + packet = pkt; | ||
| 893 | +} | ||
| 894 | + | ||
| 895 | +int SrsMessage::encode_packet() | ||
| 896 | +{ | ||
| 897 | + int ret = ERROR_SUCCESS; | ||
| 898 | + | ||
| 899 | + if (packet == NULL) { | ||
| 900 | + srs_warn("packet is empty, send out empty message."); | ||
| 901 | + return ret; | ||
| 902 | + } | ||
| 700 | 903 | ||
| 701 | return ret; | 904 | return ret; |
| 702 | } | 905 | } |
| @@ -715,6 +918,11 @@ int SrsPacket::decode(SrsStream* /*stream*/) | @@ -715,6 +918,11 @@ int SrsPacket::decode(SrsStream* /*stream*/) | ||
| 715 | return ret; | 918 | return ret; |
| 716 | } | 919 | } |
| 717 | 920 | ||
| 921 | +int SrsPacket::get_perfer_cid() | ||
| 922 | +{ | ||
| 923 | + return 0; | ||
| 924 | +} | ||
| 925 | + | ||
| 718 | SrsConnectAppPacket::SrsConnectAppPacket() | 926 | SrsConnectAppPacket::SrsConnectAppPacket() |
| 719 | { | 927 | { |
| 720 | command_name = RTMP_AMF0_COMMAND_CONNECT; | 928 | command_name = RTMP_AMF0_COMMAND_CONNECT; |
| @@ -771,3 +979,37 @@ int SrsConnectAppPacket::decode(SrsStream* stream) | @@ -771,3 +979,37 @@ int SrsConnectAppPacket::decode(SrsStream* stream) | ||
| 771 | return ret; | 979 | return ret; |
| 772 | } | 980 | } |
| 773 | 981 | ||
| 982 | +SrsSetWindowAckSizePacket::SrsSetWindowAckSizePacket() | ||
| 983 | +{ | ||
| 984 | + ackowledgement_window_size = 0; | ||
| 985 | +} | ||
| 986 | + | ||
| 987 | +SrsSetWindowAckSizePacket::~SrsSetWindowAckSizePacket() | ||
| 988 | +{ | ||
| 989 | +} | ||
| 990 | + | ||
| 991 | +int SrsSetWindowAckSizePacket::decode(SrsStream* stream) | ||
| 992 | +{ | ||
| 993 | + int ret = ERROR_SUCCESS; | ||
| 994 | + | ||
| 995 | + if ((ret = super::decode(stream)) != ERROR_SUCCESS) { | ||
| 996 | + return ret; | ||
| 997 | + } | ||
| 998 | + | ||
| 999 | + if (!stream->require(4)) { | ||
| 1000 | + ret = ERROR_RTMP_MESSAGE_DECODE; | ||
| 1001 | + srs_error("set window ack size failed. ret=%d", ret); | ||
| 1002 | + return ret; | ||
| 1003 | + } | ||
| 1004 | + | ||
| 1005 | + ackowledgement_window_size = stream->read_4bytes(); | ||
| 1006 | + srs_info("decode window ack size success. ack_size=%d", ackowledgement_window_size); | ||
| 1007 | + | ||
| 1008 | + return ret; | ||
| 1009 | +} | ||
| 1010 | + | ||
| 1011 | +int SrsSetWindowAckSizePacket::get_perfer_cid() | ||
| 1012 | +{ | ||
| 1013 | + return RTMP_CID_ProtocolControl; | ||
| 1014 | +} | ||
| 1015 | + |
| @@ -47,18 +47,42 @@ class SrsChunkStream; | @@ -47,18 +47,42 @@ class SrsChunkStream; | ||
| 47 | class SrsAmf0Object; | 47 | class SrsAmf0Object; |
| 48 | 48 | ||
| 49 | /** | 49 | /** |
| 50 | +* max rtmp header size: | ||
| 51 | +* 1bytes basic header, | ||
| 52 | +* 11bytes message header, | ||
| 53 | +* 4bytes timestamp header, | ||
| 54 | +* that is, 1+11+4=16bytes. | ||
| 55 | +*/ | ||
| 56 | +#define RTMP_MAX_FMT0_HEADER_SIZE 16 | ||
| 57 | +/** | ||
| 58 | +* max rtmp header size: | ||
| 59 | +* 1bytes basic header, | ||
| 60 | +* 4bytes timestamp header, | ||
| 61 | +* that is, 1+4=5bytes. | ||
| 62 | +*/ | ||
| 63 | +#define RTMP_MAX_FMT3_HEADER_SIZE 5 | ||
| 64 | + | ||
| 65 | +/** | ||
| 50 | * the protocol provides the rtmp-message-protocol services, | 66 | * the protocol provides the rtmp-message-protocol services, |
| 51 | * to recv RTMP message from RTMP chunk stream, | 67 | * to recv RTMP message from RTMP chunk stream, |
| 52 | * and to send out RTMP message over RTMP chunk stream. | 68 | * and to send out RTMP message over RTMP chunk stream. |
| 53 | */ | 69 | */ |
| 54 | class SrsProtocol | 70 | class SrsProtocol |
| 55 | { | 71 | { |
| 72 | +// peer in/out | ||
| 56 | private: | 73 | private: |
| 57 | - std::map<int, SrsChunkStream*> chunk_streams; | ||
| 58 | st_netfd_t stfd; | 74 | st_netfd_t stfd; |
| 59 | - SrsBuffer* buffer; | ||
| 60 | SrsSocket* skt; | 75 | SrsSocket* skt; |
| 76 | + char* pp; | ||
| 77 | +// peer in | ||
| 78 | +private: | ||
| 79 | + std::map<int, SrsChunkStream*> chunk_streams; | ||
| 80 | + SrsBuffer* buffer; | ||
| 61 | int32_t in_chunk_size; | 81 | int32_t in_chunk_size; |
| 82 | +// peer out | ||
| 83 | +private: | ||
| 84 | + char out_header_fmt0[RTMP_MAX_FMT0_HEADER_SIZE]; | ||
| 85 | + char out_header_fmt3[RTMP_MAX_FMT3_HEADER_SIZE]; | ||
| 62 | int32_t out_chunk_size; | 86 | int32_t out_chunk_size; |
| 63 | public: | 87 | public: |
| 64 | SrsProtocol(st_netfd_t client_stfd); | 88 | SrsProtocol(st_netfd_t client_stfd); |
| @@ -72,10 +96,38 @@ public: | @@ -72,10 +96,38 @@ public: | ||
| 72 | * @remark, only when success, user can use and must free the pmsg. | 96 | * @remark, only when success, user can use and must free the pmsg. |
| 73 | */ | 97 | */ |
| 74 | virtual int recv_message(SrsMessage** pmsg); | 98 | virtual int recv_message(SrsMessage** pmsg); |
| 99 | + /** | ||
| 100 | + * send out message with encoded payload to peer. | ||
| 101 | + * use the message encode method to encode to payload, | ||
| 102 | + * then sendout over socket. | ||
| 103 | + * @msg this method will free it whatever return value. | ||
| 104 | + */ | ||
| 105 | + virtual int send_message(SrsMessage* msg); | ||
| 75 | private: | 106 | private: |
| 107 | + /** | ||
| 108 | + * try to recv interlaced message from peer, | ||
| 109 | + * return error if error occur and nerver set the pmsg, | ||
| 110 | + * return success and pmsg set to NULL if no entire message got, | ||
| 111 | + * return success and pmsg set to entire message if got one. | ||
| 112 | + */ | ||
| 76 | virtual int recv_interlaced_message(SrsMessage** pmsg); | 113 | virtual int recv_interlaced_message(SrsMessage** pmsg); |
| 77 | - virtual int read_basic_header(char& fmt, int& cid, int& size); | 114 | + /** |
| 115 | + * read the chunk basic header(fmt, cid) from chunk stream. | ||
| 116 | + * user can discovery a SrsChunkStream by cid. | ||
| 117 | + * @bh_size return the chunk basic header size, to remove the used bytes when finished. | ||
| 118 | + */ | ||
| 119 | + virtual int read_basic_header(char& fmt, int& cid, int& bh_size); | ||
| 120 | + /** | ||
| 121 | + * read the chunk message header(timestamp, payload_length, message_type, stream_id) | ||
| 122 | + * from chunk stream and save to SrsChunkStream. | ||
| 123 | + * @mh_size return the chunk message header size, to remove the used bytes when finished. | ||
| 124 | + */ | ||
| 78 | virtual int read_message_header(SrsChunkStream* chunk, char fmt, int bh_size, int& mh_size); | 125 | virtual int read_message_header(SrsChunkStream* chunk, char fmt, int bh_size, int& mh_size); |
| 126 | + /** | ||
| 127 | + * read the chunk payload, remove the used bytes in buffer, | ||
| 128 | + * if got entire message, set the pmsg. | ||
| 129 | + * @payload_size read size in this roundtrip, generally a chunk size or left message size. | ||
| 130 | + */ | ||
| 79 | virtual int read_message_payload(SrsChunkStream* chunk, int bh_size, int mh_size, int& payload_size, SrsMessage** pmsg); | 131 | virtual int read_message_payload(SrsChunkStream* chunk, int bh_size, int mh_size, int& payload_size, SrsMessage** pmsg); |
| 80 | }; | 132 | }; |
| 81 | 133 | ||
| @@ -164,19 +216,35 @@ public: | @@ -164,19 +216,35 @@ public: | ||
| 164 | // decoded message payload. | 216 | // decoded message payload. |
| 165 | private: | 217 | private: |
| 166 | SrsStream* stream; | 218 | SrsStream* stream; |
| 167 | - SrsPacket* decoded_payload; | 219 | + SrsPacket* packet; |
| 220 | +public: | ||
| 221 | + SrsMessage(); | ||
| 222 | + virtual ~SrsMessage(); | ||
| 168 | public: | 223 | public: |
| 169 | /** | 224 | /** |
| 170 | - * get the decoded packet, | ||
| 171 | - * not all packets need to decode, for video/audio packet, | ||
| 172 | - * passthrough to peer are ok. | ||
| 173 | - * @remark, user must invoke decode_packet first. | 225 | + * decode packet from message payload. |
| 174 | */ | 226 | */ |
| 175 | - virtual SrsPacket* get_packet(); | ||
| 176 | virtual int decode_packet(); | 227 | virtual int decode_packet(); |
| 228 | + /** | ||
| 229 | + * get the decoded packet which decoded by decode_packet(). | ||
| 230 | + * @remark, user never free the pkt, the message will auto free it. | ||
| 231 | + */ | ||
| 232 | + virtual SrsPacket* get_packet(); | ||
| 177 | public: | 233 | public: |
| 178 | - SrsMessage(); | ||
| 179 | - virtual ~SrsMessage(); | 234 | + /** |
| 235 | + * get the perfered cid(chunk stream id) which sendout over. | ||
| 236 | + */ | ||
| 237 | + virtual int get_perfer_cid(); | ||
| 238 | + /** | ||
| 239 | + * set the encoded packet to encode_packet() to payload. | ||
| 240 | + * @remark, user never free the pkt, the message will auto free it. | ||
| 241 | + */ | ||
| 242 | + virtual void set_packet(SrsPacket* pkt); | ||
| 243 | + /** | ||
| 244 | + * encode the packet to message payload bytes. | ||
| 245 | + * @remark there exists empty packet, so maybe the payload is NULL. | ||
| 246 | + */ | ||
| 247 | + virtual int encode_packet(); | ||
| 180 | }; | 248 | }; |
| 181 | 249 | ||
| 182 | /** | 250 | /** |
| @@ -189,8 +257,15 @@ public: | @@ -189,8 +257,15 @@ public: | ||
| 189 | virtual ~SrsPacket(); | 257 | virtual ~SrsPacket(); |
| 190 | public: | 258 | public: |
| 191 | virtual int decode(SrsStream* stream); | 259 | virtual int decode(SrsStream* stream); |
| 260 | +public: | ||
| 261 | + virtual int get_perfer_cid(); | ||
| 192 | }; | 262 | }; |
| 193 | 263 | ||
| 264 | +/** | ||
| 265 | +* 4.1.1. connect | ||
| 266 | +* The client sends the connect command to the server to request | ||
| 267 | +* connection to a server application instance. | ||
| 268 | +*/ | ||
| 194 | class SrsConnectAppPacket : public SrsPacket | 269 | class SrsConnectAppPacket : public SrsPacket |
| 195 | { | 270 | { |
| 196 | private: | 271 | private: |
| @@ -207,6 +282,26 @@ public: | @@ -207,6 +282,26 @@ public: | ||
| 207 | }; | 282 | }; |
| 208 | 283 | ||
| 209 | /** | 284 | /** |
| 285 | +* 5.5. Window Acknowledgement Size (5) | ||
| 286 | +* The client or the server sends this message to inform the peer which | ||
| 287 | +* window size to use when sending acknowledgment. | ||
| 288 | +*/ | ||
| 289 | +class SrsSetWindowAckSizePacket : public SrsPacket | ||
| 290 | +{ | ||
| 291 | +private: | ||
| 292 | + typedef SrsPacket super; | ||
| 293 | +public: | ||
| 294 | + int32_t ackowledgement_window_size; | ||
| 295 | +public: | ||
| 296 | + SrsSetWindowAckSizePacket(); | ||
| 297 | + virtual ~SrsSetWindowAckSizePacket(); | ||
| 298 | +public: | ||
| 299 | + virtual int decode(SrsStream* stream); | ||
| 300 | +public: | ||
| 301 | + virtual int get_perfer_cid(); | ||
| 302 | +}; | ||
| 303 | + | ||
| 304 | +/** | ||
| 210 | * expect a specified message, drop others util got specified one. | 305 | * expect a specified message, drop others util got specified one. |
| 211 | * @pmsg, user must free it. NULL if not success. | 306 | * @pmsg, user must free it. NULL if not success. |
| 212 | * @ppacket, store in the pmsg, user must never free it. NULL if not success. | 307 | * @ppacket, store in the pmsg, user must never free it. NULL if not success. |
| @@ -30,6 +30,47 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | @@ -30,6 +30,47 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| 30 | #include <srs_core_auto_free.hpp> | 30 | #include <srs_core_auto_free.hpp> |
| 31 | #include <srs_core_amf0.hpp> | 31 | #include <srs_core_amf0.hpp> |
| 32 | 32 | ||
| 33 | +int SrsRequest::discovery_app() | ||
| 34 | +{ | ||
| 35 | + int ret = ERROR_SUCCESS; | ||
| 36 | + | ||
| 37 | + size_t pos = std::string::npos; | ||
| 38 | + std::string url = tcUrl; | ||
| 39 | + | ||
| 40 | + if ((pos = url.find("://")) != std::string::npos) { | ||
| 41 | + schema = url.substr(0, pos); | ||
| 42 | + url = url.substr(schema.length() + 3); | ||
| 43 | + srs_verbose("discovery schema=%s", schema.c_str()); | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + if ((pos = url.find("/")) != std::string::npos) { | ||
| 47 | + vhost = url.substr(0, pos); | ||
| 48 | + url = url.substr(vhost.length() + 1); | ||
| 49 | + srs_verbose("discovery vhost=%s", vhost.c_str()); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + port = "1935"; | ||
| 53 | + if ((pos = vhost.find(":")) != std::string::npos) { | ||
| 54 | + port = vhost.substr(pos + 1); | ||
| 55 | + vhost = vhost.substr(0, pos); | ||
| 56 | + srs_verbose("discovery vhost=%s, port=%s", vhost.c_str(), port.c_str()); | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + app = url; | ||
| 60 | + srs_info("discovery app success. schema=%s, vhost=%s, port=%s, app=%s", | ||
| 61 | + schema.c_str(), vhost.c_str(), port.c_str(), app.c_str()); | ||
| 62 | + | ||
| 63 | + if (schema.empty() || vhost.empty() || port.empty() || app.empty()) { | ||
| 64 | + ret = ERROR_RTMP_REQ_TCURL; | ||
| 65 | + srs_error("discovery tcUrl failed. " | ||
| 66 | + "tcUrl=%s, schema=%s, vhost=%s, port=%s, app=%s, ret=%d", | ||
| 67 | + tcUrl.c_str(), schema.c_str(), vhost.c_str(), port.c_str(), app.c_str(), ret); | ||
| 68 | + return ret; | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + return ret; | ||
| 72 | +} | ||
| 73 | + | ||
| 33 | SrsRtmp::SrsRtmp(st_netfd_t client_stfd) | 74 | SrsRtmp::SrsRtmp(st_netfd_t client_stfd) |
| 34 | { | 75 | { |
| 35 | protocol = new SrsProtocol(client_stfd); | 76 | protocol = new SrsProtocol(client_stfd); |
| @@ -119,6 +160,26 @@ int SrsRtmp::connect_app(SrsRequest* req) | @@ -119,6 +160,26 @@ int SrsRtmp::connect_app(SrsRequest* req) | ||
| 119 | if ((prop = pkt->command_object->ensure_property_string("swfUrl")) != NULL) { | 160 | if ((prop = pkt->command_object->ensure_property_string("swfUrl")) != NULL) { |
| 120 | req->swfUrl = srs_amf0_convert<SrsAmf0String>(prop)->value; | 161 | req->swfUrl = srs_amf0_convert<SrsAmf0String>(prop)->value; |
| 121 | } | 162 | } |
| 163 | + srs_info("get connect app message params success."); | ||
| 164 | + | ||
| 165 | + return req->discovery_app(); | ||
| 166 | +} | ||
| 167 | + | ||
| 168 | +int SrsRtmp::set_window_ack_size(int ack_size) | ||
| 169 | +{ | ||
| 170 | + int ret = ERROR_SUCCESS; | ||
| 171 | + | ||
| 172 | + SrsMessage* msg = new SrsMessage(); | ||
| 173 | + SrsSetWindowAckSizePacket* pkt = new SrsSetWindowAckSizePacket(); | ||
| 174 | + | ||
| 175 | + pkt->ackowledgement_window_size = ack_size; | ||
| 176 | + msg->set_packet(pkt); | ||
| 177 | + | ||
| 178 | + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { | ||
| 179 | + srs_error("send ack size message failed. ret=%d", ret); | ||
| 180 | + return ret; | ||
| 181 | + } | ||
| 182 | + srs_info("send ack size message success. ack_size=%d", ack_size); | ||
| 122 | 183 | ||
| 123 | return ret; | 184 | return ret; |
| 124 | } | 185 | } |
| @@ -50,6 +50,11 @@ struct SrsRequest | @@ -50,6 +50,11 @@ struct SrsRequest | ||
| 50 | std::string port; | 50 | std::string port; |
| 51 | std::string app; | 51 | std::string app; |
| 52 | std::string stream; | 52 | std::string stream; |
| 53 | + | ||
| 54 | + /** | ||
| 55 | + * disconvery vhost/app from tcUrl. | ||
| 56 | + */ | ||
| 57 | + virtual int discovery_app(); | ||
| 53 | }; | 58 | }; |
| 54 | 59 | ||
| 55 | /** | 60 | /** |
| @@ -68,6 +73,7 @@ public: | @@ -68,6 +73,7 @@ public: | ||
| 68 | public: | 73 | public: |
| 69 | virtual int handshake(); | 74 | virtual int handshake(); |
| 70 | virtual int connect_app(SrsRequest* req); | 75 | virtual int connect_app(SrsRequest* req); |
| 76 | + virtual int set_window_ack_size(int ack_size); | ||
| 71 | }; | 77 | }; |
| 72 | 78 | ||
| 73 | #endif | 79 | #endif |
| @@ -85,3 +85,16 @@ int SrsSocket::write(const void* buf, size_t size, ssize_t* nwrite) | @@ -85,3 +85,16 @@ int SrsSocket::write(const void* buf, size_t size, ssize_t* nwrite) | ||
| 85 | return ret; | 85 | return ret; |
| 86 | } | 86 | } |
| 87 | 87 | ||
| 88 | +int SrsSocket::writev(const iovec *iov, int iov_size, ssize_t* nwrite) | ||
| 89 | +{ | ||
| 90 | + int ret = ERROR_SUCCESS; | ||
| 91 | + | ||
| 92 | + *nwrite = st_writev(stfd, iov, iov_size, ST_UTIME_NO_TIMEOUT); | ||
| 93 | + | ||
| 94 | + if (*nwrite <= 0) { | ||
| 95 | + ret = ERROR_SOCKET_WRITE; | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + return ret; | ||
| 99 | +} | ||
| 100 | + |
| @@ -47,6 +47,7 @@ public: | @@ -47,6 +47,7 @@ public: | ||
| 47 | virtual int read(const void* buf, size_t size, ssize_t* nread); | 47 | virtual int read(const void* buf, size_t size, ssize_t* nread); |
| 48 | virtual int read_fully(const void* buf, size_t size, ssize_t* nread); | 48 | virtual int read_fully(const void* buf, size_t size, ssize_t* nread); |
| 49 | virtual int write(const void* buf, size_t size, ssize_t* nwrite); | 49 | virtual int write(const void* buf, size_t size, ssize_t* nwrite); |
| 50 | + virtual int writev(const iovec *iov, int iov_size, ssize_t* nwrite); | ||
| 50 | }; | 51 | }; |
| 51 | 52 | ||
| 52 | #endif | 53 | #endif |
| @@ -97,6 +97,20 @@ int16_t SrsStream::read_2bytes() | @@ -97,6 +97,20 @@ int16_t SrsStream::read_2bytes() | ||
| 97 | return value; | 97 | return value; |
| 98 | } | 98 | } |
| 99 | 99 | ||
| 100 | +int32_t SrsStream::read_4bytes() | ||
| 101 | +{ | ||
| 102 | + srs_assert(require(4)); | ||
| 103 | + | ||
| 104 | + int32_t value; | ||
| 105 | + pp = (char*)&value; | ||
| 106 | + pp[3] = *p++; | ||
| 107 | + pp[2] = *p++; | ||
| 108 | + pp[1] = *p++; | ||
| 109 | + pp[0] = *p++; | ||
| 110 | + | ||
| 111 | + return value; | ||
| 112 | +} | ||
| 113 | + | ||
| 100 | int64_t SrsStream::read_8bytes() | 114 | int64_t SrsStream::read_8bytes() |
| 101 | { | 115 | { |
| 102 | srs_assert(require(8)); | 116 | srs_assert(require(8)); |
| @@ -80,6 +80,10 @@ public: | @@ -80,6 +80,10 @@ public: | ||
| 80 | */ | 80 | */ |
| 81 | virtual int16_t read_2bytes(); | 81 | virtual int16_t read_2bytes(); |
| 82 | /** | 82 | /** |
| 83 | + * get 4bytes int from stream. | ||
| 84 | + */ | ||
| 85 | + virtual int32_t read_4bytes(); | ||
| 86 | + /** | ||
| 83 | * get 8bytes int from stream. | 87 | * get 8bytes int from stream. |
| 84 | */ | 88 | */ |
| 85 | virtual int64_t read_8bytes(); | 89 | virtual int64_t read_8bytes(); |
-
请 注册 或 登录 后发表评论