1 /* 2 Copyright (c) 2009-2012 Roger Light <roger@atchoo.org> 3 All rights reserved. 4 5 Redistribution and use in source and binary forms, with or without 6 modification, are permitted provided that the following conditions are met: 7 8 1. Redistributions of source code must retain the above copyright notice, 9 this list of conditions and the following disclaimer. 10 2. Redistributions in binary form must reproduce the above copyright 11 notice, this list of conditions and the following disclaimer in the 12 documentation and/or other materials provided with the distribution. 13 3. Neither the name of mosquitto nor the names of its 14 contributors may be used to endorse or promote products derived from 15 this software without specific prior written permission. 16 17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 31 #include <errno.h> 32 #include <fcntl.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #ifndef WIN32 37 #include <unistd.h> 38 #else 39 #include <process.h> 40 #include <winsock2.h> 41 #define snprintf sprintf_s 42 #endif 43 44 #include <mosquitto.h> 45 46 #define MSGMODE_NONE 0 47 #define MSGMODE_CMD 1 48 #define MSGMODE_STDIN_LINE 2 49 #define MSGMODE_STDIN_FILE 3 50 #define MSGMODE_FILE 4 51 #define MSGMODE_NULL 5 52 53 #define STATUS_CONNECTING 0 54 #define STATUS_CONNACK_RECVD 1 55 56 static char *topic = NULL; 57 static char *message = NULL; 58 static long msglen = 0; 59 static int qos = 0; 60 static int retain = 0; 61 static int mode = MSGMODE_NONE; //消息类型,默认是MSGMODE_NONE 62 static int status = STATUS_CONNECTING; 63 static uint16_t mid_sent = 0; 64 static bool connected = true; 65 static char *username = NULL; 66 static char *password = NULL; 67 static bool disconnect_sent = false; 68 static bool quiet = false; 69 70 void my_connect_callback(void *obj, int result) //obj:<mosquitto_new>中提供的用户数据;result:0-成功,1-不可接受的协议版本,2-标示符拒绝,3-broker不可达。。。 71 { 72 //mode是MSGMODE_STDIN_FILE和MSGMODE_NULL时发布消息 73 struct mosquitto *mosq = obj; 74 int rc = MOSQ_ERR_SUCCESS; 75 76 if(!result){ 77 switch(mode){ 78 case MSGMODE_CMD: //-m 79 case MSGMODE_FILE: //-f 80 case MSGMODE_STDIN_FILE: //-s 81 rc = mosquitto_publish(mosq, &mid_sent, topic, msglen, (uint8_t *)message, qos, retain); 82 break; 83 case MSGMODE_NULL: //-n 84 rc = mosquitto_publish(mosq, &mid_sent, topic, 0, NULL, qos, retain); 85 break; 86 case MSGMODE_STDIN_LINE: //-l 87 status = STATUS_CONNACK_RECVD; 88 break; 89 } 90 if(rc){ 91 if(!quiet){ 92 switch(rc){ 93 case MOSQ_ERR_INVAL: 94 fprintf(stderr, "Error: Invalid input. Does your topic contain '+' or '#'?\n"); 95 break; 96 case MOSQ_ERR_NOMEM: 97 fprintf(stderr, "Error: Out of memory when trying to publish message.\n"); 98 break; 99 case MOSQ_ERR_NO_CONN: 100 fprintf(stderr, "Error: Client not connected when trying to publish.\n"); 101 break; 102 case MOSQ_ERR_PROTOCOL: 103 fprintf(stderr, "Error: Protocol error when communicating with broker.\n"); 104 break; 105 case MOSQ_ERR_PAYLOAD_SIZE: 106 fprintf(stderr, "Error: Message payload is too large.\n"); 107 break; 108 } 109 } 110 mosquitto_disconnect(mosq); 111 } 112 }else{ 113 switch(result){ 114 case 1: 115 if(!quiet) fprintf(stderr, "Connection Refused: unacceptable protocol version\n"); 116 break; 117 case 2: 118 if(!quiet) fprintf(stderr, "Connection Refused: identifier rejected\n"); 119 break; 120 case 3: 121 if(!quiet) fprintf(stderr, "Connection Refused: broker unavailable\n"); 122 break; 123 case 4: 124 if(!quiet) fprintf(stderr, "Connection Refused: bad user name or password\n"); 125 break; 126 case 5: 127 if(!quiet) fprintf(stderr, "Connection Refused: not authorised\n"); 128 break; 129 default: 130 if(!quiet) fprintf(stderr, "Connection Refused: unknown reason\n"); 131 break; 132 } 133 } 134 } 135 136 void my_disconnect_callback(void *obj) 137 { 138 //连接状态conneted设为false 139 connected = false; 140 } 141 142 void my_publish_callback(void *obj, uint16_t mid) 143 { 144 //mode不是MSGMODE_STDIN_LINE(-l,从标准输入读取消息)且disconnect_sent是false(未断开连接)时,与broker断开连接 145 struct mosquitto *mosq = obj; 146 147 if(mode != MSGMODE_STDIN_LINE && disconnect_sent == false){ 148 mosquitto_disconnect(mosq); 149 disconnect_sent = true; 150 } 151 } 152 153 int load_stdin(void) 154 { 155 //-s,从标准输入按文件读取,复制到message,直到遇到文件终止符EOF(ctrl+D) 156 long pos = 0, rlen; 157 char buf[1024]; 158 159 mode = MSGMODE_STDIN_FILE; 160 161 while(!feof(stdin)){ 162 rlen = fread(buf, 1, 1024, stdin); 163 message = realloc(message, pos+rlen); 164 if(!message){ 165 if(!quiet) fprintf(stderr, "Error: Out of memory.\n"); 166 return 1; 167 } 168 memcpy(&(message[pos]), buf, rlen); 169 pos += rlen; 170 } 171 msglen = pos; 172 173 if(!msglen){ 174 if(!quiet) fprintf(stderr, "Error: Zero length input.\n"); 175 return 1; 176 } 177 178 return 0; 179 } 180 181 int load_file(const char *filename) 182 { 183 long pos, rlen; 184 FILE *fptr = NULL; 185 186 fptr = fopen(filename, "rb"); //只读打开一个二进制文件 187 if(!fptr){ 188 if(!quiet) fprintf(stderr, "Error: Unable to open file \"%s\".\n", filename); 189 return 1; 190 } 191 mode = MSGMODE_FILE; 192 fseek(fptr, 0, SEEK_END); 193 msglen = ftell(fptr); //获取文件当前读写位置偏移字节数,即文本长度 194 if(msglen > 268435455){ //不超过255MB 195 fclose(fptr); 196 if(!quiet) fprintf(stderr, "Error: File \"%s\" is too large (>268,435,455 bytes).\n", filename); 197 return 1; 198 } 199 if(msglen == 0){ 200 fclose(fptr); 201 if(!quiet) fprintf(stderr, "Error: File \"%s\" is empty.\n", filename); 202 return 1; 203 } 204 fseek(fptr, 0, SEEK_SET); 205 message = malloc(msglen); 206 if(!message){ 207 fclose(fptr); 208 if(!quiet) fprintf(stderr, "Error: Out of memory.\n"); 209 return 1; 210 } 211 pos = 0; 212 //读取文件内容至message中 213 while(pos < msglen){ 214 rlen = fread(&(message[pos]), sizeof(char), msglen-pos, fptr); 215 pos += rlen; 216 } 217 fclose(fptr); 218 return 0; 219 } 220 221 void print_usage(void) 222 { 223 printf("mosquitto_pub is a simple mqtt client that will publish a message on a single topic and exit.\n\n"); 224 printf("Usage: mosquitto_pub [-h host] [-p port] [-q qos] [-r] {-f file | -l | -n | -m message} -t topic\n"); 225 printf(" [-i id] [-I id_prefix]\n"); 226 printf(" [-d] [--quiet]\n"); 227 printf(" [-u username [-P password]]\n"); 228 printf(" [--will-topic [--will-payload payload] [--will-qos qos] [--will-retain]]\n\n"); 229 printf(" -d : enable debug messages.\n"); 230 printf(" -f : send the contents of a file as the message.\n"); 231 printf(" -h : mqtt host to connect to. Defaults to localhost.\n"); 232 printf(" -i : id to use for this client. Defaults to mosquitto_pub_ appended with the process id.\n"); 233 printf(" -I : define the client id as id_prefix appended with the process id. Useful for when the\n"); 234 printf(" broker is using the clientid_prefixes option.\n"); 235 printf(" -l : read messages from stdin, sending a separate message for each line.\n"); 236 printf(" -m : message payload to send.\n"); 237 printf(" -n : send a null (zero length) message.\n"); 238 printf(" -p : network port to connect to. Defaults to 1883.\n"); 239 printf(" -q : quality of service level to use for all messages. Defaults to 0.\n"); 240 printf(" -r : message should be retained.\n"); 241 printf(" -s : read message from stdin, sending the entire input as a message.\n"); 242 printf(" -t : mqtt topic to publish to.\n"); 243 printf(" -u : provide a username (requires MQTT 3.1 broker)\n"); 244 printf(" -P : provide a password (requires MQTT 3.1 broker)\n"); 245 printf(" --quiet : don't print error messages.\n"); 246 printf(" --will-payload : payload for the client Will, which is sent by the broker in case of\n"); 247 printf(" unexpected disconnection. If not given and will-topic is set, a zero\n"); 248 printf(" length message will be sent.\n"); 249 printf(" --will-qos : QoS level for the client Will.\n"); 250 printf(" --will-retain : if given, make the client Will retained.\n"); 251 printf(" --will-topic : the topic on which to publish the client Will.\n"); 252 printf("\nSee http://mosquitto.org/ for more information.\n\n"); 253 } 254 255 int main(int argc, char *argv[]) 256 { 257 char *id = NULL; //client ID 258 char *id_prefix = NULL; //client ID 前缀 259 int i; 260 char *host = "localhost"; //server IP,默认是localhost 261 int port = 1883; //server PORT,默认是1883 262 int keepalive = 60; // 263 int opt; 264 char buf[1024]; 265 bool debug = false; //是否打印debug消息 266 struct mosquitto *mosq = NULL; 267 int rc; 268 int rc2; 269 char hostname[21]; 270 char err[1024]; 271 272 uint8_t *will_payload = NULL; 273 long will_payloadlen = 0; 274 int will_qos = 0; 275 bool will_retain = false; 276 char *will_topic = NULL; 277 278 //获取命令参数 279 for(i=1; i<argc; i++){ 280 if(!strcmp(argv[i], "-p") || !strcmp(argv[i], "--port")){ //端口号 281 if(i==argc-1){ 282 fprintf(stderr, "Error: -p argument given but no port specified.\n\n"); 283 print_usage(); 284 return 1; 285 }else{ 286 port = atoi(argv[i+1]); 287 if(port<1 || port>65535){ 288 fprintf(stderr, "Error: Invalid port given: %d\n", port); 289 print_usage(); 290 return 1; 291 } 292 } 293 i++; 294 }else if(!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")){ //debug选项 295 debug = true; 296 }else if(!strcmp(argv[i], "-f") || !strcmp(argv[i], "--file")){ //-f,读取文件 297 if(mode != MSGMODE_NONE){ 298 fprintf(stderr, "Error: Only one type of message can be sent at once.\n\n"); 299 print_usage(); 300 return 1; 301 }else if(i==argc-1){ 302 fprintf(stderr, "Error: -f argument given but no file specified.\n\n"); 303 print_usage(); 304 return 1; 305 }else{ 306 if(load_file(argv[i+1])) return 1; 307 } 308 i++; 309 }else if(!strcmp(argv[i], "-h") || !strcmp(argv[i], "--host")){ //-h,server IP 310 if(i==argc-1){ 311 fprintf(stderr, "Error: -h argument given but no host specified.\n\n"); 312 print_usage(); 313 return 1; 314 }else{ 315 host = argv[i+1]; 316 } 317 i++; 318 }else if(!strcmp(argv[i], "-i") || !strcmp(argv[i], "--id")){ //-i, client id 319 if(id_prefix){ 320 fprintf(stderr, "Error: -i and -I argument cannot be used together.\n\n"); 321 print_usage(); 322 return 1; 323 } 324 if(i==argc-1){ 325 fprintf(stderr, "Error: -i argument given but no id specified.\n\n"); 326 print_usage(); 327 return 1; 328 }else{ 329 id = argv[i+1]; 330 } 331 i++; 332 }else if(!strcmp(argv[i], "-I") || !strcmp(argv[i], "--id-prefix")){ //-I,client id前缀 333 if(id){ 334 fprintf(stderr, "Error: -i and -I argument cannot be used together.\n\n"); 335 print_usage(); 336 return 1; 337 } 338 if(i==argc-1){ 339 fprintf(stderr, "Error: -I argument given but no id prefix specified.\n\n"); 340 print_usage(); 341 return 1; 342 }else{ 343 id_prefix = argv[i+1]; 344 } 345 i++; 346 }else if(!strcmp(argv[i], "-l") || !strcmp(argv[i], "--stdin-line")){ //-l,从标准输入按行读取发送消息,一行发送一条消息 347 if(mode != MSGMODE_NONE){ 348 fprintf(stderr, "Error: Only one type of message can be sent at once.\n\n"); 349 print_usage(); 350 return 1; 351 }else{ 352 mode = MSGMODE_STDIN_LINE; 353 #ifndef WIN32 354 opt = fcntl(fileno(stdin), F_GETFL, 0); //获取文件的flags 355 if(opt == -1 || fcntl(fileno(stdin), F_SETFL, opt | O_NONBLOCK) == -1){ //设置文件flags非阻塞O_NONBLOCK 356 fprintf(stderr, "Error: Unable to set stdin to non-blocking.\n"); 357 return 1; 358 } 359 #endif 360 } 361 }else if(!strcmp(argv[i], "-m") || !strcmp(argv[i], "--message")){ //-m,从cmd发送消息,消息内容跟在-m之后 362 if(mode != MSGMODE_NONE){ 363 fprintf(stderr, "Error: Only one type of message can be sent at once.\n\n"); 364 print_usage(); 365 return 1; 366 }else if(i==argc-1){ 367 fprintf(stderr, "Error: -m argument given but no message specified.\n\n"); 368 print_usage(); 369 return 1; 370 }else{ 371 message = argv[i+1]; 372 msglen = strlen(message); 373 mode = MSGMODE_CMD; 374 } 375 i++; 376 }else if(!strcmp(argv[i], "-n") || !strcmp(argv[i], "--null-message")){ //-n,发送空消息 377 if(mode != MSGMODE_NONE){ 378 fprintf(stderr, "Error: Only one type of message can be sent at once.\n\n"); 379 print_usage(); 380 return 1; 381 }else{ 382 mode = MSGMODE_NULL; 383 } 384 }else if(!strcmp(argv[i], "-q") || !strcmp(argv[i], "--qos")){ //-q,服务质量,0,1,或2 385 if(i==argc-1){ 386 fprintf(stderr, "Error: -q argument given but no QoS specified.\n\n"); 387 print_usage(); 388 return 1; 389 }else{ 390 qos = atoi(argv[i+1]); 391 if(qos<0 || qos>2){ 392 fprintf(stderr, "Error: Invalid QoS given: %d\n", qos); 393 print_usage(); 394 return 1; 395 } 396 } 397 i++; 398 }else if(!strcmp(argv[i], "--quiet")){ //-quiet,什么都不打印出来 399 quiet = true; 400 }else if(!strcmp(argv[i], "-r") || !strcmp(argv[i], "--retain")){ //-r,retained消息会在服务器上保留,但只保留带-r标志的最后一条消息 401 retain = 1; 402 }else if(!strcmp(argv[i], "-s") || !strcmp(argv[i], "--stdin-file")){ //-s,从标准输入按文件读取,到EOF时把所有输入内容用一条消息发送 403 if(mode != MSGMODE_NONE){ 404 fprintf(stderr, "Error: Only one type of message can be sent at once.\n\n"); 405 print_usage(); 406 return 1; 407 }else{ 408 if(load_stdin()) return 1; 409 } 410 }else if(!strcmp(argv[i], "-t") || !strcmp(argv[i], "--topic")){ //-t,消息主题,只能发布一个主题 411 if(i==argc-1){ 412 fprintf(stderr, "Error: -t argument given but no topic specified.\n\n"); 413 print_usage(); 414 return 1; 415 }else{ 416 topic = argv[i+1]; 417 } 418 i++; 419 }else if(!strcmp(argv[i], "-u") || !strcmp(argv[i], "--username")){ //-u,username 420 if(i==argc-1){ 421 fprintf(stderr, "Error: -u argument given but no username specified.\n\n"); 422 print_usage(); 423 return 1; 424 }else{ 425 username = argv[i+1]; 426 } 427 i++; 428 }else if(!strcmp(argv[i], "-P") || !strcmp(argv[i], "--pw")){ //-P,password 429 if(i==argc-1){ 430 fprintf(stderr, "Error: -P argument given but no password specified.\n\n"); 431 print_usage(); 432 return 1; 433 }else{ 434 password = argv[i+1]; 435 } 436 i++; 437 }else if(!strcmp(argv[i], "--will-payload")){ 438 if(i==argc-1){ 439 fprintf(stderr, "Error: --will-payload argument given but no will payload specified.\n\n"); 440 print_usage(); 441 return 1; 442 }else{ 443 will_payload = (uint8_t *)argv[i+1]; 444 will_payloadlen = strlen((char *)will_payload); 445 } 446 i++; 447 }else if(!strcmp(argv[i], "--will-qos")){ 448 if(i==argc-1){ 449 fprintf(stderr, "Error: --will-qos argument given but no will QoS specified.\n\n"); 450 print_usage(); 451 return 1; 452 }else{ 453 will_qos = atoi(argv[i+1]); 454 if(will_qos < 0 || will_qos > 2){ 455 fprintf(stderr, "Error: Invalid will QoS %d.\n\n", will_qos); 456 return 1; 457 } 458 } 459 i++; 460 }else if(!strcmp(argv[i], "--will-retain")){ 461 will_retain = true; 462 }else if(!strcmp(argv[i], "--will-topic")){ 463 if(i==argc-1){ 464 fprintf(stderr, "Error: --will-topic argument given but no will topic specified.\n\n"); 465 print_usage(); 466 return 1; 467 }else{ 468 will_topic = argv[i+1]; 469 } 470 i++; 471 }else{ 472 fprintf(stderr, "Error: Unknown option '%s'.\n",argv[i]); 473 print_usage(); 474 return 1; 475 } 476 } 477 if(id_prefix){ //有设定client ID前缀 478 id = malloc(strlen(id_prefix)+10); 479 if(!id){ 480 if(!quiet) fprintf(stderr, "Error: Out of memory.\n"); 481 return 1; 482 } 483 snprintf(id, strlen(id_prefix)+10, "%s%d", id_prefix, getpid()); 484 }else if(!id){ //没有前缀,也没有设定client ID 485 id = malloc(30); 486 if(!id){ 487 if(!quiet) fprintf(stderr, "Error: Out of memory.\n"); 488 return 1; 489 } 490 memset(hostname, 0, 21); 491 gethostname(hostname, 20); //获得主机名 492 snprintf(id, 23, "mosq_pub_%d_%s", getpid(), hostname); 493 } 494 495 if(!topic || mode == MSGMODE_NONE){ 496 fprintf(stderr, "Error: Both topic and message must be supplied.\n"); 497 print_usage(); 498 return 1; 499 } 500 501 if(will_payload && !will_topic){ 502 fprintf(stderr, "Error: Will payload given, but no will topic given.\n"); 503 print_usage(); 504 return 1; 505 } 506 if(will_retain && !will_topic){ 507 fprintf(stderr, "Error: Will retain given, but no will topic given.\n"); 508 print_usage(); 509 return 1; 510 } 511 if(password && !username){ 512 if(!quiet) fprintf(stderr, "Warning: Not using password since username not set.\n"); 513 } 514 mosquitto_lib_init(); //任何mosquitto functions之前都必须调用的函数,初始化操作 515 mosq = mosquitto_new(id, NULL); //新建一个 mosquitto client实例 516 if(!mosq){ //未建成功client实例 517 if(!quiet) fprintf(stderr, "Error: Out of memory.\n"); 518 return 1; 519 } 520 if(debug){ //需要记录debug信息,初始化 521 mosquitto_log_init(mosq, MOSQ_LOG_DEBUG | MOSQ_LOG_ERR | MOSQ_LOG_WARNING 522 | MOSQ_LOG_NOTICE | MOSQ_LOG_INFO, MOSQ_LOG_STDERR); 523 } 524 if(will_topic && mosquitto_will_set(mosq, true, will_topic, will_payloadlen, will_payload, will_qos, will_retain)){ //will信息配置,在connect之前调用 525 if(!quiet) fprintf(stderr, "Error: Problem setting will.\n"); 526 return 1; 527 } 528 if(username && mosquitto_username_pw_set(mosq, username, password)){ //设置用户名,密码 529 if(!quiet) fprintf(stderr, "Error: Problem setting username and password.\n"); 530 return 1; 531 } 532 533 mosquitto_connect_callback_set(mosq, my_connect_callback); //设置当broker给一个连接回复CONNACK时所调用的函数void callback(void *obj, int rc) 534 mosquitto_disconnect_callback_set(mosq, my_disconnect_callback); //设置当broker收到DISCONNECT命令且与client断开后调用的函数 535 mosquitto_publish_callback_set(mosq, my_publish_callback); //设置当一条被<mosquitto_publish>初始化的消息发送给broker后调用的函数 536 537 rc = mosquitto_connect(mosq, host, port, keepalive, true); //连接到一个MQTT broker 538 if(rc){ 539 //连接不成功 540 if(!quiet){ 541 if(rc == MOSQ_ERR_ERRNO){ 542 #ifndef WIN32 543 strerror_r(errno, err, 1024); 544 #else 545 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errno, 0, (LPTSTR)&err, 1024, NULL); 546 #endif 547 fprintf(stderr, "Error: %s\n", err); 548 }else{ 549 fprintf(stderr, "Unable to connect (%d).\n", rc); 550 } 551 } 552 return rc; 553 } 554 555 do{ 556 if(mode == MSGMODE_STDIN_LINE && status == STATUS_CONNACK_RECVD){ 557 if(fgets(buf, 1024, stdin)){ 558 buf[strlen(buf)-1] = '\0'; 559 rc2 = mosquitto_publish(mosq, &mid_sent, topic, strlen(buf), (uint8_t *)buf, qos, retain); 560 if(rc2){ 561 if(!quiet) fprintf(stderr, "Error: Publish returned %d, disconnecting.\n", rc2); 562 mosquitto_disconnect(mosq); 563 } 564 }else if(feof(stdin) && disconnect_sent == false){ 565 mosquitto_disconnect(mosq); 566 disconnect_sent = true; 567 } 568 } 569 rc = mosquitto_loop(mosq, -1); 570 }while(rc == MOSQ_ERR_SUCCESS && connected); 571 572 if(message && mode == MSGMODE_FILE){ 573 free(message); 574 } 575 mosquitto_destroy(mosq); //释放mosquitto实例的内存空间 576 mosquitto_lib_cleanup(); //释放library所使用的资源 577 return rc; 578 }