Go-Back-N
A. Fifth Edition
This is also a hard-working one since I spent almost two days on it even though I didn't concentrate very much.
The most of time, I feel a kind of ease of coding it since it is a just an update on previous UDP. The most of
job is just to get a clear picture of protocol.
In network, stop-and-wait is very slow and go-back-N is more practical one. It is one of sliding window
algorithm which runs smoothly well in the environment of delay and loss of packets.
1. The sender read a packet and send a packet until packets of number of window size have been filled in buffer.
2. The sender now listen to receiver's reply. If acknowledged n, then current confirmed moved to n. If timeout,
send from current n to final one in current window.
3. The receiver always confirm the correct one when received in order. If out of order one, just ignore it. If
timeout, acknowledge last one received in order.
4. The sender will make sure the last packet of file is confirmed to be received. Then it sends out EOF packet
and make sure it is confirmed. Then sender sends out an extra END packet which will assure the receiver that the
sender already know transmission is successful. However, if the receiver's acknowledgement for END is lost after
the receiver quit loop, it won't do any harm since both sides are now sure file transmission is successful.
5. When the window size is reduced to 1, the transmission is automatically reduced to stop-and-wait. Make sure
the router now is NOT allowed delay.
6. The window size is one less than buffer size which is used for modulo so that there is no misunderstanding
when window is advanced. (It is absolutely necessary to make your window size at least one smaller than the
modulo, otherwise you will mix up with windows. ) For example, the window size is 4 and you choose your modulo
as 4:
sender: send 0,1,2,3
recver: recved 0,1,2,3 and send ACK(3) and it is lost. (Or all ACK(0),ACK(1),ACK(2) are also lost.)
sender: timeout and resend old 0,1,2,3
recver: thought these are new window of 0,1,2,3 (MIXED UP!!!)
7. There are several useful lessons I learned:
a) Don't use "strncpy" and use "memcpy"! This is very subtle since strncpy will pad up '\0' when source is
shorter than target. This will cause some potential problem in socket programming since you never know how
the API is implemented. There is some unconfirmed bizarre behaviour observed by me.
b)
int recvfrom( SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR *from, int FAR *fromlen );
Be careful with "len" which is a in parameter for maximum bytes of message it could received. Make
sure your sender won't send longer message than this.
c)
BOOL ReadFile( HANDLE hFile, // handle to file LPVOID lpBuffer, // data buffer DWORD nNumberOfBytesToRead, // number of bytes to read LPDWORD lpNumberOfBytesRead, // number of bytes read LPOVERLAPPED lpOverlapped // overlapped buffer );
How do you know EOF? The return bool value seems to be an answer. But it is WRONG! You have to check if
lpNumberOfBytesRead==0.
Also by using a micro you don't have to change linking option to include necessary "library".
# pragma comment (lib, "wsock32.lib")
E.Further improvement
F.File listing
1. error.h
2. error.cpp
3. log.h
4. log.cpp
5. packet.h
6. socket.h
7. socket.cpp
8. client.cpp
9. server.cpp
10. router.h
11. router.cpp
project client:
1. error.h
2. error.cpp
3. log.h
4. log.cpp
5. packet.h
6. socket.h
7. socket.cpp
8. client.cpp
project server:
1. error.h
2. error.cpp
3. log.h
4. log.cpp
5. packet.h
6. socket.h
7. socket.cpp
8. server.cpp
project router: (This is given by the assignment.)
1. router.h
2. router.cpp
workspace setup:
1. create an empty "workspace".
2. copy all above source code under the workspace.
3. "new" a project named "client" by "add to current workspace".
4. "new" a project named "server" by "add to current workspace".
5. "new" a project named "router" by "add to current workspace".
6. switch between two project and from "project" menu "add files" to project.
The advantage:
a) By doing above, you don't have to modify the "file path" of all head files.
b) You don't have to copy&paste your code in both client and server project because they
actually using overlapped "socket" files.
c) You can debug them under same work space. (trivial)
file name: error.h
#ifndef ERROR_H #define ERROR_H #include <stdio.h> #include <stdlib.h> #include "log.h" const int ErrorNumber=17; const int SeriousProblem=9; #define CANNOT_OPEN_LOG 0 #define CANNOT_START_WSASTARTUP 1 #define GETHOSTNAMEFAILED 2 #define REMOTE_GETHOSTNAME_FAILED 3 #define SOCKET_FAILED 4 #define CONNECT_FAILED 5 #define CANNOT_BIND_SOCKET 6 #define CANNOT_SETUP_LISTEN 7 #define CONNECTION_INTERRUPT 8 #define CANNOT_OPEN_FILE_FOR_PUT 9 #define CANNOT_OPEN_FILE_FOR_GET 10 #define WAIT_TIME_OUT 11 #define ERROR_DATA_RCVED 12 #define EMPTY_FILE_TO_BE_SENT 13 #define FAILURE_IN_SELECT 14 #define GET_BUFFER_ERROR 15 #define MAX_ERROR_NUMBER_REACHED 16 static char* errorStrings[ErrorNumber]= { "CANNOT_OPEN_LOG", "Error in starting WSAStartup", "GET HOST NAME FAILED", "GET REMOTE HOST NAME FAILED", "SOCKET_FAILED", "CONNECT_FAILED", "CANNOT_BIND_SOCKET", "CANNOT_SETUP_LISTEN", "TRANSMISSION INTERRUPTED UNEXPECTEDLY", "CANNOT_OPEN_FILE_FOR_PUT", "CANNOT_OPEN_FILE_FOR_GET", "WAIT_TIME_OUT", "transmission error happened at remote host", "EMPTY_FILE_TO_BE_SENT", "FAILURE_IN_SELECT", "GET_BUFFER_ERROR", "MAX_ERROR_NUMBER_REACHED", }; struct FTPExcep { int errNo; int remoteErrNo; }; void errorHandler(int errorNo, int remoteErrNo=0); #endif
file name: error.cpp
#include "error.h" #include <stdio.h> #include <stdlib.h> #include <windows.h> void errorHandler(int errorNo, int remoteErrNo) { FTPExcep excep; char errbuf[20]; writeLog(errorStrings[errorNo]); sprintf(errbuf, "The last error is %d", GetLastError()); /* //if (errorNo==CONNECTION_INTERRUPT||errorNo==ERROR_DATA_RCVED) { sprintf(errbuf, "The last error is %d", WSAGetLastError()); } //else { sprintf(errbuf, "The last error is %d", GetLastError()); } */ writeLog(errbuf, true); excep.errNo=errorNo; //this remoteErrno would be shown at remote host excep.remoteErrNo=remoteErrNo; throw excep; //only the minor error need to be handled with exception /* switch (errorNo) { case REMOTE_GETHOSTNAME_FAILED: case CANNOT_OPEN_FILE_FOR_PUT: case CANNOT_OPEN_FILE_FOR_GET: case WAIT_TIME_OUT: case ERROR_DATA_RCVED: case EMPTY_FILE_TO_BE_SENT: case CONNECT_FAILED: { FTPExcep excep; excep.errNo=errorNo; //this remoteErrno would be shown at remote host excep.remoteErrNo=remoteErrNo; throw excep; } } //default is to exit exit(1); */ }
file name: log.h
#ifndef LOG_H #define LOG_H #include <stdio.h> #include <stdlib.h> #include "error.h" typedef unsigned long ulong; //this will only be called once void startLog(); void closeLog(); //write anything you want ulong writeLog(char* buffer, bool toDisplay=false); #endif
file name: log.cpp
#include "log.h" #include "error.h" #include <time.h> #include <windows.h> const int MaxLogNameLength=20; char logName[MaxLogNameLength]; HANDLE log=NULL; void getTimeStamp(char* buf); void getTimeStamp(char* buf) { char timeBuf[128]; _strtime(timeBuf); strcpy(buf, timeBuf); strcat(buf, " "); _strdate(timeBuf); strcat(buf, timeBuf); } void closeLog() { CloseHandle(log); } //this should be called only once void startLog() { //getTimeStamp(logName); //strcat(logName, timeBuf); //sprintf(logName, "%u", time(0)); //this should be called only once, so.... if (log==NULL) { strcpy(logName, "log"); strcat(logName, ".txt"); //if ((log=CreateFile(logName, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, // FILE_ATTRIBUTE_NORMAL, NULL))==INVALID_HANDLE_VALUE) if ((log=CreateFile(logName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL))==INVALID_HANDLE_VALUE) { errorHandler(CANNOT_OPEN_LOG); } } } ulong writeLog(char* buffer, bool toDisplay) { char timeBuf[128]; //long distance; ulong result, temp; getTimeStamp(timeBuf); strcat(timeBuf, ":\t"); //GetFileSize(log, &distance); SetFilePointer(log, 0, NULL, FILE_END); WriteFile(log, timeBuf, strlen(timeBuf), &result, NULL); WriteFile(log, buffer, strlen(buffer), &result, NULL); if (toDisplay) { printf("%s\n", buffer); } //strcpy(timeBuf, "\n"); timeBuf[0]=13; timeBuf[1]=10; timeBuf[2]=0; WriteFile(log, timeBuf, strlen(timeBuf), &temp, NULL); return result; }
file name: packet.h
#ifndef PACKET_H #define PACKET_H typedef unsigned short ushort; typedef unsigned long ulong; typedef unsigned char uchar; //const int PacketDataSize=120; //const int SequenceBitNumber=1; //all size indicates the length of "data" field of struct packet //get contains the file name as "data", size of file as "tail" // 0000.0000 const uchar GETTYPE =0x00; //put contains the file name as data, size of file as tail // 0001.0000 const uchar PUTTYPE=0x10; //ACKNOWLEDGE TYPE // 0010.0000 const uchar ACKTYPE = 0x20; //the size of data is contained in size, in tail, it may contains the index number of packet // 0011.0000 const uchar DATATYPE= 0x30; //this is a special packet, the data may also contains "data" // 0100.0000 const uchar EOFTYPE = 0x40; //this is used for confirmation of EOF at end of transmission // 1000.0000 const uchar ENDTYPE = 0x80; //whenever anything goes wrong // 1111.0000 const uchar ERRORTYPE= 0xF0; //this type_mask happens to be same as error-type????? const uchar TYPEMASK= 0xF0; //this mask get you the sequence number const uchar SEQUENCEMASK= 0x0F; //0X0F|0X030 const uchar ACKMASK=ACKTYPE|SEQUENCEMASK; #endif
file name: socket.h
#ifndef SOCKET_H #define SOCKET_H #include <winsock.h> #include "packet.h" #include "error.h" # pragma comment (lib, "wsock32.lib") //#define REQUEST_PORT 0x7070 #define SERVER_PORT 5000 //port number of peer host 1 #define CLIENT_PORT 5001 //port number of peer host 2 #define SERVER_ROUTER_PORT 7000 #define CLIENT_ROUTER_PORT 7001 #define TIMEOUT_USEC 300000 #define TIMEOUT_MSEC 300 //int port=REQUEST_PORT; enum Role { ClientSender, ClientReceiver, ServerSender, ServerReceiver }; enum Mode { StopAndWait, GoBackN }; const int MessageBufferSize=128; const int SecondStart=100; const int MaxMsgNumber=10; const int MaxMsgSize=127; //for debug static bool bTracing=true; class Socket { private: char fileBuf[MaxMsgNumber][MaxMsgSize+1]; int packetLength[MaxMsgNumber]; int winSize;//should be 1,3,7 ushort current; ushort last;//this is last packet confirmed received ushort final;//this is the final packet in buffer for sending ushort winStart; int bufferSize;//this is the modulo,should be winsize+1 //Queue queue; //socket data types bool gobackN; Role role; fd_set readfds; timeval timeout; bool bServer; int port; SOCKET s, ser; SOCKADDR_IN sa; // filled by bind SOCKADDR_IN sa_to; // fill with server info, IP, port sockaddr sa_from; //just for recv HOSTENT *hp; HOSTENT *rp; WSADATA wsadata; int byteSend, byteRecv; int recvCount, sendCount; ulong fileSize; //ushort sequence, MaxLength; char buffer[128]; char msgbuf[128]; HANDLE hfile; void writeToLog(bool toDisplay=false); void initialize(); void serverConn(); void recvFile(); void sendFile(); void connectTo(char* serverName); uchar getType(uchar theType); //void randomStartSequence(char& theType); //bool mustRepeat(); //void incSequence(); //bool isNewMsg(); void sendRequest(); void replyRequest(); void sendAllPacket(); //void sureRequest(uchar request, char* requestName); void errorCheck(); //bool checkPacket(bool senderCheck=false); //void replyEOF(); //int checkReply(); void sendEOF(); void replyEOF(); ushort getPacketNumber(); void confirm(bool positive); bool senderCheckPacket(); bool recverCheckPacket(); bool newCurrent(ushort curr, ushort newNum); public: char localhost[11]; char remotehost[11]; void clientConn(char* serverName); char incoming[MaxMsgSize]; //incoming message is here char outgoing[MaxMsgSize]; void closeFile(); void exceptionHandler(const FTPExcep& excep); ~Socket(); Socket(bool isServer=false); bool listening(); void sendPacket(); void recvPacket(); void setWindowSize(int newSize); void setRole(Role newRole); void clientTranFile(); void serverTranFile(); };
file name: socket.cpp
/******************************************************************************* *socket.h encapsulate socket functions and can act as both client and server *author: Huang, Qingzhe(Nick), ID: 5037735 GUAN, Huihua(Angel), ID:4778375 *date: nov. 16, 2004 ********************************************************************************/ /******************************************************************************** features: 1. all data and command are sent in a data unit of "packet" (see packet.h) 2. socket transimission function are similar in both server and client side, so, I decide to write socket as a single class just like "delphi". 3. socket is closed at both client and server side after each file transmission. 4. robust error handling mechnism combined with "exception" handling. a) file name is checked before send out request. b) empty file doesn't allow to be sent. c) when a packet of type of "ErrorType" is received, exception is thrown. d) when file open error occurs, an "ErrorType" packet is sent to remote host to abort current operation along with error no message so that remote host can display error information accordingly. ********************************************************************************/ /********************************************************************************* file transmission protocol: 1. put file: a) Client sends an outgoing with outgoing[0] first 4 bits representing "PUTTYPE" and second 4 bits representing the size of window. The "data" field or "outgoing+1" stores the file name to be sent. b) Server replies by sending back the "head" of request. c) Client starts sending data packet when receiving the reply from server until end of file. d) Client sends out a packet of type of "EOFType" to indicate end of transimission. e) After client receives the reply of EOF from server, it sends out another "END" packet and waiting reply from server. f) This way will increase some kind of saftey of transmission since the "END" is a sign of successful transmission of "EOF" which already indicates the success of transimission of file. In other words, if you want to ensure something, make sure the thing AFTER it is successful. 2. get file: It is almost similar to put file. *********************************************************************************/ /******************************************************************************* transfer protocal: stage 1: negotiation: case 1: client=sender, server=receiver client: send put-request if time-out repeat else transmitting starts; server: receive put-request, send ack if receive data type, receiving starts. case 2: client=receiver, server=sender client: send get-request if time-out repeat else receiving data-type and receiving starts; server: receive get-request and start sending data stage 2: file-transmission: sender: loop until all packets in window has been confirmed if time-out repeat sending between current and final one in window if received ack(n), current =n; end; receiver: last= winsize;//the last one current=0; loop until received packet of EOF if receive packet(current) ack(current); write packet(i) into file if timeout, send ack(last) which is last confirmation if received packet(i) where i!=current, just ignore it. end; stage 3: end of transmission: sender: send eof-type-packet if timeout then repeat send eof-packet receiever: if (packet.type=eof) then ack(eof); ******************************************************************************/ #include <winsock2.h> #include <time.h> #include "socket.h" #include <string.h> #include "log.h" #include "error.h" #include "packet.h" //const long TimeOutLimit=10; //const int ErrorLimit=40; const int MaxErrorNumber=16; int errorCount; void Socket::setWindowSize(int newSize) { winSize=newSize; bufferSize=winSize+1; current=0; last=winSize;//last is last before current if (winSize!=1) { gobackN=true; } else { gobackN=false; } } void Socket::setRole(Role newRole) { role= newRole; } void Socket::replyRequest() { byteSend=1; outgoing[0]=incoming[0]; errorCount=0; while (true) { if (listening()) { if (getType((uchar)incoming[0])==DATATYPE) { break; } else { errorCount++; sendPacket(); } } else { errorCount++; } errorCheck(); } } void Socket::sendRequest() { errorCount=0; do { errorCheck(); sendPacket(); }while (!listening()); } bool Socket::newCurrent(ushort curr, ushort newNum) { while (curr!=final) { if (curr==newNum) { return true; } curr=(curr+1)%bufferSize; } return false; } bool Socket::senderCheckPacket() { //recvCount=0; errorCount=0; while (current!=final) { if (listening()) { if (getType(incoming[0])==EOFTYPE) { return true; } if (getType(incoming[0])==ACKTYPE) { if (newCurrent(current, getPacketNumber())) { current=getPacketNumber(); sprintf(msgbuf, "Sender: received a ACK for packet %d\n", current); writeToLog(); //recvCount++; //current is common for both sender and recver current=(current+1)%bufferSize;//bufferSize=winSize+1 } else { return false; } //return true; } /* if (getType(incoming[0])==NAKTYPE) { sprintf(msgbuf, "Sender: received a NAK for packet %d\n", getPacketNumber()); writeToLog(); //current=getPacketNumber(); //sender will return return false; } */ } else { return false;//timeout } } //printf("one window succed\n"); return true; } //sequence already started void Socket::sendFile() { int counter=0, index; bool bLastPacket=false; fileSize=0; //now start sending data outgoing[0]=DATATYPE; //current=winStart=0; current=0; //sendCount=0; //outgoing[0]+=sequence; while (true) { index=current; final=(current+(gobackN?winSize:1))%bufferSize; while (index!=final) { ReadFile(hfile, fileBuf[index], MaxMsgSize-1, (LPDWORD)&packetLength[index], NULL); if (packetLength[index]==0) { //only execute once bLastPacket=true; final=index;//only when EOF, last is different break; //break the read loop } //sendCount++; counter++; fileSize+=packetLength[index]; index=(index+1)%bufferSize; } sendAllPacket(); //send EOF errorCount=0; while (!senderCheckPacket()) { errorCount++; //printf("errorcount=%d\n", errorCount); errorCheck(); sendAllPacket(); } if (bLastPacket) { sprintf(msgbuf, "Sender: number of packets sent: %d\n", counter); writeToLog(true); sprintf(msgbuf, "Sender: number of bytes sent: %d\n", fileSize); writeToLog(true); CloseHandle(hfile); sendEOF(); break; //sendCount++;//add this } } } void Socket::errorCheck() { errorCount++; if (errorCount==MaxErrorNumber) { errorHandler(MAX_ERROR_NUMBER_REACHED); } } void Socket::replyEOF() { outgoing[0]=EOFTYPE; errorCount=0; byteSend=1; do { sendPacket(); sprintf(msgbuf, "Receiver: send EOF packet\n"); writeToLog(); if (listening()) { if (getType(incoming[0])==ENDTYPE) { break; } } errorCheck(); } while (true); //at this point, it is even safe for receiver to quit since he knows //that sender already knows he receives all file packets outgoing[0]=ENDTYPE; sprintf(msgbuf, "Receiver: send END packet and quit transmission\n"); writeToLog(); sendPacket(); } void Socket::sendEOF() { outgoing[0]=EOFTYPE; errorCount=0; byteSend=1; while (true) { sendPacket(); sprintf(msgbuf, "Sender: send EOF packet\n"); writeToLog(); if (listening()) { if (getType(incoming[0])==EOFTYPE) { break; } } errorCheck(); } outgoing[0]=ENDTYPE; errorCount=0; while (true) { sendPacket(); sprintf(msgbuf, "Sender: send END packet\n"); writeToLog(); if (listening()) { if (getType(incoming[0])==ENDTYPE) { break; } } errorCheck(); } } void Socket::sendAllPacket() { int index=current; while (index!=final) { outgoing[0]=DATATYPE; outgoing[0]+=index; memcpy(outgoing+1, fileBuf[index], packetLength[index]); byteSend=packetLength[index]+1;//because the head sendPacket(); //counter++; //fileSize+=byteSend-1;//calculate filesize,minus size of header sprintf(msgbuf, "Sender: send packet %d\n", index); writeToLog(); index=(index+1)%bufferSize; } } ushort Socket::getPacketNumber() { return SEQUENCEMASK&incoming[0]; } void Socket::confirm(bool positive) { if (positive) { outgoing[0]=(char)current; outgoing[0]|=ACKTYPE; //strncpy(fileBuf[current], incoming+1, byteRecv-1); //packetLength[current]=byteRecv-1;//record length sprintf(msgbuf, "Receiver: sent an ACK for packet %d\n", current); writeToLog(); } else { outgoing[0]=(char)last; outgoing[0]|=ACKTYPE; //outof order no NAK //outgoing[0]|=NAKTYPE; //confirm last received packet sprintf(msgbuf, "Receiver: sent an ACK for packet %d\n", last); writeToLog(); } //byteSend=1; sendPacket(); } //sender and receiver will both use this function //but receiver will not return unless all winsize packet received or EOF detected //sender will return if NAK received plus receiver's condition bool Socket::recverCheckPacket() { //recvCount=0; errorCount=0; while (true) { if (listening()) { if (getType(incoming[0])==EOFTYPE) { //recver need to confirm and return //just confirm once and return //outgoing[0]=incoming[0]; //sendPacket(); return true; } if (current==getPacketNumber()) { sprintf(msgbuf, "Receiver: received packet %d\n", current); writeToLog(); confirm(true); return true; //recvCount++; //current is common for both sender and recver //current=(current+1)%bufferSize;//bufferSize=winSize+1 } //ignore out of order packet else { sprintf(msgbuf, "Receiver: received out of order packet %d\n", getPacketNumber()); writeToLog(); } } else { confirm(false); errorCheck(); } } } //already received data void Socket::recvFile() { int counter=0, number; //bool bStart=false; current=0; fileSize=0; byteSend=1;//always one byte to send last=winSize;//the last one acknowledged: (last+1) mod bufferSize=current while (true) { if (recverCheckPacket()) { if (getType(incoming[0])==EOFTYPE) { sprintf(msgbuf, "Receiver: transfer completed\n"); writeToLog(true); sprintf(msgbuf, "Receiver: number of packet received: %d", counter); writeToLog(true); sprintf(msgbuf, "Receiver: number of bytes received: %d", fileSize); writeToLog(true); SetEndOfFile(hfile); CloseHandle(hfile); replyEOF(); return; } //WriteFile(hfile, fileBuf[current], (DWORD)(packetLength[current]), (LPDWORD)&number, // NULL); WriteFile(hfile, incoming+1, (DWORD)(byteRecv-1), (LPDWORD)&number, NULL); counter++; fileSize+=byteRecv-1; current=(current+1)%bufferSize; last=(last+1)%bufferSize;//last still is last } } } void Socket::closeFile() { CloseHandle(hfile); hfile=NULL; } uchar Socket::getType(uchar theType) { //return theType>>4; return TYPEMASK&theType; } /* void Socket::randomStartSequence(char& theType) { sequence=rand()%MaxLength;//for the very first time theType+=sequence; } */ void Socket::clientTranFile() { char* fileName; fileName=outgoing+1; //the file name starts from second byte, plus the first type byte byteSend=strlen(fileName)+1; switch(getType((uchar)outgoing[0])) { case PUTTYPE: sprintf(msgbuf, "prepare to send file \"%s\"", fileName); writeToLog(true); if ((hfile=CreateFile(fileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0))==INVALID_HANDLE_VALUE) { //if cannot open file, then nothing is sent. errorHandler(CANNOT_OPEN_FILE_FOR_PUT); } else { //file create successfully if (GetFileSize(hfile, NULL)==0) { errorHandler(EMPTY_FILE_TO_BE_SENT); } //the send has to start the sequence sendRequest(); //randomStartSequence(outgoing[0]); sendFile(); } break; case GETTYPE: sprintf(msgbuf, "prepare to receive file \"%s\"", fileName); writeToLog(true); if ((hfile=CreateFile(fileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0))==INVALID_HANDLE_VALUE) { //if cannot open file, then nothing is sent errorHandler(CANNOT_OPEN_FILE_FOR_GET); } else { //send out the request sendRequest(); //replyRequest(); recvFile(); } break; } } void Socket::serverTranFile() { char* fileName; fileName=incoming+1; //usually the data received is no null-terminated. incoming[byteRecv]='\0'; //because the answer is always one byte //byteSend=1; //for server, this is to be replied setWindowSize(incoming[0]&SEQUENCEMASK); switch (getType((uchar)incoming[0])) { case PUTTYPE: //strncpy(fileName, packet.data, packet.size); sprintf(msgbuf, "host \"%s\" requested file \"%s\" to be received.\n", remotehost, fileName); writeToLog(true); printf("Prepare receiving file from \"%s\", waiting...\n", remotehost); if ((hfile=CreateFile(fileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0))==INVALID_HANDLE_VALUE) { //if cannot open file, then nothing is sent. errorHandler(CANNOT_OPEN_FILE_FOR_PUT); //packet.type=ERRORTYPE; //packet.tail=CANNOT_OPEN_FILE_FOR_PUT; } else { replyRequest(); recvFile(); } break; case GETTYPE: sprintf(msgbuf, "host \"%s\" requested file \"%s\" to be send.\n", remotehost, fileName); writeToLog(true); printf("Prepare sending file to \"%s\", waiting...\n", remotehost); //strncpy(fileName, packet.data, packet.size); if ((hfile=CreateFile(fileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0))==INVALID_HANDLE_VALUE) { //if cannot open file, then nothing is sent errorHandler(CANNOT_OPEN_FILE_FOR_GET); } else { if (GetFileSize(hfile, NULL)==0) { errorHandler(EMPTY_FILE_TO_BE_SENT); } //replyRequest(); //randomStartSequence(outgoing[0]); sendFile(); } break; } } //exception handling is only to send error packet out if it is //local error. For remote error, you simply display it. //in previous version, I send everything, it is meaningless. void Socket::exceptionHandler(const FTPExcep& excep) { sprintf(msgbuf, "Unexpected error of \"%s\" happened\n", errorStrings[excep.errNo]); writeToLog(true); //closeFile(); if (excep.errNo!=ERROR_DATA_RCVED) { //if it is local error, we need to notify remote. outgoing[0]=ERRORTYPE; byteSend=1; //an error packet must be sent out, otherwise remote host will be freezed sendPacket(); } closeFile(); hfile=NULL; } void Socket::writeToLog(bool toDisplay) { //only write log when tracing mode if (bTracing) { writeLog(msgbuf, toDisplay); } } void Socket::sendPacket() { if (sendto(s, outgoing, byteSend, 0, (const sockaddr*)(&sa_to), sizeof(sa_to))==SOCKET_ERROR) { errorHandler(CONNECTION_INTERRUPT); } } void Socket::recvPacket() { int fromLen=sizeof(sockaddr); if ((byteRecv=recvfrom(ser, incoming, MaxMsgSize, 0, &sa_from, &fromLen))==SOCKET_ERROR) { errorHandler(GET_BUFFER_ERROR); } } void Socket::connectTo(char* serverName) { strcpy(remotehost, serverName); sprintf(msgbuf, "Remote host name is: \"%s\"", remotehost); writeToLog(); if((rp=gethostbyname(remotehost)) == NULL) { //throw "remote gethostbyname failed\n"; errorHandler(REMOTE_GETHOSTNAME_FAILED); } /* For UDP protocol replace SOCK_STREAM with SOCK_DGRAM */ //Specify server address for client to connect to server. memset(&sa_to,0,sizeof(sa_to)); memcpy(&sa_to.sin_addr,rp->h_addr,rp->h_length); sa_to.sin_family = rp->h_addrtype; sa_to.sin_port = htons(bServer?SERVER_ROUTER_PORT:CLIENT_ROUTER_PORT); //Display the host machine internet address //sprintf(buffer, "Connecting to remote host:%s\n", inet_ntoa(sa_in.sin_addr)); //writeLog(buffer); //Connect Client to the server /* if (connect(s,(LPSOCKADDR)&sa_in,sizeof(sa_in)) == SOCKET_ERROR) //throw "connect failed\n"; { errorHandler(CONNECT_FAILED); } */ } void Socket::clientConn(char* serverName) { if((s = socket(AF_INET,SOCK_DGRAM,0))==INVALID_SOCKET) //throw "Socket failed\n"; { errorHandler(SOCKET_FAILED); } connectTo(serverName); } void Socket::serverConn() { if((ser = socket(AF_INET,SOCK_DGRAM,0))==INVALID_SOCKET) //throw "Socket failed\n"; { errorHandler(SOCKET_FAILED); } //Fill-in Server Port and Address info. sa.sin_family = AF_INET; sa.sin_port = htons(port); sa.sin_addr.s_addr = htonl(INADDR_ANY); //Bind the server port if (bind(ser,(LPSOCKADDR)&sa,sizeof(sa)) == SOCKET_ERROR) { errorHandler(CANNOT_BIND_SOCKET); //throw "can't bind the socket"; } //sprintf(buffer, "bind is successful\n"); //writeToLog(); //Successfull bind, now listen for client requests. sprintf(msgbuf, "starting at host:[%s]\n", localhost); writeToLog(true); //sprintf(buffer, "Waiting to be contacted for transferring files..."); //writeLog(buffer, true); //listening(); } bool Socket::listening() { int infds=1, outfds=0;//, outNum, inNum; FD_ZERO(&readfds); FD_SET(ser,&readfds); //always check the listener if(outfds=select(infds,&readfds,NULL,NULL,&timeout)) { if (outfds == SOCKET_ERROR) { //throw "failure in Select"; errorHandler(FAILURE_IN_SELECT); } else { if (FD_ISSET(ser,&readfds)) { recvPacket(); if (getType((uchar)incoming[0])==ERRORTYPE) { printf("error data received at listening\n"); errorHandler(ERROR_DATA_RCVED); } return true; } } } //timeout or other error???? return false; } Socket::~Socket() { closeLog(); closesocket(s); closesocket(ser); WSACleanup(); } Socket::Socket(bool isServer) { bServer=isServer; initialize(); serverConn(); } void Socket::initialize() { startLog(); //local port port=bServer?SERVER_PORT:CLIENT_PORT; //MaxLength=1; /* for (int i=0; i<SequenceBitNumber; i++) { //MaxLength is initialized to be 1 at first. MaxLength*=2; } //sequence=0; */ if (WSAStartup(0x0202,&wsadata)!=0) { errorHandler(CANNOT_START_WSASTARTUP); } hfile=NULL; gethostname(localhost,10); /* sprintf(buffer, "%s%s%s\n", "server starting on host [", localhost, "]"); writeToLog(true); sprintf(buffer, "Waiting to be contacted for transferring files..."); writeToLog(true); */ if((hp=gethostbyname(localhost)) == NULL) //throw "gethostbyname failed\n"; { errorHandler(GETHOSTNAMEFAILED); } //Create the socket timeout.tv_sec=0; timeout.tv_usec=TIMEOUT_USEC; }
file name: client.cpp
/* send and receive codes between client and server */ /* This is your basic WINSOCK shell */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "socket.h" #include "packet.h" #include "log.h" #include "error.h" //reference for used structures /* * Host structure struct hostent { char FAR * h_name; official name of host * char FAR * FAR * h_aliases; alias list * short h_addrtype; host address type * short h_length; length of address * char FAR * FAR * h_addr_list; list of addresses * #define h_addr h_addr_list[0] address, for backward compat * }; * Socket address structure struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; }; */ int main(int argc, char* argv[]) { char choice[10]; char routerName[11]; Socket client; int size; if (argc>2) { printf("usage: client.exe [trace]\n"); exit(0); } if (argc==2) { if (strcmp(argv[1], "trace")==0) { bTracing=true; } } try { printf("input host name for router:"); //Ask for name of remote server scanf("%s", routerName); client.clientConn(routerName); } catch (FTPExcep excep) { client.exceptionHandler(excep); } while (true) { printf("Input window size:"); scanf("%d", &size); client.setWindowSize(size); //printf("Input tranfer mode(0 = stop-and-wait; 1 = gobackN):"); //scanf("%d", &mode); //client.setMode(mode==1); printf("Type name of file to be transferred:"); //Ask for name of remote server //message starts from second byte scanf("%s", client.outgoing+1); if (strcmp(client.outgoing+1, "quit")==0) { break; } printf("Type direction of transfer:( get ; put)"); scanf("%s", choice); if (strcmp(choice, "get")==0) { client.setRole(ClientReceiver); client.outgoing[0]=GETTYPE; } else { client.setRole(ClientSender); client.outgoing[0]=PUTTYPE; } //passing window size to server client.outgoing[0]+=size; try { client.clientTranFile(); } catch (FTPExcep excep) { /* printf("Unexpected error of \"%s\" happened\n", errorStrings[excep.errNo]); client.closeFile(); if (excep.errNo==ERROR_DATA_RCVED) { //it is remote error, just show the message printf("remote host error \"%s\" happened\n", errorStrings[excep.remoteErrNo]); } //if (excep.errNo else { //if it is local error, we need to notify remote. client.outgoing.type=ERRORTYPE; client.outgoing.tail=excep.errNo; //an error packet must be sent out, otherwise remote host will be freezed client.sendPacket(client.outgoing); } */ client.exceptionHandler(excep); } } return 0; }
file name: server.cpp
#include "socket.h" #include "packet.h" #include <stdio.h> #include <stdlib.h> #include "error.h" int main(int argc, char* argv[]) { Socket server(true); char routerName[11]; if (argc>2) { printf("usage: server.exe [trace]\n"); exit(0); } if (argc==2) { if (strcmp(argv[1], "trace")==0) { bTracing=true; } } try { printf("input host name for router:"); scanf("%s", routerName); server.clientConn(routerName); } catch (FTPExcep excep) { server.exceptionHandler(excep); } while (true) { try { if (server.listening()) { server.serverTranFile(); } } catch (FTPExcep excep) { /* printf("Unexpected error \"%s\" happened\n", errorStrings[excep.errNo]); server.closeFile(); server.outgoing.type=ERRORTYPE; server.outgoing.tail=excep.errNo; //should send error info to remote host server.sendPacket(server.outgoing); */ server.exceptionHandler(excep); } } return 0; }
file name: router.h
//Router Head file #ifndef ROUTER_H #define ROUTER_H #include <winsock.h> #include <fstream.h> #include <iostream.h> #include <time.h> #include <list> #include <stdio.h> # pragma comment (lib, "wsock32.lib") using namespace std ; #define MAXBUFSIZE 2048 //maximum packet size #define MAXHOSTNAMELEN 256 //maximum length of host name #define ROUTER_PORT1 7000 //router port number 1 #define ROUTER_PORT2 7001 //router port number 2 #define PEER_PORT1 5000 //port number of peer host 1 #define PEER_PORT2 5001 //port number of peer host 2 #define TIMEOUT_USEC 300000 //time-out value #define TRACE 1 struct EVENT_LIST { bool empty; DWORD count; //count is the packet number short destination; //destination of this packet int len; //length of this packet char Buffer[MAXBUFSIZE]; //buffer for packet }; class Router { public: char localhost[MAXHOSTNAMELEN]; //local host name //Constructor Router(char *fn="log.txt"); //Destructor ~Router(); void Run(); private: class ofstream fout; //log file float damage_rate, delay_rate; //damage rate: dropped and delayed SOCKET Sock1, Sock2; //sockets used for communcation with peer host 1 and 2 EVENT_LIST FileBuf; //buffer for delayed packets protected: SOCKADDR_IN sa_in_peer1; // address structure for peer host 1 address SOCKADDR_IN sa_in_peer2; // address structure for peer host 2 address bool IsDamage() const; bool IsDelayed() const; void SendProc(); }; #endif
file name: router.cpp
//Router.cpp #include "Router.h" ////////////////////////////////////////////////////////// // // Router Constructor // arguements: // fn: A string of log file name // ////////////////////////////////////////////////////////// Router::Router(char *fn) //Constructor { WSADATA wsadata; HOSTENT* hp; char peer_name1[MAXHOSTNAMELEN], peer_name2[MAXHOSTNAMELEN]; SOCKADDR_IN sa_in; FileBuf.empty=true; try { if (WSAStartup(0x0202,&wsadata)!=0) throw "Error in starting WSAStartup()\n"; } //Display any needed error response. catch (char *str) { cerr<<str<<":"<<dec<<WSAGetLastError()<<endl; return;} //Get Host name gethostname(localhost,MAXHOSTNAMELEN); cout<<"Router starting on host:"<<localhost<<endl<<flush; //Open the log file fout.open(fn); try { //Create the Udp Sock1 if((Sock1 = socket(AF_INET,SOCK_DGRAM,0))==INVALID_SOCKET) throw "Create UDP Socket1 failed\n"; //Fill-in UDP Port and Address info. sa_in.sin_family = AF_INET; sa_in.sin_port = htons(ROUTER_PORT1); sa_in.sin_addr.s_addr = htonl(INADDR_ANY); //Bind the UDP port1 if (bind(Sock1,(LPSOCKADDR)&sa_in,sizeof(sa_in)) == SOCKET_ERROR) throw "can't bind the socket1"; //Create the Udp Sock2 if((Sock2 = socket(AF_INET,SOCK_DGRAM,0))==INVALID_SOCKET) throw "Create UDP Socket2 failed\n"; //Fill-in UDP Port and Address info. sa_in.sin_family = AF_INET; sa_in.sin_port = htons(ROUTER_PORT2); sa_in.sin_addr.s_addr = htonl(INADDR_ANY); //Bind the UDP port2 if (bind(Sock2,(LPSOCKADDR)&sa_in,sizeof(sa_in)) == SOCKET_ERROR) throw "can't bind the socket2"; cout<<"\nPlease enter the first peer host name:"<<flush; //enter the dropping rate. cin>>peer_name1; cout<<"\nPlease enter the second peer host name:"<<flush; //enter the dropping rate. cin>>peer_name2; cout<<"\nPlease enter the drop rate:"<<flush; //enter the dropping rate. cin>>damage_rate; cout<<"\nPlease enter the delay rate:"<<flush; //enter the dropping rate. cin>>delay_rate; //creat peer host1 if((hp=gethostbyname(peer_name1)) == NULL) throw "get server name failed\n"; memset(&sa_in_peer1,0,sizeof(sa_in_peer1)); memcpy(&sa_in_peer1.sin_addr,hp->h_addr,hp->h_length); sa_in_peer1.sin_family = hp->h_addrtype; sa_in_peer1.sin_port = htons(PEER_PORT1); //creat peer host2 if((hp=gethostbyname(peer_name2)) == NULL) throw "get client name failed\n"; memset(&sa_in_peer2,0,sizeof(sa_in_peer2)); memcpy(&sa_in_peer2.sin_addr,hp->h_addr,hp->h_length); sa_in_peer2.sin_family = hp->h_addrtype; sa_in_peer2.sin_port = htons(PEER_PORT2); if (TRACE) { fout<<"Peer host 1: "<<peer_name1<<endl; fout<<"Peer host 2: "<<peer_name2<<endl; fout<<"Damage Rate: "<<damage_rate<<endl; } } catch (char *str) {cerr<<str<<":"<<dec<<WSAGetLastError()<<endl; exit(1);} srand( (unsigned)time( NULL ) ); } ////////////////////////////////////////////////////////// // // Router::IsDamage // The function that generates random damages according to damage rate. // ////////////////////////////////////////////////////////// bool Router::IsDamage() const { return ( (((float)rand())/RAND_MAX) < ((float)damage_rate/100)); } ////////////////////////////////////////////////////////// // // Router::IsDelayed // The function that generates random delayed according to delay rate. // ////////////////////////////////////////////////////////// bool Router::IsDelayed() const { return ( (((float)rand())/RAND_MAX) < ((float)delay_rate/100)); } ////////////////////////////////////////////////////////// // // Router::Run // The function receives packets from peer hosts and forwards to destinations. // It also drops packets and stores delayed packets for future sending. // It calls SendProc to send delayed packets. // ////////////////////////////////////////////////////////// void Router::Run() { fd_set readfds; struct timeval *tp=new timeval; SOCKADDR from; int RetVal, fromlen, recvlen, wait_count; EVENT_LIST temp; DWORD CurrentTime, count1, count2; count1=0; count2=0; wait_count=0; tp->tv_sec=0; tp->tv_usec=TIMEOUT_USEC; while (1) { try { FD_ZERO(&readfds); FD_SET(Sock1,&readfds); FD_SET(Sock2,&readfds); fromlen=sizeof(from); if((RetVal=select(1,&readfds,NULL,NULL,tp))==SOCKET_ERROR) //check for incoming packets. throw "Timer error!"; else if(RetVal>0) //There are incoming packets. { if(!FileBuf.empty) wait_count++; if(FD_ISSET(Sock1, &readfds)) //incoming packet from peer host 1 { if((recvlen=recvfrom(Sock1, temp.Buffer, sizeof(temp.Buffer), 0, &from, &fromlen))==SOCKET_ERROR) { cout<<"last error is:"<<WSAGetLastError()<<endl; throw " Get buffer error!"; } if (TRACE) { fout<<"Router: Receive packet "<<count1<<" from peer host 1"<<endl; cout<<"Router: Receive packet "<<count1<<" from peer host 1"<<endl; } temp.count=count1; count1++; temp.destination=2; } else if(FD_ISSET(Sock2, &readfds)) //incoming packet from peer host 2 { if((recvlen=recvfrom(Sock2, temp.Buffer, sizeof(temp.Buffer), 0, &from, &fromlen))==SOCKET_ERROR) throw " Get buffer error!"; if (TRACE) { fout<<"Router: Receive packet "<<count2<<" from peer host 2"<<endl; cout<<"Router: Receive packet "<<count2<<" from peer host 2"<<endl; } temp.count=count2; count2++; temp.destination=1; } else continue; temp.len=recvlen; CurrentTime=GetTickCount(); if(FileBuf.empty&&IsDelayed()) //if the packet is delayed. { FileBuf=temp; FileBuf.empty=false; if (TRACE) { fout<<"Router: Packet "<<temp.count<<" received from peer host "<<(temp.destination==1?2:1)<<" has been delayed!"<<endl; cout<<"Router: Packet "<<temp.count<<" received from peer host "<<(temp.destination==1?2:1)<<" has been delayed!"<<endl; } } else if(IsDamage()) //if the packet is dropped: dropping packet by no forwarding the packet. { if (TRACE) { fout<<"Router: Packet "<<temp.count<<" received from peer host "<<(temp.destination==1?2:1)<<" has been dropped by router!"<<endl; cout<<"Router: Packet "<<temp.count<<" received from peer host "<<(temp.destination==1?2:1)<<" has been dropped by router!"<<endl; } } else //otherwise, packet is forwarded to destination { if(temp.destination==1) //forward packets received from 2 to 1. { if(sendto(Sock1, temp.Buffer, temp.len,0,(SOCKADDR*)&sa_in_peer1,sizeof(sa_in_peer1))==SOCKET_ERROR) throw "Send packet error!"; if (TRACE) { fout<<"Router: Send packet "<<temp.count<<" received from peer host "<<(temp.destination==1?2:1) <<" to host "<<temp.destination<<endl; cout<<"Router: Send packet "<<temp.count<<" received from peer host "<<(temp.destination==1?2:1) <<" to host "<<temp.destination<<endl; } if(!FileBuf.empty&&FileBuf.destination==1) { wait_count=0; SendProc(); } } else { //forward packets received from 1 to 2. if(sendto(Sock2, temp.Buffer, temp.len,0,(SOCKADDR*)&sa_in_peer2,sizeof(sa_in_peer2))==SOCKET_ERROR) throw "Send packet error1"; if (TRACE) { fout<<"Router: Send packet "<<temp.count<<" received from peer host "<<(temp.destination==1?2:1) <<" to host "<<temp.destination<<endl; cout<<"Router: Send packet "<<temp.count<<" received from peer host "<<(temp.destination==1?2:1) <<" to host "<<temp.destination<<endl; } if(!FileBuf.empty&&FileBuf.destination==2) { wait_count=0; SendProc(); } } } } else //If there is no incoming packet and there is a delayed packets storing in buffer for 3 cycle times (about 0.9 second), call SendProc to send delayed packet. { if(!FileBuf.empty) { wait_count++; if(wait_count>=3) { SendProc(); wait_count=0; } } } } //end of try catch(char *str) {cerr<<str<<":"<<dec<<WSAGetLastError()<<endl;} }//end of while } ////////////////////////////////////////////////////////// // // Router::SendProc // Send delayed packets to the destinations. // ////////////////////////////////////////////////////////// void Router::SendProc() { try { if(FileBuf.destination==1) if(sendto(Sock1, FileBuf.Buffer, FileBuf.len,0,(SOCKADDR*)&sa_in_peer1,sizeof(sa_in_peer1))==SOCKET_ERROR) throw "Send packet error!"; else if(sendto(Sock2, FileBuf.Buffer, FileBuf.len,0,(SOCKADDR*)&sa_in_peer2,sizeof(sa_in_peer2))==SOCKET_ERROR) throw "Send packet error!"; if (TRACE) { fout<<"Router: Send delayed packet "<<FileBuf.count<<" received from peer host "<<(FileBuf.destination==1?2:1)<<" to host "<<FileBuf.destination<<endl; cout<<"Router: Send delayed packet "<<FileBuf.count<<" received from peer host "<<(FileBuf.destination==1?2:1)<<" to host "<<FileBuf.destination<<endl; } } catch(char *str){cerr<<str<<":"<<dec<<WSAGetLastError()<<endl;} FileBuf.empty=true; } ////////////////////////////////////////////////////////// // // Router Destructor // arguements: // fn: A string of log file name // ////////////////////////////////////////////////////////// Router :: ~Router() { closesocket(Sock1); closesocket(Sock2); /* When done, uninstall winsock.dll (WSACleanup()) and exit */ WSACleanup(); //close log file fout.close(); } ////////////////////////////////////////////////////////// // // Main function // ////////////////////////////////////////////////////////// void main() { Router router; router.Run(); }
How to run?
1. run the router first and input the host name of client and server. Choose 1-100 for drop rate and 0 for delay rate.
2. run server first and input the host of router. Then run client and input host name of router.
3. type in command "get" or "put" and file name which must be in current file path.