Öneri Login Phase Takılması Hakkında – Işınlanma Sırasında Oluşan Bağlantı Sorunu

  • Konuyu açan Konuyu açan victory
  • Açılış Tarihi Açılış Tarihi
  • Yanıt Yanıt 21
  • Gösterim Gösterim 407
Konu sahibi önerilerinizi bekliyor. Konuya yorum yazabilirsiniz.

victory

Üye
Üye
Mesaj
29
Beğeni
11
Puan
655
Ticaret Puanı
0
İyi forumlar.

Bir süredir oyunumda yoğun olarak yaşadığım ve aslında tüm server files’larda bulunan bir takılma problemi hakkında sizlerle bazı gözlemlerimi ve çözüm önerilerimi paylaşmak istiyorum.

🔗 Örnek bir görüntü:

Linkleri görebilmek için giriş yap veya kayıt ol.


Bu problem, esasen server'ın client ile olan iletişiminin kopmasından kaynaklanıyor. Normal şartlarda, başarılı bir ışınlanma esnasında client, ışınlanılmak istenilen core’a handshake bilgilerini ve ardından login paketini yollar. Server da bu paketi alarak login işlemini tamamlar ve fazı PHASE_SELECT olarak değiştirir. Ardından gelen loading ve entergame işlemleriyle ışınlanma düzgünce tamamlanır.

🔗 Muhtemel seneryolar:
Ancak takılmaların yaşandığı durumlarda, genellikle server login işleminde hata vererek LoginFailure paketi döndürüyor ve sonrasında herhangi bir faz kapatma işlemi vs sağlamıyor. Bu durumda faz PHASE_LOGIN olarak kalıyor. Client ise bu loginfailure paketine, intrologin.py aşamasında olmadığı için yanıt veremiyor ve bekleme ekranında takılı kalıyor. "Hesabın zaten bağlı.", "Sunucuya bağlanırken hata." gibi popup mesajları da bu yüzden gösterilmiyor.

Daha da kötüsü, server herhangi bir PHASE_CLOSE ya da quit mesajı da göndermediği için iletişim tamamen kopuyor ve client orada kalıyor.

Bazı hata loglarında, server’ın ışınlanma sırasında cliente ALREADY LOG IN mesajıyla LoginFailure döndürdüğünü görebiliyorum. İşte tam bu noktada iletişim kopuyor ve takılma meydana geliyor. Ancak login işleminin neden sekteye uğradığı konusunda kesin bir fikrim yok.

🛠️ Potansiyel çözüm önerim:
Login işlemi başarısız olduğunda, LoginFailure paketi gönderilmeden önce mevcut faz sonlandırılmalı (PHASE_CLOSE) ya da doğrudan bir reconnect mekanizması tetiklenmeli. Bu sayede client boşta kalmaz, en azından düzgün bir hata mesajı ile kullanıcıya durum yansıtılmış olur.

Bu konuda daha önce karşılaşmış, çözüm üretmiş veya teknik olarak bilgisi olan arkadaşların yorumlarını bekliyorum. Yardımcı olabilecek her türlü bilgiye açığım.
Ekleme: Bu takılma sorunu farklı sebeplerden dolayı da benzer şekilde tetiklenebilir. Ben sadece benim gözlemlediğim sebebi ve tecrübelerimi paylaşmak istedim.

Teşekkürler.
 
sorun ülkemde aylardır biliniyor, birileri buna sebep oluyor ve sunucularda oynuyor, o yüzden bu sorunu yaşıyorsunuz.
bu yeni bir istismardır / birisi sıkılıyor ve her yerde buna sebep oluyor
 
Paket gönderemezsiniz, karakterden oyuna girdiğinde veya ışınlanma yaptığında, olan bağlantı sonlandırılır, sonrasında yeni bağlantı başlatılır.
Sorun işte bundan kaynaklanıyor, bağlantı sonlandıktan sonra sunucuyla tekrar bağlantı kuramazsanız, bu şekilde bugda kalırsınız.
Normal senaryo: warp paketini client alır > işleme alır bağlantıyı kapatır > yeni bağlantıyı başlatır > server gerekli paketleri gönderir > oyun yüklenir.
Koptuğu senaryo: warp paketini client alır > işleme alır bağlantıyı kapatır > yeni bağlantıyı kuramaz > oyun son ekranda kalır.

Olası çözüm: RecvWarpPacket() bulunur
İçindeki CNetworkStream::Connect fonksiyonu bool döndürür, ancak kontrol edilmez,
Değiştirilip eğer false(başarısız bağlantı) döndürürse delay verilip (örneğin 5 saniye) delaydan sonra tekrar bağlatı denenilir. Veya doğrudan phase login olarak değiştirilir.
Şunuda belirteyim, bağlantı anında gerçekleşmeyeceği için bağlantı başarısız olsa bile true döndürebilir, bu yüzden, başka yöntemler kullanmanızı tavsiye ederim,
CNetworkStream::OnConnectFailure() methodu başarısız bağlantıdan sonra çağırılır.(içinde sadece tracen var) bunu modifiye etmek daha mantıklı olabilir.
CNetworkStream içinde iswarpconnection gibi bir değişken oluşturulup, warp paketiyle true ayarlandıktan sonra OnConnectFailure içinde tekrar false yapıp bağlantının tekrar denenmesi için ayarlamalar denenebilir.
Son ek CNetworkStream::OnConnectSuccess içinde eğer iswarpconnection true ise false yapmakta unutulmamalıdır.

Sunucu bağlantısının kurulamamasının bir kaç nedeni vardır en başlıcası kötü bağlantıdır, yeni bir istismar falan değildir. Elektrik kullanan birine büyücü demek gibi birşey bu. Bağlantının her seferinde doğru kurulamaması olağan birşeydir, sorun bağlantı kurulamadığında tekrar bağlantı deneyecek mekanizmanın oyunda olmamasıdır.
 
Son düzenleme:
Aslında dilime çevrilen şeyi yanlış anladım, exploit mevcut ama başka bir şey yapıyor
Bu exploit oyuncuların %50'sini oyundan çıkarabilir ve burada ışınlanmadan bahsediyoruz, hatayı yanlış anladım ve sunucuyu yeniden başlatana kadar giriş yapamayacaklar
ancak benzer faz paketlerini kullanır
 
Işınlanma konusunda takılma sorunu hiç yaşamadım. Ancak client bir sebeple crash yediği zaman ve tekrar açıp giriş yapmaya çalıştığım zaman Hesap zaten bağlı uyarısı veriyor ve birkaç saniye sonra bağlanıyor. İşin daha garip yanı, localhostta çalışıyorum ve bu durum sürekli olmuyor. FreeBSD yi komple kapatıp açtığımda düzeliyor. Biraz random gibi. DB loglarinda ise ALREADY LOGIN KEY mesajı veriyor bu esnada. Connect, reconnect ve disconnect ile alakalı çağrıları kontrol ettim ama bir sorun da göremedim.

Mesela bu olayın yaşandığı esnada iki tane client açıksa ve karakterlerden birinin clienti çökerse, diğer client ekranında crash yiyen karakter görünmeye devam ediyor. PM atabiliyorsun ama oyunda değil diyor. Garip bir durum.. dediğim gibi ışınlanmada sorunum yok ama nedense böyle garip bir olay var bende de. Konuyu takipteyim şimdilik.
 
Ancak takılmaların yaşandığı durumlarda, genellikle server login işleminde hata vererek LoginFailure paketi döndürüyor ve sonrasında herhangi bir faz kapatma işlemi vs sağlamıyor.
bunun sebebi genellikle login kısmında query'nin uzun sürmesi ya da timeout'a düşmesi ya da alternatif olarak o kanalın db ile iletişim kuramamasından kaynaklı oluyor.

Bazı hata loglarında, server’ın ışınlanma sırasında cliente ALREADY LOG IN mesajıyla LoginFailure döndürdüğünü görebiliyorum
Burada ise yine aynı sebeple query geç yanıt aldığında ya da db'den cevap paketi geç geldiğinde oyuncu hala sistemde cache de aktif olarak gözüküyor.

yazdığınızdan çıkarımım çok uzun süre alan queryler mevcut, db ya da mysql ile iletişim kopuyor ya da net altyapısı kaynaklı bir sorun var. bunun kod ya da exploit ile meydana gelmesi çok zor bir durum ancak yinede mümkün. Örnek olarak;

C++:
Genişlet Daralt Kopyala
    auto pMsg = DBManager::Instance().DirectQuery(query.c_str());
    if (pMsg->Get())

şeklinde bir query kullanımında bunu mysql'a erişip cevap gelene kadar bekleyecektir, o süreç içinde bağlantı sağlanamazsa orada sistem kitlenir bunu aşağıdaki şekilde kullanmanız gerekiyor
C++:
Genişlet Daralt Kopyala
    auto pMsg = DBManager::Instance().DirectQuery(query.c_str());
    if (!pMsg || !pMsg.get() || pMsg->uiSQLErrno)
    {
        return false;
    }

    SQLResult* pRes = pMsg->Get();
    if (!pRes || !pRes->uiNumRows)
    {
        return false;
    }

    ...

kısacası öncelikle ne aşamada sorunun başladığını tespit etmeniz gerek bence bağlantı sorunu olması daha yüksek bir ihtimal.

Son olarak muhtemelen konu ile alakasız bir durum ancak yinede denemekte fayda var, eski sadece python ile çalışan kanal değiştiricilerde ardarda kanal değiştirdiğinizde benzer bir takılma durumu meydana geliyordu, bunu önlemek için biraz hareket edip ondan sonra tekrar kanal değiştirmeniz gerekiyordu eğer sadece kanal değiştiricide böyle bir sorun varsa sıkıntı o sistemlede alakalı olabilir.
 
En son bir moderatör tarafından düzenlenmiş:
bunun sebebi genellikle login kısmında query'nin uzun sürmesi ya da timeout'a düşmesi ya da alternatif olarak o kanalın db ile iletişim kuramamasından kaynaklı oluyor.


Burada ise yine aynı sebeple query geç yanıt aldığında ya da db'den cevap paketi geç geldiğinde oyuncu hala sistemde cache de aktif olarak gözüküyor.

yazdığınızdan çıkarımım çok uzun süre alan queryler mevcut, db ya da mysql ile iletişim kopuyor ya da net altyapısı kaynaklı bir sorun var. bunun kod ya da exploit ile meydana gelmesi çok zor bir durum ancak yinede mümkün. Örnek olarak;



şeklinde bir query kullanımında bunu mysql'a erişip cevap gelene kadar bekleyecektir, o süreç içinde bağlantı sağlanamazsa orada sistem kitlenir bunu aşağıdaki şekilde kullanmanız gerekiyor
Kod:
Genişlet Daralt Kopyala
    auto pMsg = DBManager::Instance().DirectQuery(query.c_str());
    if (!pMsg || !pMsg.get() || pMsg->uiSQLErrno)
    {
        return false;
    }

    SQLResult* pRes = pMsg->Get();
    if (!pRes || !pRes->uiNumRows)
    {
        return false;
    }

    ...

kısacası öncelikle ne aşamada sorunun başladığını tespit etmeniz gerek bence bağlantı sorunu olması daha yüksek bir ihtimal.

Son olarak muhtemelen konu ile alakasız bir durum ancak yinede denemekte fayda var, eski sadece python ile çalışan kanal değiştiricilerde ardarda kanal değiştirdiğinizde benzer bir takılma durumu meydana geliyordu, bunu önlemek için biraz hareket edip ondan sonra tekrar kanal değiştirmeniz gerekiyordu eğer sadece kanal değiştiricide böyle bir sorun varsa sıkıntı o sistemlede alakalı olabilir.
Yanıtınız için teşekkür ederim.

LoginFailure mesajının döndürülmesinin query zaman aşımından kaynaklandığını sanmıyorum.

Işınlanma işleminde DB iki iş yapar:

1-
Bulunulan güncel core'daki bağlantı terminate edilir ve oyuncunun DESC'i destroy edilir. DESC::Destroy() fonksiyonu içerisinde ise HEADER_GD_LOGOUT headeriyle birlikte DB sırasıyla; QUERY_LOGOUT, InsertLogoutPlayer ve DeleteLogonAccount işlemlerini sağlar.
Bu akışta görebildiğim kadarıyla herhangi bir senkron/asenkron query işlemi yok.

2- Işınlanılan yeni core'da (aynı core'ya da olabilir tabiki) yeni connection açılır ve yeni DESC ile login işlemleri tamamlanır. Login işlemleri sırasında DB'de bir dizi query işlemleri gerçekleşir evet fakat bu querylerde oluşacak herhangi bir performans sorunu LoginAlready mesajı döndürmeye sebebiyet vermez diye düşünüyorum.

Fakat bahsettiğiniz query performansı ya da connection sorunları yine de bu ışınlanırken takılma problemine farklı yollardan sebep olabilir, bu konuda henüz çıkarımlarım mevcut değil. Sadece LoginFailure mesajını döndürebilecek bir sorun yaratacağını düşünmüyorum.

Bu konuda biraz daha akıştan örnek verme şansınız var mı rica etsem?
 
Işınlanma konusunda takılma sorunu hiç yaşamadım. Ancak client bir sebeple crash yediği zaman ve tekrar açıp giriş yapmaya çalıştığım zaman Hesap zaten bağlı uyarısı veriyor ve birkaç saniye sonra bağlanıyor. İşin daha garip yanı, localhostta çalışıyorum ve bu durum sürekli olmuyor. FreeBSD yi komple kapatıp açtığımda düzeliyor. Biraz random gibi. DB loglarinda ise ALREADY LOGIN KEY mesajı veriyor bu esnada. Connect, reconnect ve disconnect ile alakalı çağrıları kontrol ettim ama bir sorun da göremedim.

Mesela bu olayın yaşandığı esnada iki tane client açıksa ve karakterlerden birinin clienti çökerse, diğer client ekranında crash yiyen karakter görünmeye devam ediyor. PM atabiliyorsun ama oyunda değil diyor. Garip bir durum.. dediğim gibi ışınlanmada sorunum yok ama nedense böyle garip bir olay var bende de. Konuyu takipteyim şimdilik.
Yaşadığınız sorunlar benim de çokça başıma geldi. Ve sizin yanıtınızı okurken de bu sorunların DB'ye gönderilen paketlerde gerçekleşebileceğini ve DB'nin bu paketleri okuma esnasında sorun yaşayabileceğini düşündüm.

Game core'lar ışınlanma işlemlerinde kendi üzerine düşen işleri yapıyor fakat DB'deki cache ve login işlem mekanizmaları sanırım bazen performans kaynaklı ya da yanlış kodlama kaynaklı sekteye uğrayabiliyor.

Tam olarak benim çıkarımlarımla paralel olarak; DB aslında logout olmuş bir accountu halen oyunda olarak görüp, işlem akışını bozabiliyor.
 
Son düzenleme:
Paket gönderemezsiniz, karakterden oyuna girdiğinde veya ışınlanma yaptığında, olan bağlantı sonlandırılır, sonrasında yeni bağlantı başlatılır.
Sorun işte bundan kaynaklanıyor, bağlantı sonlandıktan sonra sunucuyla tekrar bağlantı kuramazsanız, bu şekilde bugda kalırsınız.
Normal senaryo: warp paketini client alır > işleme alır bağlantıyı kapatır > yeni bağlantıyı başlatır > server gerekli paketleri gönderir > oyun yüklenir.
Koptuğu senaryo: warp paketini client alır > işleme alır bağlantıyı kapatır > yeni bağlantıyı kuramaz > oyun son ekranda kalır.

Olası çözüm: RecvWarpPacket() bulunur
İçindeki CNetworkStream::Connect fonksiyonu bool döndürür, ancak kontrol edilmez,
Değiştirilip eğer false(başarısız bağlantı) döndürürse delay verilip (örneğin 5 saniye) delaydan sonra tekrar bağlatı denenilir. Veya doğrudan phase login olarak değiştirilir.
Şunuda belirteyim, bağlantı anında gerçekleşmeyeceği için bağlantı başarısız olsa bile true döndürebilir, bu yüzden, başka yöntemler kullanmanızı tavsiye ederim,
CNetworkStream::OnConnectFailure() methodu başarısız bağlantıdan sonra çağırılır.(içinde sadece tracen var) bunu modifiye etmek daha mantıklı olabilir.
CNetworkStream içinde iswarpconnection gibi bir değişken oluşturulup, warp paketiyle true ayarlandıktan sonra OnConnectFailure içinde tekrar false yapıp bağlantının tekrar denenmesi için ayarlamalar denenebilir.
Son ek CNetworkStream::OnConnectSuccess içinde eğer iswarpconnection true ise false yapmakta unutulmamalıdır.

Sunucu bağlantısının kurulamamasının bir kaç nedeni vardır en başlıcası kötü bağlantıdır, yeni bir istismar falan değildir. Elektrik kullanan birine büyücü demek gibi birşey bu. Bağlantının her seferinde doğru kurulamaması olağan birşeydir, sorun bağlantı kurulamadığında tekrar bağlantı deneyecek mekanizmanın oyunda olmamasıdır.
Yanıtınız için teşekkür ederim.

Bahsettiğiniz yeni bağlantının kurulamaması probleminin benzer takılmalara sebep olabileceğine kesinlikle katılıyorum.
Fakat benim kendi örneklendirmelerimde yeni bağlantı zaten kurulmuş, login paketleri yeni bağlantıyla target core'a ulaşmış ve işleme başlamıştı.
Spesifik olarak benim yaşadığım sorun; client - game bağlantılarında değil de game - db bağlantılarında gerçekleşiyor gibi görünüyor.

Ekleme olarak; RecvWarpPacket fonksiyonundan sonra çağırılan CNetworkStream::Connect fonksiyonunda hali hazırda zaten bir reconnect mekanizması bulunuyor. Orada sorun yaşanması halinde 1000 MS ile sleep atıp tekrar bağlantı kurulmaya çalışıldığını görebilirsiniz. Bu tam olarak sizin bahsettiğiniz sorunla alakalı bence. Fakat nekadar net bir çözüm olur emin değilim.

Benim sorunum başarıyla kurulan yeni bağlantının server tarafından "sessizce" öldürülmesiyle alakalı. Warp ekranında ölü bağlantıyla takılı kalan client eğer gerekliyse yeni bağlantının ölü olup olmadığına bir şekilde karar vermeli. (Ping-pong mekanızması bu takılma durumunda bile çalışır durumda kalıyor bu arada.)

Ekleme: Her ne olursa olsun bahsettiğiniz tarzda bağlantı sorunlarının oyuncu sorumluluğunda olduğuna inanıyorum. Biz geliştiricilerin bu sorunlara ek olarak mesai harcamasına ihtiyacımız yok. M2 gibi tekthreadde ilerleyen, düşük buffer ve paket boyutlarıyla iletişim kuran, ilkel bir altyapının bile network yükünü üstlenemeyecek sistemlerin hala kullanılıyor olması çok yazık. Ufak bir library ekleyecekken bile hala win7 ve winxp ile bu oyunu oynayan insanların var olduğunu gözden kaçırmamak gerek.
 
Son çalışmalarımda elde ettiğim çıkarımlara göre sorunun, ışınlanılan güncel core'daki bağlantının terminate edilmesinden önce yeni connectionda login işlemlerinin başlamasına dayandırıyorum.

Eski bağlantı terminate edilmeden yani henüz DB'ye HEADER_GD_LOGOUT gönderilmeden yeni connectionda login işlemlerini üstlenen DB'nin aslında logout olmuş ama kendisine henüz bilgi gelmediği için bunu bilmemesiyle asıl sorumlu olduğunu düşünüyorum.

Basitçe şöyle bir fix denedim:

CHARACTER::WarpSet içerisinde cliente gönderilen TPacketGCWarp paketi sonrasında game artık db'ye var olan desc'in destroy edilmesini beklemeden logout bilgisi gönderecek:

1745682897528.webp


TPacketGCWarp paketini teslim alan clientin kesin olarak var olan bağlantıyı sonlandıracağına eminiz (Herhangi bir takılma sorunu yaşarsa bile connection bir şekilde abort edilip desc destroy edilecek). O yüzden bu eklentinin herhangi bir mahsuru yok. Er ya da geç logout bilgisini db'ye gönderecektik. Sadece bunun daha erken olmasını sağladık.

Oluşturduğum yeni fonksiyonun tanımı:

1745682942184.webp


Bu şekilde dbnin bu bilgiyi aldığına emin olmaya çalıştım. Buna ek olarak; DESC::Destroy fonksıyonunda HEADER_GD_LOGOUT gönderilen kısmı silmemeliyiz. Bu headerin iki defa gönderilmesinin herhangi bir mahsuru yok ama warp edilmeden connection kapanırsa ve DB bu bilgiyi alamazsa akışı kendi ellerimizle bozmuş oluruz.

Dünden beri test sunucumdaki az sayıda oyuncuyla denedim herhangi bir yan etkisi yok ama sorun çözülmüş mü kesin olarak emin değilim.

Yaptığım bu düzenlemeyle yaşadığım sorunu ve şüphelendiğim kaynağı net olarak açıklayabilmişimdir umarım. Bu geç bilgi gönderme işleminin neden olabileceğini bilen var mı? Sorunun kaynağını çözmek olası diğer sorunları da engellememizi sağlar.
 
Yaşadığınız sorunlar benim de çokça başıma geldi. Ve sizin yanıtınızı okurken de bu sorunların DB'ye gönderilen paketlerde gerçekleşebileceğini ve DB'nin bu paketleri okuma esnasında sorun yaşayabileceğini düşündüm.

Game core'lar ışınlanma işlemlerinde kendi üzerine düşen işleri yapıyor fakat DB'deki cache ve login işlem mekanizmaları sanırım bazen performans kaynaklı ya da yanlış kodlama kaynaklı sekteye uğrayabiliyor.

Tam olarak benim çıkarımlarımla paralel olarak; DB aslında logout olmuş bir accountu halen oyunda olarak görüp, işlem akışını bozabiliyor.
Her ne kadar yaşadığımız durumlar büyük benzerlik gösterse de tam olarak aynı değil gibi. Çünkü bende ışınlanma esnasında, veya normal bir şekilde oyundan çıkarken vb. hiç böyle sorunlar olmuyor dediğim gibi. Crash/Abort gibi durumlarda random bir şekilde bu sorun karşıma çıkabiliyor. Bunun da sebebini şuna yoruyorum; oyundan normal bir şekilde çıkış yaptığımızda servera command gönderiliyor ve çıkış işlemleri düzenli bir şekilde yapılıyor. (Çıkış & oyun sonu)

Ancak crash ve abort esnasında PostQuitMessage ile .exe aniden kapanıyor. Ve PostQuitMessage parametresinin Metin2 ile alakası olmayıp, Windows API'ye bağlı bir sonlandırma çağrısı olduğunu düşündüğümde, bu bahsettiğim gibi tutarsızlıkların nadiren de olsa gerçekleşmesi pek mümkün geliyor. Çünkü bu şekilde kapandığında sanıyorum ki servera herhangi bir command gitmeden exe kapanmış oluyor. Ama yine de bazen olup bazen olmaması sebebini merak ettiriyor..
 
Her ne kadar yaşadığımız durumlar büyük benzerlik gösterse de tam olarak aynı değil gibi. Çünkü bende ışınlanma esnasında, veya normal bir şekilde oyundan çıkarken vb. hiç böyle sorunlar olmuyor dediğim gibi. Crash/Abort gibi durumlarda random bir şekilde bu sorun karşıma çıkabiliyor. Bunun da sebebini şuna yoruyorum; oyundan normal bir şekilde çıkış yaptığımızda servera command gönderiliyor ve çıkış işlemleri düzenli bir şekilde yapılıyor. (Çıkış & oyun sonu)

Ancak crash ve abort esnasında PostQuitMessage ile .exe aniden kapanıyor. Ve PostQuitMessage parametresinin Metin2 ile alakası olmayıp, Windows API'ye bağlı bir sonlandırma çağrısı olduğunu düşündüğümde, bu bahsettiğim gibi tutarsızlıkların nadiren de olsa gerçekleşmesi pek mümkün geliyor. Çünkü bu şekilde kapandığında sanıyorum ki servera herhangi bir command gitmeden exe kapanmış oluyor. Ama yine de bazen olup bazen olmaması sebebini merak ettiriyor..
Client genelde servere çıkış için bilgi yollamıyor ki zaten. Asıl serverin, client bağlantısının kesildiğini anladığı an io_loop fonksiyonu içerisinde. Client penceresi kapandığı anda winsock zaten soketi kapayarak bu işlemi yürütüyor. io_loop içerisinde kapanmış soketlere sahip descler ve eğer varsa bağlı olduğu character objeleri de çok kısa süre içerisinde silinip kapatılıyor böylelikle.

Ekstra olarak sadece /logout ve /select_character gibi chat mesajları bulunuyor. Bunların manası da eğer pvp esnasındaysa engellemek ya da countdown yönetiyor olması vb.

main.cpp içerisindeki io_loop fonksiyonu bütün network io işlemlerini yönetiyor. Connection başlatma, bitirme, yazma ve okuma işlemleri.

Bir süre bu işlemler üzerinde çalışmıştım. Benim sorunumla alakalı olarak; warp işlemi sırasında io_loop eğer bulunulan bağlantıyı henüz kapamadan ışınlanılan yerin connectionun açarsa already login problemi yaşayabileceğimizi düşündüm. Ancak bu teorinin altını dolduramadım henüz.

Detay vermek gerekirse: her io_loop çalıştığında önce PHASE_CLOSE durumundaki descler tespit edilip kapatılıyor. O satır şu:

DESC_MANAGER::instance().DestroyClosed();

daha sonrasında fdwatch mekanizması soket açma, soket kapama, read ve write işlemlerini işlemesi için bir for döngüsü çalıştırıyor. Bu döngü içerisinde kapatılmış soketlere sahip DESC'ler daha sonraki io_loop çalıştırılmasında silinmek üzere PHASE_CLOSE olarak imzalanıyor.

Sanıyorum network soketi kapanmış bir DESC'i hemen kapamak yerine imzalayıp bir sonraki çağrıyı beklemesinin sebebi for döngüsünün diğer adımlarında bu descin kullanılabileceğini göz önüne almaktır.
Ancak bu yöntem çeşitli bağlantı sorunlarına yol açıyor da olabilir.
 
Son düzenleme:
Client genelde servere çıkış için bilgi yollamıyor ki zaten. Asıl serverin, client bağlantısının kesildiğini anladığı an io_loop fonksiyonu içerisinde. Client penceresi kapandığı anda winsock zaten soketi kapayarak bu işlemi yürütüyor. io_loop içerisinde kapanmış soketlere sahip descler ve eğer varsa bağlı olduğu character objeleri de çok kısa süre içerisinde silinip kapatılıyor böylelikle.

Ekstra olarak sadece /logout ve /select_character gibi chat mesajları bulunuyor. Bunların manası da eğer pvp esnasındaysa engellemek ya da countdown yönetiyor olması vb.

main.cpp içerisindeki io_loop fonksiyonu bütün network io işlemlerini yönetiyor. Connection başlatma, bitirme, yazma ve okuma işlemleri.

Bir süre bu işlemler üzerinde çalışmıştım. Benim sorunumla alakalı olarak; warp işlemi sırasında io_loop eğer bulunulan bağlantıyı henüz kapamadan ışınlanılan yerin connectionun açarsa already login problemi yaşayabileceğimizi düşündüm. Ancak bu teorinin altını dolduramadım henüz.
ESC Menüsünden Çıkış-Karakter Değiştir ve Oyun Sonu butonları servera bilgi gönderiyor. Şöyle ki;
İlk olarak burası tetiklenir:
C++:
Genişlet Daralt Kopyala
ACMD(do_cmd)
{
    if (ch->m_pkTimedEvent)
    {
        ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Ãë¼Ò µÇ¾ú½À´Ï´Ù."));
        event_cancel(&ch->m_pkTimedEvent);
        return;
    }

    switch (subcmd)
    {
        case SCMD_LOGOUT:
            ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("·Î±×ÀΠȸéÀ¸·Î µ¹¾Æ °©´Ï´Ù. Àá½Ã¸¸ ±â´Ù¸®¼¼¿ä."));
            break;

        case SCMD_QUIT:
            ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("°ÔÀÓÀ» Á¾·á ÇÕ´Ï´Ù. Àá½Ã¸¸ ±â´Ù¸®¼¼¿ä."));
            break;

        case SCMD_PHASE_SELECT:
            ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("ij¸¯Å͸¦ Àüȯ ÇÕ´Ï´Ù. Àá½Ã¸¸ ±â´Ù¸®¼¼¿ä."));
            break;
    }

    int nExitLimitTime = 10;

    if (ch->IsHack(false, true, nExitLimitTime) &&
        false == CThreeWayWar::instance().IsSungZiMapIndex(ch->GetMapIndex()) &&
           (!ch->GetWarMap() || ch->GetWarMap()->GetType() == GUILD_WAR_TYPE_FLAG))
    {
        return;
    }
    
    switch (subcmd)
    {
        case SCMD_LOGOUT:
        case SCMD_QUIT:
        case SCMD_PHASE_SELECT:
            {
                TimedEventInfo* info = AllocEventInfo<TimedEventInfo>();

                {
                    if (ch->IsPosition(POS_FIGHTING))
                        info->left_second = 10;
                    else
                        info->left_second = 3;
                }

                info->ch        = ch;
                info->subcmd        = subcmd;
                strlcpy(info->szReason, argument, sizeof(info->szReason));

                ch->m_pkTimedEvent    = event_create(timed_event, info, 1);
            }
            break;
    }
}

Daha sonra ise burası tetiklenir ve süre sonunda işlem sonlandırılır:
C++:
Genişlet Daralt Kopyala
EVENTFUNC(timed_event)
{
    TimedEventInfo * info = dynamic_cast<TimedEventInfo *>( event->info );
    
    if ( info == NULL )
    {
        sys_err( "timed_event> <Factor> Null pointer" );
        return 0;
    }

    LPCHARACTER    ch = info->ch;
    if (ch == NULL) { // <Factor>
        return 0;
    }
    LPDESC d = ch->GetDesc();

    if (info->left_second <= 0)
    {
        ch->m_pkTimedEvent = NULL;

        if (true == LC_IsEurope() || true == LC_IsYMIR() || true == LC_IsKorea())
        {
            switch (info->subcmd)
            {
                case SCMD_LOGOUT:
                case SCMD_QUIT:
                case SCMD_PHASE_SELECT:
                    {
                        TPacketNeedLoginLogInfo acc_info;
                        acc_info.dwPlayerID = ch->GetDesc()->GetAccountTable().id;

                        db_clientdesc->DBPacket( HEADER_GD_VALID_LOGOUT, 0, &acc_info, sizeof(acc_info) );

                        LogManager::instance().DetailLoginLog( false, ch );
                    }
                    break;
            }
        }

        switch (info->subcmd)
        {
            case SCMD_LOGOUT:
                if (d)
                    d->SetPhase(PHASE_CLOSE);
                break;

            case SCMD_QUIT:
                ch->ChatPacket(CHAT_TYPE_COMMAND, "quit");
                break;

            case SCMD_PHASE_SELECT:
                {
                    ch->Disconnect("timed_event - SCMD_PHASE_SELECT");

                    if (d)
                    {
                        d->SetPhase(PHASE_SELECT);
                    }
                }
                break;
        }

        return 0;
    }
    else
    {
        ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("%dÃÊ ³²¾Ò½À´Ï´Ù."), info->left_second);
        --info->left_second;
    }

    return PASSES_PER_SEC(1);
}

Bu şekilde kontrollü ve düzgün bir şekilde çıkış işlemi yönetiliyor. Ancak dediğin gibi, loop ile genel olarak da client'ın çöktüğünü normalde anlıyor olması lazım. Ki çoğu zaman da öyle zaten. Ancak ara sıra kafasına göre bu şekilde davranıyor olmasına anlam veremedim. Fırsat buldukça kurcalamaya devam edeceğim bakalım, umarım işe yarar bir şey bulurum bu konuda.
 
Ancak dediğin gibi, loop ile genel olarak da client'ın çöktüğünü normalde anlıyor olması lazım.

Malesef doğrudan anlayamaz, pencereyi kapattınızda, işlemlerden zorla sonlardırdığınızda bile İS bağlantıyı otomatik sonlandırır, ancak crash, veya ani güç kesintisi gibi durumlarda bu durum gerçekleşmez, akış üzerinde yeni veri transferi denenmeden, sunucu akışın durumunda güncelleme yapmaz. Bunun için bir kontrol mekanizması varmı bilmiyorum, teoride ping pong paketlerinin bile aslında bu işi yapması lazım.

Benzer bir sorun oyunla alakası olmayan bir projemde başıma gelmişti, kullanıcı kazara ethernet kablosunu çekmişti, ancak sunucu bağlantıyı hala var zannediyordu ve girişe izin vermiyordu, Fikir vermesi adına söylüyorum, yaptığım çözüm bir döngüyle bağlantı durumlarını yenilemekti.
Ancak oyunda benzer bir mekanizma kurulmasının kaynak tüketimini arttıracağını düşünüyorum. Bir sonraki ping paketi teoride zaten oyuncuyu disconnect etmesi lazım.
 
Son düzenleme:
Malesef doğrudan anlayamaz, pencereyi kapattınızda, işlemlerden zorla sonlardırdığınızda bile İS bağlantıyı otomatik sonlandırır, ancak crash, veya ani güç kesintisi gibi durumlarda bu durum gerçekleşmez, akış üzerinde yeni veri transferi denenmeden, sunucu akışın durumunda güncelleme yapmaz. Bunun için bir kontrol mekanizması varmı bilmiyorum, teoride ping pong paketlerinin bile aslında bu işi yapması lazım.

Benzer bir sorun oyunla alakası olmayan bir projemde başıma gelmişti, kullanıcı kazara ethernet kablosunu çekmişti, ancak sunucu bağlantıyı hala var zannediyordu ve girişe izin vermiyordu, Fikir vermesi adına söylüyorum, yaptığım çözüm bir döngüyle bağlantı durumlarını yenilemekti.
Ancak oyunda benzer bir mekanizma kurulmasının kaynak tüketimini arttıracağını düşünüyorum. Bir sonraki ping paketi teoride zaten oyuncuyu disconnect etmesi lazım.
Teoride söylediklerin doğru fakat Metin2 de tam olarak böyle işlemiyor. Örnek veriyorum, benim oyununda(geliştirme süreci için) F12 tuşu clienti komple resetliyor. Yani pencere aniden tamamen kapanıp tekrar açılıyor ve login ekranı geliyor. Sonrasında giriş yapıyorum ve bağlanıyorum. Bunu sürekli yaparım ve çoğu zaman "hesap zaten bağlı" uyarısı vermeden giriş yapar. Ancak zaman zaman durduk yere "hesap zaten bağlı" demeye başlıyor. Sonra sunucuyu(oyunu) yeniden start veriyorum tekrar deniyorum ve hala aynı. Sırf test için; hem clienti hem de serverı baştan derliyorum ve yine start veriyorum, hala aynı. Sonra FreeBSD yi tamamen kapatıp tekrar makineyi başlatıyorum ve düzelmiş oluyor. İşte bu noktada acaba sorun client-server ilişkisiyle mi alakalı yoksa server-makineyle mi alakalı diye sorguluyorum. Çünkü pek mantıklı ve tutarlı olmayan garip bir durum.
 
Sonra sunucuyu(oyunu) yeniden start veriyorum tekrar deniyorum ve hala aynı.

Yanlış anlamıyorum değil mi, hem sunucuyu hem clienti kapatıyorsunuz ancak yinede hesap zaten bağlı diyor? Bu bağlantı kaynaklı görünmüyor.
Logintoken(dwLoginKey)'ın depolanması/silinmesi ile alakalı bir sorun gibi görünüyor, ancak nerede nasıl depolandığına daha önce bakmadım.

Ek: Biraz baktımda, galiba auth, tüm corelar ve db de dahil hepsi tokenlar için kendi listelerini tutuyor ve verileri kendi aralarında senkronize ediyorlar, tekrar sorun yaşadığınızda bsd yerine hepsinin kapandığından emin olup tekrar başlatmayı denermisiniz?
Son ek: biraz daha uğraştımda bir türlü hata yaptıramadım. benim çıkarımım biri hatalı şekilde token tutuyor, diğerlerine senkronize ediyor.

Sonuç olarak: hepsinin ardarda kapanıp ardarda açıldığından emin olun demeyle yetineceğim. Yine çözülmüyorsa artık bsdnin vardır bir kerameti diyelim.
 
Son düzenleme:
Yanlış anlamıyorum değil mi, hem sunucuyu hem clienti kapatıyorsunuz ancak yinede hesap zaten bağlı diyor?
Şöyle anlatayım:

1.Senaryo(Sorunlu):
FreeBSD'yi başlatıyorum ve oyuna start veriyorum.
Oyuna giriş yapıyorum.
Sonra python taraflı kasıtlı olarak crash verdiğimde veya F12 ile clienti direkt yeniden başlattığımda tekrar giriş yapmaya çalışıyorum ve "Hesap zaten bağlı" diyor.
Bu esnada eğer iki tane client açıksa, crash yiyen karakter diğer açık olan clientte görünmeye devam ediyor ama PM falan atılmıyor. "Oyunda değil" diyor.
Sonra, FreeBSD açıkken; serverı ve clienti belki güncel olmayan .obj dosyalarındandan dolayı karışıklık oluşuyordur diye komple baştan derledim. Ve yine freebsd açıkken terminal ile oyuna stop çekiyorum ve tekrar start veriyorum. Bu noktadan sonra da değişen bir şey olmuyor ve ilk maddeden itibaren aynı şey yaşanmaya devam ediyor. Finalde FreeBSD'yi tamamen komple kapatıp tekrar açtığımda sorun gitmiş oluyor. (Random bir şekilde...)

2.Senaryo(Sorunsuz):
FreeBSD'yi başlatıyorum ve oyuna start veriyorum.
Oyuna giriş yapıyorum.
Sonra python taraflı kasıtlı olarak crash verdiğimde veya F12 ile clienti direkt yeniden başlattığımda tekrar giriş yapmaya çalışıyorum ve sorunsuz bir şekilde giriş yapıyor. Bunu üst üste denediğimde de sorunsuz bir şekilde giriş yapıyor. Defalarca denememe rağmen her defasında başarıyla giriş yapılıyor.

Dün itibari ile şu aklıma geldi; uzun zamandır server tarafında köklü bir değişiklik yapmadım çoğunlukla client tarafında çalışmalar yürütüyorum. Birkaç ay önce server tarafında yaptığım tek düzenleme Mysql 5.6'dan 8.0'a geçmek oldu. Bunun bir etkisi olup olmadığı konusunda emin değilim. 1.Senaryoyu tekrar yaşadığımda freebsdyi kapatmadan sadece mysql'e restart çekeceğim. Eğer işe yararsa bu ihtimali biraz daha güçlendirecektir sanırım.
 
socket_timeout diye bir şey var, 10 saniyeye ayarlı. server - client arasında genelde sürekli iletişim oluyor. olası bir iletişim kesintisinde 10 saniye sonra recv veya send fonksiyonu false döneceği için serverin soketi kapatması gerekir ( DESC::ProcessInput )
 
socket_timeout diye bir şey var, 10 saniyeye ayarlı. server - client arasında genelde sürekli iletişim oluyor. olası bir iletişim kesintisinde 10 saniye sonra recv veya send fonksiyonu false döneceği için serverin soketi kapatması gerekir ( DESC::ProcessInput )
Evet doğru, ama yanlış bilmiyorsam bu bahsettiğin olay sadece server tarafında bu şekilde işliyor. Yani mesela bir oyuncunun aniden internet bağlantısı kesildiğinde veya elektriği gittiğinde(Ki bu da aynı zamanda ani internet kesintisi demek..) bahsettiğin süre boyunca oyunda aktif görünebilir ve sonrasında diğer oyuncuların gözünden oyundan düşebilir.

Ama burada internetin değil de clientin aniden kapanması durumunda serverın bunu bazen algılayıp bazen algılamaması kafa karıştırıcı.
Bende de tam olarak olan bu aslında. Çoğu zaman sorun yok. Ama bazen bu bahsettiğim olayın aynısı yaşanıyor, "hesap zaten bağlı" dedikten birkaç saniye sonra karakter düşüyor ve ancak öyle giriş yapabiliyorum. Ve açıkçası bu standart bir sorun mu yoksa benden mi kaynaklı ondan da emin değilim. Ağ gecikmesi vb. şeyleri düşünüyorum ancak çok düşük bir ihtimal gibi geliyor bana, çünkü tamamen internetsiz ve sabit bir IP üzerinde çalışıyorum.
 
@Kaiser
Tekrar gözden geçirelim,
hatanın birinci durumu, ani çıkış sonrasında serverin disconnect etmemesi,
ikisinci durumu, servere stop/start yapsanız dahi hala hesap zaten bağlı demesi.

birinci durumu çözmek için yapabilecek pek birşeyimiz yok,
zira soketten veri okumak gerekir bağlantı durumunu yenileyebilmek için, bunu yapabileceğimiz uygun yer bir ihtimal void DESC::Packet içinde,
if (m_iPhase != PHASE_CLOSE)
fdwatch_add_fd(m_lpFdw, m_sock, this, FDW_WRITE, true);
bloğu üstü, zero read eklemeyi denemek olacaktır
char buf;
int ret = recv(m_sock, &buf, 1, MSG_PEEK);
if (ret <= 0) {
// Kapalı
SetPhase(PHASE_CLOSE);
}

(EKLEMEYİN, akış bozulur,referans, modifikasyon gerektirir) ancak sorun şu ki bağlantı gerçekten varsa ve bu esnada client veri gönderiyorsa akış bozulacaktır. bu yüzden bu kontrolü ekleyemeyiz.

2. socket_read içinde
if (errno == EWOULDBLOCK)
return (0);
ve
if (wsa_error == WSAEWOULDBLOCK) {
return 0;
}
içinde 0 yerine -1 döndürüp çağırılan her yerde -1 ise phase close(*zaten ekliymiş) belki denenebilir. Ancak bu durumdada, sadece ölü bağlantılar için değil anlık hatalardada(ki olağan) disconnect atacaktır. (örneğin anlık packet loss) Kısaca biraz hataya sıfır toleransı olacaktır, bu yüzden yine eklenemez duruyor. (Yinede test edilebilir)
EK:
ilk seçenekte yapılanı ikinciye uyarlamak,
socket_read içinde
if (ret == 0)
return -1;
bloğunu
if (ret <= 0)
return -1;
ile değiştirmekte denenebilir.(Edit: alttaki bloklar erişilemez olur, her hatada disconnect, yine test edilmedi, tavsiye etmiyorum)

Son seçenekse, Oyunda zaten soket durumlarını select ile yenilemeye çalışan bir mekanizma bulunuyor, ancak kullanılışından dolayı anlaşılan düzgün çalışmıyor.(yani disconnect etmiyor)
(int fdwatch içinde
if (r == -1)
return -1;

Üstünde uğraşılabilir. size kalmış. Bana kalırsa bunu boşverin, biraz denge meselesine dönüyor iş. (Ek: *Tam bir baş belası, bunu algılamak çok zor.)

ikinci durum için olası nedenler anladığım kadarı ile şu:
Gördüğüm kadarıyla 40250 SRC üzerinde sql üzerinde tokenla alakalı bir şey yok. Sqlden olacağını sanmıyorum. BSD kapatmadan tokenın silinmesi için tüm core/auth/db nin durmuş olması yeterli, BSD kapanınca hepsi mecburen kapanıyor ve bellekteki listeleri sıfırlanıyor. ancak bu durumda stop/starttan sonra mutlaka önceki başlatmadan kalan kapanmamış bir core kalıyor olmalı.
Bir ihtimal iki kez çalıştırılmış bir corede olabilir. stop verildiğinde biri kapanır diğeri açık kalır, her ne kadar aktif olamasada p2p üzerinden diğerlerine senkronize deneyebilir ki tokenları bozmaya yeter.
 
Son düzenleme:
Geri
Üst