10
Tags:
c++
Skrevet af
Bruger #2695
@ 09.03.2004
Introduktion
Hej igen allesammen.
Vi har indtil videre dækket Internet adresser og adresse opslag samt TCP kommunikation, som er den mest brugte Internet kommunikations form. I denne artikel skal vi se på en anden protokol, som er knap så brugt men i visse tilfælde meget anvendelig.
UDP protokollen
UDP (User Datagram Protocol) er en forbindelsesløs og utroværdig protokol.
At den er forbindelsesløs betyder, at når du har oprettet din socket (med
socket kaldet ca. ligesom TCP sockets), så kan du bare begynde at sende data til højre og venstre uden at kalde
connect. Du specificerer en modtager adresse i hver forsendelse.
At den er utroværdig betyder, at du kun får besked om, hvorvidt dataene blev afsendt. Du ved ikke om de blev modtaget i den anden ende, eller om de blev modtaget i den samme rækkefølge, som du sendte dem. Hvis disse informationer er vigtige for din applikation, så er UDP ikke den rigtige protokol for dig. Der er dog også fordele ved, at UDP ikke er troværdig. Hver gang et antal pakker er blevet afsendt, venter det afsendende operativsystem på, at det modtagende operativsystem har sendt besked om modtagelsen. Hvis dette ikke sker, bliver pakkerne sendt igen. Denne venten og genforsendelse tager tid, og det er ikke altid, at man har det. Og hvis det er ligegyldigt om en enkelt eller to datapakker bliver mistet undervejs, så er det ekstra spild af tid.
Forestil dig et realtime multiplayer spil (Quake, Counter Strike, osv.). Det er serveren, som ved, hvor alle enheder (spillere, AI'er, osv.) er, og sender mange gange i sekundet en opdatering ud til alle spillere. Hver opdatering kunne indeholde informationer om, at figur
F1 er på 3D koordinat
x1,
y1,,
z1 og kigger i retning
vx1,
vy1,
vz1 og figur
F2 er på 3D koordinat
x2,
y2,,
z2 og kigger i retning
vx2,
vy2,
vz2 osv. Om et tiendedel sekund er dette en oldgammel nyhed, for da har alle flyttet sig en lille smule. Dvs. at hvis en pakke mistes, er det ligegyldigt for vi får snart en nyere, og hvis vi får pakke 10 og derefter pakke 9, så smider vi bare pakke 9 væk for vi har fået nyere koordinater.
En fordel ved den manglende forbindelse (bortset fra at man ikke skal koncentrere sig om at forbinde, holde forbindelsen og lukke forbindelsen) er, at eftersom vores socket ikke er dedikeret til én klient/server, så kan den bruges til alle. Derudover kan vi adressere en pakke til et helt netværk. Dette kaldes broadcasting.
Forestil dig igen et netværksspil. Serveren starter op og sætter en UDP socket til at lytte på port 6666 og en TCP socket til at lytte på port 6543. Derefter startes en klient og broadcaster en pakke til port 6666 til hele netværket, hvori der spørges, om der er startet en server. Serveren modtager denne pakke og sender en besked tilbage med informationer om, at TCP port 6543 er åben for yderligere kommunikation samt måske antallet af forbundne spillere, og hvilken tilstand spillet er i. Klienten kan nu forbinde til serveren.
De fleste realtime spil (??? alle ???) benytter faktisk både TCP og UDP sockets og udnytter dermed hver protokols styrker og svagheder.
TCP protokollen bruges til at sende de vigtige opdateringer (nye spillere, "jeg ønsker at flytte min spiller fremad", indlæs level 7, og lignende), medens UDP bruges til de mindre vigtige informationer (placeringer og retninger).
En UDP echo applikation
Vi vil lave endnu en echo applikation, som enten sættes til at lytte efter echo requests eller som sender echo requests.
Hvis applikationen sættes til at lytte, venter den på en UDP pakke. Hvis denne pakke indeholder teksten "CLOSEDOWN", så lukkes applikationen. Ellers bliver pakken bare sendt tilbage. Ligesom i sidste artikel.
Vi kommer til at kigge på følgende funktionskald:
int sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
int recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);
sendto sender en besked på en socket. Den tager også en adresse som parameter, og det er denne adress, dataene vil blive sendt til.
recvfrom modtager en besked på en socket. Funktionen tager også en pointer til en adresse som parameter, og denne adresse vil efter kaldet indeholde adresse fortælle os, hvem der sendte dataene.
Når man har oprettet en UDP socket, kan man ikke sende broadcasts fra starten. Det er en option, som skal sættes, og det gør vi med
setsockopt.
getsockopt kan bl.a. fortælle os, om broadcasts er mulige på vores socket.
Både
getsockopt og
setsockopt kan sætte og læse et hav af options på både UDP og TCP sockets, men det er de færreste, som man bruger. I de fleste tilfælde er alt, sat fornuftigt op, men vi vil gerne broadcaste, så denne option vil vi sætte.
Lad os gå i koder mode:
//test.cpp
#include "Net.h"
#include "Exception.h"
#include "IPAddress.h"
#include "Types.h"
#include <cstdlib>
#if defined(__linux__)
#include <unistd.h>
#endif
using namespace std;
void runServer(int argc, char ** argv)
{
//Default adresse at binde til
//NULL betyder alle
IPAddress * bindAddr = NULL;
//Default port at binde til
UInt16 port = 5555;
int i,sock,bytesRead;
//Buffer til indkommende data
char buffer[1024];
//Så længe denne er true venter vi på data
bool again = true;
//Til at indehode adresse som vi binder til
//samt adresse på klienter som sender data til os
struct sockaddr_in addr;
for(i = 2; i < argc; i++)
{
if(string(argv[ i ]) == string("-a"))
{
i++;
if(i == argc)
{
cout << "Parametren '-a' skal have et argument." << endl;
return;
}
if(bindAddr != NULL)
delete bindAddr;
string adr(argv[ i ]);
bindAddr = new IPAddress(adr);
}
else if(string(argv[ i ]) == string("-p"))
{
i++;
if(i == argc)
{
cout << "Parametren '-p' skal have et argument." << endl;
return;
}
port = atoi(argv[ i ]);
}
}
//Tjek om vi skal binde til et bestemt interface
if(bindAddr != NULL)
{
cout << "Binder til: " << bindAddr->getAddressString(0) << endl;
}
else
{
cout << "Binder til alle adresser" << endl;
}
cout << "Port: " << port << endl;
//Opret socket
sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(sock == 0)
{
cout << "Kunne ikke oprette socket." << endl;
return;
}
//Adresse familie
addr.sin_family = AF_INET;
//Port
addr.sin_port = htons(port);
//Interface
if(bindAddr == NULL)
{
addr.sin_addr.s_addr = INADDR_ANY;
}
else
{
addr.sin_addr = bindAddr->getAddress(0);
delete bindAddr;
bindAddr = NULL;
}
//Bind til adresse og port
if(bind(sock,(struct sockaddr*)&addr,sizeof(struct sockaddr_in)) != 0)
{
//Vi kunne ikke binde
cout << "Kunne ikke binde socket." << endl;
//Frigiv socket
#if defined(__linux__)
close(sock);
#elif defined(_WIN32)
closesocket(sock);
#endif
return;
}
while(again)
{
cout << "Venter på data." << endl;
#if defined(__linux__)
socklen_t fromLen = sizeof(sockaddr_in);
#elif defined(_WIN32)
int fromLen = sizeof(sockaddr_in);
#endif
//Blokér til der er data
bytesRead = recvfrom(sock,buffer,1024,0,(struct sockaddr*)&addr,&fromLen);
//Se hvem det er fra
IPAddress clientAddr(addr.sin_addr);
cout << "Modtog data fra " << (string)clientAddr << endl;
string message(buffer,0,bytesRead);
if(message == string("CLOSEDOWN"))
{
again = false;
}
//Skriv dataene ud
cout << message << endl << endl;
//Send dataene tilbage:
sendto(sock,buffer,bytesRead,0,(struct sockaddr*)&addr,fromLen);
}
//Frigiv vores socket
#if defined(__linux__)
close(sock);
#elif defined(_WIN32)
closesocket(sock);
#endif
}
void runClient(int argc, char ** argv)
{
//Adresse på server
IPAddress * serverAddr = NULL;
//Servers port
UInt16 port = 5555;
int i,sock,bytesRead;
//Buffer til indkommende data
char buffer[1024];
//Adresse struktur som vi sender til og modtager fra
struct sockaddr_in addr;
//Besked som vi sender
char * message = NULL;
//Læs parametre
for(i = 1; i < argc; i++)
{
if(string(argv[ i ]) == string("-a"))
{
i++;
if(i == argc)
{
cout << "Parametren '-a' skal have et argument." << endl;
return;
}
if(serverAddr != NULL)
delete serverAddr;
string adr(argv[ i ]);
serverAddr = new IPAddress(adr);
}
else if(string(argv[ i ]) == string("-p"))
{
i++;
if(i == argc)
{
cout << "Parametren '-p' skal have et argument." << endl;
return;
}
port = atoi(argv[ i ]);
}
else
{
message = argv[ i ];
}
}
//Tjek om der blev specificeret en adresse
if(serverAddr == NULL)
{
cout << "Du har ikke skervet en adresse." << endl;
return;
}
//Tjeck om der blev specificeret en besked
if(message == NULL)
{
cout << "Du har ikke skrevet en besked." << endl;
return;
}
//Opret socket
sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(sock == 0)
{
cout << "Kunne ikke oprette socket." << endl;
return;
}
//Gør socket klar til at boradcaste
int value = 1;//1 betyder true
#if defined(__linux__)
if(setsockopt(sock,SOL_SOCKET,SO_BROADCAST,&value,sizeof(int)) != 0)
#elif defined(_WIN32)
if(setsockopt(sock,SOL_SOCKET,SO_BROADCAST,(char*)&value,sizeof(int)) != 0)
#endif
{
cout << "Kunne ikke sætte socket op til broadcasting." << endl;
//Vi behøver ikke at returnere da vi måske ikke broadcaster
}
//Adresse familie
addr.sin_family = AF_INET;
//Port
addr.sin_port = htons(port);
//Adresse
addr.sin_addr = serverAddr->getAddress(0);
delete serverAddr;
serverAddr = NULL;
#if defined(__linux__)
socklen_t fromLen = sizeof(sockaddr_in);
#elif defined(_WIN32)
int fromLen = sizeof(sockaddr_in);
#endif
//Send besked
sendto(sock,message,strlen(message),0,(struct sockaddr*)&addr,fromLen);
//Vi vil højst vente 5 sekunder på, at serveren svarer
fd_set set;
struct timeval timeout;
int err;
FD_ZERO(&set);
FD_SET(sock,&set);
//Specificér hvor lang tid vi skal vente
timeout.tv_sec = 5;
timeout.tv_usec = 0;
//Vent på data
err = select(FD_SETSIZE,&set,NULL,NULL,&timeout);
if(err > 0)
{
//Modtag noget igen
bytesRead = recvfrom(sock,buffer,1024,0,(struct sockaddr*)&addr,&fromLen);
//Vi har fået data. Se hvem det er fra
IPAddress server(addr.sin_addr);
cout << "Modtog data fra " << (string)server << endl;
//Skriv dataene
cout.write(buffer,bytesRead);
//Og en newline
cout << endl;
}
else
{
cout << "Vi fik intet i 5 sekunder." << endl;
}
//Frigiv socket
#if defined(__linux__)
close(sock);
#elif defined(_WIN32)
closesocket(sock);
#endif
}
int main(int argc, char ** argv)
{
if(argc == 1)
{
cout << "Brug: " << argv[0] << " <-l> -p port <-a address> <message>" << endl;
return 0;
}
try
{
//Initialisér netværk
netInit();
//Skal vi køre som server eller klient?
if(string(argv[1]) == string("-l"))
{
//Kør i server mode
runServer(argc, argv);
}
else
{
//Kør i client mode
runClient(argc, argv);
}
//Frigiv netværk
netRelease();
} catch (Exception & ex)
{
cout << ex << endl;
}
return 0;
}
Det var også mere kode end vi gider at se på normalt, så det skal selvfølgelig
kapsles ind. Men det er jo slet ikke så kompliceret igen.
Følgende billede viser et screenshot, hvor jeg kører serveren og klienten:
Først sender klienten beskeden "Hello, World" til localhost. Det bliver modtaget fint. Derefter sendes beskeden "Direkte besked" direkte til IP adressen 192.168.1.3, og derefter til 192.168.1.0 nettets broadcast adresse. Det bliver faktisk modtaget af alle computere på hele mit netværk (to desktops, en laptop og en router), men det er kun min ene desktop som lytter. Til sidst sender jeg luk-ned kommandoen til broadcast adressen.
Det virker jo bare.
En UDP socket klasse
Så er det på tide at skjule kompleksiteten bag et simpelt interface. Jeg har følgende klasse definition:
//UDPSocket.h
#if !defined(UDPSOCKET_H)
#define UDPSOCKET_H
#include "IPAddress.h"
#include "Mutex.h"
class UDPSocket
{
public:
//Opretter en UDP socket som ikke
//er bundet til hverken port eller adresse
//Denne socket SKAL sende den første pakke
UDPSocket();
//Opretter en UDP socket som binder
//til den specificerede port og alle netkort
UDPSocket(UInt16 port);
//Opretter en UDP socket som binder
//til den specificerede port og adresse
UDPSocket(IPAddress adr, UInt16 port);
//Frigiver den interne socket
~UDPSocket();
//Sætter et nyt timeout som virker fra næste gang vi kalder 'read()'
//Der ventes 'timeOut' millisekunder hvis 'timeOut' > 0. Ellers ventes
//til der er data
void setTimeOut(UInt32 timeOut);
//Returnerer timeout
UInt32 getTimeOut();
//Skriver 'size' bytes fra 'buffer' til den angivne adresse og port
//Returnerer antallet af bytes, som blev skrevet
SInt32 write(const void * buffer, UInt32 size, IPAddress * adr, UInt16 port);
//Læser højst 'size' bytes og placerer dem i 'buffer'
//Hvis 'adr' ikke er NULL indeholder den efter dette kald afsenderens adresse
//Adressen bliver dynamisk allokeret
//Hvis 'port' ikke er NULL bliver den opdateret med afsenderens port
SInt32 read(void * buffer, UInt32 size, IPAddress ** adr, UInt16 * port);
//Returnerer 'true' hvis broadcasting er tilladt på denne UDP socket
bool broadcastEnabled();
//Slår broadcasting til eller fra på denne UDP socket
void enableBroadcast(bool doEnable);
private:
//Denne funktion blokerer i 'm_timeOut' millisekunder, eller til
//der er data at læse.
void waitForData();
//Vores socket
int m_socket;
//Antallet af millisekunder vi skal vente på blokerende kald.
//Hvis denne er 0, venter vi evigt.
UInt32 m_timeOut;
};
#endif
Og implementeringen:
//UDPSocket.cpp
#include "UDPSocket.h"
#include "NetException.h"
#include "TimerException.h"
#if defined(__linux__)
#include <unistd.h>
#endif
UDPSocket::UDPSocket()
: m_socket(0), m_timeOut(0)
{
m_socket = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(m_socket == 0)
{
#if defined(__linux__)
throw NetException(strerror(h_errno));
#elif defined(_WIN32)
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);
#endif
}
}
UDPSocket::UDPSocket(UInt16 port)
: m_socket(0), m_timeOut(0)
{
m_socket = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
//Adresse struktur som vi binder til
struct sockaddr_in addr;
//Adresse familie
addr.sin_family = AF_INET;
//Port
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
//Bind til adresse og port
if(bind(m_socket,(struct sockaddr*)&addr,sizeof(struct sockaddr_in)) != 0)
{
#if defined(__linux__)
//Frigiv m_socket
close(m_socket);
throw NetException(strerror(h_errno));
#elif defined(_WIN32)
//Frigiv m_socket
closesocket(m_socket);
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);
#endif
}
}
UDPSocket::UDPSocket(IPAddress adr, UInt16 port)
: m_socket(0), m_timeOut(0)
{
m_socket = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
//Adresse struktur som vi binder til
struct sockaddr_in addr;
//Adresse familie
addr.sin_family = AF_INET;
//Port
addr.sin_port = htons(port);
addr.sin_addr = adr.getAddress(0);
//Bind til adresse og port
if(bind(m_socket,(struct sockaddr*)&addr,sizeof(struct sockaddr_in)) != 0)
{
#if defined(__linux__)
//Frigiv m_socket
close(m_socket);
throw NetException(strerror(h_errno));
#elif defined(_WIN32)
//Frigiv m_socket
closesocket(m_socket);
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);
#endif
}
}
UDPSocket::~UDPSocket()
{
if(m_socket != 0)
{
#if defined(__linux__)
close(m_socket);
#elif defined(_WIN32)
closesocket(m_socket);
#endif
m_socket = 0;
}
}
void UDPSocket::setTimeOut(UInt32 timeOut)
{
m_timeOut = timeOut;
}
UInt32 UDPSocket::getTimeOut()
{
return m_timeOut;
}
SInt32 UDPSocket::write(const void * buffer, UInt32 size, IPAddress * adr, UInt16 port)
{
struct sockaddr_in addr;
//Adresse familie
addr.sin_family = AF_INET;
//Port
addr.sin_port = htons(port);
//Adresse
addr.sin_addr = adr->getAddress(0);
//Send pakken
SInt32 bytesWritten = sendto(m_socket,(char*)buffer,size,0,(struct sockaddr*)&addr,sizeof(addr));
if(bytesWritten == -1)
{
#if defined(__linux__)
throw NetException(strerror(h_errno));
#elif defined(_WIN32)
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);
#endif
}
return bytesWritten;
}
SInt32 UDPSocket::read(void * buffer, UInt32 size, IPAddress ** adr, UInt16 * port)
{
//Afsender adresse
struct sockaddr_in addr;
#if defined(__linux__)
socklen_t fromLen = sizeof(addr);
#elif defined(_WIN32)
int fromLen = sizeof(addr);
#endif
waitForData();
SInt32 bytesRead = recvfrom(m_socket,(char*)buffer,size,0,(struct sockaddr*)&addr,&fromLen);
if(bytesRead == -1)
{
#if defined(__linux__)
throw NetException(strerror(h_errno));
#elif defined(_WIN32)
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);
#endif
}
//Fortæl om afsenderen
if(adr != NULL)
{
*adr = new IPAddress(addr.sin_addr);
}
if(port != NULL)
{
*port = ntohs(addr.sin_port);
}
return bytesRead;
}
bool UDPSocket::broadcastEnabled()
{
int value;
#if defined(__linux__)
socklen_t optlen = sizeof(value);
if(getsockopt(m_socket,SOL_SOCKET,SO_BROADCAST,&value,&optlen) != 0)
#elif defined(_WIN32)
int optlen = sizeof(value);
if(getsockopt(m_socket,SOL_SOCKET,SO_BROADCAST,(char*)&value,&optlen) != 0)
#endif
{
#if defined(__linux__)
throw NetException(strerror(h_errno));
#elif defined(_WIN32)
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);
#endif
}
return (value == 1);
}
void UDPSocket::waitForData()
{
fd_set set;
struct timeval timeout;
int err;
FD_ZERO(&set);
FD_SET(m_socket,&set);
//Hvis m_timeOut er større end 0
if(m_timeOut > 0)
{
//Specificér hvor lang tid vi skal vente
timeout.tv_sec = (unsigned int)(m_timeOut / 1000);
timeout.tv_usec = (unsigned int)(m_timeOut % 1000) * 1000;
err = select(FD_SETSIZE,&set,NULL,NULL,&timeout);
}
else
{
//Vent til der er data eller til forbindelsen bliver lukket
err = select(FD_SETSIZE,&set,NULL,NULL,NULL);
}
//Hvis 'err' er 0 er tiden gået
if(err == 0)
{
throw TimerException("Timer timed out");
}
//Eller hvis 'err' er -1, er der gået noget galt
else if(err == -1)
{
#if defined(__linux__)
throw NetException(strerror(h_errno));
#elif defined(_WIN32)
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);
#endif
}
}
void UDPSocket::enableBroadcast(bool doEnable)
{
int value = (doEnable ? 1 : 0);
#if defined(__linux__)
if(setsockopt(m_socket,SOL_SOCKET,SO_BROADCAST,&value,sizeof(int)) != 0)
#elif defined(_WIN32)
if(setsockopt(m_socket,SOL_SOCKET,SO_BROADCAST,(char*)&value,sizeof(int)) != 0)
#endif
{
#if defined(__linux__)
throw NetException(strerror(h_errno));
#elif defined(_WIN32)
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);
#endif
}
}
UDPSocket filen skal selvfølgelig også tilføjes til vores makefil:
#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 \ TimerException.cpp ConnectionLostException.cpp \ TCPSocket.cpp Mutex.cpp TCPServerSocket.cpp \ UDPSocket.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
TimerException.o: TimerException.cpp
ConnectionLostException.o: ConnectionLostException.cpp
TCPSocket.o: TCPSocket.cpp
TCPServerSocket.o: TCPServerSocket.cpp
Mutex.o: Mutex.cpp
UDPSocket.o: UDPSocket.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
Et revideret UDP echo program
Det reviderede echo program, som bruger vores klasse, ser således ud:
//test.cpp
#include "Net.h"
#include "Exception.h"
#include "NetException.h"
#include "TimerException.h"
#include "IPAddress.h"
#include "Types.h"
#include "UDPSocket.h"
#include <cstdlib>
using namespace std;
void runServer(int argc, char ** argv)
{
//Default adresse at binde til
//NULL betyder alle
IPAddress * bindAddr = NULL;
//Default port at binde til
UInt16 port = 5555;
SInt32 i,bytesRead;
//Buffer til indkommende data
char buffer[1024];
//Så længe denne er true venter vi på data
bool again = true;
for(i = 2; i < argc; i++)
{
if(string(argv[ i ]) == string("-a"))
{
i++;
if(i == argc)
{
cout << "Parametren '-a' skal have et argument." << endl;
return;
}
if(bindAddr != NULL)
delete bindAddr;
string adr(argv[ i ]);
bindAddr = new IPAddress(adr);
}
else if(string(argv[ i ]) == string("-p"))
{
i++;
if(i == argc)
{
cout << "Parametren '-p' skal have et argument." << endl;
return;
}
port = atoi(argv[ i ]);
}
}
//Tjek om vi skal binde til et bestemt interface
if(bindAddr != NULL)
{
cout << "Binder til: " << bindAddr->getAddressString(0) << endl;
}
else
{
cout << "Binder til alle adresser" << endl;
}
cout << "Port: " << port << endl;
//Opret socket
UDPSocket * sock;
if(bindAddr == NULL)
{
sock = new UDPSocket(port);
}
else
{
sock = new UDPSocket(*bindAddr, port);
delete bindAddr;
bindAddr = NULL;
}
while(again)
{
cout << "Venter på data." << endl;
IPAddress * adr = NULL;
UInt16 port;
//Blokér til der er data
bytesRead = sock->read(buffer,1024,&adr,&port);
//Se hvem det er fra
cout << "Modtog data fra " << (string)(*adr) << endl;
string message(buffer,0,bytesRead);
if(message == string("CLOSEDOWN"))
{
again = false;
}
//Skriv dataene ud
cout << message << endl << endl;
//Send dataene tilbage:
sock->write(buffer,bytesRead,adr,port);
delete adr;
adr = NULL;
}
//Frigiv vores socket
delete sock;
}
void runClient(int argc, char ** argv)
{
//Adresse på server
IPAddress * serverAddr = NULL;
//Servers port
UInt16 port = 5555;
SInt32 i,bytesRead;
//Buffer til indkommende data
char buffer[1024];
//Besked som vi sender
char * message = NULL;
//Læs parametre
for(i = 1; i < argc; i++)
{
if(string(argv[ i ]) == string("-a"))
{
i++;
if(i == argc)
{
cout << "Parametren '-a' skal have et argument." << endl;
return;
}
if(serverAddr != NULL)
delete serverAddr;
string adr(argv[ i ]);
serverAddr = new IPAddress(adr);
}
else if(string(argv[ i ]) == string("-p"))
{
i++;
if(i == argc)
{
cout << "Parametren '-p' skal have et argument." << endl;
return;
}
port = atoi(argv[ i ]);
}
else
{
message = argv[ i ];
}
}
//Tjek om der blev specificeret en adresse
if(serverAddr == NULL)
{
cout << "Du har ikke skervet en adresse." << endl;
return;
}
//Tjeck om der blev specificeret en besked
if(message == NULL)
{
cout << "Du har ikke skrevet en besked." << endl;
return;
}
//Opret socket
UDPSocket sock;
try
{
sock.enableBroadcast(true);//Vi vil gerne kunne broadcaste
} catch (NetException & ne)
{
//Men hvis vi ikke kan så er det ok
}
sock.setTimeOut(5000);//Vi vil højst vente 5 sekunder på, at serveren svarer
//Send besked
sock.write(message,strlen(message),serverAddr,port);
delete serverAddr;
serverAddr = NULL;
try
{
bytesRead = sock.read(buffer,1024,&serverAddr,&port);
cout << "Modtog data fra " << (string)(*serverAddr) << endl;
cout.write(buffer,bytesRead);
cout << endl;
delete serverAddr;
serverAddr = NULL;
} catch (TimerException & te)
{
cout << "Vi fik intet i 5 sekunder." << endl;
}
}
int main(int argc, char ** argv)
{
if(argc == 1)
{
cout << "Brug: " << argv[0] << " <-l> -p port <-a address> <message>" << endl;
return 0;
}
try
{
//Initialisér netværk
netInit();
//Skal vi køre som server eller klient?
if(string(argv[1]) == string("-l"))
{
//Kør i server mode
runServer(argc, argv);
}
else
{
//Kør i client mode
runClient(argc, argv);
}
//Frigiv netværk
netRelease();
} catch (Exception & ex)
{
cout << ex << endl;
}
return 0;
}
Der røg en hel del linjer kode og kompleksitet, så det var jo udemærket.
Konklusion
Det var det!!
Nu er vi alle (næsten) eksperter i netværks programmering. Vi kan både lave multitrådede servere og programmere med UDP og TCP og vælge, hvornår den ene protokol er bedre til at klare opgaven end den anden. Og vi har et meget lille interface, som er let at bruge. Og vores kode kan compile lige godt på Windows og Linux. Kan det næsten blive bedre ?
Ja...det kan det helt sikkert. Netværksprogrammering er et enormt emne og bare fordi vi har fået nogle simple eksempler til at virke, skal vi ikke tro, at det stopper dér. Beklager.
Skulle der være noget, jeg mangler at beskrive (mon ikke der er?), og som gør, at I ikke kan komme videre, så skriv jeres ønsker ned. Enten som en kommentar til artiklen eller på udviklerposten. Så vil jeg måske skrive noget, når jeg får tid.
Indtil da.....happy hacking
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 (1)
det crasher hele tiden...
Du skal være
logget ind for at skrive en kommentar.