![]() |
[Studienarbeiten: Kommunikation zwischen PDAs] | ![]() |
Start Server
Mit diesem Befehl wird der Server gestartet. Ein anderes Gerät mit IrChat kann nun eine Verbindung zu diesem Server aufbauen.
Stop Server
Mit diesem Befehl wird der Server gestoppt. Dieser Befehl kann nur dann angewählt werden, wenn der Server läuft.
Connect to Server
Mit diesem Befehl wird versucht, eine Verbindung zu einem Server aufzubauen.
Disconnect
Mit diesem Befehl wird eine bestehende Verbindung beendet. Wenn keine Verbindung besteht, kann dieser Befehl nicht ausgeführt werden.
Properties
Im auf diesen Befehl folgenden Dialog, können folgende Einstellungen vorgenommen werden.
Zu beachten ist, daß die entsprechenden Service-Namen bzw. LSap-Selektoren auf den beiden zu verbindenden Geräten natürlich gleich sein müssen. Für eine Kommunikation mit dem Psion funktioniert leider nur die direkte Angabe eines LSap-Selektors.
Send File
Mit diesem Befehl, kann eine beliebige Datei an die Gegenstelle übertragen werden. Die zu übertragende Datei wird im folgenden Filedialog ausgewählt
Exit
Schließt alle offenen Verbindungen, stoppt den Server und beendet das Programm.
Dieses Menü enthält nur die Standardkommandos eines Windows Bearbeiten Menüs. Dies sind Cut, Copy und Paste.
Dieses Menü enthält nur den Punkt "Über IrChat". Dieser zeigt den für Windowsprogramme üblichen Informationsdialog an.
IRChat wurde mit Hilfe der Microsoft Foundation Classes (MFC) als Single-Document-Interface (SDI) Applikation erstellt. Die MFC bieten ein standardisiertes objektorientiertes Applikationsgerüst und eine objektorientierte Kapselung vieler Funktionen des Windows API (Application-Programmers-Interface). Im folgenden werden grundlegende Kenntnisse über das Applikationsgerüst und die Funktionsweise der MFC vorausgesetzt.
IrChat besitzt als SDI-Applikation ein Hauptfenster (CMainframe) und eine Hauptansicht (CIrchatview). Die Hauptansicht ist ein CFormView, der zwei Edit-Controls enthält. Das erste (im folgenden Empfangsfenster genannt) füllt fast das gesamte Programmfenster aus und dient zum Darstellen der empfangenen Nachrichten und etwaiger Meldungen. Da dieses nur zur Ausgabe von Text bestimmt ist ist es schreibgeschützt. Das zweite (im folgenden Sendefenster genannt) besteht aus einer einzelnen Zeile am unteren Fensterrand und ermöglicht die Eingabe des zu sendenden Textes.
Das Programm verwendet intern die UNICODE Darstellung von Texten. Die Übertragung erfolgt allerdings wie im Kapitel 3.2 Allgemeine Anforderungen definiert in ASCII.
Für die Kommunikation wurde die IrSocket Erweiterung benutzt. Da diese allerdings nur blockierende Funktionen zur Verfügung stellt, wurden für die meisten grundlegenden Programmfunktionen eigene Threads implementiert. Da ein Chat-Programm von Natur aus asynchronen Datentransfer benötigt, war dies vor allem für das Warten auf ankommenden Text von der Gegenseite nötig. Nur so konnte ermöglicht werden, daß eigener Text eingegeben und versendet werden kann, während gleichzeitig auf Text von der Gegenseite gewartet wird. Diese Threads erschweren an dieser Stelle ein durchgehend objektorientiertes Konzept, da auf MFC-Klassen nicht über Threadgrenzen hinweg problemlos zugegriffen werden kann. Die Threads sind daher als C-Funktionen implementiert, die gemeinsam auf eine Reihe von globalen Variablen zugreifen.
Ein weiteres Problem war in diesem Zusammenhang die nötige Ausgabe der Threads im Empfangsfenster. Die Ausgabe wird in der Funktion DisplayMsg der zur Hauptansicht gehörenden Klasse IrchatView erledigt. Um einen Text auszugeben (egal ob empfangener Text oder Fehlermeldung) wird die Windows-Message IRMSG_DISPLAYMSG an das Hauptansichtsfenster geschickt. Der auszugebende Text wird dabei in den Local-Heap kopiert und der Handle darauf als LParam mitgeschickt. Die DisplayMessage-Funktion im View ist für die Freigabe zuständig.
/////////////////////////////////////////////////////////////////////////////
// Makrodefinitionen
// Die Makros sind so definiert, dass sie von allen Threads benutzt werden
// koennen.
/////////////////////////////////////////////////////////////////////////////
// Kommando versenden (Esc+MSG)
#define SENDCMD(MSG) {CString str=MSG;char buf[255];buf[0]='\033';for(int i=0;i<str.GetLength();i++)buf[i+1]=(char)str.GetAt(i);buf[i+1]='\0';if(send(SendSock,buf,strlen(buf)+1,0)==SOCKET_ERROR){FEHLER;}}
// Fehlermeldung auslesen und ausgeben (inklusive Filename und Programmzeile)
#define FEHLER { TCHAR buf[255];for(UINT i=0;i<=strlen(__FILE__);i++)buf[i]=__FILE__[i];CString str;str.Format(_T(" %s Zeile %d "),buf,__LINE__);GETAPP->FehlerMeldung(str);}
// Gibt das Applikationsobjekt zurueck (richtig gecastet)
#define GETAPP ((CIrchatApp*)AfxGetApp())
///////////////////////////////////////////////////////////
// Thread-Routinen
///////////////////////////////////////////////////////////
// ConnectThread
// Dieser Thread versucht eine Verbindung zu einem Server
// zu erzeugen. Dazu wird zunaechst nach Geraeten in
// Reichweite gesucht und dann versucht mit dem ersten
// gefundenen Geraet eine Verbindung zu oeffnen.
UINT ConnectThreadFunction(LPVOID data)
{
TCHAR buffer[1024];
TCHAR devname[22];
const NumRetries=5; // Anzahl der Versuche fuer die Suche nach Geraeten
SOCKET sock;
DEVICELIST devlist[3];
int devlistlen=sizeof(devlist);int i;
if( g_bConnected)
return -1; // Wenn schon verbunden nichts tun und Thread-beenden
//Hilfssocket erzeugen. Diese wird fuer die Suche nach Geraeten benoetigt
sock = socket(AF_IRDA,SOCK_STREAM,0);
if(sock==INVALID_SOCKET)
{
FEHLER;
return 0;
}
devlist[0].numDevice=0;
PRINT("Looking for Server");
// Suche starten
for(int cnt=1;cnt<=NumRetries;cnt++)
{
PRINT(_T("Looking for Server..."));
if(getsockopt(sock,SOL_IRLMP,IRLMP_ENUMDEVICES,(char*)&devlist,&devlistlen)==SOCKET_ERROR)
{
FEHLER;
closesocket(sock);
return 0;
}
if(devlist[0].numDevice==0)
{
PRINT(_T("[!!!!!!] No Server found"));
continue;
}
else
{
// Geraet wurde gefunden. Namen Ausgeben.
//
for(int j=0;j<22;j++)
devname[j]=devlist[0].Device[0].irdaDeviceName[j];
wsprintf(buffer,_T("[!!!!!!] Found: %x.%x.%x.%x ** %s ** "),
(BYTE)devlist[0].Device[0].irdaDeviceID[0],
(BYTE)devlist[0].Device[0].irdaDeviceID[1],
(BYTE)devlist[0].Device[0].irdaDeviceID[2],
(BYTE)devlist[0].Device[0].irdaDeviceID[3],
devname,
devlist[0].Device[0].Reserved);
PRINT(buffer);
break;
}
}
if(cnt>=NumRetries)
{
// Es wurde kein Geraet gefunden. Also Abbrechen.
PRINT(_T("[!!!!!!] No Device found!"));
return 0;
}
// Jetzt wird die Adresse des gefundenen Geraets kopiert.
for(i=0;i<4;i++)
{
ConnectAdr.irdaDeviceID[i]=devlist[0].Device[0].irdaDeviceID[i];
}
// Socket an lokale Adresse binden.
if(bind(RemoteSock,(struct sockaddr*)&ListenerAdr,sizeof(ListenerAdr))==SOCKET_ERROR)
{
FEHLER;
closesocket(RemoteSock);
return 0;
}
if(connect(RemoteSock,(struct sockaddr *)&ConnectAdr,sizeof(SOCKADDR_IRDA))==SOCKET_ERROR)
{
FEHLER;
closesocket(RemoteSock);
return 0;
}
// Verbindung aufgebaut. Flags setzen und ReceiveThread starten
g_bConnected=TRUE;
g_bExitRecv=FALSE;
g_pRecvThread=AfxBeginThread(CRecvThreadFunction,NULL);// UserName und Begruessung senden
SENDCMD(_T("U")+GETAPP->m_MyName);
SEND(_T("Mit WindowsCE PDA verbunden") );
return -1;
}///////////////////////////////////////////////////////////
// ReceiveThread
// Dieser Thread wartet in einer Schleife auf ankommende Daten
// von der Gegenseite. Je nachdem, ob es sich bei den Daten um
// Text oder um Kommandos handlet, wird entweder der Text im
// Empfangsfenster ausgegeben oder das Kommando bearbeitet.
// Der Thread wird beendet, durch setzen des Flags gbExitRecv.
// Dieser Thread steuert sowohl den Textempfang, als auch den
// Empfang von Dateien.
UINT CRecvThreadFunction(LPVOID data)
{
char inbuf[2550];
TCHAR inbufU[2550];
int ncount;
CString strRemoteFileName;
CString strLocalFileName;
CFile file;
BOOL bFileOpen=FALSE;
UINT FileLen;
// Am Anfang ist der Zustand CHAT, kein Filetransfer
ReceiveState=CHAT;
CIrchatApp* app=(CIrchatApp*)data;
if(!g_bConnected)
{
PRINT(_T("[!!!!!!] NOT CONNECTED RECEIVE FAILED!"));
return -1;
}
PRINT(_T("[!!!!!!] Starting RecvThread!"));
// Hauptschleife
while(!g_bExitRecv)
{
memset( inbuf, 0, 255);
// Warte auf Daten von der Gegenseite!
if((ncount=recv(RemoteSock,inbuf,sizeof(inbuf),0))==SOCKET_ERROR)
{
FEHLER;
GETAPP->Disconnect(); // Bei einem Fehler Verbindung abbrechen
return -2;
}
// Wenn 0 Bytes empfangen wurden, hat die Gegenseite die Verbindung beendet.
if(ncount==0)
{
PRINT(_T("[!!!!!!] Remote has closed the connection!"));
g_bExitRecv=TRUE;
closesocket(RemoteSock);
g_bConnected=FALSE;
break;
}
// Umwandeln in UNICODE
for(int i=0;i<=sizeof(inbuf);i++)
inbufU[i]=inbuf[i];
inbufU[i]=0;// Waren die Daten ein Kommando?
if(inbufU[0]=='\033')
{
CString ps=_T("RECEIVE COMMAND: ");
ps+=inbufU[1];
//AfxMessageBox(ps);
ps.Format(_T("%d Bytes"),ncount);
// Die Daten waren ein Kommando, fragt sich nur welches.
switch(inbufU[1])
{
case 'U':
// USER -> Den Namen der Gegenseite neu setzen
GETAPP->m_RemoteName=(const TCHAR*)&inbufU[2];
PRINT(_T("[!!!!!!] Remote User changed Name to: ")+GETAPP->m_RemoteName);
break;
case 'N':
// FILENAME -> Die Gegenseite will eine Datei uebertragen
// und sendet nun zunaechst einmal den Dateinamen.
{
strRemoteFileName=(const TCHAR*)&inbufU[2];
// Nun wird ein "Datei speichern"-Dialog geoeffnet, mit dem man
// Position und Namen der zu empfangenden Datei festlegen kann.
CFileDialog dlg(FALSE,NULL,strRemoteFileName);
if(dlg.DoModal()!=IDOK)
{
// Benutzer hat Dialog abgebrochen => Dateitransfer abbrechen
bFileOpen=FALSE;
ReceiveState=CHAT;
SENDCMD(_T("C")); // Transfer abbrechen durch senden von CANCEL
}
strLocalFileName=dlg.GetPathName();
// Alles OK. Datei erzeugen und oeffnen.
if(!file.Open(strLocalFileName, CFile::modeCreate | CFile::modeWrite | CFile::shareExclusive))
{
// Fehler beim Erzeugen=> Abbruch
PRINT(_T("[!!FILE!!] Datei konnte nicht erzeugt werden!"));
bFileOpen=FALSE;
ReceiveState=CHAT;
SENDCMD(_T("C"));
}
// Flag setzen, dass das file-Objekt gueltig ist.
bFileOpen=TRUE;
break;
}
case 'R':
// FILEREADY -> Datei ist zu senden bereit.
if(bFileOpen)
{
// Zustand ist jetzt FILEREADY
// Gegenseite wartet auf ACK...
ReceiveState=FILEREADY;
SENDCMD(_T("A")); // ACK senden
}
else
{
// Wenn Datei nicht erzeugt wurde. Abbrechen!
ReceiveState=CHAT;
SENDCMD(_T("C"));
}
break;
case 'A':
// ACK -> Gegenseite hat ein Acknowledge gesendet.
// Falls wir selber eine Datei senden und im Zustand
// FILEWAITACK sind, bedeutet das, dass wir mit der
// Uebertragung beginnen koennen. Das erledigt
// allerdings der SendFileThread, sobald der Zustand
// auf FILEACK steht.
// Falls eine Datei empfangen, bedeutet das, dass alle
// Bloecke uebertragen wurden.
if(ReceiveState==FILEWAITACK)
ReceiveState=FILEACK;
else if(ReceiveState==FILEREADY)
{
// Uebertragung beendet. Zustand wieder auf CHAT und Datei schliessen
ReceiveState=CHAT;
bFileOpen=FALSE;
file.Close();
PRINT(_T("[!!FILE!!] Filetransfer complete!"));
}
break;
case 'C':
// FILECANCEL -> Uebertraggung abbrechen und wieder in Zustand CHAT.
ReceiveState=CHAT;
if(bFileOpen)
file.Close();
bFileOpen=FALSE;
break;
case 'B':
// BLOCK -> Hinter diesem Kommando folgt ein Datenblock
// Die Daten werden in das erzeugte File geschrieben.
// Die Anzahl der Daten betraegt ncount-2
// (empfangene Bytes-2 Bytes fuer den Befehl)
if((bFileOpen)&&(ReceiveState==FILEREADY))
{
CString ps;
ps.Format(_T("BLOCK mit %d Bytes geschrieben"),ncount);
file.Write(&inbuf[2],(UINT)ncount-2);
}
else
PRINT(_T("[!!FILE!!] Protokollfehler!"));
break;
default:
PRINT(_T("[!!!!!!] UNKNOWN COMMAND !"));
break;
}
}
else
{
// Es war kein Kommando. Also gesendeten Text ausgeben.
CString str=_T("[")+GETAPP->m_RemoteName+_T("] ");
str+=inbufU;
PRINT(str);
}
}
PRINT(_T("[!!!!!!] Stopping RecvThread!"));
return 0;
}///////////////////////////////////////////////////////////
// ListenerThread
// Dieser Thread wartet in einer Schleife auf ankommende
// Verbindungswuensche und akzeptiert diese.
UINT CListenerThreadFunction(LPVOID data)
{
const NumPending=1;
SOCKET tmpSock;closesocket(ListenerSock);
ListenerSock=socket(AF_IRDA,SOCK_STREAM,0);
if(ListenerSock==INVALID_SOCKET)
{
FEHLER;
goto fehler;
}
// ListenerSocket binden
if(bind(ListenerSock,(struct sockaddr*)&ListenerAdr,sizeof(ListenerAdr))==SOCKET_ERROR)
{
FEHLER;
closesocket(ListenerSock);
goto fehler;
}
// Listenbefehl
if(listen(ListenerSock,NumPending)==SOCKET_ERROR)
{
FEHLER;
closesocket(ListenerSock);
goto fehler;
}g_bServerRunning=TRUE;
PRINT(_T("Server gestartet"));
// Warten auf Verbindung
while(!g_bExitServer)
{
// Accept
if((tmpSock=accept(ListenerSock,NULL,NULL))==INVALID_SOCKET)
{
FEHLER;
closesocket(ListenerSock);
goto fehler;
}
if(!g_bConnected)
{
RemoteSock=tmpSock;
// Verbindung aufgebaut!!
// Jetzt Identifikation schicken, Meldung ausgeben und
// ReceiveThread starten
PRINT(_T("Connection established"));
SENDCMD(_T("U")+GETAPP->m_MyName);
SEND(_T("Mit WindowsCE PDA verbunden"));
g_bConnected=TRUE;
g_bExitRecv=FALSE;
g_pRecvThread=AfxBeginThread(CRecvThreadFunction,NULL);
}
else
{
closesocket(tmpSock);
PRINT(_T("Connect from Remote failed!!"));
}
}
g_bServerRunning=FALSE;
PRINT(_T("[!!!!!!] Server exiting."));
closesocket(ListenerSock);
return -1;
fehler:
g_bServerRunning=FALSE;
PRINT(_T("[!!!!!!] Server failed ! Server exiting."));
closesocket(ListenerSock);
return 0;
}///////////////////////////////////////////////////////////
// SendFileThread
// Dieser Thread wird gestartet, um eine Datei zur
// Gegenstelle zu schicken. Er wird automatisch beendet,
// wenn die Uebertragung (erfolgreich oder durch Fehler/Abbruch)
// beendet wurde.
UINT SendFileThreadFunction(LPVOID data)
{
CFileDialog dlg(TRUE);
int ncount;
const ncountMax=250;
char buffer[ncountMax+2];if((!g_bConnected)||(ReceiveState!=CHAT))
{
PRINT(_T("[!!FILE!!] NOT CONNECTED OR TRANSFER IN PROGRESS !!"));
return 0;
}
// File-Dialog anzeigen, zur Auswahl des zu übertragenden Files
if(dlg.DoModal()!=IDOK)
return 0;
CString strPathName=dlg.GetPathName();
CFile file(strPathName, CFile::modeRead);
CString strFileName=file.GetFileName();
// FileNamen uebertragen. Dies signalisiert gleichzeitig der Gegenseite,
// dass eine Datei uebertragen werden soll.
SENDCMD(_T("N")+strFileName);
CString str;
// FILEREADY senden
SENDCMD(_T("R"));
ReceiveState=FILEWAITACK;
// Warten, bis Gegenseite ein ACK zuruecksendet
while(ReceiveState==FILEWAITACK)
Sleep(100);
if(ReceiveState!=FILEACK)
return 0;
buffer[0]='\033'; // Esc-Zeichen
buffer[1]='B';
// Nun werden nacheinander alle Bloecke uebertragen
do
{
ncount=file.Read(&buffer[2],ncountMax);
CString s;
s.Format(_T("%d"),ncount);
if(ncount>0)
{
if(send(RemoteSock,buffer,ncount+2,0)==SOCKET_ERROR) // ncount +2 wegen ESCB
{
FEHLER;
SENDCMD(_T("C"));
return 0;
}
}
}while ((ncount>0)&&(ReceiveState==FILEACK));
// Zum Schluss noch ein ACK, um der Gegenseite das Uebertragungsende mitzuteilen
SENDCMD(_T("A"));
// Zustand wieder auf CHAT setzen
ReceiveState=CHAT;
return -1;
}
///////////////////////////////////////////////////////////
// Globale-Funktionen
///////////////////////////////////////////////////////////
// Dies Funktion gibt den übergebenen Text im Empfangsfenster aus.
// Dazu wird der Text als UNICODE-String auf dem lokalen Heap erzeugt.
// Der Handle wird als lParam mit der Message verschickt.
// Der Message-Handler ist für die Freigabe des Speichers zuständig.
void PRINT(CString MSG)
{
HLOCAL hmem;
LPTSTR ptr;
if((hmem=LocalAlloc(LPTR,MSG.GetLength()*2+2))!=NULL)
{
ptr=(LPTSTR)LocalLock(hmem);
wcscpy(ptr,(LPCTSTR)MSG);
AfxGetMainWnd()->PostMessage(IRMSG_SENDMSG,0,(long)hmem);
LocalUnlock(hmem);
}
else
{
AfxMessageBox(_T("Could not allocate Memory for Send!!"));
}
}///////////////////////////////////////////////////////////
// SEND
// Dies Funktion sendet den übergebenen Text an die Gegenstelle
// Dazu wird der Text als UNICODE-String auf dem lokalen Heap erzeugt.
// Der Handle wird als lParam mit der Message verschickt.
// Der Message-Handler ist für die Freigabe des Speichers zuständig.
void SEND(CString MSG)
{
HLOCAL hmem;
LPTSTR ptr;
if((hmem=LocalAlloc(LPTR,MSG.GetLength()*2+2))!=NULL)
{
ptr=(LPTSTR)LocalLock(hmem);
wcscpy(ptr,(LPCTSTR)MSG);
AfxGetMainWnd()->PostMessage(IRMSG_DISPLAYMSG,0,(long)hmem);
LocalUnlock(hmem);
}
else
{
AfxMessageBox(_T("Could not allocate Memory for ErrorMsg!!"));
}
}
![]() |
[Studienarbeiten: Kommunikation zwischen PDAs] | ![]() |