我正在尝试编写一个不可知的回显服务器,它可以同时接受IPv4和IPv6连接。我正在使用addrinfo结构,使用getaddrinfo设置。
Ipv4连接没有问题,而我无法获得有效的ipV6连接。我认为我的问题可能是由于错误的getaddrinfo参数造成的,但我无法看到我出错的地方。
这是我的代码
客户端.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int simpleSocket = 0, simplePort = 0,returnStatus = 0, n;
char buffer[1024] = "";
struct hostent *hostinfo;
struct addrinfo simpleServer, *res;
if (3 != argc) {
fprintf(stderr, "Usage: %s <server> <port>n", argv[0]);
exit(1);
}
simplePort = atoi(argv[2]);
memset(&simpleServer, 0, sizeof simpleServer);
simpleServer.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever
simpleServer.ai_socktype = SOCK_STREAM;
simpleServer.ai_flags = AI_PASSIVE; // fill in my IP for me
returnStatus = getaddrinfo(argv[1], argv[2], &simpleServer, &res);
simpleSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
char *s = NULL;
switch(res->ai_addr->sa_family) {
case AF_INET: {
struct sockaddr_in *addr_in = (struct sockaddr_in *)res;
s = malloc(INET_ADDRSTRLEN);
inet_ntop(AF_INET, &(addr_in->sin_addr), s, INET_ADDRSTRLEN);
returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
break;
}
case AF_INET6: {
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)res;
s = malloc(INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, &(addr_in6->sin6_addr), s, INET6_ADDRSTRLEN);
returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
break;
}
default:
break;
}
fprintf(stdout, "IP address: %sn", s);
returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
fprintf(stdout, "Type a message n");
memset(buffer, ' ', strlen(buffer));
fgets(buffer, sizeof(buffer), stdin);
returnStatus = write(simpleSocket, buffer, sizeof(buffer));
memset(&buffer, ' ', sizeof(buffer));
fprintf(stdout, "Waiting server..n");
returnStatus = read(simpleSocket, buffer, sizeof(buffer));
fprintf(stdout, "Message: %sn", buffer);
close(simpleSocket);
return 0;
}
服务器.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int simpleSocket = 0, simplePort = 0, returnStatus = 0, check = 1, n;
char buffer[1024];
struct addrinfo simpleServer, *res;
if (2 != argc) {
fprintf(stderr, "Usage: %s <port>n", argv[0]);
exit(1);
}
simplePort = atoi(argv[1]);
memset(&simpleServer, 0, sizeof simpleServer);
simpleServer.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever
simpleServer.ai_socktype = SOCK_STREAM;
simpleServer.ai_flags = AI_PASSIVE; // fill in my IP for me
getaddrinfo(NULL, argv[1], &simpleServer, &res);
simpleSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
returnStatus =bind(simpleSocket, res->ai_addr, res->ai_addrlen);
returnStatus = listen(simpleSocket, 5);
struct addrinfo clientName = { 0 };
int clientNameLength = sizeof(clientName);
int simpleChildSocket = 0;
while (1) {
while (1) {
simpleChildSocket = accept(simpleSocket,(struct sockaddr *)&clientName, &clientNameLength);
fprintf(stdout,"Waiting..n");
memset(&buffer, ' ', sizeof(buffer));
returnStatus = read(simpleChildSocket, buffer, sizeof(buffer));
fprintf(stdout, "Message: %sn", buffer);
write(simpleChildSocket, buffer, sizeof(buffer));
}
}
close(simpleChildSocket);
close(simpleSocket);
return 0;
}
正如@JoachimPileborg注释中提到的,问题是您的服务器代码没有为getaddrinfo()
为您提供的每个地址打开侦听套接字。 您在simpleServer
结构中指定AF_UNSPEC
,因此getaddrinfo()
将为您提供 IPv4 和 IPv6 地址的列表。您正在仅为该列表中的第一个条目(恰好是 IPv4 地址)创建侦听套接字。 这就是您的 IPv4 客户端成功而您的 IPv6 客户端失败的原因。 您需要遍历列表,为每个条目创建一个单独的侦听套接字。
您还犯了其他错误,例如每次调用accept()
时都没有重置clientNameLength
变量,在应该使用sockaddr_storage
时使用addrinfo
作为clientName
缓冲区,以及不注意read()
的返回值。
尝试更多类似的东西:
include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#define MAX_SERVERS 10
#define MAX_CLIENTS 50
#define MAX_SOCKETS (MAX_SERVERS + MAX_CLIENTS)
int main(int argc, char *argv[])
{
int simpleSocket, simplePort, returnStatus, n, m;
char buffer[1024];
pollfd simpleSockets[MAX_SOCKETS];
int numSockets = 0, numServers = 0;
struct addrinfo simpleServer, *res, *addr;
if (2 != argc)
{
fprintf(stderr, "Usage: %s <port>n", argv[0]);
exit(1);
}
simplePort = atoi(argv[1]);
memset(&simpleServer, 0, sizeof simpleServer);
simpleServer.ai_family = AF_UNSPEC; // use IPv4 and/or IPv6, whatever is available
simpleServer.ai_socktype = SOCK_STREAM;
simpleServer.ai_flags = AI_PASSIVE; // fill in my IP for me
if (0 != getaddrinfo(NULL, argv[1], &simpleServer, &res))
{
fprintf(stderr, "getaddrinfo failedn");
exit(1);
}
addr = res;
while (NULL != addr)
{
simpleSocket = socket(res->ai_family, addr->ai_socktype, addr->ai_protocol);
if (-1 == simpleSocket)
{
fprintf(stderr, "socket failedn");
}
else
{
returnStatus = bind(simpleSocket, addr->ai_addr, addr->ai_addrlen);
if (0 == returnStatus)
returnStatus = listen(simpleSocket, 5);
if (0 == returnStatus)
{
simpleSockets[numSockets].fd = simpleSocket;
simpleSockets[numSockets].events = POLLIN;
simpleSockets[numSockets].revents = 0;
++numSockets;
++numServers;
if (MAX_SERVERS == numServers)
break;
}
else
{
fprintf(stderr, "bind/listen failedn");
close(simpleSocket);
}
}
addr = addr->next;
}
freeaddrinfo(res);
if (0 == numServers)
{
fprintf(stderr, "no servers are listeningn");
exit(1);
}
struct sockaddr_storage clientName;
int clientNameLength;
while (1)
{
returnStatus = poll(simpleSockets, numSockets, -1);
if (-1 == returnStatus)
{
fprintf(stderr, "poll failedn");
exit(1);
}
if (0 == returnStatus)
continue;
for (n = 0; n < numSockets; ++n)
{
if (simpleSockets[n].revents & POLLIN)
{
if (n < numServers)
{
clientNameLength = sizeof(clientName);
simpleSocket = accept(simpleSockets[n].fd, (struct sockaddr *)&clientName, &clientNameLength);
if (-1 == simpleSocket)
{
fprintf(stderr, "accept failedn");
continue;
}
for (m = numServers; m < numSockets; ++m)
{
if (-1 == simpleSockets[m].fd)
{
simpleSockets[m].fd = simpleSocket;
simpleSockets[m].events = POLLIN;
simpleSockets[m].revents = 0;
simpleSocket = -1;
break;
}
}
if ((-1 != simpleSocket) && (MAX_SOCKETS > numSockets))
{
simpleSockets[numSockets].fd = simpleSocket;
simpleSockets[numSockets].events = POLLIN;
simpleSockets[numSockets].revents = 0;
++numSockets;
simpleSocket = -1;
}
if (-1 != simpleSocket)
{
fprintf(stderr, "Too many clients connectedn");
close(simpleSocket);
}
else
fprintf(stdout, "Client connectedn");
}
else
{
returnStatus = read(simpleSockets[n].fd, buffer, sizeof(buffer));
if (0 >= returnStatus)
{
if (0 > returnStatus)
fprintf(stdout, "Client error, disconnectingn");
else
fprintf(stdout, "Client disconnectedn");
close(simpleSockets[n].fd);
simpleSockets[n].fd = -1;
simpleSockets[n].events = 0;
simpleSockets[n].revents = 0;
continue;
}
fprintf(stdout, "Message: %.*sn", returnStatus, buffer);
write(simpleSockets[n].fd, buffer, returnStatus);
}
}
if (simpleSockets[n].revents & (POLLERR|POLLHUP|POLLNVAL))
{
if (simpleSockets[n].revents & POLLHUP)
fprintf(stdout, "Client disconnectedn");
else if (n >= numServers)
fprintf(stdout, "Client error, disconnectingn");
else
fprintf(stdout, "Server error, closingn");
close(simpleSockets[n].fd);
simpleSockets[n].fd = -1;
simpleSockets[n].events = 0;
simpleSockets[n].revents = 0;
}
}
}
for (n = 0; n < numSockets; ++n)
{
if (-1 != simpleSockets[n].fd)
close(simpleSockets[n].fd);
}
return 0;
}
话虽如此,如果您在支持双堆栈套接字的平台上运行,则您的服务器根本不需要创建任何 IPv4 侦听套接字。 它只能创建 IPv6 套接字,然后禁用其IPV6_V6ONLY
选项。 这将允许他们同时接受 IPv4 和 IPv6 客户端。 accept()
返回的客户端地址将告诉您 IPv4 或 IPv6 客户端是否已连接。
memset(&simpleServer, 0, sizeof simpleServer);
simpleServer.ai_family = AF_INET6;
simpleServer.ai_socktype = SOCK_STREAM;
simpleServer.ai_flags = AI_PASSIVE;
if (0 != getaddrinfo(NULL, argv[1], &simpleServer, &res))
{
fprintf(stderr, "getaddrinfo failedn");
exit(1);
}
addr = res;
while (NULL != addr)
{
simpleSocket = socket(res->ai_family, addr->ai_socktype, addr->ai_protocol);
if (-1 == simpleSocket)
{
fprintf(stderr, "socket failedn");
}
else
{
n = 0;
returnStatus = setsockopt(simpleSocket, IPPROTO_IPV6, IPV6_V6ONLY, &n, sizeof(n));
...
}
addr = addr->next;
}
...
client.c 代码有缺陷,它将完整的 addrinfo 存储在sockaddr_in结构中。RES 属于 addrinfo
型 (http://man7.org/linux/man-pages/man3/getaddrinfo.3.html)
struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
socklen_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
};
所以而不是
struct sockaddr_in *addr_in = (struct sockaddr_in *)res;
它应该是 struct sockaddr_in *addr_in = (struct sockaddr_in *)res->ai_addr;
喜欢
case AF_INET: {
struct sockaddr_in *addr_in = (struct sockaddr_in *)res->ai_addr;
s = malloc(INET_ADDRSTRLEN);
inet_ntop(AF_INET, &(addr_in->sin_addr), s, INET_ADDRSTRLEN);
returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
break;
}
case AF_INET6: {
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)res->ai_addr;
s = malloc(INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, &(addr_in6->sin6_addr), s, INET6_ADDRSTRLEN);
returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
break;
}
在此修改后,代码按预期工作。