Python ile Socket Programlama 3

Python ile One to One Chat(1’e 1 mesajlaşma uygulaması) yapmak isterseniz Socketleri nasıl yöneteceğim ? Bir kullanıcıdan gelen mesajı nasıl diğerine aktaracağım ? gibi sorular sorabilirsiniz. Python veya herhangi bir dil için bunları cevaplar yok denecek kadar az.

Python Socket 1’e 1 Mesajlaşma Örneği(Thread ile)

1’e 1 mesajlaşma yapabilmemiz için kullanıcının ve kullanıcının mesaj attığı kişinin servera bağlı olması gerekir. Bağlı olmasının yanında bu kişilerin socket bağlantılarının bir yerde depolamamamız gerekir.

Biz burada Basit bir şekilde 2 kullanıcının birbiri ile haberleşmesini sağlayacağız. Basit bir şekilde olacağından dolayı gerçek kullanımlar için bir çok kontrol yapısı eklenmesi gerekir.

Not: Bu örnekte string ve seperatorleri kullanarak haberleşmeyi sağladık. Daha iyi bir kullanım ve kontrol için json kullanabilirsiniz.

# server.py
import socket
import threading

HEADER = 64
IP = socket.gethostbyname(socket.gethostname())
PORT = 9999
ADDR = (IP, PORT)
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "DISCONNECT_SERVER_CODE"
USERNAME_MESSAGE = "EXAMPLE_APP_USERNAME_FIELD"
SEP = "///**//"
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(ADDR)

users = []


def send_message(to_username, msg):
    for user in users:
        if user['username'] == to_username:
            connection = user["connection"]
            msg = msg.encode(FORMAT)
            msg_lenth = str(len(msg)).encode(FORMAT)
            header = msg_lenth + b' ' * (HEADER - len(msg_lenth))
            connection.send(header)
            connection.send(msg)
            break
    # we can add if check for if there is no user named like client want


def delete_connection(username):
    for (idx, user) in enumerate(users):
        if user['username'] == username:
            del users[idx]


def handle_client(conn, addr):
    print("[New Connection] {addr} connected...")

    connected = True
    while connected:
        max_lenth = conn.recv(HEADER).decode(FORMAT)
        if max_lenth:
            max_lenth = int(max_lenth)
            msg = conn.recv(max_lenth).decode(FORMAT)
            write_active_connections()
            messages = msg.split(SEP)
            username = messages[0]
            print(f"{users}")
            if USERNAME_MESSAGE in msg:
                # first connect write to users list
                data = {
                    "username": username,
                    "connection": conn
                }
                users.append(data)
                continue

            if DISCONNECT_MESSAGE in msg:
                # connection closed delete from user list
                print("[Disconnect] Disconnecting from server")
                delete_connection(username)
                connected = False
            if len(messages) > 2:
                # one to one messagging
                print(f"[{addr}] message will send -> {msg}")
                to_username = messages[1]
                send_message(to_username, msg)

    conn.close()
    write_active_connections()


def write_active_connections():
    print(f"Active Connection Count is {threading.activeCount()}")


def start():
    server.listen()
    print(f"[Listening] Server is Listening now on {IP}")
    while True:
        conn, addr = server.accept()
        thread = threading.Thread(target=handle_client, args=(conn, addr))
        thread.start()
        write_active_connections()


if __name__ == "__main__":
    print("[Starting] Socket Server is starting... Stand By")
    start()

Server kodunu incelediğimizde server’a connect olunduğunda username ve connection bilgisi içeren bir dict objesini users listesine atıyoruz bu sayede server’a connect olan herkes birbirine mesaj atabilecek.

Kodumuzda özel bir ayırma işlemini gerçekleştireceğimiz random sayılabilecek karakterler var client tarafı bilgileri gönderirken bu SEP(bölmek için gereken karakterler) ile ayırıyor.

gonderen{SEP}gonderdigi_kisi{SEP}mesaj

Gelen bilgileri ve tasarımlara göre bilgiyi parçalayıp kimden geldiğini kime gideceğini ve mesajı ayırabiliyoruz. Bu tasarımları kendinize göre yapabilirsiniz.

send_message fonksiyonunda users listesi içerisinden gönderilecek user’in socket bağlantısı bulunuyor ve mesaj ilk geldiği gibi user’a gönderiliyor.

# client.py
import socket
from threading import Thread

HEADER = 64
PORT = 9999
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "DISCONNECT_SERVER_CODE"
USERNAME_MESSAGE = "EXAMPLE_APP_USERNAME_FIELD"
SEP = "///**//"
IP = socket.gethostbyname(socket.gethostname())
ADDR = (IP, PORT)

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(ADDR)


def send(msg):
    message = msg.encode(FORMAT)
    msg_lenth = str(len(message)).encode(FORMAT)
    header = msg_lenth + b' ' * (HEADER - len(msg_lenth))
    client.send(header)
    client.send(message)


def main(username, to_username):
    # send(f"{username}{SEP}{DISCONNECT_MESSAGE}")
    while True:
        message = input("")
        if message == "disconnect":
            send(f"{username}{SEP}{DISCONNECT_MESSAGE}")
        send(f"{username}{SEP}{to_username}{SEP}{message}")


def listen(username):
    # should check for main thread if it closed we need to terminate listen thread.
    connected = True
    while connected:
        max_lenth = client.recv(HEADER).decode(FORMAT)
        if max_lenth:
            max_lenth = int(max_lenth)
            msg = client.recv(max_lenth).decode(FORMAT)
            messages = msg.split(SEP)
            if len(messages) > 2:
                if messages[1] == username:
                    print(f"\n {messages[0]} -->  {messages[2]} \n")
                else:
                    print("security issue check server.py ")


if __name__ == "__main__":
    username = input("Please write your user name: ")
    if username:
        send(f"{username}{SEP}{USERNAME_MESSAGE}")
        to_username = input(f"who you will send message: ")
        print("you can write your message and press enter")
        main_thread = Thread(target=main, args=(username, to_username,))
        listen_thread = Thread(target=listen,  args=(username,))
        main_thread.daemon = True
        main_thread.start()
        listen_thread.start()

Client kodunu incelediğimizde 2 thread ile çalışmakta olduğunu görüyoruz. İlk thread Kullanıcıdan aldığı inputlari server’a gönderirken 2. thread serverdan gelen mesajları dinliyor. Threadler başlamadan önce kullanıcıya username ve kime mesaj atmak istediği soruluyor. Basit bir örnek olduğunu için bunları kendimiz ayarlıyoruz uygulama yapmak isteseydik online kullanıcıların listelenmesi gerekirdi.

Client tarafında da ayni SEP karakterlerini kullanarak gelen mesajı ayırıyoruz ve kimden geldiğini ve mesaj bilgisini konsola yazdırıyoruz.

Mesaj gönderirken ise tekrar kullanıcı, gönderdiği kişi ve mesaj SEP ile birleştirilerek gönderiliyor.

Server ve client kodlarını çalıştırdığımızda 2 kullanıcının birbiri ile haberleşebildiğini göreceğiz.

Kaynak Kod

Kod Örneği GitHub FurkanOzkaya

Bir sonraki blog yazımda bu kodu AsyncIO ile yazmaya çalışacağız.

3 Comments

Add a Comment

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir