Related
So I've been creating a winsock server/client in UE4. I can get the client to connect to the server however once the client sends the first message it seems to close the socket, preventing any further messages to be sent to the server. It also seems like the server is doing the same thing when sending data.
client
// Convert IP & port to standard lib
const std::string IP = std::string(TCHAR_TO_UTF8((*GameInstance->GetIPAddress())));
const std::string PORT = std::string(TCHAR_TO_UTF8(*GameInstance->GetPort()));
// Set the version of WSA we are using
auto Version = MAKEWORD(2, 2);
WSAData WSData;
struct addrinfo* Result = nullptr, * ptr = nullptr, hints;
int iResult; // Store Initializing results
std::string message; // Define a message to send to the server
UE_LOG(LogTemp, Log, TEXT("Starting Client"));
// Initialize WinSock
iResult = WSAStartup(Version, &WSData); // Start winsock
if(iResult != 0)
{
UE_LOG(LogTemp, Error, TEXT("Failed to initialize winsock"));
return ECreateConnectionFlag::WINSOCK_FAILED;
}
UE_LOG(LogTemp, Log, TEXT("Initialized WinSock"));
// Setup hints
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
UE_LOG(LogTemp, Log, TEXT("Connecting"));
// Get the address details
iResult = getaddrinfo(IP.c_str(), PORT.c_str(), &hints, &Result);
if(iResult != 0)
{
UE_LOG(LogTemp, Error, TEXT("Error getting address info from the server"));
WSACleanup();
return 0;
}
// Connect the player
for(ptr = Result; ptr != nullptr; ptr->ai_next)
{
GameInstance->SetPlayerSocket(socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol));
if(GameInstance->GetLoggedInPlayer().PlayerSocket == INVALID_SOCKET)
{
UE_LOG(LogTemp, Error, TEXT("Failed to create socket"));
WSACleanup();
return 0;
}
iResult = connect(GameInstance->GetLoggedInPlayer().PlayerSocket, ptr->ai_addr, ptr->ai_addrlen);
if(iResult == SOCKET_ERROR)
{
closesocket(GameInstance->GetLoggedInPlayer().PlayerSocket);
GameInstance->SetPlayerSocket(INVALID_SOCKET);
continue;
}
break;
}
freeaddrinfo(Result); // Release Address information as it's no longer required
// Ensure the socket is valid
if(GameInstance->GetLoggedInPlayer().PlayerSocket == INVALID_SOCKET)
{
UE_LOG(LogTemp, Error, TEXT("Unable to connect to server..."));
WSACleanup();
return 0;
}
// WE ARE CONNECTED
/* CONNECT AND SEND USERNAME */
FString SignInMessage = FString("Username-" + GameInstance->GetLoggedInPlayer().Username);
std::string ConnectionMessage = std::string(TCHAR_TO_UTF8(*SignInMessage));
iResult = send(GameInstance->GetLoggedInPlayer().PlayerSocket, ConnectionMessage.c_str(), (int)strlen(ConnectionMessage.c_str()), 0);
if(iResult <= 0)
{
int error = WSAGetLastError();
UE_LOG(LogTemp, Error, TEXT("Failed to send message: %d"), error);
return 0;
}
while(bRunThread)
{
UE_LOG(LogTemp, Log, TEXT("Receiving Data"));
/* DISCONNECT FROM SERVER */
const std::string msg = "Hello World";
iResult = send(GameInstance->GetLoggedInPlayer().PlayerSocket, msg.c_str(), (int)strlen(msg.c_str()), 0);
if(iResult <= 0)
{
int error = WSAGetLastError();
UE_LOG(LogTemp, Error, TEXT("Failed to send message: %d"), error);
return 0;
}
FPlatformProcess::Sleep(1.0f);
}
/* DISCONNECT FROM SERVER */
const std::string DisconnectMsg = "Disconnect";
iResult = send(GameInstance->GetLoggedInPlayer().PlayerSocket, DisconnectMsg.c_str(), (int)strlen(DisconnectMsg.c_str()), 0);
if(iResult <= 0)
{
int error = WSAGetLastError();
UE_LOG(LogTemp, Error, TEXT("Failed to send message: %d"), error);
return 0;
}
UE_LOG(LogTemp, Warning, TEXT("Client Disconnected"));
closesocket(GameInstance->GetLoggedInPlayer().PlayerSocket);
WSACleanup();
return 0;
}
server
WSAData wsa;
struct addrinfo hints; // Server Hint details
struct addrinfo* server = NULL; // Address info of the server
SOCKET serverSocket = INVALID_SOCKET; // Server Listening Socket
PlayerArray* Players = new PlayerArray(); // Reference to all the players in the server
LobbyArray* Lobbies = new LobbyArray(); // Reference to all the lobbies in the server
// Initialize the winsock library
std::cout << "Initializing WinSock..." << std::endl;
int WSA_Init = WSAStartup(MAKEWORD(2, 2), &wsa);
if (WSA_Init != 0)
{
std::cerr << "Error Initializing Winsock";
WSACleanup();
return;
}
else
{
std::cout << "Winsock Initialized" << std::endl;
}
// Setup Hints
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// Setup the server
std::cout << "Setting up Server" << std::endl;
getaddrinfo(IP_ADDRESS, PORT, &hints, &server);
// Create the listening socket
std::cout << "Creating Listening socket" << std::endl;
serverSocket = socket(server->ai_family, server->ai_socktype, server->ai_protocol);
if (serverSocket == INVALID_SOCKET)
{
std::cerr << "Failed creating listening socket" << std::endl;
WSACleanup();
return;
}
else
{
std::cout << "Created listen socket" << std::endl;
}
// Set the socket to be TCP
setsockopt(serverSocket, IPPROTO_TCP, TCP_NODELAY, &OPTION_VALUE, sizeof(int));
// Bind the socket
std::cout << "Binding Socket..." << std::endl;
bind(serverSocket, server->ai_addr, (int)server->ai_addrlen);
// Start the server
std::cout << "Server has started & is listening..." << std::endl;
listen(serverSocket, SOMAXCONN);
while (true)
{
std::cout << "Players Connected: " << Players->Count() << std::endl;
SOCKET Incoming = INVALID_SOCKET; // Define a socket for anything incoming
Incoming = accept(serverSocket, NULL, NULL); // Accept the incoming message from the socket
// If the socket is not valid than continue through the loop
if (Incoming == INVALID_SOCKET)
{
std::cout << "Invalid Socket" << std::endl;
continue;
}
else
{
std::cout << "Valid Socket" << std::endl;
}
char tempmsg[DFT_BUFLEN] = ""; // Define a temp msg to store the message from the client
int received = recv(Incoming, tempmsg, DFT_BUFLEN, 0); // Receive a message from the client
std::string convertedMessage = tempmsg;
// Check that the received message is from a valid socket
if (received != SOCKET_ERROR)
{
std::string message = tempmsg; // Assing the temp message to a string to split
if (convertedMessage == "Disconnect")
{
Players->RemovePlayer(Incoming);
std::cout << "Player Disconnected..." << std::endl;
continue;
}
else
{
std::cout << tempmsg << std::endl; // === DEBUG ===
// Split the string
char* next_split;
char* split_string = strtok_s(tempmsg, "-", &next_split);
std::string FirstMsg = split_string;
if (FirstMsg == "Username")
{
std::cout << next_split << " Has joined the server" << std::endl; // Server message
// Get the player that we want to set the username to
// Create the player and add it to the server list
Player* NewPlayer = new Player();
Players->AddPlayer(NewPlayer);
NewPlayer->SetUsername(next_split); // Set the usernames
continue;
}
else if (split_string == "Lobby")
{
if (next_split == "Create")
{
Lobby* NewLobby = Lobbies->CreateLobby(); // Create a new lobby
Player* SocketPlayer = Players->GetPlayerBySocket(Incoming); // Get the player creating it by socket
// ensure that the player is valid, if so add the player to the lobby
// Otherwise send an error message to the console.
if (SocketPlayer != nullptr)
{
NewLobby->AddPlayerToLobby(SocketPlayer);
}
else
{
std::cerr << "Failed Locate player to add to lobby" << std::endl;
}
}
else if (next_split == "Destroy")
{
// TODO: Destroy Specific lobby
}
continue;
}
else
{
std::cout << "Error Reading Message" << std::endl;
}
}
}
else
{
std::cerr << "Socket Error when recieving message" << std::endl;
}
}
// Clean up the server
delete Players;
delete Lobbies;
closesocket(serverSocket);
WSACleanup();
return;
Console output after disconnecting
You are making a fundamental TCP mistake. TCP is a stream protocol., its only gurantees are
the bytes you send will be received in the same order they were sent
they will be received only once
BUT there are no 'messages' or 'records' in TCP. You can send a 100 byte message and the other end can receive
one 100 byte message
25 4 byte messages
100 1 byte messages
one 25, one 12, one 3, and one 60 (hope my math is correct)
So in the receive logic you must do this
char buffer[1000]; // or whatever
int length = ????;
char* bptr = buffer;
while(length > 0){
int recvLen = recv(sock, bptr, length,0);
if (recvLen < 1){
// error - disconnect or other failure
break;
}
bptr += recvLen;
length -= recvLen;
}
Ie keep pulling data till you have the whole message
BUT this means you need to know the messages length in advance. So either
send a well known sized length first
send fixed length messages
Or you can have a recognizable termination sequence- ie 10 byte of FF means end of message (see crlfcrlf at end of HTTP get for example)
The first option is the most robust (send length then data)
I code a small client for IPv4 / IPv6 with a hostname resolver.
For IPv4 and resolver it's fine but not with IPv6 when connect() I have a problem WSAGetLastError() say WSAEAFNOSUPPORT.
I have a switch all structures (AF_INET -> AF_INET6, SOCKADDR_IN -> SOCKADDR_IN6) to IPv6 versions.
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
printf("Simple_Client IPv4 & IPv6\n\n");
// Initiates Winsock
WSADATA WSAData;
WSAStartup(MAKEWORD(2, 0), &WSAData);
// Get Parameters IP/PORT and request
std::string str_HOSTNAME = "mirror.neostrada.nl";
int PORT = 21;
// RESOLVE IP
BOOL is_IPv6 = FALSE;
std::string str_dest_ip = "";
addrinfo hints = { 0 };
hints.ai_flags = AI_ALL;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
addrinfo * pResult;
getaddrinfo(str_HOSTNAME.c_str(), NULL, &hints, &pResult);
if (pResult == NULL)
{
printf("pResult error\n");
return -1;
}
if (pResult->ai_family == AF_INET)
{
printf("getaddrinfo = AF_INET (IPv4)\n");
is_IPv6 = FALSE;
}
if (pResult->ai_family == AF_INET6)
{
printf("getaddrinfo = AF_INET6 (IPv6)\n");
is_IPv6 = TRUE;
}
char str[128];
memset(str, 0, sizeof(str));
if (is_IPv6 == FALSE) // IPv4
{
if (inet_ntop(AF_INET, &(*((ULONG*)&(((sockaddr_in*)pResult->ai_addr)->sin_addr))), str, INET_ADDRSTRLEN))
str_dest_ip = char_to_string(str, strlen(str)); // Copy char in std::string
else
printf("inet_ntop error\n");
}
if (is_IPv6 == TRUE) // IPv6
{
if (inet_ntop(AF_INET6, &(*((ULONG*)&(((sockaddr_in6 *)pResult->ai_addr)->sin6_addr))), str, INET6_ADDRSTRLEN))
str_dest_ip = char_to_string(str, strlen(str)); // Copy char in std::string
}
printf("%s : %s | Port : %i\n", is_IPv6 ? "IPv6" : "IPv4", str_dest_ip.c_str(), PORT);
// Connect to the HOSTNAME
SOCKET sock;
if (is_IPv6 == TRUE)
{
SOCKADDR_IN6 sin;
sin.sin6_family = AF_INET6;
if(inet_pton(sin.sin6_family, str_dest_ip.c_str(), &sin) != 1)
printf("ERROR inet_pton %i\n", WSAGetLastError());
sin.sin6_port = htons(PORT);
sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET)
return -2;
if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) != SOCKET_ERROR)
{
printf("Connect Success to %s | PORT : %i\n", str_dest_ip.c_str(), PORT);
}
else
{
printf("ERROR connect to %s | PORT : %i : %i\n", str_dest_ip.c_str(), PORT, WSAGetLastError());
Sleep(10000);
return -2;
}
}
char buf[1024] = { 0 };
int size_recv = recv(sock, buf, sizeof(buf), 0);
printf("SIZE RECV = %i | DATA RECV = %s\n", size_recv, char_to_string(buf, size_recv).c_str());
WSACleanup();
getchar();
return 0;
}
If somebody have a idea, thanks for reading.
The problem is here:
inet_pton(sin.sin6_family, str_dest_ip.c_str(), &sin)
This writes the IPv6 address on top of the sin6_family field, damaging the whole structure.
It should be:
inet_pton(sin.sin6_family, str_dest_ip.c_str(), &sin.sin6_addr)
It's also a good idea to zero-initialize the whole sin structure in the beginning because it has more fields than you're filling in.
You are not using getaddrinfo() correctly.
For one thing, getaddrinfo() returns an error code that you are ignoring.
For another thing, getaddrinfo() returns a linked list that potentially contains multiple addresses in a mix of IPv4 and/or IPv6, due to your use of AF_UNSPEC. If you are only interested in IPv6, set hints.ai_family to AF_INET6 rather than AF_UNSPEC.
But either way, a given hostname may have multiple IPs associated with it, and they might not all be reachable from your location, so you should be connect()'ing to each address in the list, either one at a time or in parallel, until one of them succeeds.
Also, there is no need to use inet_pton() in this situation at all (which you are not using correctly, as explained by #rustyx's answer). getaddrinfo() returns fully populated sockaddr_in(6) structs that you can pass as-is to connect().
Try something more like this instead:
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <string>
#pragma comment(lib, "ws2_32.lib")
std::string addr_to_str(addrinfo *addr)
{
char str[128];
switch (addr->ai_family)
{
case AF_INET: // IPv4
{
if (inet_ntop(AF_INET, &(((sockaddr_in*)(addr->ai_addr))->sin_addr), str, INET_ADDRSTRLEN))
return str;
ret = WSAGetLastError();
break;
}
case AF_INET6: // IPv6
{
if (inet_ntop(AF_INET6, &(((sockaddr_in6*)(addr->ai_addr))->sin6_addr), str, INET6_ADDRSTRLEN))
return str;
ret = WSAGetLastError();
break;
}
default:
ret = WSAEAFNOSUPPORT;
break;
}
std::cerr << "inet_ntop error: " << ret << std::endl;
return "";
}
int main()
{
std::cout << "Simple_Client IPv4 & IPv6" << std::endl << std::endl;
// Initiates Winsock
WSADATA WSAData;
int ret = WSAStartup(MAKEWORD(2, 0), &WSAData);
if (ret != 0)
{
std::cerr << "WSAStartup error: " << ret << std::endl;
return -1;
}
// Get Parameters IP/PORT and request
std::string str_HOSTNAME = "mirror.neostrada.nl";
int PORT = 21;
// RESOLVE IP
addrinfo hints = { 0 };
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
addrinfo *pResult = NULL;
ret = getaddrinfo(str_HOSTNAME.c_str(), std::to_string(PORT).c_str(), &hints, &pResult);
if (ret != 0)
{
std::cerr << "getaddrinfo error: " << ret << std::endl;
WSACleanup();
return -1;
}
// Log the IPs
bool has_IPv4 = false;
bool has_IPv6 = false;
for (addrinfo *addr = pResult; addr != NULL; addr = addr->ai_next)
{
switch (addr->ai_family)
{
case AF_INET: // IPv4
{
has_IPv4 = true;
std::cout << "IPv4 : " << addr_to_str(addr);
break;
}
case AF_INET6: // IPv6
{
has_IPv6 = true;
std::cout << "IPv6 : " << addr_to_str(addr);
break;
}
}
}
// Connect to the HOSTNAME
SOCKET sock = INVALID_SOCKET;
if (has_IPv6)
{
// try IPv6 first...
for (addrinfo *addr = pResult; addr != NULL; addr = addr->ai_next)
{
if (addr->ai_family != AF_INET6)
continue;
std::cout << "Connecting to IPv6 : " << addr_to_str(addr) << " | Port : " << PORT << std::endl;
sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (sock == INVALID_SOCKET)
{
ret = WSAGetLastError();
std::cerr << "socket error: " << ret << std::endl;
continue;
}
if (connect(sock, addr->ai_addr, addr->ai_addrlen) == SOCKET_ERROR)
{
ret = WSAGetLastError();
std::cerr << "connect error: " << ret << std::endl;
closesocket(sock);
sock = INVALID_SOCKET;
continue;
}
break;
}
}
if ((sock == INVALID_SOCKET) && (has_IPv4))
{
// try IPv4 next...
for (addrinfo *addr = pResult; addr != NULL; addr = addr->ai_next)
{
if (addr->ai_family != AF_INET)
continue;
std::cout << "Connecting to IPv4 : " << addr_to_str(addr) << " | Port : " << PORT << std::endl;
sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (sock == INVALID_SOCKET)
{
ret = WSAGetLastError();
std::cerr << "socket error: " << ret << std::endl;
continue;
}
if (connect(sock, addr->ai_addr, addr->ai_addrlen) == SOCKET_ERROR)
{
ret = WSAGetLastError();
std::cerr << "connect error: " << ret << std::endl;
closesocket(sock);
sock = INVALID_SOCKET;
continue;
}
break;
}
}
freeaddrinfo(pResult);
if (sock == INVALID_SOCKET)
{
WSACleanup();
return -2;
}
std::cout << "Connect Successful" << std::endl;
char buf[1024];
int size_recv = recv(sock, buf, sizeof(buf), 0);
if (size_recv == SOCKET_ERROR)
{
ret = WSAGetLastError();
std::cerr << "recv error: " << ret << std::endl;
}
else
{
std::cout << "SIZE RECV = " << size_recv;
if (size_recv > 0)
{
std::cout << " | DATA RECV = ";
std::cout.write(buf, size_recv);
}
std::cout << std::endl;
}
closesocket(sock);
WSACleanup();
std::cin.get();
return 0;
}
I have adapted the code from http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html (selectserver.c -- a cheezy multiperson chat server) to compile on Windows. The complete code follows below. I compile using gcc version 6.1.0 (x86_64-posix-seh, Built by MinGW-W64 project). I compile using gcc6.1.0 on Linux, too.
Basically, you run it, telnet 2 or more times to port 9034, and whatever you type in one telnet session gets echoed to the other telnet sessions (depending on the system, one has to type Enter before it gets echoed - on Windows it echoes every character typed).
Now the problem :
On Linux AMD64 or ARM, I can connect to it from localhost and from another system, be that Windoes or Linux. On Windows, it only works on localhost, and I fail to understand why. The fact that hints.ai_flags = AI_PASSIVE; is specified makes it listen on all interfaces, if I understand things correctly.
The MSDN doc states:
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.
The code reads :
hints.ai_flags = AI_PASSIVE;
if ((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0)
How do I make this behave correctly on Windows?
It is compiled with :
g++ -O0 -g3 -Wall -c -fmessage-length=0 -o "src\chatserver.o" "..\src\chatserver.cpp"
and linked with
g++ -mwindows -o chatserver.exe "src\chatserver.o" -lws2_32
What do I need to change in the code please?
This is the complete code:
#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() can return multiple IP addresses. You are correctly looping through all of the returned addresses, but you are breaking the loop after the first successful bind(), and then you are calling listen() on that one single socket, regardless of its socket family. Since you are using AF_UNSPEC when calling getaddrinfo(), it is possible that it is returning BOTH INADDR_ANY for IPv4 AND IN6ADDR_ANY_INIT for IPv6.
Change your code to listen on every IP address that getaddrinfo() returns, and to keep track of those sockets so you can use all of them in your select() loop. If you just wanted to listen on either INADDR_ANY or IN6ADDR_ANY_INIT, there would be no point in using getaddrinfo() at all, as you could just hard-code the socket()/bind() calls for those two addresses and get rid of the loop altogether. The purpose of using getaddrinfo() in this manner is to let it decide what you should be listening on, given the AI_PASSIVE hint you provided. Don't make assumptions about its output.
You also cannot use fdmax on Windows, so you need to re-write your select() loop. Sockets on Windows do not use file descriptors, so you can't simply loop from 0 <= fdmax when calling FD_ISSET(), and the first parameter of select() is ignored as well. I suggest not storing your active socket descriptors/handles in a master fd_set to begin with. Use a std::list or other suitable container instead, and then dynamically create a new fd_set whenever you need to call select(). This would be more portable across different platforms.
Try something more like this:
#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;
}
If you are running the code on a system that supports dual-stack sockets (like Windows), you can change AF_UNSPEC to AF_INET6 (or just hard-code socket()/bind() without using getaddrinfo()) to create only IPv6 listener(s) on IN6ADDR_ANY_INIT, and then disable the IPV6_V6ONLY socket option on them. This will allow IPv6 listen sockets to accept both IPv4 and IPv6 clients, reducing the number of listen sockets you need to create.
I'm trying to download data from Wikipedia. I send a GET request, but the return status - "HTTP 301 redirection". Now I need to parse that to get the target URL, and from there I can access the article content.
I do the parse but but I cannot access to the article content. I appreciate your help if you can help me to get to the article content.
code -
#include <winsock2.h>
#include <WS2tcpip.h>
#include <windows.h>
#include <iostream>
#include <vector>
#include <ostream>
using namespace std;
int main(){
string query = "Google";
//int depth = 0;
// Initialize Dependencies to the Windows Socket.
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
cout << "WSAStartup failed.\n";
system("pause");
}
struct addrinfo hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_socktype = SOCK_STREAM;
/* connect to wikipedia //and download the article */
static const char wiki_host[] = "en.wikipedia.org";
struct addrinfo* targetAdressInfo = NULL;
DWORD getAddrRes = getaddrinfo(wiki_host, NULL, &hints, &targetAdressInfo);
if (getAddrRes != 0 || targetAdressInfo == NULL)
{
cout << "Could not resolve the Host Name" << endl;
system("pause");
WSACleanup();
return -1;
}
SOCKADDR_IN sockAddr;
sockAddr.sin_addr = ((struct sockaddr_in*) targetAdressInfo->ai_addr)->sin_addr;
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(80);
freeaddrinfo(targetAdressInfo);
SOCKET webSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (webSocket == INVALID_SOCKET)
{
cout << "Creation of the Socket Failed" << endl;
system("pause");
WSACleanup();
return -1;
}
if (connect(webSocket, (SOCKADDR*)&sockAddr, sizeof(sockAddr)) != 0)
{
cout << "Could not connect";
system("pause");
closesocket(webSocket);
WSACleanup();
return -1;
}
string http_query = "GET /wiki/" + query + " HTTP/1.1\r\n";
http_query += string("Host: ") + wiki_host + "\r\n";
http_query += "\r\n\r\n";
if (send(webSocket, http_query.c_str(), http_query.length(), 0) == -1) {
cout << "Could not send the request to the Server" << endl;
system("pause");
closesocket(webSocket);
WSACleanup();
return -1;
}
/* prepare to fetch the wiki article */
string response = "";
while (true) {
static char recv_buffer[4096];
const int bytes_read = recv(webSocket, recv_buffer, sizeof(recv_buffer) - 1, 0);
if (!bytes_read) {
break;
}
if (bytes_read == -1) {
closesocket(webSocket);
WSACleanup();
}
recv_buffer[bytes_read] = '\0';
response += recv_buffer;
};
/* finished with the socket */
closesocket(webSocket);
WSACleanup();
/* parse the http response headers */
size_t cursor = 0;
string response_content;
vector<std::string> response_headers;
const size_t headers_end = response.find("\r\n\r\n");
while (true) {
const size_t line_end = response.find("\r\n", cursor);
if (line_end == std::string::npos) { /* probably due to http error */
break;
}
response_headers.push_back(response.substr(cursor, line_end - cursor));
if (line_end == headers_end) { /* found content */
response_content = response.substr(headers_end + 4); /* skip \r\n\r\n */
break;
}
cursor = line_end + 2; /* skip \r\n */
}
// print the respone
for (int i = 0; i < sizeof(response); i++){
cout << response[i];
}
/* print response_content not work
for (int i = 0; i < sizeof(response); i++){
cout << response_content[i];
}*/
system("pause");
return 0;
}
I'm not sure what you mean by saying "I do the parse but but I cannot access to the article content". What you need to do when you get 301 status is to look at "Location" header you received back. That header contains the URL you can use to fetch the content you are looking for.
I try to read site body from get request but I only get the status code and some of start from html text (This is what I got -"HTTP/1.1 200 OK Server: Apa!DOCTYPE html html lang=e"). I would appreciate if you could help me fix the problem. Thanks
Code -
#include <winsock2.h>
#include <WS2tcpip.h>
#include <windows.h>
#include <iostream>
#include <vector>
#include <ostream>
int main(){
// Initialize Dependencies to the Windows Socket.
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
cout << "WSAStartup failed.\n";
system("pause");
}
struct addrinfo hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_socktype = SOCK_STREAM;
/* connect and download the article */
static const char wiki_host[] = "en.wikipedia.org";
struct addrinfo* targetAdressInfo = NULL;
DWORD getAddrRes = getaddrinfo(wiki_host, NULL, &hints, &targetAdressInfo);
if (getAddrRes != 0 || targetAdressInfo == NULL)
{
cout << "Could not resolve the Host Name" << endl;
system("pause");
WSACleanup();
return -1;
}
SOCKADDR_IN sockAddr;
sockAddr.sin_addr = ((struct sockaddr_in*) targetAdressInfo->ai_addr)->sin_addr;
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(80);
freeaddrinfo(targetAdressInfo);
SOCKET webSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (webSocket == INVALID_SOCKET)
{
cout << "Creation of the Socket Failed" << endl;
system("pause");
WSACleanup();
return -1;
}
if (connect(webSocket, (SOCKADDR*)&sockAddr, sizeof(sockAddr)) != 0)
{
cout << "Could not connect";
system("pause");
closesocket(webSocket);
WSACleanup();
return -1;
}
string http_query = "GET / https://en.wikipedia.org/w/api.php?titles=StackOverflow&action=query&prop=extracts&format=json\r\nConnection: close\r\n\r\n";
if (send(webSocket, http_query.c_str(), http_query.length(), 0) == -1) {
cout << "Could not send the request to the Server" << endl;
system("pause");
closesocket(webSocket);
WSACleanup();
return -1;
}
/* prepare to fetch the wiki article */
string response = "";
while (true) {
static char recv_buffer[4096];
const int bytes_read = recv(webSocket, recv_buffer, sizeof(recv_buffer) - 1, 0);
if (!bytes_read) {
break;
}
if (bytes_read == -1) {
closesocket(webSocket);
WSACleanup();
}
recv_buffer[bytes_read] = '\0';
response += recv_buffer;
};
/* finished with the socket */
closesocket(webSocket);
WSACleanup();
/* parse the http response headers */
size_t cursor = 0;
string response_content;
vector<std::string> response_headers;
const size_t headers_end = response.find("\r\n\r\n");
while (true) {
const size_t line_end = response.find("\r\n", cursor);
if (line_end == std::string::npos) { /* probably due to http error */
break;
}
response_headers.push_back(response.substr(cursor, line_end - cursor));
if (line_end == headers_end) { /* found content */
response_content = response.substr(headers_end + 4); /* skip \r\n\r\n */
break;
}
cursor = line_end + 2; /* skip \r\n */
}
// print the respone
for (int i = 0; i < sizeof(response); i++){
cout << response[i];
}
//print response_content
for (int i = 0; i < sizeof(response); i++){
cout << response_content[i];
}
system("pause");
return 0;
}
This doesn't work:
for (int i = 0; i < sizeof(response); i++){
cout << response[i];
}
Because sizeof(response) is the size of the string object, not the length of the string. You should simply do
cout << response;
If you really want to iterate through the string (which is slower and not recommended) you'd have to use response.size() instead of sizeof(response).
There are a few other issue with your code as well, but this should solve the problem at hand.