gpt4 book ai didi

c++ - getaddrinfo, AI_PASSIVE - 不同的行为 windows <-> linux

转载 作者:塔克拉玛干 更新时间:2023-11-03 02:05:49 29 4
gpt4 key购买 nike

我已经修改了 http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html 中的代码(selectserver.c——一个俗气的多人聊天服务器)在 Windows 上编译。完整代码如下。我使用 gcc 版本 6.1.0(x86_64-posix-seh,由 MinGW-W64 项目构建)进行编译。我也在 Linux 上使用 gcc6.1.0 编译。

基本上,您运行它,telnet 2 次或更多次到端口 9034,并且您在一个 telnet session 中键入的任何内容都会回显到其他 telnet session (取决于系统,必须键入 Enter 在它被回显之前 - 在 Windows 上它会回显输入的每个字符)。

现在的问题:

在 Linux AMD64 或 ARM 上,我可以从本地主机和另一个系统(Windows 或 Linux)连接到它。在 Windows 上,它只适用于本地主机,我不明白为什么。事实上,hints.ai_flags = AI_PASSIVE; 被指定使得它监听所有接口(interface),如果我理解正确的话。

MSDN doc状态:

Setting the AI_PASSIVE flag indicates the caller intends to use the returned socket address structure in a call to the bind function.

When the AI_PASSIVE flag is set and pNodeName is a NULL pointer, the IP address portion of the socket address structure is set to INADDR_ANY for IPv4 addresses and IN6ADDR_ANY_INIT for IPv6 addresses.

代码如下:

hints.ai_flags = AI_PASSIVE;
if ((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0)

如何使其在 Windows 上正常运行?

它是用

编译的:

g++ -O0 -g3 -Wall -c -fmessage-length=0 -o "src\chatserver.o" "..\src\chatserver.cpp"

并链接到

g++ -mwindows -o chatserver.exe "src\chatserver.o" -lws2_32

请问我需要在代码中更改什么?

这是完整的代码:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

#ifdef __linux__
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif

#ifdef _WIN32
#include <ws2tcpip.h>

#endif

#define PORT "9034" // port we're listening on
// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
if (sa->sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); }
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
int main(void)
{
#ifdef _WIN32
WSADATA wsaData; // Initialize Winsock
int nResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (NO_ERROR != nResult) {
printf ("Error occurred while executing WSAStartup().");
}
#endif

fd_set master; // master file descriptor list
fd_set read_fds; // temp file descriptor list for select()
int fdmax; // maximum file descriptor number
int listener; // listening socket descriptor
int newfd; // newly accept()ed socket descriptor
struct sockaddr_storage remoteaddr; // client address
socklen_t addrlen;
char buf[256]; // buffer for client data
int nbytes;

char remoteIP[INET6_ADDRSTRLEN];
int yes=1; // for setsockopt() SO_REUSEADDR, below
int i, j, rv;
struct addrinfo hints, *ai, *p;
FD_ZERO(&master); // clear the master and temp sets
FD_ZERO(&read_fds);
// get us a socket and bind it
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0) {
fprintf(stderr, "selectserver: %s\n", gai_strerror(rv));
exit(1);
}
for(p = ai; p != NULL; p = p->ai_next) {
listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (listener < 0) { continue; }
// lose the pesky "address already in use" error message
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(int));
//setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, "1", sizeof(int));
if (bind(listener, p->ai_addr, p->ai_addrlen) < 0) {
close(listener);
continue;
}
break;
}
// if we got here, it means we didn't get bound
if (p == NULL) {
fprintf(stderr, "selectserver: failed to bind\n");
exit(2);
}
freeaddrinfo(ai); // all done with this
// listen
if (listen(listener, 10) == -1) {
perror("listen");
exit(3);
}
// add the listener to the master set
FD_SET(listener, &master);
// keep track of the biggest file descriptor
fdmax = listener; // so far, it's this one
// main loop
for(;;) {
read_fds = master; // copy it
if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
perror("select");
exit(4);
}
// run through the existing connections looking for data to read
for(i = 0; i <= fdmax; i++) {
if (FD_ISSET(i, &read_fds)) { // we got one!!
if (i == listener) {
// handle new connections
addrlen = sizeof remoteaddr;
newfd = accept(listener,
(struct sockaddr *)&remoteaddr,
&addrlen);
if (newfd == -1) {
perror("accept");
}
else {
FD_SET(newfd, &master); // add to master set
if (newfd > fdmax) { // keep track of the max
fdmax = newfd;
}
std::cout << "selectserver: new connection on socket " << newfd;
/*
printf("selectserver: new connection from %s on "
"socket %d\n",
inet_ntop(remoteaddr.ss_family,get_in_addr((struct sockaddr*)&remoteaddr),remoteIP, INET6_ADDRSTRLEN),newfd);
*/
}
}
else {
// handle data from a client
if ((nbytes = recv(i, buf, sizeof buf, 0)) <= 0) {
// got error or connection closed by client
if (nbytes == 0) {
// connection closed
std::cout << "selectserver: socket " << i << " hung up";
}
else {
perror("recv");
}
close(i); // bye!
FD_CLR(i, &master); // remove from master set
}
else {
// we got some data from a client
for(j = 0; j <= fdmax; j++) {
// send to everyone!
if (FD_ISSET(j, &master)) {
// except the listener and ourselves
if (j != listener && j != i) {
if (send(j, buf, nbytes, 0) == -1) {
perror("send");
}
}
}
}
}
} // END handle data from client
} // END got new incoming connection
} // END looping through file descriptors
} // END for(;;)--and you thought it would never end!
return 0;
}

最佳答案

getaddrinfo()可以返回多个IP地址。您正确地遍历了所有返回的地址,但是在第一次成功 bind() 之后就中断了循环, 然后你调用 listen()在那个单一的套接字上,不管它的套接字系列。由于您使用的是 AF_UNSPEC打电话时 getaddrinfo() , 它可能返回 BOTH INADDR_ANY对于 IPv4 AND IN6ADDR_ANY_INIT用于 IPv6。

更改您的代码以监听 getaddrinfo() 的每个 IP 地址返回,并跟踪这些套接字,以便您可以在 select() 中使用所有这些套接字环形。如果你只是想听INADDR_ANYIN6ADDR_ANY_INIT , 使用 getaddrinfo() 是没有意义的完全可以,因为您可以硬编码 socket()/bind()调用这两个地址并完全摆脱循环。使用目的getaddrinfo()给定AI_PASSIVE,以这种方式让它决定你应该听什么。提示你提供。不要对其输出做出假设。

您也不能使用 fdmax在 Windows 上,所以你需要重写你的 select()环形。 Windows 上的套接字不使用文件描述符,因此您不能简单地从 0 <= fdmax 开始循环。打电话时 FD_ISSET() , 和 select() 的第一个参数也被忽略。我建议不要将您的事件套接字描述符/句柄存储在主服务器中 fd_set首先。使用 std::list或其他合适的容器,然后动态创建一个新的 fd_set每当你需要调用select() .这将更便于跨不同平台。

尝试更像这样的东西:

#include <unistd.h>
#include <sys/types.h>

#ifdef __linux__
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define SOCKET int
#define SOCKET_ERROR -1
#define INVALID_SOCKET -1

inline int closesocket(int s) { return close(s); }
inline int getLastSocketError() { return errno; }
#endif

#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>

inline int getLastSocketError() { return WSAGetLastError(); }
#endif

#include <iostream>
#include <list>
#include <algorithm>
#include <utility>

#define PORT "9034" // port we're listening on

#ifdef _WIN32
#define SELECT_MAXFD 0
#else
#define SELECT_MAXFD fdmax+1
#endif

enum eSocketType { stListener, stClient };

struct SocketInfo
{
SOCKET sckt;
eSocketType type;
};

SocketInfo makeSocketInfo(SOCKET sckt, eSocketType type) {
SocketInfo info;
info.sckt = sckt;
info.type = type;
return info;
}

// get sockaddr, IPv4 or IPv6:
void* get_in_addr(struct sockaddr *sa)
{
if (sa->sa_family == AF_INET) {
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
std::list<SocketInfo> master; // socket descriptors
std::list<SocketInfo>::iterator i, j;
SOCKET sckt, newsckt; // socket descriptors

fd_set read_fds; // temp file descriptor list for select()
#ifndef _WIN32
int fdmax; // maximum file descriptor number
#endif

struct sockaddr_storage remoteaddr; // client address
socklen_t addrlen;
char buf[256]; // buffer for client data
int nbytes;

char ipAddr[INET6_ADDRSTRLEN];
int yes = 1; // for setsockopt() SO_REUSEADDR, below
int rv;
struct addrinfo hints, *ai, *p;

#ifdef _WIN32
WSADATA wsaData; // Initialize Winsock
rv = WSAStartup(MAKEWORD(2,2), &wsaData);
if (NO_ERROR != rv) {
std::cerr << "WSA startup failed, error: " << rv << std::endl;
return 1;
}
#endif

// get us the listening sockets and bind them

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;

rv = getaddrinfo(NULL, PORT, &hints, &ai);
if (rv != 0) {
std::cerr << "selectserver: getaddrinfo failed, error: " << gai_strerror(rv) << std::endl;
return 2;
}

for(p = ai; p != NULL; p = p->ai_next) {
sckt = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (INVALID_SOCKET == sckt) {
std::cerr << "selectserver: socket failed, error: " << getLastSocketError() << std::endl;
continue;
}

// lose the pesky "address already in use" error message
setsockopt(sckt, SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(int));
//setsockopt(sckt, SOL_SOCKET, SO_REUSEADDR, "1", sizeof(int));
if (bind(sckt, p->ai_addr, p->ai_addrlen) < 0) {
std::cerr << "selectserver: bind failed, error: " << getLastSocketError() << std::endl;
closesocket(sckt);
continue;
}

// listen
if (listen(sckt, 10) < 0) {
std::cerr << "selectserver: listen failed, error: " << getLastSocketError() << std::endl;
closesocket(sckt);
continue;
}

/*
std::cout << "selectserver: listening on IP " << inet_ntop(p->ai_family, get_in_addr(p->ai_addr), ipAddr, sizeof(ipAddr)) << ", socket " << sckt << std::endl,
*/

// add the listener to the master list
master.push_back(makeSocketInfo(sckt, stListener));
}

freeaddrinfo(ai); // all done with this

// if we got here, it means we didn't get bound
if (master.empty()) {
std::cerr << "selectserver: failed to bind" << std::endl;
return 3;
}

// main loop
while (1) {
#ifndef _WIN32
fdmax = 0;
#endif

FD_ZERO(&read_fds);
for (i = master.begin(); i != master.end(); ++i) {
sckt = i->sckt;
FD_SET(sckt, &read_fds);
#ifndef _WIN32
fdmax = std::max(fdmax, sckt);
#endif
}

if (select(SELECT_MAXFD, &read_fds, NULL, NULL, NULL) < 0) {
std::cerr << "select failed, error: " << getLastSocketError() << std::endl;
return 4;
}

// run through the existing connections looking for data to read

for(i = master.begin(); i != master.end(); ) {
sckt = i->sckt;

if (!FD_ISSET(sckt, &read_fds)) {
++i;
continue;
}

// we got one!!
if (stListener == i->type) {
// handle a new connection
addrlen = sizeof(remoteaddr);
newsckt = accept(sckt, (struct sockaddr *)&remoteaddr, &addrlen);
if (INVALID_SOCKET == newsckt) {
std::cerr << "accept failed on socket " << sckt << ", error: " << getLastSocketError() << std::endl;
}
else {
master.push_back(makeSocketInfo(newsckt, stClient)); // add to master list

std::cout << "selectserver: new connection, socket " << newsckt << std::endl;
/*
std::cout << "selectserver: new connection from " << inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), ipAddr, sizeof(ipAddr)) << ", socket " << newsckt << std::endl,
*/
}
}
else {
// handle data from a client
nbytes = recv(sckt, buf, sizeof(buf), 0);
if (nbytes <= 0) {
// got error or connection closed by client
if (nbytes == 0) {
// connection closed
std::cout << "selectserver: socket " << sckt << " disconnected" << std::endl;
}
else {
std::cerr << "selectserver: recv failed on socket " << sckt << ", error: " << getLastSocketError() << std::endl;
}
closesocket(sckt); // bye!
i = master.erase(i); // remove from master list
continue;
}

// send to everyone!
// except a listener and ourselves

for(j = master.begin(); j != master.end(); ) {
if ((j->sckt != sckt) && (stClient == j->type)) {
if (send(j->sckt, buf, nbytes, 0) < 0) {
std::cerr << "selectserver: send failed on socket " << j->sckt << ", error: " << getLastSocketError() << std::endl;
closesocket(j->sckt); // bye!
j = master.erase(j); // remove from master list
continue;
}
}
++j;
}
}
++i;
}
}

for(i = master.begin(); i != master.end(); ++i) {
closesocket(i->sckt);
}

#ifdef _WIN32
WSACleanup();
#endif

return 0;
}

如果您在支持双栈套接字的系统(如 Windows)上运行代码,您可以更改 AF_UNSPECAF_INET6 (或者只是硬编码 socket()/bind() 而不使用 getaddrinfo() )在 IN6ADDR_ANY_INIT 上只创建 IPv6 监听器, 然后禁用 IPV6_V6ONLY他们的套接字选项。这将允许 IPv6 监听套接字接受 IPv4 和 IPv6 客户端,减少您需要创建的监听套接字的数量。

关于c++ - getaddrinfo, AI_PASSIVE - 不同的行为 windows <-> linux,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41609525/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com