10
Tags:
c++
Skrevet af
Bruger #2695
@ 29.02.2004
Introduktion
Hej igen.
Så skal vi igen til at kode noget netværk, og denne gang bliver det nok lidt mere spændende. Vi skal nemlig i gang med at lave netværks forbindelser. Vi vil kigge på, hvad sockets er, og hvordan man bruger dem. Vi bruger den IP adresse klasse, vi udviklede i første artikel om netværk, så jeg håber, at du har gemt projektet.
Målet med vores kode bliver, at forbinde til en webserver og hente forsiden. Det vil illustrere både forsendelse og læsning af data. Til slut vil vi lave en klasse, som indkapsler al funktionaliteten, som vi gennemgår.
Sockets og file descriptors
En file descriptor er et IO objekt, dvs. et objekt som man kan skrive til og/eller læse fra. En socket er også en file descriptor men lidt speciel, idet dét, der skrives/læses, ofte er netværkstrafik. Når man skal forbinde til en computer, laver man først en socket, som man derefter forbinder til serveren. Derefter kan man læse fra og skrive til sin socket og til sidst lukke forbindelsen. Ca. ligesom man kan åbne en fil, skrive til den og læse fra den og til sidst lukke den.
Der findes mange forskellige socket typer, men når vi snakker Internet trafik, koncentrerer vi os om to typer: TCP og UDP.
Denne artikel koncentrerer sig udelukkende om TCP.
TCP protokollen
TCP (Transmission Control Protocol) er en pålidelig protokol, hvilket betyder, at når du skriver 10 beskeder, så kan du være sikker på, at de bliver modtaget i samme rækkefølge, som du skrev dem, og at de alle nåede sin destination, medmindre du fik en fejlbesked. Dette står i kontrast til UDP (User Datagram Protocol), som vi skal se nærmere på i en senere artikel. Det foregår ved, at TCP pakkerne indeholder fortløbende numre, så når operativ systemet på den modtagende computer modtager TCP pakker, så tjekker den om alle pakker, som har et nummer, som er mindre end den nye pakkes, er nået frem. Hvis dette ikke er tilfældet, så sendes en besked tilbage, hvor der bedes om at få tilsendt de manglende pakker. Dette sker uden at vi, som programmerer på et højere niveau, ved det. Det var lige lidt baggrundsviden, så vi ved, hvad vi har med at gøre.
En simpel klient
I denne artikel ser vi udelukkende på, hvordan man laver en netværks klient. En klient er et program, som tager initiativet til at påbegynde kommunikation. Det er den, som "ringer op" til serveren, og jeg har valgt at vise et eksempel, som læser en web servers forside. Vi fortsætter med projektet fra første artikel, hvor der er en fil kaldet test.cpp. Det er den, vi ændrer på i dette eksempel.
De funktioner, vi skal bruge under Linux, er følgende:
int socket(int domain, int type, int protocol);
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
uint16_t htons(uint16_t hostshort);
uint16_t ntohs(uint16_t netshort);
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
int send(int s, const void *msg, size_t len, int flags);
int recv(int s, void *buf, size_t len, int flags);
int close(int fd);
socket opretter en socket tilhørende en protokol familie (
domain som skal være
PF_INET for Internet IPv4), en type (som skal være
SOCK_STREAM for TCP), og en protokol (som kan sættes til 0 eller
IPPROTO_TCP).
connect forbinder en socket til en server. Her skal gives en IP adresse og et port nummer med som parameter.
htons konverterer en
short datatype fra det format, som bruges på din computer til det format, som bruges på Internettet, og
ntohs konverterer tilbage. Vi bruger dem til at konvertere port nummeret til Internet format.
select lader os vente et stykke tid på at noget sker. Hvis vi læser fra en socket, som ikke har modtaget noget, så blokerer vi, indtil der er noget.
select lader os vente kortvarigt og fortæller os, om der er noget at læse.
send skriver et antal bytes fra en buffer til en socket.
flags sætter vi bare til 0.
recv læser et antal bytes fra en socket og placerer dem i en buffer, og igen sætter vi
flags til 0. Den returnerer antallet af bytes, som blev læst (eller -1 hvis der gik noget galt), og da den blokerer, til der er noget, returnerer den aldrig 0,
MEDMINDRE forbindelsen blev lukket i den anden ende.
close lukker en forbindelse og frigiver alle resourcer, der er forbundet med en socket.
På Windows har vi de samme funktioner, bortset fra én, som hedder noget andet og to andre, som tager andre parametre:
int closesocket(SOCKET s);
int recv(SOCKET s, char * buf, int len, int flags);
int send(SOCKET s, char * buf, int len, int flags);
Det var en hel del, men nu skal vi endelig til at kode. Koden til vores eksempel bliver ret lang, men jeg kommenterer, hvad jeg gør undervejs.
//test.cpp
#include "Net.h"
#include "IPAddress.h"
#include "UnknownHostException.h"
#include "NetException.h"
#include <iostream>
#if defined(__linux__)
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
using namespace std;
void connectAndRead(string host)
{
//Slå adressen op
IPAddress adr(host);
//Denne buffer bruger vi til at indeholde de data vi modtager
char buffer[1024];
//Til at gemme fejlkoder i
int error;
//Til at gemme antallet af læste bytes
int bytesRead;
//Dette er vores socket
int sock;
//Denne struktur skal indeholde adressen og portnummeret
//som vi vil forbinde til
struct sockaddr_in adr_srvr;
//Lav vores socket
sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
#if defined(__linux__)
if(sock == -1)
#elif defined(_WIN32)
if(sock == INVALID_SOCKET)
#endif
{
cout << "Kunne ikke oprette socket." << endl;
return;
}
//Nulstil adresse
memset(&adr_srvr,0,sizeof(adr_srvr));
adr_srvr.sin_family = AF_INET; //Adresse familie
adr_srvr.sin_port = htons(80); //Port 80 er webserver porten
adr_srvr.sin_addr = adr.getAddress(0); //Tag den første adresse
//Forbind til web serveren
error = connect(sock,(sockaddr*)&adr_srvr,sizeof(adr_srvr));
#if defined(__linux__)
if(error == -1)
{
cout << "Kunne ikke forbinde til " << host << endl;
//Vi skal stadig frigive vores socket
close(sock);
return;
}
#elif defined(_WIN32)
if(error == SOCKET_ERROR)
{
cout << "Kunne ikke forbinde til " << host << endl;
//Vi skal stadig frigive vores socket
closesocket(sock);
return;
}
#endif
//Vi har nu en forbindelse til web serveren. Skriv at vi vil have deres
//forside. Dette er HTTP protokol snak.
string toSay = string("GET / HTTP/1.1\\r\\nHost: ") + host + string("\\r\\nConnection: close\\r\\n\\r\\n");
//Sig det
send(sock,toSay.c_str(),toSay.size(),0);
//Nu sender de deres side og en masse headere. Læs dem og skriv det hele
//til standard out.
while((bytesRead = recv(sock,buffer,sizeof(buffer),0)) > 0)
{
cout.write(buffer,bytesRead);
cout.flush();
}
//Tjek om noget gik galt
if(bytesRead == -1)
{
cout << "Der skete en fejl under læsning." << endl;
}
//Luk vores socket
#if defined(__linux__)
close(sock);
#elif defined(_WIN32)
closesocket(sock);
#endif
}
int main(int argc, char ** argv)
{
//Tjek om der er parametre
if(argc == 1)
{
cout << "Brug: " << argv[0] << " <IP|host>" << endl;
}
else
{
//netInit kan kaste exceptions
try
{
//Initialisér net komponenten
netInit();
try
{
string adrstr(argv[1]);
//Læs en hjemmeside
connectAndRead(adrstr);
} catch (NetException & nex)
{
cout << nex << endl;
}
//Frigiv net komponenten
netRelease();
} catch (NetException ex)
{
cerr << "Kunne ikke initialisere net komponenten: " << ex << endl;
}
}
return 0;
}
Og igen compiler det uden problemer. Vi prøver at køre det:
[robert@codemachine Socket2]$ ./test 192.168.1.1
Kunne ikke forbinde til 192.168.1.1
[robert@codemachine Socket2]$ ./test www.google.com
HTTP/1.1 302 Found
Location: http://www.google.dk/cxfer?c=PREF%3D:TM%3D1076721259:S%3DQhm8oewdI0GODm-D
Set-Cookie: PREF=ID=3ebed643cacf7f51:CR=1:TM=1076721259:LM=1076721259:S=8C3PecDWBI8XMzth; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
Content-Type: text/html
Server: GWS/2.1
Content-length: 203
Date: Sat, 14 Feb 2004 01:14:19 GMT
<HTML><HEAD><TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.dk/cxfer?c=PREF%3D:TM%3D1076721259:S%3DQhm8oewdI0GODm-D">here</A>.
</BODY></HTML>
[robert@codemachine Socket2]$
Den første kørsel går galt, fordi jeg prøver at forbinde til min routers port 80, hvor der ikke kører en service. Fair nok. Google derimod har en masse at sige.
Det var slet ikke så slemt.
Nu brugte jeg slet ikke
select kaldet. Det er fordi, jeg ønskede den blokerende funktionalitet. Hvis programmet var multitrådet, ville jeg nok lytte i et sekund, derefter tjekke om jeg skulle lytte videre og så lytte et sekund igen, osv. Det lægger vi ind i vores klasse, når vi designer den.
En TCPSocket klasse
Så skal vi i gang. Klassen skal, ligesom i tidligere artikler, kaste exceptions for at indikere fejl og undtagelsestilstande, og til det bruger vi følgende udviddelser til exception klasserne fra tidligere:
ConnectionLostException kastes, hvis computeren, vi kommunikerer med, lukker forbindelsen.
TimerException kastes for at vise, at en timer er udløbet. I TCPSocket definerer jeg en mulighed for at standse en ellers blokerende funktion efter et antal millisekunder. Når denne tid er gået, kastes en
TimerException. Jeg har ikke lagt den under
NetException, da jeg måske vil bruge den andre steder også.
Alle andre fejl kaster
NetExceptions.
Som alle andre exceptions, er implementeringen meget ligetil:
//ConnectionLostException.h
#if !defined(CONNECTIONLOSTEXCEPTION_H)
#define CONNECTIONLOSTEXCEPTION_H
#include "NetException.h"
class ConnectionLostException : public NetException
{
public:
ConnectionLostException(string description);
};
#endif
//ConnectionLostException.cpp
#include "ConnectionLostException.h"
ConnectionLostException::ConnectionLostException(string description) : NetException(description)
{
}
og
//TimerException.h
#if !defined(TIMEREXCEPTION_H)
#define TIMEREXCEPTION_H
#include "Exception.h"
class TimerException : public Exception
{
public:
TimerException(string description);
};
#endif
//TimerException.cpp
#include "TimerException.h"
TimerException::TimerException(string description) : Exception(description)
{
}
Jeg har defineret følgende interface til TCPSocket klassen:
//TCPSocket.h
#if !defined(TCPSOCKET_H)
#define TCPSOCKET_H
#include "IPAddress.h"
class TCPSocket
{
//Dette gør, at TCPServerSocket, som vi laver i næste artikel, kan bruge vores
//private constructor
friend class TCPServerSocket;
public:
//Opretter en socket som forbinder til en adresse
//baseret på IPAddress klassen og en port
TCPSocket(IPAddress & adr, UInt16 port, UInt32 timeOut = 0);
//Opretter en socket som forbinder til en adresse
//baseret på string klassen og en port
TCPSocket(string & adr, UInt16 port, UInt32 timeOut = 0);
//Deallokerer socket objektet, lukker evt. forbindelsen
~TCPSocket();
//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);
//Opretter en socket og forbinder til den angivne adresse og port
void connect();
//Læser op til 'size' bytes og placerer dem i 'buffer'.
//Returnerer antallet af bytes, som blev læst.
//Hvis 'm_timeOut' er større end 0 ventes dette antal
//millisekunder eller til der er data at læst. Udløber
//tiden kastes en TimerException.
SInt32 read(void * buffer, SInt32 size);
//Skriver 'size' bytes fra 'buffer'.
//Returnerer antallet af bytes, som blev skrevet.
SInt32 write(const void * buffer, SInt32 size);
//Lukker forbindelsen og frigiver vores socket
void close();
//Returnerer en pointer til adressen
IPAddress * getAddress();
//Returnerer porten
UInt16 getPort();
//Returnerer timeout
UInt32 getTimeOut();
private:
//Denne constructor opretter et TCPSocket objekt ud fra en eksisterende
//socket. Det bliver nødvendigt i næste artikel, hvor vi programmerer
//servere. Den vil blive brugt af TCPServerSocket klassen
TCPSocket(int socket, struct sockaddr_in sock_addr, UInt32 timeOut = 0);
//Denne funktion blokerer i 'm_timeOut' millisekunder, eller til
//der er data at læse.
void waitForData();
//Adressen som vi skal forbinde til
IPAddress m_address;
//Porten som vi forbinder til
UInt16 m_port;
//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
Implementeringen er ret ligetil:
//TCPSocket.cpp
#include "TCPSocket.h"
#include "NetException.h"
#include "ConnectionLostException.h"
#include "TimerException.h"
#if defined(__linux__)
#include <unistd.h>
#endif
TCPSocket::TCPSocket(int socket, struct sockaddr_in sock_addr, UInt32 timeOut)
: m_address(sock_addr.sin_addr), m_port(ntohs(sock_addr.sin_port)), m_socket(socket), m_timeOut(timeOut)
{
}
TCPSocket::TCPSocket(IPAddress & adr, UInt16 port, UInt32 timeOut)
: m_address(adr), m_port(port), m_socket(0), m_timeOut(timeOut)
{
}
TCPSocket::TCPSocket(string & adr, UInt16 port, UInt32 timeOut)
: m_address(adr), m_port(port), m_socket(0), m_timeOut(timeOut)
{
}
TCPSocket::~TCPSocket()
{
//Hvis m_socket ikke er 0, er vi stadig forbundet
if(m_socket != 0)
{
close();
}
}
void TCPSocket::setTimeOut(UInt32 timeOut)
{
m_timeOut = timeOut;
}
void TCPSocket::connect()
{
//Denne struktur bruger vi til at forbinde mod
struct sockaddr_in adr_srvr;
//Denne tæller bruges til looping
UInt32 i;
//Denne variabel bruges til at tjekke for fejl
int error;
//Hvis m_socket ikke er 0 er vi allerede forbundet
if(m_socket != 0)
{
throw NetException("Already connected.");
}
//Opret en socket
m_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
#if defined(__linux__)
if(m_socket == -1)
{
throw NetException(strerror(h_errno));
}
#elif defined(_WIN32)
if(m_socket == INVALID_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
//Nulstil adressen
memset(&adr_srvr,0,sizeof(adr_srvr));
//Sæt adresse familie
adr_srvr.sin_family = AF_INET;
//Sæt port
adr_srvr.sin_port = htons(m_port);
//Vi bliver ved med at prøve at forbinde til alle adresser
//eller til det lykkes
error = -1;
i = 0;
while(error == -1 && i < m_address.numAdresses())
{
//Prøv med adresse nummer 'i'
adr_srvr.sin_addr = m_address.getAddress(i);
//Prøv at forbinde
error = ::connect(m_socket, (sockaddr*)&adr_srvr, sizeof(adr_srvr));
//Tæl 'i' op så vi næste gang tager næste adresse
i++;
}
//Hvis 'error' er -1 kunne vi ikke forbinde til nogen adresse
if(error == -1)
{
//Frigiv først vores socket
close();
//Kast derefter en exception
#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
}
}
SInt32 TCPSocket::read(void * buffer, SInt32 size)
{
//Denne variabel indeholder antallet af bytes
//som vi læser
SInt32 bytesRead;
//Vent først på data
waitForData();
//Hvis vi nåede til denne linje er der data til os
bytesRead = recv(m_socket,(char*)buffer,size,0);
//Hvis 'bytesRead' er -1, gik noget galt
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
}
//Hvis vi læste 0 bytes er forbindelsen stoppet
else if(bytesRead == 0)
{
throw ConnectionLostException("Connection reset by peer.");
}
return bytesRead;
}
SInt32 TCPSocket::write(const void * buffer, SInt32 size)
{
SInt32 bytesWritten = send(m_socket,(char*)buffer,size,0);
//Hvis 'bytesWritten' er -1 gik noget galt
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;
}
void TCPSocket::close()
{
//Frigiv kun hvis vi har en socket
if(m_socket != 0)
{
#if defined(__linux__)
::close(m_socket);
#elif defined(_WIN32)
closesocket(m_socket);
#endif
m_socket = 0;
}
}
void TCPSocket::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
}
}
IPAddress * TCPSocket::getAddress()
{
return &m_address;
}
UInt16 TCPSocket::getPort()
{
return m_port;
}
UInt32 TCPSocket::getTimeOut()
{
return m_timeOut;
}
Ja, det var vist heller ikke så slemt selvom, der var meget kode.
En revideret web læser
Nu hvor vi har en TCPSocket klasse skal vi som sædvanligt kode vores eksempel om, så det bruger klassen. Det har jeg gjort her:
//test.cpp
#include "Net.h"
#include "UnknownHostException.h"
#include "NetException.h"
#include "ConnectionLostException.h"
#include "TCPSocket.h"
#include <iostream>
#if defined(__linux__)
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
using namespace std;
void connectAndRead(string host)
{
//Denne buffer kommer til at indeholde, hvad vi læser
char buffer[1024];
//Denne variabel fortæller os, hvor mange bytes vi læser
int bytesRead;
//Lav et socket objekt uden timeout
TCPSocket sock(host,80);
//Forbind
sock.connect();
//HTTP request
string toSay = string("GET / HTTP/1.1\\r\\nHost: ") + host + string("\\r\\nConnection: close\\r\\n\\r\\n");
sock.write(toSay.c_str(),toSay.size());
try
{
//Denne løkke forlades, når forbindelsen lukkes
while(1)
{
//Læs til buffer
bytesRead = sock.read(buffer,sizeof(buffer));
//Skriv til standard out
cout.write(buffer,bytesRead);
cout.flush();
}
} catch (ConnectionLostException & cle)
{
//Serveren stoppede forbindelsen. Dette lader os bare forlade den
//ellers uendelige løkke
}
sock.close();
}
int main(int argc, char ** argv)
{
//Tjek om der er parametre
if(argc == 1)
{
cout << "Brug: " << argv[0] << " <IP|host>" << endl;
}
else
{
//netInit kan kaste exceptions
try
{
//Initialisér net komponenten
netInit();
try
{
string adrstr(argv[1]);
//Læs en hjemmeside
connectAndRead(adrstr);
} catch (NetException & nex)
{
cout << nex << endl;
}
//Frigiv net komponenten
netRelease();
} catch (NetException ex)
{
cerr << "Kunne ikke initialisere net komponenten: " << ex << endl;
}
}
return 0;
}
connectAndRead funktionen er blevet lidt pænere og lettere at læse. Til Linux skal Makefilen også ændres lidt til at compile de nye filer:
#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
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
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
Det skulle være det!!!
Konklusion
Det var slutningen. Håber du har lært noget. Vi har fået udviklet en klasse, som gør det noget nemmere at kode netværk. Det er et mindre og simplere interface, som er nemmere at huske, men vi har indtil videre været nødt til at bruge en webserver til at teste vores kode. Det ændrer sig i næste artikel, hvor vi skal se på server programmering. Det er faktisk også ret let.
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 (2)
Endnu engang guffeluf, dejligt med alle de kommentarer du smider undervejs.
En rigtig fin artikel
test www.google.com virker fint i wondows kommandoprompt, men hvis jeg prøver med en ip-adresse fx 127.0.0.1, så sker der ikke andet end at programmet låser indtil der kommer en runtime-error.
Er der en fejl i koden omkring TCPSocket-objektet?
Du skal være
logget ind for at skrive en kommentar.