55
Tags:
c++
Skrevet af
Bruger #2695
@ 29.02.2004
Introduktion
Hej alle
Der har været en del spørgsmål i C++ foraet om netværksprogrammering, så jeg har valgt at skrive en række artikler om emnet. Netværksprogrammering er ret komplekst, og der er mange funktioner og strukturer at beskrive, så jeg har valgt at skrive mindst fire artikler:
* Netværksprogrammering 1 - Hostnames og IP adresser
* Netværksprogrammering 2 - TCP client sockets
* Netværksprogrammering 3 - TCP server sockets
* Netværksprogrammering 4 - UDP sockets
Gennem artikelserien vil vi udvikle et sæt af genbrugelige klasser, som kan hjælpe os i vores programmering.
Denne artikel vil beskrive hostnames og IP adresser, samt hvordan man konverterer mellem de forskellige notationer. Gennem artiklen vil vi udvikle en klasse, som indkapsler en internet adresse, og som kan skaffe informationer om adressen fra DNS. Nu er det heldigvis sådan at der er en standard for netværksprogrammering, som kaldes Berkeley Sockets, men desværre har Microsoft valgt ikke at tilbyde et 100% kompatibelt interface til deres socket library. Derfor er vi nødt til at udvikle koden, som jeg har beskrevet i artiklen om multiplatform udvikling, så jeg vil foreslå, at du læser den først. Derudover vil vi bruge exceptions til at indikere fejl, og der vil jeg bygge videre på det exception klasse hieraki, som jeg begyndte i artiklen om multithreading, så den artikel vil jeg også foreslå, at du læser.
Eftersom IPv6 ikke er så brugt endnu, har jeg valgt at koncentrere mig om IPv4 adresser. Måske kommer der en artikel om IPv6 senere. Hvem ved ?
Jeg regner med, at min kode virker med alle compilere, men jeg tester kun med Dev-C++ (altså GCC) under Windows, og GNU's tools under Linux.
IPv4 adresser
Som bekendt består en IP adresse af fire tal i intervallet 0-255. Det er tal som ligger inden for grænserne i en unsigned char og en IP adresse er derfor også repræsenteret af en integer på fire bytes. En sådan værdi er dog meget uhandy at læse for et menneske, så vi opfandt en notations form, som er lidt lettere at gennemskue, kaldet dotted notation (192.168.147.72 f.eks.). Denne notation kan vi godt nok ikke bruge til meget i softwaren, for når vi skal lave netværksforbindelser skal vi bruge den mere kompakte version med de fire bytes. Derfor har de gode Berkeley folk defineret et sæt funktioner til at konvertere mellem notationerne. Under Linux er der:
char *inet_ntoa(struct in_addr in);
int inet_aton(const char *cp, struct in_addr *inp);
inet_ntoa (ntoa står for 'network to ascii') konverterer fra vores fire byte lange adresse til dottet notation og
inet_aton (aton står for 'ascii to network') konverterer den anden vej.
Under Windows har vi:
char* FAR inet_ntoa(struct in_addr in);
unsigned long inet_addr(const char * cp);
inet_ntoa virker på samme måde som under Linux.
inet_addr findes også til Linux men det anbefales, at man ikke bruger den, for hvis man giver adressen 255.255.255.255, som er gyldig, får man adressen -1 tilbage som indikerer en fejl.
inet_aton findes ikke til Windows.
Man bruger funktionerne sådan her:
#include <iostream>
#if defined(__linux__)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#elif defined(_WIN32)
#include <winsock2.h>
#endif
using namespace std;
int main(int argc, char ** argv)
{
//Tjek om vi har fået vores parameter
if(argc != 2)
{
cout << "Brug: " << argv[0] << " <IP address>" << endl;
return 0;
}
#if defined(_WIN32)
//Under Windows skal WinSock initialiseres
WSADATA wsaData;
int err = WSAStartup(MAKEWORD(2,2),&wsaData);
if(err != 0)
{
cerr << "Kunne ikke initialisere WinSock." << endl;
return -1;
}
#endif
//Denne struktur kommer til at indeholde den konverterede adresse
struct in_addr adr;
//Konvertér
#if defined(__linux__)
int error = inet_aton(argv[1], &adr);
//Tjek om adressen kunne godkendes
if(error == 0)
#elif defined(_WIN32)
adr.S_un.S_addr = inet_addr(argv[1]);
//Tjek om adressen kunne godkendes
if(adr.S_un.S_addr == INADDR_NONE)
#endif
{
cerr << "Du har ikke angivet en valid IP adresse." << endl;
return 0;
}
#if defined(__linux__)
cout << "Adressen er godkendt: " << adr.s_addr << endl;
#elif defined(_WIN32)
cout << "Adressen er godkendt: " << adr.S_un.S_addr << endl;
#endif
//Lad os konvertere adressen tilbage
string strAddress(inet_ntoa(adr));
cout << "Adressen konverteret tilbage til ASCII: " << strAddress << endl;
#if defined(_WIN32)
err = WSACleanup();
if(err != 0)
{
cerr << "Kunne ikke release WinSock." << endl;
return -1;
}
#endif
return 0;
}
Det compiler uden problemer under Linux, men under Windows skal vi lænke med
ws2_32 biblioteket. Det gør du i Dev-C++ gennem "Project->Project
Options->Parameters->Add Library or Object". Så finder du dit Dev-C++
bibliotek, hvorunder der er et 'lib' bibliotek. Her vælger du 'libws2_32.a' og
trykker OK. Så skulle alt virke. Vi prøver at køre det under Linux:
[robert@codemachine Socket]$ ./test 192.168.53.3
Adressen er godkendt: 53848256
Adressen konverteret tilbage til ASCII: 192.168.53.3
[robert@codemachine Socket]$ ./test www.udvikleren.dk
Du har ikke angivet en valid IP adresse.
[robert@codemachine Socket]$ ./test 255.255.255.255
Adressen er godkendt: 4294967295
Adressen konverteret tilbage til ASCII: 255.255.255.255
Vi får fejl når vi giver www.udvikleren.dk som adresse. Det er fordi det ikke er en IP adresse. Vi kigger nærmere på DNS opslag senere.
Og under Windows:
H:\\Socket>test 192.168.53.3
Adressen er godkendt: 53848256
Adressen konverteret tilbage til ASCII: 192.168.53.3
H:\\Socket>test www.udvikleren.dk
Du har ikke angivet en valid IP adresse.
H:\\Socket>test 255.255.255.255
Du har ikke angivet en valid IP adresse.
Der giver også 255.255.255.255 problemer, så det er vi nødt til at tjekke for,
når vi senere udvikler vores internet adresse klasse.
DNS opslag
IP adresser kan være svære for mennesker at huske, og hvis en server går ned, og en anden skal tage over, skal alle der brugte den første server, have dette at vide. Derfor opfandt man internet navne servicen DNS (Domain Name Service), hvor man kan tilknytte ét eller flere navne til en server. Det betyder så, at hvis udvikleren.dk gik ned, så kunne Kasper bare flytte en backup over på en anden server og rette i DNS, så udvikleren.dk peger på den nye server, og ingen ville opdage det.
Nu er det bare sådan, at al Internet trafik
kun bruger IP adresser, så når du forbinder til www.udvikleren.dk, så bliver navnet først slået op hos en DNS server, som giver din browser IP adressen. Derefter kan du forbinde til serveren. Lidt ligesom du ikke kan ringe til Hans Peter. Du kan slå hans navn og adresse op i en telefon bog og så ringe til det telefon nummer, der står angivet.
Da DNS er en meget brugt protokol, har alle operativ systemer også implementeret den for os og givet os et interface til at lave opslag.
For en gangs skyld er Microsoft kompatibel med Berkeley Socket interfacet så både under Linux og Windows bruger vi:
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const char *addr, int len, int type);
struct hostent {
char *h_name; /* official name of host */
char **h_aliases; /* alias list */
int h_addrtype; /* host address type */
int h_length; /* length of address */
char **h_addr_list; /* list of addresses */
}
gethostbyname slår et host navn op (f.eks. www.udvikleren.dk) og returnerer information om denne.
gethostbyaddr slår en IP adresse op og returnerer information om denne. Man får en
hostent struktur tilbage, som indeholder det officielle hostnavn (
h_name), en liste af alternative navne (
h_aliases), som denne server også har.
h_addrtype er typen af adresse, og den er altid
AF_INET, når vi snakker Internet opslag. Vi kunne også have lavet IPX eller AppleTalk opslag, og så havde vi fået andre adressetyper, men disse protokoller vil vi ikke koncentrere os om.
h_length fortæller os, hvor mange bytes hver adresse i den efterfølgende adresseliste har.
h_addr_list er en liste af netværks adresser, som denne maskine kan nåes på. En server kan godt have tilknyttet flere IP adresser, men vi bruger bare den første i listen, når vi senere vil forbinde til en server.
Vi udbygger programmet fra tidligere til at lave et opslag på adressen, som gives som parameter, og først brokke sig, hvis der ikke kan laves et opslag:
#include <iostream>
#if defined(__linux__)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
extern int h_errno;
#elif defined(_WIN32)
#include <winsock2.h>
#endif
using namespace std;
int main(int argc, char ** argv)
{
//Tjek om vi har fået vores parameter
if(argc != 2)
{
cout << "Brug: " << argv[0] << " <IP address|hostnavn>" << endl;
return 0;
}
#if defined(_WIN32)
//Under Windows skal WinSock initialiseres
WSADATA wsaData;
int err = WSAStartup(MAKEWORD(2,2),&wsaData);
if(err != 0)
{
cerr << "Kunne ikke initialisere WinSock." << endl;
return -1;
}
#endif
//Denne struktur kommer til at indeholde den konverterede adresse
struct in_addr adr;
struct hostent * entity;
//Konvertér
#if defined(__linux__)
int error = inet_aton(argv[1], &adr);
//Tjek om adressen kunne godkendes
if(error == 0)
#elif defined(_WIN32)
adr.S_un.S_addr = inet_addr(argv[1]);
//Tjek om adressen kunne godkendes
if(adr.S_un.S_addr == INADDR_NONE && string(argv[1]) != string("255.255.255.255"))
#endif
{
//Det er ikke en IP adresse. Se om det er et host navn
entity = gethostbyname(argv[1]);
}
else
{
//Det er en IP adresse. Slå op med gethostbyaddr
entity = gethostbyaddr((char*)&adr,sizeof(adr),AF_INET);
}
//entity er NULL hvis opslaget slog fejl
if(entity == NULL)
{
#if defined(__linux__)
switch(h_errno)
{
case HOST_NOT_FOUND:
cout << argv[1] << " blev ikke fundet." << endl;
break;
case NO_ADDRESS:
cout << argv[1] << " er et gyldigt navn men har ikke en IP adresse." << endl;
break;
case NO_RECOVERY:
cout << "Der skete en fejl på navne serveren." << endl;
break;
case TRY_AGAIN:
cout << "Der skete en midlertidig fejl på navne serveren. Prøv igen senere." << endl;
break;
}
#elif defined(_WIN32)
switch(WSAGetLastError())
{
case WSAENETDOWN:
cout << "Fejl i netværks komponenten." << endl;
break;
case WSAHOST_NOT_FOUND:
cout << argv[1] << " blev ikke fundet." << endl;
break;
case WSATRY_AGAIN:
cout << "Der skete en midlertidig fejl på navne serveren. Prøv igen senere." << endl;
break;
case WSANO_RECOVERY:
cout << "Der skete en fejl på navne serveren." << endl;
break;
case WSANO_DATA:
cout << argv[1] << " er et gyldigt navn men har ikke en IP adresse." << endl;
break;
case WSAEINPROGRESS:
cout << "WinSock kan ikke processere dit kald." << endl;
break;
}
#endif
}
else
{
int i = 0;
cout << "Officielt navn: " << entity->h_name << endl;
while(entity->h_aliases[ i ])
{
cout << "Alias " << (i+1) << ": " << entity->h_aliases[ i ] << endl;
i++;
}
i = 0;
while(entity->h_addr_list[ i ])
{
cout << "IP adresse " << (i+1) << ": " << inet_ntoa(*((struct in_addr*)entity->h_addr_list[ i ])) << endl;
i++;
}
}
#if defined(_WIN32)
err = WSACleanup();
if(err != 0)
{
cerr << "Kunne ikke release WinSock." << endl;
return -1;
}
#endif
return 0;
}
Kort forklaret så tjekker vi først, om argumentet er en IP adresse, og det kan konverterings funktionen jo gøre for os. Derefter laver vi et opslag med enten
gethostbyname eller
gethostbyadr baseret på dette tjek.
Når vi har fået en pointer til vores
hostent struktur skriver vi først det officielle navn efterfulgt af aliaser og IP adresser. Hvis vi fik NULL, undersøger vi, hvad der gik galt og skriver en passende fejlbesked. Som det ses, tjekker vi for fejl forskelligt mellem Windows og Linux. Lad os prøve at teste det engang:
[robert@codemachine Socket]$ ./test www.udvikleren.dk
Officielt navn: www.udvikleren.dk
IP adresse 1: 217.116.225.52
[robert@codemachine Socket]$ ./test 217.116.225.52
Officielt navn: cgi2.webhotel.net
IP adresse 1: 217.116.225.52
[robert@codemachine Socket]$ ./test www.google.com
Officielt navn: www.google.akadns.net
Alias 1: www.google.com
IP adresse 1: 216.239.59.104
IP adresse 2: 216.239.59.99
[robert@codemachine Socket]$ ./test 192.168.1.1
192.168.1.1 blev ikke fundet.
[robert@codemachine Socket]$ ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) from 192.168.1.4 : 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=0.835 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=0.885 ms
--- 192.168.1.1 ping statistics ---
2 packets transmitted, 2 received, 0% loss, time 1003ms
rtt min/avg/max/mdev = 0.835/0.860/0.885/0.025 ms
[robert@codemachine Socket]$
Som det kan ses, er det ikke altid, at vi får alle informationer med. F.eks. får vi ikke at vide, at www.udvikleren.dk og cgi2.webhotel.net faktisk er den samme host. Ét af disse navne er et alias (sandsynligvis udvikleren.dk).
Vi kan også se, at der ikke findes en DNS record for 192.168.1.1, selvom jeg kan pinge den. Sådan er livet. Ikke alle servere er registreret nogen steder, så det skal vi tage højde for i vores klasse.
Internet adresse klasse
Vi har nu været lidt omkring i socket API'et, som handler om IP adresser, så vi kan nu definere en klasse, som indkapsler denne funktionalitet.
Da jeg udvikler klasserne så objekt orienteret som muligt, bruger jeg også exceptions til at indikere fejl, og som sagt bygger jeg videre på de klasser, jeg lavede i artiklen om multithreading. Det nye hieraki er:
UnknownHostException bliver kastet, hvis et hostname ikke findes i DNS, og
NetException bruges til alle andre fejltilstande. Man kunne have lavet flere specialiseringer, men jeg har aldrig haft brug for andre når det gælder adresse opslag.
Koden til Exception og ThreadException blev listet i artiklen om multithreading. De nye er implementeret således:
//NetException.h
#if !defined(NETEXCEPTION_H)
#define NETEXCEPTION_H
#include "Exception.h"
class NetException : public Exception
{
public:
NetException(string description);
};
#endif
//NetException.cpp
#include "NetException.h"
NetException::NetException(string description) : Exception(description)
{
}
og
//UnknownHostException.h
#if !defined(UNKNOWNHOSTEXCEPTION_H)
#define UNKNOWNHOSTEXCEPTION_H
#include "NetException.h"
class UnknownHostException : public NetException
{
public:
UnknownHostException(string description);
};
#endif
//UnknownHostException.cpp
#include "UnknownHostException.h"
UnknownHostException::UnknownHostException(string description) : NetException(description)
{
}
For at gøre initialiseringen af WinSock arkitektur uafhængig vil jeg lave en define, som under Linux ikke gør noget, men som under Windows laver et kald til
WSAStartup. Det gør jeg i følgende header og source fil:
//Net.h
#if !defined(NET_H)
#define NET_H
#if defined(__linux__)
#define netInit()
#define netRelease()
#elif defined _WIN32
void NetInit();
void NetRelease();
#define netInit() NetInit()
#define netRelease() NetRelease()
#endif
#endif
og
//Net.cpp
#if defined(_WIN32)
#include "NetException.h"
#include <windows.h>
//Denne variabel bruger vi til at undgå
//at initialisere WinSock flere gange
static bool g_netInitialised = false;
void NetInit()
{
WSADATA wsaData;
int err;
if(g_netInitialised == true)
return;
if((err = WSAStartup(MAKEWORD(2,2),&wsaData)) != 0)
{
//Lad Windows fortælle os, hvad der gik galt
LPVOID lpMsgBuf;
int err = WSAGetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0,
NULL
);
string description((char*)lpMsgBuf);
LocalFree( lpMsgBuf );
throw NetException(description);
}
g_netInitialised = true;
}
void NetRelease()
{
if(g_netInitialised == false)
return;
if(WSACleanup() != 0)
{
//Lad Windows fortælle os, hvad der gik galt
LPVOID lpMsgBuf;
int err = WSAGetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0,
NULL
);
string description((char*)lpMsgBuf);
LocalFree( lpMsgBuf );
throw NetException(description);
}
g_netInitialised = false;
}
#endif
Vi prøver at lave koden arkitektur uafhængig, men det er ikke alle platforme, hvor en
int fylder fire bytes. Derfor omdefinerer man ofte de simple datatyper. Det gør jeg i følgende fil:
//Types.h
#if !defined(TYPES_H)
#define TYPES_H
typedef signed char SInt8;
typedef unsigned char UInt8;
typedef signed short SInt16;
typedef unsigned short UInt16;
typedef signed int SInt32;
typedef unsigned int UInt32;
#endif
Det var forarbejdet. Nu kommer det spændende....IPAddress. Jeg har lavet følgende interface:
//IPAddress.h
#if !defined(IPADDRESS_H)
#define IPADDRESS_H
#include <string>
#include <vector>
#include "Types.h"
#if defined(_WIN32)
#include <winsock2.h>
#elif defined(__linux__)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif
using namespace std;
class IPAddress
{
public:
//Opretter et IPAddress objekt ud fra en streng
//som enten indeholder en IP adresse i dotted notation
//eller et host navn.
IPAddress(string & adr);
//Ipretter et IPAddress objekt ud fra en in_addr struktur.
//Dette bliver nødvendigt i en senere artikel om server programmering.
IPAddress(struct in_addr adr);
//Fortæller hvor mange adresser, der er tilknyttet denne adresse
UInt32 numAdresses();
//Returnerer adressen med det givne nummer som in_addr struktur
struct in_addr getAddress(UInt32 index);
//Returnerer adressen med det givne nummer som streng
string getAddressString(UInt32 index);
//Fortæller hvor mange aliaser, der findes for denne adresse
UInt32 numAliases();
//Returnerer aliaset med det givne nummer
string getAlias(UInt32 index);
//Returnerer det officielle navn for denne adresse
string getOfficialName();
//Konverterer denne adresse til en streng
operator string();
private:
//Laver opslaget fra en in_addr struktur
void fromInAddr(struct in_addr adr);
//Definerer en vector indeholdende strenge
typedef vector<string> StringVector;
//Definerer en vector indeholdende in_addr strukturer
typedef vector<struct in_addr> InAddrVector;
//Vores liste af aliaser
StringVector m_aliases;
//Vores liste af adresser
InAddrVector m_adresses;
//Det officielle navn
string m_officialName;
};
#endif
Og implementeringen bliver:
//IPAddress.cpp
#include "IPAddress.h"
#include "NetException.h"
#include "UnknownHostException.h"
#if defined(__linux__)
extern int h_errno;
#elif defined(_WIN32)
#include <windows.h>
#endif
IPAddress::IPAddress(string & adr)
{
struct in_addr adrIn;
struct hostent * entity;
#if defined(__linux__)
int error = inet_aton(adr.c_str(), &adrIn);
//Tjek om adressen kunne godkendes
if(error == 0)
#elif defined(_WIN32)
adrIn.S_un.S_addr = inet_addr(adr.c_str());
//Tjek om adressen kunne godkendes
if(adrIn.S_un.S_addr == INADDR_NONE && adr != string("255.255.255.255"))
#endif
{
//Det er ikke en IP adresse. Slå op som et host name
entity = gethostbyname(adr.c_str());
if(entity == NULL)
{
#if defined(__linux__)
switch(h_errno)
{
case HOST_NOT_FOUND:
//I dette tilfælde kaster vi en speciel exception
//hstrerror lader Linux oprette en fejlbesked
throw UnknownHostException(hstrerror(h_errno));
break;
default:
throw NetException(hstrerror(h_errno));
break;
}
#elif defined(_WIN32)
//Lad Windows fortælle os, hvad der gik galt
LPVOID lpMsgBuf;
int err = WSAGetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0,
NULL
);
string description((char*)lpMsgBuf);
LocalFree( lpMsgBuf );
switch(err)
{
case WSAHOST_NOT_FOUND:
//I dette tilfælde kaster vi en speciel exception
throw UnknownHostException(description);
break;
default:
throw NetException(description);
break;
}
#endif
}
else //Entity er ikke NULL
{
int i = 0;
//Sæt det officielle navn
m_officialName = string(entity->h_name);
//Sæt alle aliaser
while(entity->h_aliases[ i ])
{
string alias(entity->h_aliases[ i ]);
m_aliases.push_back(alias);
i++;
}
//Sæt alle adresser
i = 0;
while(entity->h_addr_list[ i ])
{
struct in_addr inAdr = *((struct in_addr*)entity->h_addr_list[ i ]);
m_adresses.push_back(inAdr);
i++;
}
}
}
else
{
//Det er en IP adresse
fromInAddr(adrIn);
}
}
IPAddress::IPAddress(struct in_addr adr)
{
fromInAddr(adr);
}
UInt32 IPAddress::numAdresses()
{
return m_adresses.size();
}
struct in_addr IPAddress::getAddress(UInt32 index)
{
return m_adresses[index];
}
string IPAddress::getAddressString(UInt32 index)
{
return string(inet_ntoa(m_adresses[index]));
}
UInt32 IPAddress::numAliases()
{
return m_aliases.size();
}
string IPAddress::getAlias(UInt32 index)
{
return m_aliases[index];
}
string IPAddress::getOfficialName()
{
return m_officialName;
}
IPAddress::operator string()
{
return m_officialName;
}
void IPAddress::fromInAddr(struct in_addr adr)
{
//Kan vi lave et opslag?
struct hostent * entity;
entity = gethostbyaddr((char*)&adr,sizeof(adr),AF_INET);
if(entity != NULL)
{
int i = 0;
//Sæt det officielle navn
m_officialName = string(entity->h_name);
//Sæt alle aliaser
while(entity->h_aliases[ i ])
{
string alias(entity->h_aliases[ i ]);
m_aliases.push_back(alias);
i++;
}
//Sæt alle adresser
i = 0;
while(entity->h_addr_list[ i ])
{
struct in_addr inAdr = *((struct in_addr*)entity->h_addr_list[ i ]);
m_adresses.push_back(inAdr);
}
}
else
{
//Ellers bruger vi bare adressen direkte
//da det jo kan være en adresse som ikke står
//i DNS
m_adresses.push_back(adr);
//Og brug adressen som officielt navn
m_officialName = getAddressString(0);
}
}
Det var det. Slet ikke så besværligt.
Revideret internet opslags program
Nu hvor vi har lavet vores IPAddress klasse, skal vi selvfølgelig også se et eksempel, hvor den bliver brugt. Vi har designet og implementeret klassen, så IP adresser, som ikke står i DNS, stadig kan bruges, men så host navne, som ikke er i DNS, kaster exceptions. Vores program tager et uendeligt antal parametre og laver et opslag for hver, hvorefter det skriver informationer ud om hver adresse:
//test.cpp
#include "Net.h"
#include "IPAddress.h"
#include "UnknownHostException.h"
#include "NetException.h"
#include <iostream>
using namespace std;
int main(int argc, char ** argv)
{
//Tjek om der er parametre
if(argc == 1)
{
cout << "Brug: " << argv[0] << " <IP|host> <IP|host> <...>" << endl;
}
else
{
//netInit kan kaste exceptions
try
{
//Initialisér net komponenten
netInit();
SInt32 i;
for(i = 1; i < argc; i++)
{
try
{
//Konvertér først til C++ string objekt
string str(argv[ i ]);
IPAddress adr(str);
//Hvis vi nåede hertil, er opslaget sket eller også er der tale om
//en IP adresse, som ikke står i DNS. Skriv vi ved om den ud.
UInt32 j;
cout << "Officielt navn: " << adr.getOfficialName() << endl;
for(j = 0; j < adr.numAliases(); j++)
cout << "Alias " << (j+1) << ": " << adr.getAlias(j) << endl;
for(j = 0; j < adr.numAdresses(); j++)
cout << "Adresse " << (j+1) << ": " << adr.getAddressString(j) << endl;
} catch (UnknownHostException & uhe)
{
cout << "Kunne ikke slå adressen (" << argv[ i ] << ") op: " << uhe << endl;
} catch (NetException & nex)
{
cout << "Der skete en fejl under opslaget: " << nex << endl;
}
//Lav en linje hvis dette ikke er den sidste adresse
if(i != argc - 1)
cout << "---------------------------------------------------" << endl;
}
//Frigiv net komponenten
netRelease();
} catch (NetException ex)
{
cerr << "Kunne ikke initialisere net komponenten: " << ex << endl;
}
}
return 0;
}
Det var en hel del nemmere end før, og koden er også meget pænere.
Koden skulle meget gerne compile lige godt under både Linux og Windows, men da projektet begynder at blive stort (både koden fra multithreading artiklen og denne), og det bliver større i de efterfølgende artikler, så vil vi bruge en makefil til at compile under Linux:
#Makefile
#Brug g++ som compiler
CXX=g++
#Lænk med pthread biblioteket
LDFLAGS=-lpthread
#Compile med debugging support og alle warnings slået til
CXXFLAGS=-ggdb -Wall -c
CPPSOURCE= Exception.cpp IPAddress.cpp Net.cpp \ NetException.cpp test.cpp Thread.cpp \ ThreadException.cpp UnknownHostException.cpp
OBJECTS=$(CPPSOURCE:.cpp=.o)
test: $(OBJECTS)
$(CXX) $(LDFLAGS) -o $@ $(OBJECTS)
#Ovenstående linje har et tabulator stop i starten af linjen
Exception.o: Exception.cpp
ThreadException.o: ThreadException.cpp
NetException.o: NetException.cpp
UnknownHostException.o: UnknownHostException.cpp
IPAddress.o: IPAddress.cpp
Net.o: Net.cpp
Thread.o: Thread.cpp
test.o: test.cpp
%.o:
$(CXX) $(CXXFLAGS) -o $@ $<
#Ovenstående linje har et tabulator stop i starten af linjen
.PHONY: clean
clean:
rm -f $(OBJECTS) test
#Ovenstående linje har et tabulator stop i starten af linjen
Så skal man bare skrive 'make' for at compile og lænke koden:
[robert@codemachine Socket]$ make
g++ -ggdb -Wall -c -o Exception.o Exception.cpp
g++ -ggdb -Wall -c -o IPAddress.o IPAddress.cpp
g++ -ggdb -Wall -c -o Net.o Net.cpp
g++ -ggdb -Wall -c -o NetException.o NetException.cpp
g++ -ggdb -Wall -c -o test.o test.cpp
g++ -ggdb -Wall -c -o Thread.o Thread.cpp
g++ -ggdb -Wall -c -o ThreadException.o ThreadException.cpp
g++ -ggdb -Wall -c -o UnknownHostException.o UnknownHostException.cpp
g++ -lpthread -o test Exception.o IPAddress.o Net.o NetException.o test.o Thread.o ThreadException.o UnknownHostException.o
[robert@codemachine Socket]$
Lad os prøve det:
H:\\Socket>test 255.255.255.255 www.google.com 192.168.0.1 fjsakjfdsjfsad www.udvikleren.dk
Officielt navn: 255.255.255.255
Adresse 1: 255.255.255.255
---------------------------------------------------
Officielt navn: www.google.akadns.net
Alias 1: www.google.com
Adresse 1: 216.239.59.104
Adresse 2: 216.239.59.99
---------------------------------------------------
Officielt navn: 192.168.0.1
Adresse 1: 192.168.0.1
---------------------------------------------------
Kunne ikke slå adressen (fjsakjfdsjfsad) op: No such host is known.
---------------------------------------------------
Officielt navn: www.udvikleren.dk
Adresse 1: 217.116.225.52
H:\\Socket>
Det kører jo bare.
Konklusion
Det var alt for denne gang. Vi har kun set på adresser indtil videre, men har allerede en pænt stor mængde kode. Det er fordi netværksprogrammering er komplekst, men så er det jo godt, at vi kan abstrahere lidt væk og kun koncentrere os om Internet adresser. Så bliver det hele lidt mere overskueligt.
I næste artikel går vi rigtig i gang med netværks forbindelser, og her får vi også brug for vores IPAddress klasse. Glæd jer.
Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.
Del også gerne artiklen med dine Facebook venner:
Kommentarer (13)
Rigtig fed artikel!.
det var satme den bedste artikel jeg har læst længe !!!
rødme rødme Tak skal I ha :-)
2. artikel ligger klar til læsning. Jeg håber, at I også kan lide dén.
3. artikel om server programmering er lige røget til godkendelse
Guf guf guf
Kan jeg sige andet?
DEN ER SUPER
Endelig en rigtig god artikkel... men jeg får nogle fejl... jeg tror det har noget at gøre med når man skal bruge filen 'libws2_32.a'
[Linker error] undefined reference to `WSAStartup@8'
[Linker error] undefined reference to `inet_addr@4'
[Linker error] undefined reference to `inet_ntoa@4'
[Linker error] undefined reference to `WSACleanup@0'
C:\\Documents and Settings\\Kasper Nielsen\\Skrivebord\\Windows & Linux\\Ip program\\Makefile.win
[Build Error] [Projekt1.exe] Error 1
Nogle der ved hvad der er galt??
Ja Kasper. Problemet er, at du ikke lænker med ws2_32.ii
Det gør du Dev-C++ ved gennem:
Project->Project options->Parameters->Add Library or object
Så finder du dit lib bibliotek under Dev-C++ (c:\\dev-cpp\\lib) og derunder vælger du libws2_32.a
Så skulle det virke.
Men det har jeg gjordt jeg får stadig fejlen
Forrygende artikler. Jeg læser til SW-ingeniør og bruger meget dine artikler til inspiration.
Fuck en lækker artikel!
Skide godt - meget inspirerende.
5 herfra!
Hej, og tak for nogle fine artikler :b
Jeg sidder og leger med det, men får følgende fejl når jeg kør min makefile:
martin@martin-ub:~/workspace/cpp/Network_play$ make
g++ -ggdb -Wall -c -o Exception.o Exception.cpp
g++ -ggdb -Wall -c -o IPAddress.o IPAddress.cpp
g++ -ggdb -Wall -c -o Net.o Net.cpp
g++ -ggdb -Wall -c -o NetException.o
g++: no input files
make: *** [ NetException.o] Error 1
Er det fordi at funktionen i NetException.cpp er tom?
mvh. martin
Jeg kan ikke compile din klasse, men alt det andet virker fint. Jeg har alle filerne, men den spørger efter filen Exception.h, den har jeg ikke. Og den er ikke i projektet. Men så lavede jeg den selv, men så får jeg bare syntax error, som : expected { before class osv. Jeg har Dev-C++ Version 4.9.9.2
Du skal være
logget ind for at skrive en kommentar.