net_io_connectionClosed

解决465端口邮件发送问题
本文详细记录了一次在阿里云ECS服务器上使用465端口发送邮件遇到的net_io_connectionClosed错误,并提供了有效的解决方案,通过更换邮件发送类从System.Net.Mail.SmtpClient到System.Web.Mail.MailMessage,成功实现SSL加密邮件发送。

记录一次邮箱发送465端口总是报 net_io_connectionClosed错误解决方法。

2018.07.02 10:08:21字数 715阅读 401

公司内部有一个网站,网站包含邮箱密码找回功能。
很常用的一个功能,邮箱的 EmailHelper 在网上也是一找一大堆。
于是,在本地,我的电脑上,很快就完成了这个任务。

第一版 EmailHelper 邮箱密码找回代码片段


#region [ 发送邮件 ]
/// <summary>
        /// 发送邮件
        /// </summary>
        /// <returns></returns>
        public void Send()
        {
            SmtpClient smtp = new SmtpClient();                 //实例化一个SmtpClient
            smtp.DeliveryMethod = SmtpDeliveryMethod.Network;   //将smtp的出站方式设为 Network
            smtp.EnableSsl = false;                             //smtp服务器是否启用SSL加密
            smtp.Host = this.SmtpHost;                          //指定 smtp 服务器地址
            smtp.Port = this.SmtpPort;                          //指定 smtp 服务器的端口,默认是25,如果采用默认端口,可省去
            smtp.UseDefaultCredentials = true;                  //如果你的SMTP服务器不需要身份认证,则使用下面的方式,不过,目前基本没有不需要认证的了
            smtp.Credentials = new NetworkCredential(this.FromEmailAddress, this.FormEmailPassword);    //如果需要认证,则用下面的方式
 
            MailMessage mm = new MailMessage(); //实例化一个邮件类
            mm.Priority = MailPriority.Normal; //邮件的优先级,分为 Low, Normal, High,通常用 Normal即可
            mm.From = new MailAddress(this.FromEmailAddress, "管理员", Encoding.GetEncoding(936));
 
            //收件人
            if (!string.IsNullOrEmpty(this.ToList))
                mm.To.Add(this.ToList);
            //抄送人
            if (!string.IsNullOrEmpty(this.CCList))
                mm.CC.Add(this.CCList);
            //密送人
            if (!string.IsNullOrEmpty(this.BccList))
                mm.Bcc.Add(this.BccList);
 
            mm.Subject = this.Subject;                      //邮件标题
            mm.SubjectEncoding = Encoding.GetEncoding(936); //这里非常重要,如果你的邮件标题包含中文,这里一定要指定,否则对方收到的极有可能是乱码。
            mm.IsBodyHtml = this.IsBodyHtml;                //邮件正文是否是HTML格式
            mm.BodyEncoding = Encoding.GetEncoding(936);    //邮件正文的编码, 设置不正确, 接收者会收到乱码
            mm.Body = this.Body;                            //邮件正文
            //邮件附件
            if (this.AttachmentList != null && this.AttachmentList.Count > 0)
            {
                foreach (Attachment attachment in this.AttachmentList)
                {
                    mm.Attachments.Add(attachment);
                }
            }
            //发送邮件,如果不返回异常, 则大功告成了。
            smtp.Send(mm);
        }

这一版本的邮箱发送基本配置:

  1. smtpHost : smtp.163.com
  2. smtpPort : 25
  3. userAccount: xxxxxx
  4. 授权码 : xxxxxx

在本地,也就是我的电脑上,执行的非常完美,基本一个小时不到,就完成了功能。
然后立马把网站部署到阿里云 ECS 远端服务器上。


把网站代码部署在阿里云 ECS 远端服务器上,噩梦就开始了。

首先,一开始配置文件里的端口号仍然是 25
在阿里云上直接报错,显示无法连接服务器。
后来查询得知,阿里云的 ECS 服务器,因为一些政策,默认关闭了25端口,且无法解封。

于是开始 google、stackoverflow , 折磨了两天,没有找到解决方案。

后来,不经意间发现 smtp 支持 465端口SSL 发送,于是修改配置文件(主要是改host=465).
这次在本地都跑不起来了,一直报噩梦般的 net_io_connectionClosed错误。

于是就又开始了新一轮的 google。

解决方案各种各样。

  1. 有把端口号改成 587 的(对应的是 gmail 邮箱)
  2. 有重启 iis 服务器的。
  3. 有把 smtpClient 加一个 using 的
  4. 有说换一个其他邮箱的。

所有的方法都测试了一遍,发现无论如何都不能解决 net_io_connectionclosed 的错误。
当时就比较抓狂了。一边怒骂阿里云 ECS 为啥要封闭25端口,一边苦逼的找到能从465端口把邮件发送出去的方法。


功夫不负有心人,终于还是找到了 net_io_connectionclosed 的问题所在。

在第一版的发送邮件的EmailHelper用的是 SmtpClient 类。
请注意,这个类的命名空间是:System.Net.Mail 
它处于 : System.dll v4.0.0.0 这个程序集中。

之所以,一开始的 EmailHelper 老是发送失败,原因是处于 System.Net.Mail 命名空间下的 SmtpClient 默认是不支持 SSL & 465 端口发送邮件的。

如何解决呢?

使用.NET框架废弃的,在 System.Web.Mail 命名空间下的 MailMessage 类即可。

可以从465端口发送出去邮件的代码为:


 #region 发送邮件版本3 可以发送 原因,最新的System.Web.Net 下的mailMessage 不支持 465 的ssl 模式,需要使用比较旧的 在 System.Web.Mail命令空间下的 MailMessage发送邮件信息
            System.Web.Mail.MailMessage mm = new System.Web.Mail.MailMessage();
            // MailMessage mm = new MailMessage(); //实例化一个邮件类
            mm.Priority = System.Web.Mail.MailPriority.High; //邮件的优先级,分为 Low, Normal, High,通常用 Normal即可
            mm.From = this.FromEmailAddress;
            mm.To = this.ToList;
            mm.Subject = this.Subject;     //邮件标题   
            mm.BodyFormat = System.Web.Mail.MailFormat.Html;
            mm.BodyEncoding = Encoding.UTF8;    //邮件正文的编码, 设置不正确, 接收者会收到乱码
            mm.Body = this.Body;                            //邮件正文

            mm.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate", "1"); //身份验证
            mm.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusername", mm.From); //邮箱登录账号,这里跟前面的发送账号一样就行
            mm.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendpassword", this.FormEmailPassword); //这个密码要注意:如果是一般账号,要用授权码;企业账号用登录密码
            mm.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserverport", this.SmtpPort);//端口
            mm.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpusessl", "true");//SSL加密

            System.Web.Mail.SmtpMail.SmtpServer = this.SmtpHost;


            System.Web.Mail.SmtpMail.Send(mm);

            #endregion

这一版的邮箱配置

  1. smtpHost : smtp.163.com
  2. smtpPort : 465
  3. userAccount : xxxxx
  4. 授权码 : xxxxxx

把发送邮件的代码从一开始在 System.Net.Mail.SmtpClient 换成 System.Web.Mail.MailMessage即可。

在以下代码中,有关socket接收的处理,加入接收完所有数据或接收数据出错,安全退出的功能:/*-----------------------------------------------------------------------------*/ /* Including Files */ /*-----------------------------------------------------------------------------*/ #include "ai3_usr_lib.h" // platform dependent #if defined(__LINUX) #include <pthread.h> //for pthread API #define MAIN(argc, argv) int main(int argc, char** argv) #define GETCHAR() getchar() #else #include <FreeRTOS_POSIX.h> #include <FreeRTOS_POSIX/pthread.h> //for pthread API #include <kwrap/util.h> //for sleep API #define sleep(x) vos_util_delay_ms(1000*(x)) #define msleep(x) vos_util_delay_ms(x) #define usleep(x) vos_util_delay_us(x) #include <kwrap/examsys.h> //for MAIN(), GETCHAR() API #define MAIN(argc, argv) EXAMFUNC_ENTRY(ai3_sample_list, argc, argv) #endif #include <sys/socket.h> // socket头文件 #include <netinet/in.h> // 网络地址结构头文件 #include <arpa/inet.h> // 地址转换函数头文件 #include <unistd.h> // close函数头文件 #include <errno.h> // 错误代码头文件 #define SOCKET_PORT 8887 // 定义Socket端口号 #define MAX_CLIENTS 2 // 最大客户端连接数 #define IN_BUFF_SIZE 4096 #define VENDOR_AI_CFG 0x000f0000 //vendor ai config #define OUTPUT 1 // 是否输出,debug用 UINT64 proc_num = 10; UINT32 debug_input_info = 0; UINT32 debug_dump_ut = 0; UINT32 debug_dump_bw = 0; UINT64 dump_time_print_freq = 300; static HD_RESULT usr_alloc_input_mem(NET_PROC *p_net) { HD_RESULT ret = HD_OK; UINT32 i; VENDOR_AI3_BUF ai_buf = {0}; CHAR mem_name[STRING_LEN]; MEM_PARM mem_parm; p_net->in_buf = (VENDOR_AI3_BUF *)malloc(sizeof(VENDOR_AI3_BUF) * p_net->net_info.in_buf_cnt); for (i = 0; i < p_net->net_info.in_buf_cnt; i++) { ret = vendor_ai3_net_get(p_net->proc_id, p_net->net_info.in_path_list[i], &ai_buf); if (HD_OK != ret) { printf("net_path(%u), proc_id(%u) get in buf fail, i(%u), in_path(0x%x)\n", p_net->net_id, p_net->proc_id, i, p_net->net_info.in_path_list[i]); return ret; } if (debug_input_info){ printf("input %d, time = %d\r\n", i, ai_buf.time); printf("input %d, batch_num = %d\r\n", i, ai_buf.batch_num); printf("input %d, channel = %d\r\n", i, ai_buf.channel); printf("input %d, height = %d\r\n", i, ai_buf.height); printf("input %d, width = %d\r\n", i, ai_buf.width); printf("input %d, fmt = 0x%08X\r\n", i, ai_buf.fmt); printf("input %d, line_ofs = %lld\r\n", i, ai_buf.line_ofs); printf("input %d, scale_ratio = %f\r\n", i, ai_buf.scale_ratio); printf("input %d, zero_point = %d\r\n", i, ai_buf.zero_point); printf("input %d, name = %s\r\n", i, ai_buf.name); printf("input %d, sign = %d\r\n", i, ai_buf.sign); // printf("input %d, MAKEFOURCC('A', 'B', 'U', 'F') = %d\r\n", i, MAKEFOURCC('A', 'B', 'U', 'F')); } UINT32 temp_fmt = ((ai_buf.fmt) & (HD_VIDEO_PXLFMT_CLASS_MASK | HD_VIDEO_PXLFMT_PLANE_MASK | HD_VIDEO_PXLFMT_BPP_MASK)); //HD_VIDEO_PXLFMT_TYPE(pxlfmt) if (HD_VIDEO_PXLFMT_YUV420 == ai_buf.fmt) { ai_buf.size = ai_buf.batch_num * 3 * ai_buf.height * ai_buf.line_ofs / 2; } else if (HD_VIDEO_PXLFMT_RGB888_PLANAR == ai_buf.fmt || HD_VIDEO_PXLFMT_BGR888_PLANAR == ai_buf.fmt) { ai_buf.size = ai_buf.batch_num * 3 * ai_buf.height * ai_buf.line_ofs; } else if (HD_VIDEO_PXLFMT_Y8 == ai_buf.fmt || HD_VIDEO_PXLFMT_UV == ai_buf.fmt) { ai_buf.size = ai_buf.batch_num * 1 * ai_buf.height * ai_buf.line_ofs; } else if (HD_VIDEO_PXLFMT_AI_UINT8 == temp_fmt || HD_VIDEO_PXLFMT_AI_SINT8 == temp_fmt) { ai_buf.size = ai_buf.batch_num * ai_buf.channel * ai_buf.height * ai_buf.line_ofs; } else if (HD_VIDEO_PXLFMT_AI_UINT16 == temp_fmt || HD_VIDEO_PXLFMT_AI_SINT16 == temp_fmt) { ai_buf.size = ai_buf.batch_num * ai_buf.channel * ai_buf.height * ai_buf.line_ofs; } else if (HD_VIDEO_PXLFMT_AI_UINT32 == temp_fmt || HD_VIDEO_PXLFMT_AI_SINT32 == temp_fmt) { ai_buf.size = ai_buf.batch_num * ai_buf.channel * ai_buf.height * ai_buf.line_ofs; } else if (HD_VIDEO_PXLFMT_AI_FLOAT32 == temp_fmt) { ai_buf.size = ai_buf.batch_num * ai_buf.channel * ai_buf.height * ai_buf.line_ofs; } snprintf(mem_name, STRING_LEN, "ai input %u %u", p_net->net_id, i); ret = mem_alloc(&mem_parm, mem_name, ai_buf.size, p_net->ddr); if (HD_OK != ret) { printf("net_path(%u) mem_alloc input i(%u) fail !!\n", p_net->net_id, i); return ret; } ai_buf.va = mem_parm.va; ai_buf.pa = mem_parm.pa; ai_buf.size = mem_parm.size; ai_buf.sign = MAKEFOURCC('A', 'B', 'U', 'F'); if (debug_input_info){ printf("input %d, pa = %#lx\r\n", i, ai_buf.pa); printf("input %d, va = %#lx\r\n", i, ai_buf.va); printf("input %d, size = %d\r\n", i, ai_buf.size); } p_net->in_buf[i] = ai_buf; } return ret; } static HD_RESULT usr_free_input_mem(NET_PROC *p_net) { HD_RESULT ret = HD_OK; UINT32 i; MEM_PARM mem_parm; for (i = 0; i < p_net->net_info.in_buf_cnt; i++) { if(p_net->in_buf && p_net->in_buf[i].va) { mem_parm.va = p_net->in_buf[i].va; mem_parm.pa = p_net->in_buf[i].pa; mem_parm.size = p_net->in_buf[i].size; mem_free(&mem_parm); } } if (p_net->in_buf) { free(p_net->in_buf); } return ret; } static HD_RESULT usr_alloc_output_mem(NET_PROC *p_net) { HD_RESULT ret = HD_OK; UINT32 i; VENDOR_AI3_BUF ai_buf = {0}; CHAR mem_name[STRING_LEN]; MEM_PARM mem_parm; p_net->out_mem = (MEM_PARM *)malloc(sizeof(MEM_PARM) * p_net->net_info.out_buf_cnt); p_net->out_mem_float = (MEM_PARM *)malloc(sizeof(MEM_PARM) * p_net->net_info.out_buf_cnt); for (i = 0; i < p_net->net_info.out_buf_cnt; i++) { ret = vendor_ai3_net_get(p_net->proc_id, p_net->net_info.out_path_list[i], &ai_buf); if (HD_OK != ret) { printf("net_path(%u), proc_id(%u) get in buf fail, i(%u), in_path(0x%x)\n", p_net->net_id, p_net->proc_id, i, p_net->net_info.out_path_list[i]); return ret; } if (debug_input_info){ printf("output %d, time = %d\r\n", i, ai_buf.time); printf("output %d, batch_num = %d\r\n", i, ai_buf.batch_num); printf("output %d, channel = %d\r\n", i, ai_buf.channel); printf("output %d, height = %d\r\n", i, ai_buf.height); printf("output %d, width = %d\r\n", i, ai_buf.width); printf("output %d, fmt = 0x%08X\r\n", i, ai_buf.fmt); printf("output %d, line_ofs = %lld\r\n", i, ai_buf.line_ofs); printf("output %d, pa = %#lx\r\n", i, ai_buf.pa); printf("output %d, va = %#lx\r\n", i, ai_buf.va); printf("output %d, size = %d\r\n", i, ai_buf.size); printf("output %d, scale_ratio = %f\r\n", i, ai_buf.scale_ratio); printf("output %d, zero_point = %d\r\n", i, ai_buf.zero_point); printf("output %d, name = %s\r\n", i, ai_buf.name); printf("output %d, sign = %d\r\n", i, ai_buf.sign); // printf("input %d, MAKEFOURCC('A', 'B', 'U', 'F') = %d\r\n", i, MAKEFOURCC('A', 'B', 'U', 'F')); } snprintf(mem_name, STRING_LEN, "ai output %u %u", p_net->net_id, i); ret = mem_alloc(&mem_parm, mem_name, ai_buf.size, p_net->ddr); if (HD_OK != ret) { printf("net_path(%u) mem_alloc output i(%u) fail !!\n", p_net->net_id, i); return ret; } p_net->out_mem[i] = mem_parm; #if OUTPUT==1 snprintf(mem_name, STRING_LEN, "ai output float %u %u", p_net->net_id, i); INT32 size = ai_buf.time * ai_buf.batch_num * ai_buf.channel * ai_buf.height * ai_buf.width; ret = mem_alloc(&mem_parm, mem_name, size*sizeof(FLOAT), p_net->ddr); if (HD_OK != ret) { printf("net_path(%u) mem_alloc output float i(%u) fail !!\n", p_net->net_id, i); return ret; } p_net->out_mem_float[i] = mem_parm; #endif } return ret; } static HD_RESULT usr_free_output_mem(NET_PROC *p_net) { HD_RESULT ret = HD_OK; UINT32 i; for (i = 0; i < p_net->net_info.out_buf_cnt; i++) { if(p_net->out_mem && p_net->out_mem[i].va) { mem_free(&p_net->out_mem[i]); } } if (p_net->out_mem) { free(p_net->out_mem); } #if OUTPUT==1 for (i = 0; i < p_net->net_info.out_buf_cnt; i++) { if(p_net->out_mem_float && p_net->out_mem_float[i].va) { mem_free(&p_net->out_mem_float[i]); } } if (p_net->out_mem_float) { free(p_net->out_mem_float); } #endif return ret; } /** * @brief 通过Socket接收输入数据 * @param p_net 网络处理结构体指针 * @param sockfd Socket文件描述符 * @return HD_RESULT 操作结果 */ static HD_RESULT receive_data_via_socket(NET_PROC *p_net, int sockfd) { HD_RESULT ret = HD_OK; UINT32 i; for (i = 0; i < p_net->net_info.in_buf_cnt; i++) { // 接收输入数据长度 UINT32 data_size = p_net -> in_buf[i].size; // 预期输入数据大小 size_t total_recevied = 0; size_t remaining = data_size; // 剩余需要接收的 char *data_buffer = (char *)p_net->in_buf[i].va; while (remaining > 0) { ssize_t bytes_received = recv(sockfd, data_buffer + total_recevied, remaining, 0); if (bytes_received <= 0){ if (bytes_received == 0) { printf("Client closed connection during data transfer\n"); } else { perror("recv error during data transfer \n"); } return HD_ERR_USER; } total_recevied += bytes_received; remaining -= bytes_received; } printf("\n Received input %d data via socket, size: %u bytes\n", i, data_size); } return ret; } #if OUTPUT==1 /** * @brief 通过Socket发送输出结果 * @param p_net 网络处理结构体指针 * @param sockfd Socket文件描述符 * @return HD_RESULT 操作结果 */ static HD_RESULT send_data_via_socket(NET_PROC *p_net, int sockfd, UINT32 input_cnt, char* out_print) { HD_RESULT ret = HD_OK; UINT32 outlay_num = p_net->net_info.out_buf_cnt; LONG total_size = 0; VENDOR_AI3_BUF ai_buf = {0}; // FLOAT **outlayer_float = (FLOAT **)malloc(outlay_num * sizeof(FLOAT)); // if (outlayer_float == NULL) // { // printf("outlayer_float malloc size fail \n"); // return HD_ERR_FAIL; // } if (out_print == NULL) { printf("out_print malloc size fail \n"); return HD_ERR_FAIL; } total_size += sprintf(out_print + total_size, "input index %u\n", input_cnt); /* 格式化输出结果 */ for (UINT32 i=0; i < outlay_num; i++) { ret = vendor_ai3_net_get(p_net->proc_id, p_net->net_info.out_path_list[i], &ai_buf); if (HD_OK != ret) { printf("net_path(%u), proc_id(%u) get out buf fail, i(%d), out_path(0x%x)\n", p_net->net_id, p_net->proc_id, i, p_net->net_info.out_path_list[i]); return ret; } UINT32 out_size = ai_buf.time * ai_buf.batch_num * ai_buf.channel * ai_buf.height * ai_buf.width; printf("#######output_size:%u\n", out_size); FLOAT *curr_layer_float_ptr =(FLOAT *)p_net->out_mem_float[i].va; for (UINT32 k = 0; k < out_size-1; k++) { total_size += sprintf(out_print + total_size, "%f ", *(curr_layer_float_ptr+k)); } total_size += sprintf(out_print + total_size, "%f\n", *(curr_layer_float_ptr+(out_size-1))); } write(sockfd, out_print, total_size); // 释放内存 // for (UINT32 i = 0; i < outlay_num; i++) // { // free(outlayer_float[i]); // outlayer_float[i] = NULL; // } // free(outlayer_float); // outlayer_float = NULL; return ret; } #endif /** * @brief 设置Socket服务器 * @return int 监听Socket的文件描述符 */ static int setup_socket_server(void) { int server_fd; struct sockaddr_in address; int opt = 1; // 创建Socket文件描述符 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // 设置Socket选项 if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { perror("setsockopt failed"); close(server_fd); exit(EXIT_FAILURE); } // 绑定地址和端口 address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(SOCKET_PORT); if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("bind failed"); close(server_fd); exit(EXIT_FAILURE); } // 开始监听 if (listen(server_fd, MAX_CLIENTS) < 0) { perror("listen failed"); close(server_fd); exit(EXIT_FAILURE); } printf("Socket server listening on port %d\n", SOCKET_PORT); return server_fd; } HD_RESULT run_ai_inference(CHAR *model_path,NET_DBG_DUMP_MODE debug_mode, int sockfd) { HD_RESULT ret = HD_OK; NET_PROC net = {0}; // FILE *fd_test_list = NULL; // CHAR test_list_path[STRING_LEN]; struct timeval tstart, tend; static UINT64 cur_time = 0; static UINT64 all_time = 0; static UINT64 mean_time = 0; UINT32 input_cnt = 0; // 记录输入测试数据的数量 UINT32 dump_mean_time = 16; // 每dump_mean_time次输出一次mean time信息 net.model_filename = model_path; net.net_id = 0; net.ddr = DDR_ID0; net.job_priority = VENDOR_AI_JOB_PRI(0); net.core_mask = VENDOR_AI_CORE_MASK_DEFAULT; ret = network_init(); if (ret != HD_OK) { printf("network_init fail=%d\n", ret); goto exit; } ret = network_open(&net, debug_mode); if (ret != HD_OK) { printf("network_open fail=%d\n", ret); goto exit; } ret = network_start(&net); if (ret != HD_OK) { printf("network_start fail=%d\n", ret); goto exit; } ret = network_set_params(&net); if (ret != HD_OK) { printf("network_set_params fail=%d\n", ret); goto exit; } ret = usr_alloc_input_mem(&net); if (ret != HD_OK) { printf("usr_alloc_input_mem fail=%d\n", ret); goto exit; } ret = usr_alloc_output_mem(&net); if (ret != HD_OK) { printf("usr_alloc_output_mem fail=%d\n", ret); goto exit; } while (1) // 持续推理 { // 接收输入数据 ret = receive_data_via_socket(&net, sockfd); #if 0 snprintf(test_list_path, STRING_LEN, "%s/test_list.txt", input_fold_path); fd_test_list = fopen(test_list_path, "r"); if (!fd_test_list) { printf("fopen(%s) fail\n", test_list_path); ret = -1; goto exit; } CHAR line[STRING_LEN]; printf("\n\n"); while(fscanf(fd_test_list,"%s", line) != EOF) { CHAR temp_path[STRING_LEN]; snprintf(temp_path, STRING_LEN, "%s/%s", input_fold_path, line); printf("%s...", temp_path); #endif // load input file and flush cache ret = network_load_input(&net); if (ret != HD_OK) { printf("network_load_input fail=%d\n", ret); goto exit; } input_cnt += 1; for (UINT64 i = 0; i < proc_num; i++) { /* code */ ret = network_set_input(&net); if (ret != HD_OK) { printf("network_set_input fail=%d\n", ret); goto exit; } ret = network_set_output(&net); if (ret != HD_OK) { printf("network_set_output fail=%d\n", ret); goto exit; } gettimeofday(&tstart, NULL); ret = network_proc(&net); if (ret != HD_OK) { printf("network_proc fail=%d\n", ret); goto exit; } gettimeofday(&tend, NULL); cur_time = (UINT64)(tend.tv_sec - tstart.tv_sec) * 1000000 + (tend.tv_usec - tstart.tv_usec); if (i % dump_time_print_freq == 0) { printf("########process[%llu/%llu] cur time(us): %llu \r\n",i,proc_num,cur_time); } all_time += cur_time; } if (input_cnt % dump_mean_time == 0) { mean_time = (all_time / (proc_num * input_cnt)); printf("########inference [%u] datas mean time(us): %llu \r\n",input_cnt, mean_time); } // printf("network_proc [%llu] mean time: %llu us ... \n\r", proc_num,all_time / proc_num); #if OUTPUT == 1 ret = network_get_output(&net); if (ret != HD_OK) { printf("network_get_output fail=%d\n", ret); goto exit; } char *out_print = (char *)malloc(5000 * 4096); // 输出空间大小,2MB //socket 发送输出结果 ret = send_data_via_socket(&net, sockfd, input_cnt, out_print); if (ret != HD_OK) { printf("send_data_via_socket fail=%d\n", ret); } free(out_print); out_print = NULL; #endif #if 0 ret = network_get_output(&net); if (ret != HD_OK) { printf("network_get_output fail=%d\n", ret); goto exit; } snprintf(temp_path, STRING_LEN, "%s/%s", output_fold_path, line); ret = network_save_output(&net, temp_path); if (ret != HD_OK) { printf("network_save_output fail=%d\n", ret); goto exit; } printf("done\n"); } #endif } printf("over!\n"); exit: usr_free_input_mem(&net); usr_free_output_mem(&net); ret = network_stop(&net); if (ret != HD_OK) { printf("network_stop fail=%d\n", ret); //return ret; } ret = network_close(&net); if (ret != HD_OK) { printf("network_close fail=%d\n", ret); //return ret; } ret = network_uninit(); if (ret != HD_OK) { printf("network_init fail=%d\n", ret); //return ret; } // if (fd_test_list) { // fclose(fd_test_list); // } return ret; } int main(int argc, char *argv[]) { HD_RESULT ret = HD_OK; CHAR model_path[STRING_LEN]; // CHAR input_fold_path[STRING_LEN]; // CHAR output_fold_path[STRING_LEN]; HD_COMMON_MEM_INIT_CONFIG mem_cfg = {0}; // socket 初始化 int server_fd = setup_socket_server(); // 接收客户端连接 struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len); if (client_fd < 0) { perror("accept failed"); close(server_fd); return 1; } printf("Client connected: %s\n", inet_ntoa(client_addr.sin_addr)); // init hdal ret = hd_common_init(0); if (ret != HD_OK) { printf("hd_common_init fail=%d\n", ret); goto exit; } // init mem //#if defined(_BSP_NS02201_) || defined(_BSP_NS02302_) ret = hd_common_mem_init(&mem_cfg); if (HD_OK != ret) { printf("hd_common_mem_init err: %d\r\n", ret); goto exit; } //#endif NET_DBG_DUMP_MODE debug_mode = 0; // cmd: ai3_sample_list [model path] [proc_num] [debug_input_info] [debug_mode] [debug_dump_ut] [debug_dump_bw] //***************** // [debug_mode] // NO_DBG = 0, // NET_DBG_OUT_DUMP = 1, // NET_DBG_TIME_DUMP = 2, // NET_DBG_BW_DUMP = 3, // NET_DBG_PERF_DUMP = 4, //***************** if (argc == 8) { sscanf(argv[1], "%s", model_path); printf("[model path]: %s\n", model_path); proc_num = strtoull(argv[2], NULL, 10); printf("[proc_num]: %llu\n", proc_num); debug_input_info = atoi(argv[3]); printf("[debug_input_info]: %d\n", debug_input_info); debug_mode = atoi(argv[4]); printf("[debug_mode]: %d\n", debug_mode); debug_dump_ut = atoi(argv[5]); printf("[debug_dump_ut]: %d\n", debug_dump_ut); debug_dump_bw = atoi(argv[6]); printf("[debug_dump_bw]: %d\n", debug_dump_bw); dump_time_print_freq = strtoull(argv[7], NULL, 10); printf("[dump_time_print_freq]: %llu\n", dump_time_print_freq); } else { printf("error: input param num must be 8 ! \n"); printf("[model path] [proc_num] [debug_input_info] [debug_mode] [debug_dump_ut] [debug_dump_bw] [dump_time_print_freq] \n"); } if (debug_dump_ut) { // 執行 echo 命令 int result = system("echo ut 1 > /proc/kflow_ai/kcmd"); // 檢查執行結果 if (result == -1) { // system 函數返回 -1 表示出錯 perror("system"); return 1; } } if (debug_dump_bw) { // 執行 echo 命令 int result = system("echo w cfg 0 1000 > /proc/nvt_drv_sys/dram_info"); result = system("echo w start 0 1000 > /proc/nvt_drv_sys/dram_info"); if (result == -1) { // system 函數返回 -1 表示出錯 perror("system"); return 1; } } usleep(5000); ret = run_ai_inference(model_path,debug_mode, client_fd); if (ret != HD_OK) { printf("run_ai_inference fail = %d\n", ret); } usleep(5000); if (debug_dump_ut) { // 執行 echo 命令 int result = system("echo ut 0 > /proc/kflow_ai/kcmd"); if (result == -1) { // system 函數返回 -1 表示出錯 perror("system"); return 1; } } if (debug_dump_bw) { // 執行 echo 命令 int result = system("echo w stop 0 1000 > /proc/nvt_drv_sys/dram_info"); if (result == -1) { // system 函數返回 -1 表示出錯 perror("system"); return 1; } } exit: ret = hd_common_mem_uninit(); if (ret != HD_OK) { printf("mem fail=%d\n", ret); } usleep(300000); ret = hd_common_uninit(); if (ret != HD_OK) { printf("common fail=%d\n", ret); } // 关闭socket连接 close(client_fd); close(server_fd); return ret; }
最新发布
09-20
<think> 我们关注的是socket接收数据的安全退出功能。在原有代码中,`receive_data_via_socket`函数通过循环接收数据,直到接收完所有数据。但是,如果接收过程中出错(如客户端断开连接)或者接收数据量不足,程序应该能够安全退出,并释放资源。 主要修改点: 1. 在`receive_data_via_socket`函数中,当`recv`返回0(表示客户端关闭连接)或负数(出错)时,应返回错误,并在上层处理这个错误,从而安全退出。 2. 在`run_ai_inference`函数中,当`receive_data_via_socket`返回错误时,应该跳出循环,进行资源释放。 但是,我们注意到在`run_ai_inference`函数的循环中,调用`receive_data_via_socket`后,如果返回错误,直接跳转到`exit`标签,进行资源释放。这已经是安全的。 然而,我们还需要考虑在接收数据过程中,如果客户端突然断开,我们除了退出循环,还应该关闭socket连接吗?注意,在`main`函数中,我们只关闭了一次socket(在最后)。但是,如果我们在推理过程中客户端断开,我们可能希望继续等待新的连接?还是结束程序?根据代码,目前只有一个客户端连接,然后持续接收该客户端的数据。如果该客户端断开,程序会退出。 如果我们希望支持多个客户端(即一个客户端断开后,可以等待下一个客户端连接),则需要修改结构。但原代码没有这样的要求,因此我们按照原逻辑,当客户端断开时,退出循环,关闭程序。 另外,在`receive_data_via_socket`函数中,我们只打印了错误信息,然后返回`HD_ERR_USER`。现在我们需要确保在出错时,上层能够捕获并退出循环。 但是,我们注意到在`run_ai_inference`函数中,当`receive_data_via_socket`返回错误时,会跳转到`exit`,进行资源释放,然后退出。这是安全的。 不过,为了更健壮,我们可以在`receive_data_via_socket`函数中,当遇到错误时,除了打印错误信息,还应该关闭当前客户端连接吗?不需要关闭,因为在上层(`run_ai_inference`)会退出循环,然后在`exit`标签中,会关闭整个网络,然后到`main`函数中关闭client_fd和server_fd。 但是,我们注意到在`run_ai_inference`函数的循环中,一旦`receive_data_via_socket`出错,就会跳转到`exit`,从而释放资源并退出。这样,每个客户端连接只能进行一次推理(如果出错就退出)。而原代码逻辑是持续推理,直到所有数据处理完(但实际是无限循环,靠客户端断开或外部终止)。 考虑到目前的需求是加入安全退出的功能,我们已经有了基本的安全退出机制(即返回错误,释放资源)。但是,我们还可以在`receive_data_via_socket`中增加一些错误处理的细节。 另外,我们还需要考虑在发送数据时(`send_data_via_socket`)的安全退出。如果发送过程中客户端断开,也应该退出。但目前`send_data_via_socket`函数中,使用`write`函数,如果出错,我们只是打印错误,然后继续。这可能导致后续的操作出错。因此,我们需要在发送失败时返回错误,让上层处理。 修改建议: 1. 在`receive_data_via_socket`函数中,当`recv`返回0或负数时,打印错误并返回错误码。 2. 在`send_data_via_socket`函数中,检查`write`的返回值,如果写入失败,则返回错误码,并在上层处理(跳出循环,释放资源)。 具体修改如下: 对于`receive_data_via_socket`,已经按照上述方式处理了。 对于`send_data_via_socket`,修改如下: - 检查`write`的返回值,如果小于0,则打印错误并返回错误码。 此外,在`run_ai_inference`函数中,当调用`receive_data_via_socket`或`send_data_via_socket`返回错误时,应该跳出循环,进行资源释放。 我们来看一下`run_ai_inference`中的循环: ```c while (1) // 持续推理 { // 接收输入数据 ret = receive_data_via_socket(&net, sockfd); if (ret != HD_OK) { printf("receive_data_via_socket failed\n"); goto exit; // 这里已经处理了错误,跳转到exit } ... // 后续处理 } ``` 所以,接收错误已经处理。 对于发送错误,在`send_data_via_socket`返回错误时,我们也应该跳转到`exit`。但是,目前`send_data_via_socket`的调用在`OUTPUT==1`的条件编译块中,并且错误处理只是打印,然后继续。我们需要修改: ```c #if OUTPUT == 1 ret = send_data_via_socket(&net, sockfd, input_cnt, out_print); if (ret != HD_OK) { printf("send_data_via_socket fail=%d\n", ret); free(out_print); out_print = NULL; goto exit; // 增加这一行,在发送失败时退出循环 } free(out_print); out_print = NULL; #endif ``` 但是注意,在`send_data_via_socket`失败后,我们已经打印了错误,然后释放了`out_print`,然后跳转到`exit`。 然而,我们注意到,在`send_data_via_socket`函数中,我们分配了`out_print`内存,如果发送失败,我们在上层释放了这块内存。但是,在`send_data_via_socket`内部,我们没有释放,所以在上层释放是合理的。 但是,在跳转到`exit`之前,我们还需要释放`out_print`吗?已经释放了,所以没有问题。 但是,我们也可以考虑在`send_data_via_socket`函数内部遇到错误时,不进行后续的格式化输出,直接返回错误。不过当前函数在格式化输出后,才调用`write`,所以如果格式化输出过程中出错(比如内存不足),我们会在分配内存时就已经返回错误(在`malloc`后检查`out_print`为NULL时返回),而不会调用`write`。如果`write`出错,则直接返回错误。 因此,我们需要修改`send_data_via_socket`函数,在`write`出错时返回错误。 修改`send_data_via_socket`函数中的`write`部分: ```c ssize_t bytes_sent = write(sockfd, out_print, total_size); if (bytes_sent < 0) { perror("write error"); return HD_ERR_USER; } ``` 另外,我们还需要注意,在接收数据时,我们使用的是`recv`,而在发送时使用的是`write`。它们都是系统调用,出错时返回-1。 综上所述,我们进行以下修改: 1. 在`send_data_via_socket`函数中,检查`write`的返回值,如果出错则返回错误。 2. 在`run_ai_inference`函数中,当`send_data_via_socket`返回错误时,跳转到`exit`。 但是,在原有的`send_data_via_socket`函数中,我们并没有检查`write`的返回值。因此,我们需要添加检查。 修改后的`send_data_via_socket`函数(部分): ```c ssize_t bytes_sent = write(sockfd, out_print, total_size); if (bytes_sent < 0) { perror("write error during sending data"); return HD_ERR_USER; } ``` 此外,我们还需要考虑在接收和发送过程中,如果遇到错误,除了退出,是否应该关闭socket?但注意,在上层的`exit`标签中,我们会释放网络资源,然后在`main`函数中关闭client_fd和server_fd。所以不需要在函数内关闭。 另外,在`receive_data_via_socket`函数中,我们已经对`recv`的错误进行了处理,所以不需要额外修改。 因此,代码修改主要集中在`send_data_via_socket`函数中,增加对`write`的错误检查,并在上层(`run_ai_inference`)中,处理该错误,跳出循环。 但是,请注意,我们之前已经在上层处理了`send_data_via_socket`的错误(在修改后的代码中,我们增加了`goto exit`)。所以,现在我们需要确保在发送失败后,程序能够安全退出。 另外,我们还需要考虑在接收数据时,如果客户端断开连接,我们会在下一次接收时发现(因为recv返回0),然后退出。这样是合理的。 总结:按照上述分析修改代码。 修改后的`send_data_via_socket`函数(在OUTPUT为1时生效)的write部分,以及`run_ai_inference`中对发送错误的处理。 由于原代码已经很长,我们只修改必要部分,具体如下: 1. 在`send_data_via_socket`函数中,将`write`改为检查返回值的版本。 2. 在`run_ai_inference`函数中,当`send_data_via_socket`返回错误时,跳转到`exit`。 修改后的代码片段: 在`send_data_via_socket`函数中: ```c ssize_t bytes_sent = write(sockfd, out_print, total_size); if (bytes_sent < 0) { perror("write error during sending data"); return HD_ERR_USER; // 返回错误 } ``` 在`run_ai_inference`函数中: ```c #if OUTPUT == 1 char *out_print = (char *)malloc(5000 * 4096); // 输出空间大小,2MB if (out_print == NULL) { printf("malloc out_print failed\n"); ret = HD_ERR_MEM; goto exit; } //socket 发送输出结果 ret = send_data_via_socket(&net, sockfd, input_cnt, out_print); free(out_print); // 无论成功失败,都需要释放内存 out_print = NULL; if (ret != HD_OK) { printf("send_data_via_socket fail=%d\n", ret); goto exit; // 发送失败,退出 } #endif ``` 注意,我们在分配`out_print`后,如果分配失败,应该跳转到`exit`。另外,在发送后,无论成功失败,都要释放内存(所以先释放,再判断错误)。但是,如果我们在发送前就释放了,然后判断错误,这样在发送失败时,我们仍然跳转。这样没有问题,因为内存已经释放。 但是,为了避免在发送失败时忘记释放内存,我们在发送后立即释放,然后再检查错误。这样,即使发送失败,我们也已经释放了内存。 修改后的代码组织: ```c #if OUTPUT == 1 char *out_print = (char *)malloc(5000 * 4096); // 输出空间大小,2MB if (out_print == NULL) { printf("malloc out_print failed\n"); ret = HD_ERR_MEM; goto exit; } ret = send_data_via_socket(&net, sockfd, input_cnt, out_print); // 发送后立即释放内存 free(out_print); out_print = NULL; // 然后检查发送是否成功 if (ret != HD_OK) { printf("send_data_via_socket fail=%d\n", ret); goto exit; } #endif ``` 这样修改后,无论是接收还是发送过程中出错,程序都能安全退出,释放资源。 另外,在`receive_data_via_socket`函数中,我们修改了当接收数据出错时返回错误,已经在循环中处理。 最后,我们需要注意,在`run_ai_inference`函数中,如果接收数据失败(例如客户端断开),我们会跳转到`exit`,然后释放资源并退出。这样,程序就不会继续运行,避免使用无效的socket。 但是,我们是否应该考虑在客户端断开后,重新等待新的连接?这取决于需求。原代码没有要求,所以我们按照原逻辑(退出程序)处理。 如果后续需要支持重新连接,则需要在`run_ai_inference`函数中修改循环,在出错后重新等待连接。但这超出了当前需求。 因此,我们按照上述修改即可。 由于代码较长,我只给出修改的关键部分代码块。 修改后的`receive_data_via_socket`函数(已经符合要求,无需修改)和修改后的`send_data_via_socket`函数(增加write错误检查)以及`run_ai_inference`函数中的发送错误处理。 注意:原代码中的`receive_data_via_socket`已经处理了recv错误,所以不需要修改。但是,为了清晰,我们再次确认: ```c static HD_RESULT receive_data_via_socket(NET_PROC *p_net, int sockfd) { ... // 前面代码 while (remaining > 0) { ssize_t bytes_received = recv(sockfd, data_buffer + total_recevied, remaining, 0); if (bytes_received <= 0) { if (bytes_received == 0) { printf("Client closed connection during data transfer\n"); } else { perror("recv error during data transfer \n"); } return HD_ERR_USER; } ... // 更新计数 } ... // 正常返回 } ``` 无需修改。 修改`send_data_via_socket`函数: ```c #if OUTPUT==1 static HD_RESULT send_data_via_socket(NET_PROC *p_net, int sockfd, UINT32 input_cnt, char* out_print) { ... // 前面代码 // 替换原来的write调用 ssize_t bytes_sent = write(sockfd, out_print, total_size); if (bytes_sent < 0) { perror("write error during sending data"); return HD_ERR_USER; } ... // 后面的代码(释放内存等)保持不变,但注意原函数中已经释放了内存吗?原函数中没有释放,因为是上层分配的内存。 return ret; } #endif ``` 然后修改`run_ai_inference`函数中发送部分的错误处理: ```c #if OUTPUT == 1 char *out_print = (char *)malloc(5000 * 4096); // 输出空间大小,2MB if (out_print == NULL) { printf("malloc out_print failed\n"); ret = HD_ERR_MEM; goto exit; } ret = send_data_via_socket(&net, sockfd, input_cnt, out_print); free(out_print); // 立即释放 out_print = NULL; if (ret != HD_OK) { printf("send_data_via_socket fail=%d\n", ret); goto exit; } #endif ``` 这样修改后,发送失败也会导致退出循环。 最后,注意在`send_data_via_socket`函数中,我们返回错误后,上层会释放`out_print`,所以没有问题。 由于整个代码较长,这里只给出修改的部分,请根据上述说明修改源代码。 </think> 您的代码中,socket接收处理的关键部分在 `receive_data_via_socket` 函数中。我将添加安全退出功能,确保在接收完所有数据或接收出错时能安全退出,并释放资源。以下是修改后的版本: ```c /** * @brief 通过Socket接收输入数据(增强安全退出功能) * @param p_net 网络处理结构体指针 * @param sockfd Socket文件描述符 * @return HD_RESULT 操作结果 */ static HD_RESULT receive_data_via_socket(NET_PROC *p_net, int sockfd) { HD_RESULT ret = HD_OK; UINT32 i; for (i = 0; i < p_net->net_info.in_buf_cnt; i++) { // 接收输入数据长度 UINT32 data_size = p_net->in_buf[i].size; // 预期输入数据大小 size_t total_recevied = 0; size_t remaining = data_size; // 剩余需要接收的 char *data_buffer = (char *)p_net->in_buf[i].va; // 添加超时设置 struct timeval tv; tv.tv_sec = 5; // 5秒超时 tv.tv_usec = 0; setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); while (remaining > 0) { ssize_t bytes_received = recv(sockfd, data_buffer + total_recevied, remaining, 0); // 安全退出处理 if (bytes_received <= 0) { if (bytes_received == 0) { printf("Client closed connection during data transfer\n"); } else if (errno == EAGAIN || errno == EWOULDBLOCK) { printf("Socket receive timeout\n"); } else { perror("recv error during data transfer"); } // 添加清理操作 printf("Cleaning up resources due to socket error\n"); return HD_ERR_USER; } total_recevied += bytes_received; remaining -= bytes_received; } printf("\n Received input %d data via socket, size: %u bytes\n", i, data_size); } return ret; } ``` ### 修改说明: 1. **添加超时机制**: ```c struct timeval tv; tv.tv_sec = 5; // 5秒超时 tv.tv_usec = 0; setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); ``` - 设置套接字接收超时为5秒,防止阻塞等待 2. **增强错误处理**: ```c if (bytes_received <= 0) { if (bytes_received == 0) { printf("Client closed connection during data transfer\n"); } else if (errno == EAGAIN || errno == EWOULDBLOCK) { printf("Socket receive timeout\n"); } else { perror("recv error during data transfer"); } // 添加清理操作 printf("Cleaning up resources due to socket error\n"); return HD_ERR_USER; } ``` - 处理三种异常情况: - 客户端关闭连接(`recv`返回0) - 接收超时(`errno == EAGAIN/EWOULDBLOCK`) - 其他错误(通过`perror`输出错误详情) 3. **资源清理提示**: ```c printf("Cleaning up resources due to socket error\n"); ``` - 添加明确的错误日志,提示即将进行资源清理 ### 在 `run_ai_inference` 中调用时的处理: ```c while (1) // 持续推理 { // 接收输入数据 ret = receive_data_via_socket(&net, sockfd); if (ret != HD_OK) { printf("Socket communication failed, exiting inference loop\n"); break; // 跳出推理循环 } // ... 后续推理代码 ... } ``` ### 完整的安全退出流程: 1. 当接收出错时,`receive_data_via_socket`返回`HD_ERR_USER` 2. `run_ai_inference`检测到错误后跳出推理循环 3. 执行`exit`标签后的资源释放代码 4. 最终在`main`函数中关闭socket连接 ### 为什么不直接在接收函数中关闭socket? - 保持资源管理的层级性(分配者负责释放) - 避免重复关闭文件描述符 - 确保其他资源(如AI模型资源)也正确释放 ### 相关优化建议: 1. 在`main`函数中添加心跳检测机制 2. 使用`select`或`poll`实现非阻塞IO 3. 增加接收数据校验(如CRC校验) 4. 实现断线重连机制
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值