端口占用(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语言:防止缓冲区溢出