Python Socket Ve Threading Kaynak Bilen Var mı?

Artık büyük boyutlu dosyalar da gönderilebilir. Kodlar güncellenmiştir.

Merhabalar,

Öncelikle, son derece yakışıklı duruyor yazdığınız örnek fakat bir server’in yazdığınız gibi çalışmaması gerektiğini düşünüyorum.

Server sadece clientler arasındaki iletişimi düzenliyor olmalı. Aşağıda yazdığım basit server örneğini incelerseniz, ne demek istediğimi daha iyi anlarsınız. (Bir client yazmaya gerek duymadım, mantık anlaşılıyor diye düşünüyorum.)

Link: http://dpaste.com/2ZJ2FMY.txt

#!/usr/bin/env python3
#-*- coding: utf-8 -*-
#
#

__author__ = '@r0ark'

import os, sys, struct
import socket, threading, base64

def log(msg):
    sys.stdout.write('[%s] - %s\n' % (time.ctime(), msg))
    sys.stdout.flush()

def send(conn, data):
    if type(data) != bytes:
        data = bytes(data, 'utf-8')

    data = struct.pack('>I', len(data)) + data
    conn.sendall(data)

def _recv(conn, buff):
    data = b''

    while len(data) < buff:
        packet = conn.recv(buff - len(data))

        if not packet:
            break

        data += packet

    return data

def recv(conn):
    data_len = _recv(conn, 4)

    if not data_len:
        return None

    data_len = struct.unpack('>I', data_len)[0]
    return _recv(conn, data_len)

class Connection:
    def __init__(self, conn, addr):
        self.conn
        self.addr

class Server:
    clients = []
    
    def __init__(self, host='0.0.0.0', port=8000):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        self.socket.bind((host, port))
        self.socket.listen(10)

    def send_all(self, message):
        for client in self.clients:
            try:
                send(client.conn, message)
            except Exception as err:
                log('Error: ' + err.message)

    def handle_client(self, connection):
        while True:
            message = recv(connection.conn)

            if message:
                message = ': '.join(connection.addr[0], message)
                self.send_all(message)

    def serve(self):
        while True:
            conn, addr = self.socket.accept()
            log('New connection -> ' + addr[0])
            
            connection = Connection(conn, addr)
            self.clients.append(connection)

            threading.Thread(self.handle_client, args=(connection, )).start()

if __name__ = '__main__':
    server = Server()
    server.serve()

Şu an group chat gibi bir şey ama tabii ki odalar, private kısımlar vs bir çok özellik eklenebilir. Bu arada, bağlanan client için Connection adında bir sınıf tanımlamak işi daha da kolaylaştırabilir. Özellikle açık anahtarlı şifreleme kullanacak olursak.

Merhaba, katılımınız için teşekkür ederim. Dediğiniz gibi bir sunucunun işlevi istemciler arasındaki iletişimi düzenliyor olmalı. Yalnız şu ana kadar sunucuyu bahsettiğiniz gibi ayarlamak için fırsat olmadı. Açıkçası şu an nasıl yapacağımı da bilmiyorum. Biraz araştırmam lazım.

Edit: Az önce bahsettiğiniz konuyla alakalı bir örnek yapıyordum. Sunucu herhangi bir istemciden gelen mesajları, gelir gelmez diğer istemcilere gönderdi. Neyse uygulamayı biraz geliştirmeye çalışayım. Kodlar aşağıdadır:

server.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import socket
import threading

DATA = ""


class Server(socket.socket):
    clients = []

    def __init__(self):
        socket.socket.__init__(self, socket.AF_INET, socket.SOCK_STREAM)
        self.conn = None

    def __connect__(self):
        self.bind(("", 12346))
        self.listen(10)

    def __accept__(self):
        try:
            conn, addr = self.accept()
            self.conn = conn
            self.clients.append(conn)
            print('{} connected.'.format(addr))
        except OSError:
            pass

    def __receive__(self):
        global DATA
        if self.conn is not None:
            data = str(self.conn.recv(1024))[1:]
            DATA = data
            if len(data) == 2:
                pass
            else:
                print("\n{}".format(data[1:-1]), flush=True)

    def __send__(self):
        global DATA
        for i in self.clients:
            try:
                i.send(bytes(DATA.encode("utf-8")))
            except BrokenPipeError:
                print("\nClient has been disconnected\n")
                self.clients.remove(i)
        DATA = ""


server = Server()
thread_connect = threading.Thread(target=server.__connect__)
thread_connect.start()
while True:
    thread_accept = threading.Thread(target=server.__accept__)
    thread_accept.daemon = True
    thread_accept.start()
    thread_accept.join(1)
    thread_receive = threading.Thread(target=server.__receive__)
    thread_receive.daemon = True
    thread_receive.start()
    thread_receive.join(1)
    thread_send = threading.Thread(target=server.__send__)
    thread_send.daemon = True
    thread_send.start()
    thread_send.join(1)

client.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import socket
import threading

nickname = input("Nickname: ")


class Client(socket.socket):
    def __init__(self):
        socket.socket.__init__(self, socket.AF_INET, socket.SOCK_STREAM)

    def __connect__(self):
        self.connect(("", 12346))

    def __receive__(self):
        try:
            data = str(self.recv(1024))[1:]
            if len(data) == 2:
                pass
            else:
                print(data[2:-2])
        except OSError:
            pass

    def __send__(self):
        respond = input()
        self.sendall(bytes("<{}>: {}".format(nickname, respond).encode("utf-8")))


client = Client()
thread_connect = threading.Thread(target=client.__connect__)
thread_connect.start()
while True:
    thread_receive = threading.Thread(target=client.__receive__)
    thread_receive.start()
    thread_send = threading.Thread(target=client.__send__)
    thread_send.daemon = True
    thread_send.start()
    thread_send.join(1)

Vaktim olmadığı için bir client yazamadım ama bahsettiğim olay, şu projede uygulanmış.

1 Beğeni

Bu çalışmaya da bakacağım. Teşekkür ederim.

Uygulamayı indirip çalıştırdım, mantığını anlamak için programı biraz kurcalıyordum da:
Mesela:

<join> oda_ismi

yazarak bir odaya girdim.

Daha sonra başka bir istemciyi sunucuya bağladım.

<list>

yazdığımda açık olan odalar ekrana yazdırıldı…
Ama önce başka bir oda daha oluşturayım dedim.

<join> yeni_oda

yazdım. Ve bu odadayken de

<join> oda_ismi

yazarak odadan odaya geçiş yaptım.
Kullanıcıların mesajlaşabilmesi için bir odaya girmesi gerekiyor. Bazı komutlar yardımıyla odalar oluşturmayı veya odalardan çıkmayı ve ancak bu sayede istemciler arasında iletişim kurulmasını sağlayan bir program oluşturmayı deneyeceğim. Sizinle yukarıda paylaştığım son program ise, herhangi bir odaya girmeden, istemcilerin birbirleriyle mesajlaşmasını sağlıyor.
Tekrar teşekkür ederim.

Hocam İyi Güzel Yapmışsınız Hepinizin Ellerine Sağlıkta Bu Örnekler Beni Aşar :smiley: Anlaması 10 Yılımı Alır Zaten :smiley:

Her gün bir kaç saatinizi ayırabilirseniz, daha kısa sürelerde beni aşar dediğiniz örnekleri rahat anlayabilirsiniz. Sadece biraz ilgi istiyor. :slight_smile:

Hocam İyi Güzelde Thread Ve Socket Falan Hakkında Hiç Bilgim Yok Kaynak Bulamadım Okuyamadım O Yüzden Aşar :slight_smile: Fakat Kaynak Bulabilirsem Tabiiki İncelerim.

Kaynak mı bulamadınız? Nasıl bulamadınız anlamıyorum. Google’a “threading python türkçe” yazdığınızda karşınıza inceleyebileceğiniz en az 5, 6 sayfa çıkar zaten. Aynı şekilde socket’i de aratsanız yine kaynak bulursunuz. Üstelik bu forumda hatta bu başlıkta en basitinden socket örnekleri mevcut daha da basiti yoktur bence. Yani yapmanız gereken okuduğunuz kodları incelemek, “o ne işe yarıyormuş acaba” diye merak edip sağını solunu değiştirmek ve sonuçları gözlemlemek.

1 Beğeni

@r0ark
Dediğiniz gibi sunucuyu sadece istemciler arasındaki mesajlaşmayı ayarlayacak şekilde bir program yazdım. İstemciler mesajlaşabilmek için chat odalarına girmek zorundalar, yoksa mesajlaşamıyorlar. Paylaştığınız örnek programda kullanıcı komutları girmek için <komut> yazıyordu, bu çalışmada /komut şeklinde değiştirildi. Ama işlev olarak diğer programa benziyor diyebilirim. Program henüz yeni bitti sayılır bu yüzden hatalar olabilir, tespit ettiğiniz hataları paylaşırsanız sevinirim.

Kodlar:

server.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import re
import socket
import threading

DATA = ""
NICKNAME = ""
ROOMS = []
NICKNAMES = []
ROOMS_NICKNAMES = []


class Server(socket.socket):
    clients = []

    def __init__(self):
        socket.socket.__init__(self, socket.AF_INET, socket.SOCK_STREAM)

    def __connect__(self):
        self.bind(("", 12346))
        self.listen(10)
        print("Waiting for clients...")

    def __accept__(self):
        try:
            conn, addr = self.accept()
            self.clients.append(conn)
            print('{} connected.'.format(addr))
        except OSError:
            pass

    def __receive__(self):
        for i in self.clients:

            def receive():
                global DATA, NICKNAME
                data = str(i.recv(1024))[1:]
                DATA = data
                if len(data) == 2:
                    pass
                else:
                    if ":nickname:" in DATA:
                        nickname = DATA[len(" :nickname: "):-1]
                        if nickname not in NICKNAMES:
                            NICKNAMES.append(nickname)
                        else:
                            NICKNAMES.append(nickname)
                            message = "This nickname is used."
                            self.clients[NICKNAMES.index(nickname, 1)].send(bytes(message.encode("utf-8")))
                            NICKNAMES.remove(NICKNAMES[NICKNAMES.index(nickname, 1)])
                        DATA = ""
                    elif ":room:" in DATA:
                        room, NICKNAME = DATA[8:-1].split(" ")
                        ROOMS_NICKNAMES.append(room)
                        ROOMS_NICKNAMES.append(NICKNAME)
                        if room not in ROOMS:
                            ROOMS.append(room)
                        DATA = ""
                        NICKNAME = ""
                    elif ":list:" in DATA:
                        self.clients[NICKNAMES.index(DATA[len(" :list: "):-1])].send(
                            bytes("Room: {}".format(", ".join(ROOMS)).encode("utf-8")))
                        DATA = ""

            receive_thread = threading.Thread(target=receive)
            receive_thread.start()

    def __send__(self):
        global DATA, NICKNAME
        if ".:!:." in DATA:
            regex = "".join(re.findall("\.:!:\..+\.:!:\.", DATA))
            NICKNAME = regex.replace(".:!:.", "")
            DATA = DATA.replace(regex, "")
            try:
                chat_groups = []
                for x, y in enumerate(ROOMS):
                    group = []
                    for i, j in enumerate(ROOMS_NICKNAMES):
                        if j == ROOMS[x]:
                            group.append(ROOMS_NICKNAMES[i + 1])
                    if group not in chat_groups:
                        chat_groups.append(group)
                for i in chat_groups:
                    if NICKNAME in i:
                        for j in i:
                            self.clients[NICKNAMES.index(j)].send(bytes(DATA.encode("utf-8")))
            except ValueError:
                pass
        DATA = ""


server = Server()
thread_connect = threading.Thread(target=server.__connect__)
thread_connect.start()
while True:
    thread_accept = threading.Thread(target=server.__accept__)
    thread_accept.daemon = True
    thread_accept.start()
    thread_accept.join(1)
    thread_receive = threading.Thread(target=server.__receive__)
    thread_receive.start()
    thread_send = threading.Thread(target=server.__send__)
    thread_send.daemon = True
    thread_send.start()
    thread_send.join(1)

client.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import time
from datetime import datetime
import socket
import threading

DATA = ""
NICKNAME = ""
ROOM = ""


class Client(socket.socket):
    def __init__(self):
        socket.socket.__init__(self, socket.AF_INET, socket.SOCK_STREAM)

    def __connect__(self):
        self.connect(("", 12346))
        print("| {} | Welcome to the chat program.\n".format(str(datetime.now())[:-7]))
        print("Commands:\n\t/nick <new_nickname>\n\t/join <room_name>\n\t/list\n\t/quit\n".expandtabs(4))

    def __receive__(self):
        global DATA
        try:
            data = str(self.recv(1024))[1:]
            if len(data) == 2:
                pass
            else:
                if "Room:" in data:
                    for i in data[6:-1].split(","):
                        print("\t{}".expandtabs(4).format(i[1:]))
                elif "This nickname is used." in data:
                    print("| {} | {}".format(str(datetime.now())[:-7], data[1:-1]))
                    DATA = data[1:-1]
                else:
                    print(data[2:-2])
        except OSError:
            pass

    def __send__(self):
        global NICKNAME, ROOM, DATA
        respond = input()
        if "/nick" in respond:
            if respond == "/nick":
                print("| {} | You must create a nickname.".format(str(datetime.now())[:-7]))
            else:
                NICKNAME = respond[6:]
                if " " in NICKNAME or NICKNAME == "":
                    print("| {} | Do not use space in your nickname.".format(str(datetime.now())[:-7]))
                else:
                    self.sendall(bytes(":nickname: {}".format(NICKNAME).encode("utf-8")))
                    time.sleep(0.1)
                    if "This nickname is used." not in DATA:
                        print("| {} | Your nickname has changed to {}.".format(str(datetime.now())[:-7], NICKNAME))
                    else:
                        NICKNAME = ""
            DATA = ""
        elif "/join" in respond:
            if respond == "/join":
                print("| {} | You must write a room.".format(str(datetime.now())[:-7]))
            else:
                ROOM = respond[6:]
                if " " in ROOM or ROOM == "":
                    print("| {} | Do not use space in room name.".format(str(datetime.now())[:-7]))
                else:
                    if NICKNAME != "":
                        self.sendall(bytes(":room: {} {}".format(ROOM, NICKNAME).encode("utf-8")))
                        print("| {} | Joined {}.".format(str(datetime.now())[:-7], ROOM))
                    else:
                        print("| {} | You must create a nickname.".format(str(datetime.now())[:-7]))
        elif "/list" in respond:
            if NICKNAME != "":
                self.sendall(bytes(":list: {}".format(NICKNAME).encode("utf-8")))
                print("| {} | Available Rooms:".format(str(datetime.now())[:-7]))
            else:
                print("| {} | You must create a nickname.".format(str(datetime.now())[:-7]))
        elif "/quit" in respond:
            import os
            import signal
            os.kill(os.getpid(), signal.SIGKILL)
        else:
            if NICKNAME != "":
                if ROOM != "":
                    self.sendall(bytes(".:!:.{}.:!:.{}: {}".format(NICKNAME, NICKNAME, respond).encode("utf-8")))
                else:
                    print("| {} | Join a room.".format(str(datetime.now())[:-7]))
            else:
                print("| {} | Create a nickname.".format(str(datetime.now())[:-7]))


client = Client()
thread_connect = threading.Thread(target=client.__connect__)
thread_connect.start()
while True:
    thread_receive = threading.Thread(target=client.__receive__)
    thread_receive.start()
    thread_send = threading.Thread(target=client.__send__)
    thread_send.daemon = True
    thread_send.start()
    thread_send.join(1)

bir şey soracaktım bu ne işe yarıyor :smiley:
ve client ile serveri nasıl bağlayabilirim ikisinide ayrı ayrı açtıktan sonra nasıl birbirine bağlayacam?

Bu, istemcilerin birbirleriyle mesajlaşmasına yarıyor.
Client ve serveri şöyle bağlayabilirsiniz:

  1. Önce server.py’yi çalıştırırsınız.
  2. Sonra başka bir oturumda client.py’yi çalıştırırsınız.
  3. client.py’yi çalıştırdıktan sonra ekranda gözüken yönergelere bakarsınız. Orada komutlar “Commands:” adı altında listelenmiştir. Bu listelenmiş komutlardan olan /nick komutuyla bir nick belirtirsiniz:
/nick tekbir
  1. sonra ya bir odaya girersiniz.
/join herhangi_bir_oda
  1. ya da mevcut bir oda var mı ona bakarsınız:
/list
  1. Sonra başka bir oturumda bir tane daha client.py açıp aynı sunucuya bağlanırsınız.
  2. Bu istemciyle daha önce bağlanan istemcinin mesajlaşmasını istiyorsanız, daha önce bağlanan istemcinin girdiği odaya girersiniz.
# tabi önce nick seçmeniz gerekir.
/nick yeni_nick
# sonra odayı yazarsınız
/join herhangi_bir_oda
  1. Ve artık bu iki istemci birbirleriyle yazışabilirler.

Bu programdaki sunucunun görevi istemciler arasındaki iletişimi düzenlemektir.
Anlattıklarım sizin kendi bilgisayarınızda yapabileceğiniz şeyler. Bu tarz programlarla bir çok kişiyle chat yapılabilir veya karşı tarafa dosyalar gönderebilirsiniz.

1 Beğeni

Hataları tespit için unittest modülünü kullanmaya ne dersiniz?

1 Beğeni

Olur, neden olmasın. Teşekkür ederim. :slight_smile:

1 Beğeni

Bir hatayla karşılaşmadım.

Yalnız bu programın biraz geliştirilmesi gerekiyor. Bir istemci sunucuya bağlandıktan sonra, sunucuda tanımlanmış olan listelere istemcinin girmiş olduğu kullanıcı adı ve oda ismi kaydedilir. İstemci sunucudan çıktığı zaman istemcinin açtığı odanın ismi ve seçmiş olduğu kullanıcı adının bu listelerden silinmesi gerekiyor.

İstemcilerin daha önceden oluşturmuş oldukları kullanıcı adı ve oda isimleri, istemciler sunucuyu terk eder etmez artık siliniyor.
Kodlar:
https://github.com/dildeolupbiten/A-console-chat-program-with-chat-rooms/blob/master/server.py
https://github.com/dildeolupbiten/A-console-chat-program-with-chat-rooms/blob/master/client.py

Edit: Sanırım bir hata var. Bu sefer de mesaj gönderme işlemi engelleniyor.
Edit: Mesaj gönderme işleminin engellenmesi sorunu çözüldü.

1 Beğeni

Her programda bir bug vardır. Siz işinizi riske atmayın. Testlerinizi otomatikleştirin. :slight_smile:

1 Beğeni

Mesela programda şöyle bir bug tespit ettim. Diyelim istemci birden fazla odaya girdi, o zaman yazdığı mesaj girdiği oda sayısı kadar kendisine gönderiliyor. Şimdi düşünüyorum da yazdığım kodlara göre beklenmesi gereken bir sonuç bu. O zaman sonucun böyle olacağını düşünememiştim. Denemelerimi de hep tek bir oda üzerinden yaptım. Aynı odaya girmiş istemciler mesajlaşabiliyorlarsa sorun yoktur diye düşünüyordum. Ama farklı ihtimallerin de gerçekleşip gerçekleşmeyeceğini görmek için denemeler yapınca, başka bir sorun daha açığa çıktı. :smiley: Neyse bir ara bu sorunla ilgileneyim.

Edit: Bu sorun da çözülmüştür. Artık istemci istediği kadar odaya girsin, gönderdiği mesajlar sadece bir kere kendi ekranına yazdırılır. Yukarıdaki kodlar yenilenmiştir.