Socket编程中常见错误

本文探讨了在Socket编程中遇到的端口占用问题,通常由于TCP的TIME_WAIT状态导致,并提供了设置SO_REUSEADDR选项的解决方案。此外,文章还介绍了C语言编程中常见的缓冲区溢出问题,建议避免使用不安全的字符串函数如strcpy和gets,并推荐使用fgets等安全替代方法。最后,提醒程序员注意避免此类陷阱以确保程序的安全稳定。
摘要由CSDN通过智能技术生成

端口占用(Address already in use)

问题

在socket编程时可能会遇到这样的问题:在成功的运行了第一次之后,如果先ctrl+c/ctrl+z结束服务器端程序的话,再次启动服务器就会出现Address already in use这个错误,或者你的程序在正常关闭服务器端socket后还是有这个问题。经常会出现在bind()这个函数中。
在这里插入图片描述
下面是IBM官网上对这一情况的具体解释:
IBM官网
bind 普遍遭遇的问题是试图绑定一个已经在使用的端口。该陷阱是也许没有活动的套接字存在,但仍然禁止绑定端口(bind 返回 EADDRINUSE),它由 TCP 套接字状态 TIME_WAIT 引起。该状态在套接字关闭后约保留 2 到 4 分钟。在 TIME_WAIT 状态退出之后,套接字被删除,该地址才能被重新绑定而不出问题。

等待 TIME_WAIT 结束可能是令人恼火的一件事,特别是如果您正在开发一个套接字服务器,就需要停止服务器来做一些改动,然后重启。幸运的是,有方法可以避开 TIME_WAIT 状态。可以给套接字应用 SO_REUSEADDR 套接字选项,以便端口可以马上重用。

复现

1、

/***************************************************************
 * @file       tcpmaster.c
 * @author     xy
 * @brief      modbustcp server
 * @version    v1
 * @return     null
 * @date       2022/01/18
 **************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <error.h>
#include <memory.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <errno.h>
#include "modbus.h"

int main(void)
{ 
    int i = 0;
    int rc = 0;
    modbus_t *ctx = NULL;
    int server_socket,master_socket = 0;
    modbus_mapping_t *mb_mapping = NULL;
    uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
    fd_set reads;
    uint32_t old_response_to_sec;
    uint32_t old_response_to_usec;

    //To listen any addresses on port 502
    ctx = modbus_new_tcp(NULL, 502);
    //modbus_set_debug(ctx, debugging);
    if (ctx == NULL)
    {
        printf("There was an error allocating the modbus\n");
        return -1;
    }

    //设置线圈,离散输入,保持寄存器,输入寄存器个数
    mb_mapping = modbus_mapping_new(MODBUS_MAX_READ_BITS, 500,
                                    MODBUS_MAX_READ_REGISTERS, 500);
    if (mb_mapping == NULL) 
    {
        printf("Failed to allocate the mapping\n");
        modbus_free(ctx);
        return -1;
    }

    //初始化线圈
    mb_mapping->tab_bits[0] = 1;
    mb_mapping->tab_bits[1] = 0;
    mb_mapping->tab_bits[2] = 1;
    mb_mapping->tab_bits[3] = 1;
    mb_mapping->tab_bits[4] = 1;
    mb_mapping->tab_bits[5] = 0;
    mb_mapping->tab_bits[6] = 1;
    mb_mapping->tab_bits[7] = 1;
    //初始离散量输入
    mb_mapping->tab_input_bits[0] = 1;
    mb_mapping->tab_input_bits[1] = 0;
    mb_mapping->tab_input_bits[2] = 1;
    mb_mapping->tab_input_bits[3] = 1;
    mb_mapping->tab_input_bits[4] = 1;
    mb_mapping->tab_input_bits[5] = 0;
    mb_mapping->tab_input_bits[6] = 1;
    mb_mapping->tab_input_bits[7] = 1;
    //初始化保持寄存器
    mb_mapping->tab_registers[0] = 1;
    mb_mapping->tab_registers[1] = 2;
    mb_mapping->tab_registers[2] = 3;
    mb_mapping->tab_registers[3] = 4;
    mb_mapping->tab_registers[4] = 5;
    mb_mapping->tab_registers[5] = 6;
    mb_mapping->tab_registers[6] = 7;
    mb_mapping->tab_registers[7] = 8;
    //初始化输入寄存器
    mb_mapping->tab_input_registers[0] = 9;
    mb_mapping->tab_input_registers[1] = 10;
    mb_mapping->tab_input_registers[2] = 11;
    mb_mapping->tab_input_registers[3] = 12;
    mb_mapping->tab_input_registers[4] = 13;
    mb_mapping->tab_input_registers[5] = 14;
    mb_mapping->tab_input_registers[6] = 15;
    mb_mapping->tab_input_registers[7] = 16;

    /* Save original timeout */
    modbus_get_response_timeout(ctx, &old_response_to_sec, &old_response_to_usec);

    /* Define a new timeout of 200ms */
    modbus_set_response_timeout(ctx, 0, 200000);

    //Handle until 1 established connections
    server_socket = modbus_tcp_listen(ctx, 1);
    if(server_socket == -1)
    {
      printf("modbus_tcp_listen failed: %s\n", modbus_strerror(errno));
      modbus_free(ctx);
      return 1;
    }
    printf("modbus_tcp_listen finish\n");

    /* Clear the reference set of socket */
//    FD_ZERO(&reads);

    /* Add the server socket */
//    FD_SET(server_socket, &reads);
    
    master_socket = modbus_tcp_accept(ctx, &server_socket); 
    printf("有客户端接入\n");

    while (1)
    {
          printf("\n循环应答\n");
          //query数组清0
          memset(query, 0, sizeof(query));
          //读数据
          rc = modbus_receive(ctx, query);

          if (rc > 0) 
          {
            printf("读取请求报文字节数:%d\n",rc);
            printf("读取到请求报文:%d %d %d %d %d %d %d %d %d %d %d %d\n",
                     query[0],query[1],query[2],query[3],query[4],query[5],
                     query[6],query[7],query[8],query[9],query[10],query[11]); 
            //fprintf(stderr,"读取到query内容:%s %s %s %s %s %s %s %s\n",
                       //query[0],query[1],query[2],query[3],
                       //query[4],query[5],query[6],query[7]);
            modbus_reply(ctx, query, rc, mb_mapping);

            if (query[7] == 6) 
            {
              printf("请求写第%d个保持寄存器\n",query[9]+1);
              printf("修改后寄存器为:%d %d %d %d %d %d %d %d\n",
                      mb_mapping->tab_registers[0],mb_mapping->tab_registers[1],
                      mb_mapping->tab_registers[2],mb_mapping->tab_registers[3],
                      mb_mapping->tab_registers[4],mb_mapping->tab_registers[5],
                      mb_mapping->tab_registers[6],mb_mapping->tab_registers[7]);              
            }
          } 
          else if (rc  == -1) 
          {
            //Connection closed by the client or error
            printf("Connection closed by the client or error\n");
            break;
          }
    }

    //printf("Quit the loop: %s\n", modbus_strerror(error));

    modbus_mapping_free(mb_mapping);
    //if (s != -1) {
    //    close(s);
    //}
    modbus_close(ctx);
    modbus_free(ctx);
}

在这里插入图片描述
2、比如modbus tcp的server端代码tcpmaster.c(实际上应该命名为tcpslave),ctrl+z重复执行后会报错

/***************************************************************
 * @file       tcpmaster.c
 * @author     xy
 * @brief      modbustcp server
 * @version    v1
 * @return     null
 * @date       2022/01/18
 **************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <error.h>
#include <memory.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <errno.h>
#include "modbus.h"

int main(void)
{ 
    int i = 0;
    int rc = 0;
    modbus_t *ctx = NULL;
    int server_socket = -1;
    int master_socket = -1;
    modbus_mapping_t *mb_mapping = NULL;
    uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
    fd_set reads,writes;

    uint32_t old_response_to_sec;
    uint32_t old_response_to_usec;

    //To listen any addresses on port 502
    ctx = modbus_new_tcp(NULL, 502);
    if (ctx == NULL)
    {
        fprintf(stderr, "There was an error allocating the modbus\n");
        return -1;
    }

    //设置调试模式
    int ret = modbus_set_debug(ctx, TRUE);
    if (ret == -1)
    {
      printf("modbus_set_debug failed...\n");
      modbus_free(ctx);
      return 1;
    }

    /* Save original timeout */
    modbus_get_response_timeout(ctx, &old_response_to_sec, &old_response_to_usec);

    /* Define a new timeout of 200ms */
    modbus_set_response_timeout(ctx, 0, 200000);

    //Handle until 1 established connections
    server_socket = modbus_tcp_listen(ctx, 1);
    if(server_socket == -1)
    {
      fprintf(stderr, "modbus_tcp_listen failed...\n");
      modbus_free(ctx);
      return 1;
    }

    master_socket = modbus_tcp_accept(ctx, &server_socket);
    if(master_socket == -1)
    {
      fprintf(stderr, "modbus_tcp_accept failed...\n");
      modbus_free(ctx);
      return 1;
    }
    else
    {
      fprintf(stderr, "有客户端接入\n");
    }

    /* Clear the reference set of socket */
    FD_ZERO(&reads);
    FD_ZERO(&writes);

    /* Add the server socket */
    //FD_SET(server_socket, &reads);
    FD_SET(master_socket, &reads);
    //FD_SET(server_socket, &writes);
    
    //设置线圈,离散输入,保持寄存器,输入寄存器个数
    mb_mapping = modbus_mapping_new(MODBUS_MAX_READ_BITS, 8,
                                    MODBUS_MAX_READ_REGISTERS, 8);
    if (mb_mapping == NULL) 
    {
        fprintf(stderr, "Failed to allocate the mapping\n");
        modbus_free(ctx);
        return -1;
    }

    //初始化线圈
    mb_mapping->tab_bits[0] = 1;
    mb_mapping->tab_bits[1] = 0;
    mb_mapping->tab_bits[2] = 1;
    mb_mapping->tab_bits[3] = 1;
    mb_mapping->tab_bits[4] = 1;
    mb_mapping->tab_bits[5] = 0;
    mb_mapping->tab_bits[6] = 1;
    mb_mapping->tab_bits[7] = 1;
    //初始离散量输入
    mb_mapping->tab_input_bits[0] = 1;
    mb_mapping->tab_input_bits[1] = 0;
    mb_mapping->tab_input_bits[2] = 1;
    mb_mapping->tab_input_bits[3] = 1;
    mb_mapping->tab_input_bits[4] = 1;
    mb_mapping->tab_input_bits[5] = 0;
    mb_mapping->tab_input_bits[6] = 1;
    mb_mapping->tab_input_bits[7] = 1;
    //初始化保持寄存器
    mb_mapping->tab_registers[0] = 1;
    mb_mapping->tab_registers[1] = 2;
    mb_mapping->tab_registers[2] = 3;
    mb_mapping->tab_registers[3] = 4;
    mb_mapping->tab_registers[4] = 5;
    mb_mapping->tab_registers[5] = 6;
    mb_mapping->tab_registers[6] = 7;
    mb_mapping->tab_registers[7] = 8;
    //初始化输入寄存器
    mb_mapping->tab_input_registers[0] = 9;
    mb_mapping->tab_input_registers[1] = 10;
    mb_mapping->tab_input_registers[2] = 11;
    mb_mapping->tab_input_registers[3] = 12;
    mb_mapping->tab_input_registers[4] = 13;
    mb_mapping->tab_input_registers[5] = 14;
    mb_mapping->tab_input_registers[6] = 15;
    mb_mapping->tab_input_registers[7] = 16;

    fprintf(stderr, "循环等待\n");
    while (1)
    {
      //检测读是否就绪(是否可读可写)的文件描述符集合
      //if (select(master_socket + 1, &reads, &writes, NULL, NULL)) 
      //{
        //延时
        //for(i=0;i<100;i++)
        //sleep(5);

        if (FD_ISSET(master_socket, &reads)) 
        { 
//          modbus_set_socket(ctx, master_socket);
          fprintf(stderr, "\n/**************************************/\n");
          //query数组清0
          memset(query, 0, sizeof(query));
          //读数据
          rc = modbus_receive(ctx, query);

          if (rc > 0) 
          {
            fprintf(stderr,"读取客户端的请求报文字节数:%d\n",rc);
            fprintf(stderr,"读取到客户端发来的请求报文:%d %d %d %d %d %d %d %d %d %d %d %d\n",
                     query[0],query[1],query[2],query[3],query[4],query[5],
                     query[6],query[7],query[8],query[9],query[10],query[11]); 
            modbus_reply(ctx, query, rc, mb_mapping);

              if (query[7] == 6) 
              {
                fprintf(stderr,"请求写服务端第%d个保持寄存器\n",query[9]+1);
                fprintf(stderr,"修改后服务端寄存器为:%d %d %d %d %d %d %d %d\n",
                      mb_mapping->tab_registers[0],mb_mapping->tab_registers[1],
                      mb_mapping->tab_registers[2],mb_mapping->tab_registers[3],
                      mb_mapping->tab_registers[4],mb_mapping->tab_registers[5],
                      mb_mapping->tab_registers[6],mb_mapping->tab_registers[7]);              
              }
            fprintf(stderr, "/**************************************/\n");
          } 
//          else if (rc  == -1) 
//          {
            //Connection closed by the client or error
//            printf("Connection closed by the client or error\n");
//            break;
//          }
        }  
      //}
    }

    //printf("Quit the loop: %s\n", modbus_strerror(error));

    modbus_mapping_free(mb_mapping);
    //if (s != -1) {
    //    close(s);
    //}
    modbus_close(ctx);
    modbus_free(ctx);
}

在这里插入图片描述
众所周知,listen会立刻返回,accept才会造成阻塞。listen函数仅由服务器端调用,主要干了两个事情:

  • 将套接字从closed转态转为listen状态;
  • 函数的第二个参数backlog指定了内核为相应套接字排队的最大连接个数。

对于每个给定的监听套接字,内核都会维护两个队列:未完成连接队列(incomplete connection queue)和已完成连接队列(completed connection queue)。而listen函数的第二个参数backlog就规定了这两个队列的大小。

在tcp三次握手中,当客户端第一次发送连接到服务端时,客户端套接字由closed转为syn_sent,当服务端收到后,这个连接就进入未完成连接队列,此时服务端套接字状态有listen转为syn_rcvd;当第三次握手客户端向服务端返回服务端syn的ack时,客户端套接字状态由syn_sent转为established,服务端收到第三次握手后,连接信息就从未完成连接队列中转移到已完成连接队列,套接字状态也从syn_rcvd转为established。
在这里插入图片描述
之后就是调用accept函数从已完成连接队列中获取连接,若该队列为空,则accept函数陷入阻塞状态,直到有新的连接过来为止。
在这里插入图片描述

解决

通过ps -a命令查找进程号
在这里插入图片描述
并使用kill命令杀死进程即可

sudo kill -9 进程号

结束命令:
-kill:通过进程ID来结束进程
killall:通过进程名字结束进程

常使用的结束进程的信号是:
在这里插入图片描述
在这里插入图片描述
或者使用下面命令,再使用kill

ps -aux | grep 进程名
/*
aux选项如下所示:
a-显示所有用户的进程
u-显示进程的用户和拥有者
x-显示不依附于终端的进程
*/

在这里插入图片描述
“root”是用户;“4021”是PID;“0.0”是%CPU-占用CPU的百分比;“0.1”是%MEM-占用内存的百分比;

缓冲区溢出(buffer overflow detected)

介绍

缓冲区溢出是一种非常普遍、非常危险的漏洞,在各种操作系统、应用软件中广泛存在。利用缓冲区溢出攻击,可以导致程序运行失败、系统宕机、重新启动等后果。更为严重的是,可以利用它执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作。

问题

C中大多数缓冲区溢出问题可以直接追溯到标准 C 库。最有害的罪魁祸首是不进行自变量检查的、有问题的字符串操作strcpy、strcat、sprintf 和 gets。一般来讲,像“避免使用 strcpy()和永远不使用gets()这样严格的规则接近于这个要求。

例如永远不要使用 gets()。该函数从标准输入读入用户输入的一行文本,它在遇到 EOF字符或换行字符之前,不会停止读入文本。也就是:gets() 根本不执行边界检查。因此,使用 gets()总是有可能使任何缓冲区溢出。作为一个替代方法,可以使用方法 fgets()。它可以做与 gets()所做的同样的事情,但它接受用来限制读入字符数目的大小参数,因此,提供了一种防止缓冲区溢出的方法。例如,不要使用以下代码:

void main()
{
    char buf[1024];
    gets(buf);
}

而使用以下代码:

#define BUFSIZE 1024
void main()
{
    char buf[BUFSIZE];
    fgets(buf, BUFSIZE, stdin);
}

避免C编程中的主要陷阱

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
参考:C语言:防止缓冲区溢出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只嵌入式爱好者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值