C++ 服务器 Linux 机器上的分段错误 - 适用于 Mac
Posted
技术标签:
【中文标题】C++ 服务器 Linux 机器上的分段错误 - 适用于 Mac【英文标题】:Segmentation Fault on C++ Server Linux Machines - Works on Mac 【发布时间】:2015-09-13 22:18:12 【问题描述】:我正在处理这个家庭作业 作业,并且在这一点上花费了相当多的时间。我已经能够让我的 Mac 完美地运行客户端和服务器,但要求说它必须在 Linux 机器上运行,这是我发现它的主要问题所在。在客户端连接到服务器后,我立即收到分段错误,并且它发生在两条 cout 行之间,这对我来说毫无意义。此时我已经花了大约 4 个小时来研究这个问题,但没有运气。任何人都可以给我任何有用的指示吗?提前致谢!
服务器.cpp
//header files
//input - output declarations included in all C programs
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <stdexcept>
#include <iostream>
//contains definitions of a number of data types used in system calls
#include <sys/types.h>
//definitions of structures needed for sockets
#include <sys/socket.h>
//in.h contains constants and structures needed for internet domain addresses
#include <netinet/in.h>
using namespace std;
int* clientNumbers = new int[50];
void initializeArray()
for(int i = 0; i < 50; i++)
clientNumbers[i] = 0;
//This function is called when a system call fails. It displays a message about the error on stderr and then aborts the program.
void error(const char *msg)
perror(msg);
exit(1);
int numberClients = 0;
bool checkNumber(int number)
for(int i = 0; i < 50; i++)
if(number == clientNumbers[i])
return false;
clientNumbers[numberClients] = number;
numberClients++;
return true;
int main(int argc, char *argv[])
pid_t childPID;
initializeArray();
//sockfd and newsockfd are file descriptors,These two variables store the values returned by the socket system call and the accept system call.
//portno stores the port number on which the server accepts connections.
int sockfd, newsockfd, portno;
//clilen stores the size of the address of the client. This is required for the accept system call.
socklen_t clilen;
//serv_addr will contain the address of the server, and cli_addr will contain the address of the client which connects to the server.
struct sockaddr_in serv_addr, cli_addr;
int n;
//create socket
//it take three arguments - address domain, type of socket, protocol (zero allows the OS to choose thye appropriate protocols based on type of socket)
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");
string portNumber;
cout << "Please Enter Port Number:";
getline(cin, portNumber);
//stoi() function can be used to convert port number from a string of digits to an integer, if your input is in the form of a string.
try
portno = stoi(portNumber);
catch(const std::invalid_argument e)
error("ERROR No Port Number Entered.");
if(portno > 65535 || portno < 1)
error("Invalid Port Number. Exiting.");
//contains a code for the address family
serv_addr.sin_family = AF_INET;
//contains the IP address of the host
serv_addr.sin_addr.s_addr = INADDR_ANY;
//contain the port number
serv_addr.sin_port = htons(portno);
//bind() system call binds a socket to an address, in this case the address of the current host and port number on which the server will run.
//three arguments, the socket file descriptor, the address to which is bound, and the size of the address to which it is bound.
if (::bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
error("ERROR on binding");
//listen system call allows the process to listen on the socket for connections.
//The first argument is the socket file descriptor, and second is number of connections that can be waiting while the process is handling a particular connection.
listen(sockfd,5);
while(1)
cout << "Server waiting...\n";
//This is where the program experiences a Segmentation Fault
cout << "Just Before Clilen";
clilen = (socklen_t) sizeof(cli_addr);
//accept() system call causes the process to block until a client connects to the server.
newsockfd = accept(sockfd,
(struct sockaddr *) &cli_addr,
&clilen);
cout << "WE MADE IT THIS FAR!";
if (newsockfd < 0)
error("ERROR on accept");
string clientNumberString;
int clientNumber = 0;
n = read(newsockfd,&clientNumberString,15);
if (n < 0) error("ERROR reading from socket\n");
clientNumber = stoi(clientNumberString);
if(checkNumber(clientNumber))
//reads from the socket into a buffer for a maximum of 255 characters
//read call uses new file descriptor, the one returned by accept()
if(fork() == 0)
cout << "Established Connection With: " << clientNumber << "\n";
while(1)
string lowerCase;
//Read in String from Client
n = read(newsockfd, &lowerCase, 255);
if(n == 0)break;
//transform(lowerCase.begin(), lowerCase.end(), lowerCase.begin(), ::toupper);
string upperCase = "";
for(int i = 0; i < lowerCase.length(); i++)
upperCase += toupper(lowerCase.at(i));
n = write(newsockfd, &upperCase, upperCase.length() + 1);
cout << "Disconnecting from Client\n";
//close connections using file descriptors
close(newsockfd);
else
n = write(newsockfd, 0, 1);
cout << "CONNECTION REFUSED: Client Number already Taken.\n";
break;
//
close(sockfd);
return 0;
运行输出示例:
./server
Please Enter Port Number:6666
Server waiting...
Segmentation fault (core dumped)
来自 GDB 的回溯:
Program received signal SIGSEGV, Segmentation fault.
__GI_____strtol_l_internal (
nptr=0x2388058 <error: Cannot access memory at address 0x2388058>,
endptr=0x7fffffffde00, base=10, group=<optimized out>,
loc=0x7ffff78ba060 <_nl_global_locale>) at ../stdlib/strtol_l.c:298
298 ../stdlib/strtol_l.c: No such file or directory.
(gdb) backtrace
#0 __GI_____strtol_l_internal (
nptr=0x2388058 <error: Cannot access memory at address 0x2388058>,
endptr=0x7fffffffde00, base=10, group=<optimized out>,
loc=0x7ffff78ba060 <_nl_global_locale>) at ../stdlib/strtol_l.c:298
#1 0x0000000000401ca1 in __gnu_cxx::__stoa<long, int, char, int> (
__convf=0x401350 <strtol@plt>, __name=0x401da9 "stoi",
__str=0x2388058 <error: Cannot access memory at address 0x2388058>,
__idx=0x0) at /usr/include/c++/4.8/ext/string_conversions.h:62
#2 0x0000000000401bab in std::stoi (__str=..., __idx=0x0, __base=10)
at /usr/include/c++/4.8/bits/basic_string.h:2825
#3 0x00000000004017fd in main (argc=1, argv=0x7fffffffdfe8) at main.cpp:143
【问题讨论】:
gdb 的回溯在哪里? 因为输出没有被刷新,你实际上不知道它在哪里崩溃。 “就在克莱伦之前”;当 seg 错误发生时,很可能坐在输出缓冲区中等待写入控制台。 “我们做到了这一点!”也可能在缓冲区中。无法判断是否通过屏幕转储进行调试。在调试输出的末尾添加<<std::endl
可能是提高看到这些消息的几率的最简单方法。
我已经添加了 gdb 回溯。感谢您对 endl 的提示,我会进行这些调整。我是 C++ 新手!
全局变量、手动动态内存和幻数是灾难的根源。使用int clientNumbers[50] = ;
创建一个包含 50 个 0 的数组。或者更好的是使用一个类来表示您使用的客户,以及一个可以根据需要增长的向量,并使用std::find
进行此搜索
我猜你在这里破坏了你的字符串的内部结构:n = read(newsockfd,&clientNumberString,15);
你需要在尝试读入它的缓冲区之前调整字符串的大小。
【参考方案1】:
string clientNumberString;
int clientNumber = 0;
n = read(newsockfd,&clientNumberString,15);
你通过读取上面的数据来丢弃你的字符串。 string
不仅仅是一个字符数组,它是一个包含指针、长度和可能的其他信息的结构。您不能只是在其地址写入任意数据并期望它能够工作。
另外,您似乎对 TCP 的工作原理有一些基本的误解:
n = read(newsockfd,&clientNumberString,15);
if (n < 0) error("ERROR reading from socket\n");
clientNumber = stoi(clientNumberString);
在第一行之后,n
,只有n
保存了您收到的字节数。那么stoi
应该如何知道要查看多少字节呢?另外,假设数字是“12”,但read
只读取“1”,因为“2”尚未发送。你会得到错误的号码。这似乎表明您根本不了解 TCP 是字节流。
【讨论】:
【参考方案2】:寻找可以发生段错误的东西,考虑函数 CheckNumber。
如果测试有可能超过 50 的计数,它将出现段错误。这是因为在函数结束时没有测试来确保 numberClients 保持在范围内。
此外,除非您为读写编写了重载,否则您的调用将无法正常工作。考虑”
std::string s;
读(袜子, &s, 10);
这应该在更好的编译器中给出警告,但不会起作用。 s 以这种方式作为 void * 传入时不会作为字符串类运行。
您可能必须提供一个字符数组:
字符[12];
读取(袜子, s, 10);
然后,如果你想从那里使用字符串类,你可以将该缓冲区移动到 std::string 中。
【讨论】:
您可能不会在搞砸 read 的 buf 参数时收到错误消息,因为它是void *
。只要您给void *
一个指针,它就会很高兴。回避他们的原因之一。以上是关于C++ 服务器 Linux 机器上的分段错误 - 适用于 Mac的主要内容,如果未能解决你的问题,请参考以下文章