using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using BeWo.Data; using BeWo.Data.Access; using BeWo.Data.Entities; using BS.Shared; using BS.Shared.Extensions; namespace BeWoChatServer { public class Server { // Fehlercodes zwischen 1000 und 1499 private static readonly object _Lock = string.Empty; private static readonly ConcurrentDictionary clientList = new ConcurrentDictionary(); protected static Socket serverSocket; private static AsyncCallback asyncCallback; private static int clientCount; public static void Main(string[] args) { try { Console.WriteLine(DateTime.Now + ": Starte Server"); serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); var server = new IPEndPoint(IPAddress.Any, 5000); serverSocket.Bind(server); serverSocket.Listen(4); serverSocket.BeginAccept(OnClientConnected, null); while (true) { } } catch (Exception e) { WriteErrorMessageToConsole(e.Message, e.StackTrace, e); } } public static void OnClientConnected(IAsyncResult asyncResult) { try { var workerSocket = serverSocket.EndAccept(asyncResult); Interlocked.Increment(ref clientCount); var couldAdd = clientList.TryAdd(workerSocket, new LoginData(0, "", DateTime.Now)); if(!couldAdd) { Console.WriteLine("Socket war schon in der clientListe enthalten und konnte nicht hinzugefügt werden"); } Console.WriteLine(DateTime.Now + ": Client hinzugefügt (" + workerSocket.RemoteEndPoint + ")"); WaitForClientData(workerSocket, "", 0, 0, true); serverSocket.BeginAccept(OnClientConnected, null); LogAllClients(); } catch (SocketException e) { WriteErrorMessageToConsole(e.Message, e.StackTrace, e); } } public static void WaitForClientData(Socket socket, string clientName, long oid, long recipientPersonOid, bool isEmployee) { try { if (asyncCallback == null) { asyncCallback = OnReceiveData; } var packet = new Packet(socket, clientName, oid, recipientPersonOid, isEmployee); socket.BeginReceive(packet.DataBuffer, 0, packet.DataBuffer.Length, SocketFlags.None, asyncCallback, packet); } catch (Exception e) { WriteErrorMessageToConsole(e.Message, e.StackTrace, e); } } private static void LogAllClients() { Console.BackgroundColor = ConsoleColor.Black; Console.ForegroundColor = ConsoleColor.DarkMagenta; Console.WriteLine("Folgende Benutzer sind angemeldet:"); var abc = ""; clientList.DoForEach(c =>abc += "\t" + c.Key.RemoteEndPoint.ToString() + "; Angemeldete Person: " + c.Value.PersonOid + "; MAC: " + c.Value.MacAddress + "; Zeitpunkt: " + c.Value.LoginTime.ToString("dd.MM.yyyy HH:mm:ss.ffff") + "\n"); Console.Write(abc); Console.BackgroundColor = ConsoleColor.Black; Console.ForegroundColor = ConsoleColor.Gray; } public static void OnReceiveData(IAsyncResult asyncResult) { var dataPacket = (Packet) asyncResult.AsyncState; Console.WriteLine(DateTime.Now + ": Empfange Nachricht (" + dataPacket.DataIdentifier.ToString() + ")"); LogAllClients(); try { var responseByteCount = dataPacket.Socket.EndReceive(asyncResult); var chars = new char[responseByteCount]; var decoder = Encoding.UTF8.GetDecoder(); decoder.GetChars(dataPacket.DataBuffer, 0, responseByteCount, chars, 0); if(chars.Count(f => f.Equals(';')) >= 4) { var message = new string(chars); dataPacket.GetData(message); } else { Console.WriteLine("dataPacket ist korrupt: " + dataPacket); } switch(dataPacket.DataIdentifier) { case DataIdentifier.KeepAlive: //spiele Ping-Pong var keepAlivePacket = new Packet( dataPacket.Socket, dataPacket.ClientName, dataPacket.SenderPersonOid, 0, dataPacket.IsEmployee) { DataIdentifier = DataIdentifier.KeepAlive, Tenant = dataPacket.Tenant }; dataPacket.Socket.Send(keepAlivePacket.GetDataStream()); break; case DataIdentifier.Login: try { Console.WriteLine(DateTime.Now + ": Login von " + dataPacket.ClientName + " mit MAC: " + dataPacket.ChatMessage); Monitor.Enter(_Lock); var endPoint = dataPacket.Socket.RemoteEndPoint; var endPointCollection = clientList.Select(s => s.Key.RemoteEndPoint).ToList(); var istEnthalten = endPointCollection.Contains(endPoint); var alteLogins = clientList.Values.Any(a => a.MacAddress.Equals(dataPacket.ChatMessage)); if(alteLogins) { var alt = clientList.Where(w => w.Value.MacAddress.Equals(dataPacket.ChatMessage) && !w.Key.Equals(dataPacket.Socket)); clientList.RemoveRange(alt); } if(istEnthalten) { clientList[dataPacket.Socket].PersonOid = dataPacket.SenderPersonOid; clientList[dataPacket.Socket].MacAddress = dataPacket.ChatMessage; } var notificationPacket = new Packet( dataPacket.Socket, dataPacket.ClientName, dataPacket.SenderPersonOid, 0, dataPacket.IsEmployee) { DataIdentifier = DataIdentifier.StatusNotificationLogin, Tenant = dataPacket.Tenant }; Console.WriteLine(DateTime.Now + ": Benachrichtige die anderen angemeldeten Clients. Alte Logins enthalten: " + alteLogins); foreach (var client in clientList.Where(w => !w.Value.PersonOid.Equals(dataPacket.SenderPersonOid))) { Console.WriteLine("Sende Login-Benachrichtigung an " + client.Value.MacAddress + " mit PersonOid " + client.Value.PersonOid); client.Key.Send(notificationPacket.GetDataStream()); } var successfulLoginNotification = new Packet(dataPacket.Socket, dataPacket.ClientName, dataPacket.SenderPersonOid, 0, dataPacket.IsEmployee) { DataIdentifier = DataIdentifier.LoginSuccess, Tenant = dataPacket.Tenant }; dataPacket.Socket.Send(successfulLoginNotification.GetDataStream()); // Boradcast var abc = clientList.Where(w => w.Value.PersonOid != dataPacket.SenderPersonOid && w.Value.PersonOid > 0).Select(s => s.Value.PersonOid).Distinct().ToList(); var msg = ""; foreach(var oid in abc) { msg += oid; if(abc.IndexOf(oid) != abc.Count - 1) { msg += ","; } } if(msg.Length > 0) { var packet = new Packet {ChatMessage = msg, ClientName = dataPacket.ClientName, DataIdentifier = DataIdentifier.StatusNotificationLoginBroadcast, MessageTimeStamp = DateTime.Now, Tenant = dataPacket.Tenant, SenderPersonOid = dataPacket.SenderPersonOid}; dataPacket.Socket.Send(packet.GetDataStream()); } LogAllClients(); } finally { Monitor.Exit(_Lock); } break; case DataIdentifier.Message: if(responseByteCount == 0) { try { Console.WriteLine(DateTime.Now + ": Chat-Nachricht ist leer und sorgt dafür, dass der Client ausgeloggt wird"); Monitor.Enter(_Lock); var notificationPacketLogout2 = new Packet(dataPacket.Socket, dataPacket.ClientName, dataPacket.SenderPersonOid, 0, dataPacket.IsEmployee) { DataIdentifier = DataIdentifier.StatusNotificationLogout, Tenant = dataPacket.Tenant}; LoginData wtf = null; var wasAbleToRemoveClient = clientList.TryRemove(dataPacket.Socket, out wtf); if(!wasAbleToRemoveClient) { Console.WriteLine("Konnte den Client nicht aus der Liste entfernen."); } Console.WriteLine(DateTime.Now + ": Leite Logout an die anderen Clients weiter"); foreach(var client in clientList) { client.Key.Send(notificationPacketLogout2.GetDataStream()); } dataPacket.Socket.Close(); } finally { Monitor.Exit(_Lock); } return; } try { Console.BackgroundColor = ConsoleColor.Black; Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine(DateTime.Now + ": Chat-Nachricht erhalten: " + dataPacket.MessageTimeStamp.ToString("dd.MM.yyyy HH:mm:ss")); Console.BackgroundColor = ConsoleColor.Black; Console.ForegroundColor = ConsoleColor.Gray; Monitor.Enter(_Lock); DefaultHibernateSessionManager.CurrentTenant = dataPacket.Tenant; var chatMessage = new ChatMessage { SenderPersonOid = dataPacket.SenderPersonOid, ChatText = Encoding.UTF8.GetBytes(dataPacket.ChatMessage), EmpfaengerPersonOid = dataPacket.RecipientPersonOid, Uhrzeit = DateTime.Now, InsUser = dataPacket.ClientName, IsDelivered = true, MessageId = dataPacket.MessageId, MediaTyp = dataPacket.MediaTyp }; Console.WriteLine(DateTime.Now + ": Speichere die Chat-Nachricht in der Datenbank"); DAOFactory.GenericDAO.Insert(chatMessage); var newestMessage = DAOFactory.SearchDAO.GetNewestChatMessageForConversation(dataPacket.RecipientPersonOid, dataPacket.SenderPersonOid, false); var newestChatMessage = new NewestChatMessage { ChatMessageOid = chatMessage.Oid, IsTeam = false, RecipientPersonOid = dataPacket.RecipientPersonOid, SenderPersonOid = dataPacket.SenderPersonOid }; if (newestMessage != null) { DAOFactory.GenericDAO.Delete(newestMessage); } DAOFactory.GenericDAO.Insert(newestChatMessage); //Media Bereich mit Bild und Dok. if(dataPacket.MediaDatei != null) { byte[] media = dataPacket.MediaDatei; byte[] ThImage = null; if (dataPacket.MediaTyp == 1) { ThImage = media; } var chatMediaMessage = new ChatMediaMessage { Media = media, InsUser = dataPacket.ClientName, ImageThumpNail = BS.Shared.Core.Utils.ErstelleThumbnail(ThImage, 150, 150), ChatMessageOid = chatMessage.Oid }; Console.WriteLine(DateTime.Now + ": Speichere die Chat-Media-Nachricht in der Datenbank"); DAOFactory.GenericDAO.Insert(chatMediaMessage); } if(chatMessage.Oid != null) { Console.WriteLine(DateTime.Now + ": Chat-Nachricht erfolgreich mit Oid " + chatMessage.Oid.Value + " gespeichert"); dataPacket.ServerseitigeOid = chatMessage.Oid.Value; } if(dataPacket.RecipientPersonOid > 0) { if(clientList.Values.Any(a => a.PersonOid.Equals(dataPacket.RecipientPersonOid))) { var recipients = clientList.Where(w => w.Value.PersonOid.Equals(dataPacket.RecipientPersonOid)); Console.WriteLine(DateTime.Now + ": Leite die Chat-Nachricht an die Empfänger (Derselbe User angemeldet an verschiedenen Clients) weiter"); foreach(var recipient in recipients) { recipient.Key.Send(dataPacket.GetDataStream()); } } var packet = new Packet { ClientseitigeOid = dataPacket.ClientseitigeOid, ServerseitigeOid = dataPacket.ServerseitigeOid, DataIdentifier = DataIdentifier.MessageResponse, MessageTimeStamp = chatMessage.Uhrzeit, MessageId = dataPacket.MessageId }; Console.WriteLine(DateTime.Now + ": Sende Datenbank-Insert-Bestätigung an den Absender, damit die Nachricht später nicht doppelt auf den Client heruntergeladen wird"); dataPacket.Socket.Send(packet.GetDataStream()); } } finally { Monitor.Exit(_Lock); } break; case DataIdentifier.Logout: LoginData ld; var wasAbleToRemoveClient2 = clientList.TryRemove(dataPacket.Socket, out ld); if(!wasAbleToRemoveClient2) { Console.WriteLine("Konnte den Client nicht aus der Liste entfernen."); } var notificationPacketLogout = new Packet(dataPacket.Socket, dataPacket.ClientName, dataPacket.SenderPersonOid, 0, dataPacket.IsEmployee) { DataIdentifier = DataIdentifier.StatusNotificationLogout, Tenant = dataPacket.Tenant }; Console.WriteLine(DateTime.Now + ": Sende Logout von " + dataPacket.ClientName + " an alle anderen Clients"); foreach (var client in clientList.Where(w => !w.Value.PersonOid.Equals(dataPacket.SenderPersonOid))) { client.Key.Send(notificationPacketLogout.GetDataStream()); } LogAllClients(); break; case DataIdentifier.Team: try { Console.WriteLine(DateTime.Now + ": Team-Nachricht erhalten"); Monitor.Enter(_Lock); DefaultHibernateSessionManager.CurrentTenant = dataPacket.Tenant; var chatMessage = new ChatMessage { SenderPersonOid = dataPacket.SenderPersonOid, ChatText = Encoding.UTF8.GetBytes(dataPacket.ChatMessage), EmpfaengerPersonOid = dataPacket.RecipientPersonOid, Uhrzeit = dataPacket.MessageTimeStamp, InsUser = dataPacket.ClientName, TeamOid = dataPacket.RecipientPersonOid, IsDelivered = true, MessageId = dataPacket.MessageId, MediaTyp = dataPacket.MediaTyp }; var team = DAOFactory.GenericDAO.LoadByID(dataPacket.RecipientPersonOid); Console.WriteLine(DateTime.Now + ": Speichere Team-Nachricht in der Datenbank"); DAOFactory.GenericDAO.Insert(chatMessage); var newestMessage = DAOFactory.SearchDAO.GetNewestChatMessageForConversation(dataPacket.RecipientPersonOid, dataPacket.SenderPersonOid, true); var newestChatMessage = new NewestChatMessage { ChatMessageOid = chatMessage.Oid, IsTeam = true, RecipientPersonOid = dataPacket.RecipientPersonOid, SenderPersonOid = dataPacket.SenderPersonOid }; if (newestMessage != null) { DAOFactory.GenericDAO.Delete(newestMessage); } DAOFactory.GenericDAO.Insert(newestChatMessage); //Media Bereich mit Bild und Dok. if (dataPacket.MediaDatei != null) { byte[] media = dataPacket.MediaDatei; byte[] ThImage = null; if (dataPacket.MediaTyp == 1) { ThImage = media; } var chatMediaMessage = new ChatMediaMessage { Media = media, InsUser = dataPacket.ClientName, ImageThumpNail = BS.Shared.Core.Utils.ErstelleThumbnail(ThImage, 150, 150), ChatMessageOid = chatMessage.Oid }; Console.WriteLine(DateTime.Now + ": Speichere die Chat-Media-Nachricht in der Datenbank"); DAOFactory.GenericDAO.Insert(chatMediaMessage); } var personOids = team.MemberList.Select(s => s.Person.Oid.Value); if (chatMessage.Oid != null) { Console.WriteLine(DateTime.Now + ": Team-Nachricht erfolgreich mit Oid " + chatMessage.Oid.Value + " gespeichert"); dataPacket.ServerseitigeOid = chatMessage.Oid.Value; } Console.WriteLine(DateTime.Now + ": Leite die Team-Nachricht an die Empfänger weiter"); foreach (var recipient in clientList.Where(w => w.Value.PersonOid != dataPacket.SenderPersonOid && personOids.Contains(w.Value.PersonOid))) { recipient.Key.Send(dataPacket.GetDataStream()); } var packet = new Packet { ClientseitigeOid = dataPacket.ClientseitigeOid, ServerseitigeOid = dataPacket.ServerseitigeOid, DataIdentifier = DataIdentifier.MessageResponse, MessageId = dataPacket.MessageId }; Console.WriteLine(DateTime.Now + ": Sende Datenbank-Insert-Bestätigung an den Absender, damit die Nachricht später nicht doppelt auf den Client heruntergeladen wird"); dataPacket.Socket.Send(packet.GetDataStream()); } finally { Monitor.Exit(_Lock); } break; case DataIdentifier.StatusNotificationBeschäftigt: SendNotificationPacket(dataPacket, DataIdentifier.StatusNotificationBeschäftigt); break; case DataIdentifier.StatusNotificationOnline: SendNotificationPacket(dataPacket, DataIdentifier.StatusNotificationOnline); break; case DataIdentifier.StatusNotificationOffline: SendNotificationPacket(dataPacket, DataIdentifier.StatusNotificationOffline); break; case DataIdentifier.Null: var asfdabc = dataPacket.ChatMessage; break; case DataIdentifier.StatusRequest: var onlinePersons = clientList.Where(w => w.Value.PersonOid != dataPacket.SenderPersonOid && w.Value.PersonOid > 0).Select(s => s.Value.PersonOid).Distinct().ToList(); var oidCSV = ""; foreach (var oid in onlinePersons) { oidCSV += oid; if (onlinePersons.IndexOf(oid) != onlinePersons.Count - 1) { oidCSV += ","; } } if (oidCSV.Length > 0) { var packet = new Packet { ChatMessage = oidCSV, ClientName = dataPacket.ClientName, DataIdentifier = DataIdentifier.StatusRequest, MessageTimeStamp = DateTime.Now, Tenant = dataPacket.Tenant, SenderPersonOid = dataPacket.SenderPersonOid }; dataPacket.Socket.Send(packet.GetDataStream()); } break; } WaitForClientData(dataPacket.Socket, dataPacket.ClientName, dataPacket.SenderPersonOid, dataPacket.RecipientPersonOid, dataPacket.IsEmployee); } catch (Exception e) { Console.WriteLine(DateTime.Now + ": Ein Fehler ist aufgetreten oder die App ist abgestürzt. Der Client wird ausgeloggt (" + dataPacket.ClientName + ")"); var sender = dataPacket.Socket; var notificationPacketLogout = new Packet(dataPacket.Socket, dataPacket.ClientName, dataPacket.SenderPersonOid, 0, dataPacket.IsEmployee) { DataIdentifier = DataIdentifier.StatusNotificationLogout, Tenant = dataPacket.Tenant}; try { Monitor.Enter(_Lock); if(clientList.ContainsKey(sender)) { LoginData loginData; var wasAbleToRemoveClient3 = clientList.TryRemove(sender, out loginData); if(!wasAbleToRemoveClient3) { Console.WriteLine("Konnte den Client nicht aus der Liste entfernen."); } LogAllClients(); Console.WriteLine(DateTime.Now + ": Leite Logout an die anderen Clients weiter"); foreach(var client in clientList) { client.Key.Send(notificationPacketLogout.GetDataStream()); } sender.Close(); } } catch(SocketException se) { WriteErrorMessageToConsole(se.Message, se.StackTrace, se); } finally { Monitor.Exit(_Lock); } WriteErrorMessageToConsole(e.Message, e.StackTrace, e); } } private static Random random = new Random(); public static string RandomString(int length) { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; return new string(Enumerable.Repeat(chars, length) .Select(s => s[random.Next(s.Length)]).ToArray()); } private static void SendNotificationPacket(Packet dataPacket, DataIdentifier dataIdentifier) { var notificationPacket = new Packet(dataPacket.Socket, dataPacket.ClientName, dataPacket.SenderPersonOid, 0, dataPacket.IsEmployee) { DataIdentifier = DataIdentifier.StatusNotificationLogout, Tenant = dataPacket.Tenant}; Console.WriteLine(DateTime.Now + ": Sende Statusänderung(" + dataIdentifier + ") an alle anderen Clients"); foreach (var client in clientList.Where(w => w.Value.PersonOid != dataPacket.SenderPersonOid)) { client.Key.Send(notificationPacket.GetDataStream()); } } private static void WriteErrorMessageToConsole(string message, string stackTrace, Exception ex = null) { Console.BackgroundColor = ConsoleColor.Black; Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(DateTime.Now + ": " + message); Console.WriteLine(stackTrace); if(ex != null) { var st = new StackTrace(ex, true); var frame = st.GetFrame(st.FrameCount - 1); var line = frame.GetFileLineNumber(); Console.WriteLine("Zeile: " + line); } Console.BackgroundColor = ConsoleColor.Black; Console.ForegroundColor = ConsoleColor.Gray; } } public class LoginData { public long PersonOid { get; set; } public string MacAddress { get; set; } public DateTime LoginTime { get; set; } public LoginData(long pPersonOid, string pMacAddress, DateTime pLoginTime) { PersonOid = pPersonOid; MacAddress = pMacAddress; LoginTime = pLoginTime; } } }