Python socket server üzerinden client-client iletişimi

Öncelikle herkese iyi forumlar.
Socket kütüphanesi ile server-client arası iletişim kurabiliyor ve mesaj yollayabiliyorum ama
client-client arası mesaj yollayabilirmiyim ?
Şimdiden teşekkürler.

Zaten sunucunun asıl görevi istemciler arasındaki mesaj trafiğini düzenlemektir. Sunucu, istemcilerden gelen mesajları alır ve onları gönderilmesi istenen istemcilere gönderir.

1 Beğeni

Örnek olarak web sitesi veya kod paylaşırmısınız ?

tam olarak neyin web sitesi ?

Socket server-client iletişimi örnekleri bulunan bir web sitesi.

bu kanaldaki dersler arasında mesajlaşma ve oyun programlanmış eğitimler var. socket client ile alakalı. Göz atabilirsiniz. Oynatma listelerinde mevcut

video değil kod metinleri okumak istiyorum diyorsanız. Eğitimlerini yazılı olarak paylaştığı web sitesi de mevcut;

https://techwithtim.net/

1 Beğeni

Sunucu soketlerin (yani belli bir adrese bind edilmiş soketlerin) tek görevi gelen bağlantıları kabul etmek (socket.accept) ve bu bağlantıyı temsil eden bir soket döndürmek. Daha sonra bu soket üzerinden bağlantı kurulan istemci ile haberleşebilirsiniz.

Sizin buradaki server ve client’ten kastınız soketlerin kullanım şeklinden çok cihazlar ile alakalı sanırım. Sunucu cihaz kendisine bağlanan cihazların arasındaki veri trafiğini yönetebilir, mesela A istemcisinden gelen bir veriyi B istemcisine gönderebilir. Bu bir yöntem.

Diğer bir yöntem de sunucu cihazı diğer istemcileri bulmak için kullanmak, mesela sunucu kendisine bağlanan iki istemcinin IP adreslerini karşılıklı olarak onlara geri yollarsa bu iki istemci kendi aralarında bağlantı kurabilir.

2 Beğeni

Eskiden yazdığım, iyileştirilmesi gereken basit bir uygulama var, onu paylaşabilirim.

server.py

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

import re
import time
import socket
import threading

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

while True:
    try:
        PORT = int(input("PORT: "))
        break
    except ValueError:
        print("Port value is invalid.")


def exit_program():
    import os
    if os.name == "posix":
        import signal
        os.kill(os.getpid(), signal.SIGKILL)
    elif os.name == "nt":
        os.system("TASKKILL /F /PID {}".format(os.getpid()))


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

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

    def connect_server(self):
        try:
            self.bind(("0.0.0.0", PORT))
            self.listen(10)
            print("Waiting for clients...")
        except PermissionError:
            print("Permission denied.")
            exit_program()
        except OverflowError:
            print("Port must be 0-65535.")
            exit_program()

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

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

            def receive():
                global DATA, NICKNAME
                try:
                    data = i.recv(1024).decode("utf-8")
                    DATA = data
                    if len(data) == 0:
                        pass
                    else:
                        if ":nickname:" in DATA:
                            nick = DATA[len(":nickname: "):]
                            if nick not in NICKNAMES:
                                NICKNAMES.append(nick)
                            else:
                                NICKNAMES.append(nick)
                                message = "This nickname is used."
                                self.clients[
                                    NICKNAMES.index(nick, 1)
                                ].send(bytes(message.encode("utf-8")))
                                NICKNAMES.remove(
                                    NICKNAMES[NICKNAMES.index(nick, 1)]
                                )
                            DATA = ""
                        elif ":room:" in DATA:
                            room, NICKNAME = DATA[7:].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: "):])
                            ].send(
                                f"Room: {', '.join(ROOMS)}"
                                .encode("utf-8")
                            )
                            DATA = ""
                except ConnectionResetError:
                    pass

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

    def send_data(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)
                count = 0
                for i in chat_groups:
                    if NICKNAME in i:
                        count += 1
                        if count > 1:
                            for j in i:
                                if j != NICKNAME:
                                    try:
                                        self.clients[
                                            NICKNAMES.index(j)
                                        ].send(DATA.encode("utf-8"))
                                    except BrokenPipeError:
                                        pass
                        else:
                            for j in i:
                                try:
                                    self.clients[
                                        NICKNAMES.index(j)
                                    ].send(DATA.encode("utf-8"))
                                except BrokenPipeError:
                                    pass
            except ValueError:
                pass
        DATA = ""

    def manage_variables(self):
        for i in self.clients:
            try:
                i.send(bytes(".:!:.test.:!:.".encode("utf-8")))
            except (BrokenPipeError, ConnectionResetError):
                try:
                    nick = NICKNAMES[self.clients.index(i)]
                    try:
                        room = ROOMS_NICKNAMES[
                            ROOMS_NICKNAMES.index(nick) - 1
                        ]
                        print("{} is disconnected.".format(nick))
                        if ROOMS_NICKNAMES.count(room) == 1:
                            ROOMS.remove(room)
                        while ROOMS_NICKNAMES.count(nick) > 0:
                            ROOMS_NICKNAMES.pop(
                                ROOMS_NICKNAMES.index(nick) - 1
                            )
                            ROOMS_NICKNAMES.remove(nick)
                        NICKNAMES.remove(nick)
                        self.clients.remove(i)
                    except ValueError:
                        print("{} is disconnected.".format(nick))
                        NICKNAMES.remove(nick)
                except IndexError:
                    self.clients.remove(i)
        time.sleep(10)


if __name__ == "__main__":
    server = Server()
    thread_connect = threading.Thread(target=server.connect_server)
    thread_connect.start()
    while True:
        thread_accept = threading.Thread(target=server.accept_connections)
        thread_accept.daemon = True
        thread_accept.start()
        thread_accept.join(1)
        thread_receive = threading.Thread(target=server.receive_data)
        thread_receive.start()
        thread_send = threading.Thread(target=server.send_data)
        thread_send.daemon = True
        thread_send.start()
        thread_send.join(1)
        thread_manage = threading.Thread(target=server.manage_variables)
        thread_manage.daemon = True
        thread_manage.start()
        thread_manage.join(1)

client.py

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

import os
import time
import socket
import threading

from datetime import datetime as dt

DATA = ""
NICKNAME = ""
NICKNAME_CONTROLLER = 0
ROOM = ""
HOST = input("HOST: ")

now = lambda: dt.now().strftime("%Y-%m-%d %H:%M:%S")

if HOST == "":
    HOST = "127.0.0.1"
while True:
    try:
        PORT = int(input("PORT: "))
        break
    except ValueError:
        print("Port value is invalid.")


def exit_program():
    if os.name == "posix":
        import signal
        os.kill(os.getpid(), signal.SIGKILL)
    elif os.name == "nt":
        os.system("TASKKILL /F /PID {}".format(os.getpid()))


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

    def connect_server(self):
        try:
            self.connect((HOST, PORT))
            print(f"| {now()} | Welcome to the chat program.\n")
            print(
                "Commands:\n\t"
                "/nick <new_nickname>\n\t"
                "/join <room_name>\n\t"
                "/list\n\t"
                "/clear\n\t"
                "/quit\n".expandtabs(4)
            )
        except socket.gaierror:
            print("Name or service not known.")
            exit_program()
        except ConnectionRefusedError:
            print("Connection refused.")
            exit_program()
        except OverflowError:
            print("Port must be 0-65535.")
            exit_program()
        except ConnectionAbortedError:
            print("Software caused connection abort.")
            exit_program()

    def receive_data(self):
        global DATA
        try:
            data = self.recv(1024).decode("utf-8")
            if len(data) == 0:
                pass
            else:
                if "Room:" in data:
                    for i in data[6:].split(","):
                        print("\t{}".expandtabs(4).format(i))
                elif "This nickname is used." in data:
                    print(f"| {now()} | {data}")
                    DATA = data
                elif ".:!:.test.:!:." in data:
                    pass
                else:
                    print(data)
        except OSError:
            pass

    def send_data(self):
        global NICKNAME, ROOM, DATA, NICKNAME_CONTROLLER
        response = input()
        if "/nick" in response:
            if NICKNAME_CONTROLLER < 1:
                if response == "/nick":
                    print(f"| {now()} | You must create a nickname.")
                else:
                    NICKNAME = response[6:]
                    if " " in NICKNAME or NICKNAME == "":
                        print(
                            f"| {now()} | "
                            "Do not use space in your nickname."
                        )
                    else:
                        self.sendall(
                            f":nickname: {NICKNAME}".encode("utf-8")
                        )
                        time.sleep(0.1)
                        if "This nickname is used." not in DATA:
                            print(
                                f"| {now()} | "
                                "Your nickname has changed to " 
                                f"{NICKNAME}."
                            )
                            NICKNAME_CONTROLLER += 1
                        else:
                            NICKNAME = ""
            else:
                print(f"| {now()} | You can not change your nickname.")

            DATA = ""
        elif "/join" in response:
            if response == "/join":
                print(f"| {now()} | You must write a room.")
            else:
                ROOM = response[6:]
                if " " in ROOM or ROOM == "":
                    print(f"| {now()} | Do not use space in room name.")
                else:
                    if NICKNAME != "":
                        self.sendall(
                            f":room: {ROOM} {NICKNAME}".encode("utf-8")
                        )
                        print(f"| {now()} | Joined {ROOM}.")
                    else:
                        print(f"| {now()} | You must create a nickname.")
        elif response == "/list":
            if NICKNAME != "":
                self.sendall(f":list: {NICKNAME}".encode("utf-8"))
                print(f"| {now()} | Available Rooms:")
            else:
                print(f"| {now()} | You must create a nickname.")
        elif response == "/quit":
            exit_program()
        elif response == "/clear":
            os.system("clear" if os.name == "posix" else "cls")
        else:
            if NICKNAME != "":
                if ROOM != "":
                    self.sendall(
                        f".:!:.{NICKNAME}.:!:.{NICKNAME}: {response}"
                        .encode("utf-8")
                    )
                else:
                    print(f"| {now()} | Join a room.")
            else:
                print(f"| {now()} | Create a nickname.")


if __name__ == "__main__":
    client = Client()
    thread_connect = threading.Thread(target=client.connect_server)
    thread_connect.start()
    while True:
        thread_receive = threading.Thread(target=client.receive_data)
        thread_receive.start()
        thread_send = threading.Thread(target=client.send_data)
        thread_send.daemon = True
        thread_send.start()
        thread_send.join(1)
2 Beğeni

Şimdi ben bağlı olduğu bütün clientlere mesaj atabilen bir kod yazmaya çalışıyorum ama bir client’in attığı mesajı diğeri atmıyor neden olabilir ?
Server.py :

import socket

host = "localhost"
port = 8131


with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as s:
	s.bind((host,port))
	s.listen(5)
	while True:
		client,adress = s.accept()
		print(f"client {adress} connecting")
		msg = client.recv(1024)
		client.send(msg)
		print(msg.decode("utf-8"))

Client:

import socket

host = "localhost"
port = 8131

with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as s:
	s.connect((host,port))
	while True:
		a = input("mesaj gönderilsinmi ?")
		if a == "e" or a == "E":
			s.sendall(bytes("hello world","utf-8"))
	
			msg = s.recv(1024).decode("utf-8")
			print(msg)
	else:
		pass

Socket uygulamalarında accept fonksiyonu çağrıldığı zaman, sunucu istemcinin kendisine bağlanmasını beklemeye başlar. Bağlandıktan sonra bu fonksiyon işlemini tamamlar. Dolayısıyla sizin öyle bir kod yazmanız gerekiyor ki, sunucu kullanıcının kendisine bağlanmasını beklememeli ama aynı zamanda kullanıcının bağlanmasına olanak tanımalıdır. Bunun nasıl yaparsınız? Mesela threading kullanabilirsiniz. Aşağıdaki kodlar, sizin kodlarınızın biraz değiştirilmiş hali. Bir bakın isterseniz.

server.py

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

import socket

from threading import Thread

host = "localhost"
port = 8131

clients = []


def accept(s):
    conn, addr = s.accept()
    if (conn, addr) not in clients:
        clients.append((conn, addr))
        print(f"client {addr} connected.")
        
        
def send(c):
	msg = c.recv(1024)
	c.send(msg)
	print(msg.decode("utf-8"))    
        
        
def receive():
    for c in clients:
        t = Thread(target=lambda: send(c[0]), daemon=True)
        t.start()


with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
	s.bind((host,port))
	s.listen(5)
	while True:
		t1 = Thread(target=lambda: accept(s), daemon=True)
		t1.start()
		t1.join(1)
		t2 = Thread(target=receive, daemon=True)
		t2.start()
		t2.join(1)

client.py’de herhangi bir değişiklik yapmadım.

1 Beğeni

Burdaki method ne işe yarıyor açıklayabilirmisiniz @dildeolupbiten.

join fonksiyonunun timeout isimli bir argümanı var. Bu, thread’in zaman aşımına uğrayacağı süreyi belirtir.

Örnek:

from threading import Thread

t = Thread(target=lambda: input(">>> "), daemon=True)
t.start()
t.join(3)

Normalde, input fonksiyonu sizden bir şeyler yazıp enter tuşuna basmanızı bekler. Ancak yukarıdaki kod çalıştırıldığında, 3 saniye içinde bir yazı yazmazsanız program sonlanır.

1 Beğeni

Burasını anlayamadım :frowning:

Bir iş parçacığını yukarıdaki örnekte olduğu gibi belli bir süre sonra sonlandırmak için onun daemon özelliğinin aktif edilmiş olması gerekir.

1 Beğeni

Ayrıca ana programınız bir main_thread'tir (ana iş parçacığıdır). Ve eğer arka planda çalışan başka iş parçacıklarınız var ise ve bu iş parçacıkları daemonic değilse, programınız sonlanmayacaktır ve bu iş parçacıklarının sonlanmasını bekleyecektir. Program ancak geriye sadece daemon threadler kaldığında sonlanır.

1 Beğeni

Basit bir örnek:

from threading import Thread


def f():
    while True:
        continue
        
        
t1 = Thread(target=f, daemon=True)
t2 = Thread(target=lambda: print("hello"))
t1.start()
t2.start()

Bir önceki mesajımda şöyle bir şey demiştim.

Şimdi yukarıdaki örneğe göre while döngüsünü içeren f fonksiyonunu çağıran iş parçacığının sürekli çalışması gerekiyor gibi geliyor. Ancak onu daemonic olarak ayarladığımız için, print fonksiyonu sonlandığında geriye sadece daemonic iş parçacığı kaldığı için while döngüsünü çalıştıran iş parçacığı da sonlanır.

Hatta bu kodu şöyle değiştirin:

from threading import Thread


def f():
    while True:
        continue
        
                
t1 = Thread(target=f, daemon=True)
# t2 = Thread(target=lambda: print("hello"))
t1.start()
# t2.start()

Görecekseniz ki, program çalışır çalışmaz sonlanıyor, çünkü geriye sadece daemonic iş parçacığı kalmış oluyor. daemon özelliğini kaldırırsanız, while döngüsünün bulunduğu fonksiyon da çalışmaya devam eder.

1 Beğeni

Bir hatırlatmada bulunmak istiyorum. Hem server, hem de client'in mesaj gönderme ve mesaj alma işlemlerinin güvenli bir şekilde yapılabilmesi için bazı düzenlemeler yapmanız gerekecektir. Örneğin, recv fonksiyonuna yazılan 1024 veya 2048 gibi arabellek boyutları bir verinin alınabilecek maksimum boyutunu ifade eder. Verinin bu boyuttan daha düşük bir boyutta olan yığınını (chunk) da alabilirsiniz.

@aib arkadaşımız bizi bu konuda daha önce uyarmıştı. Sabit bir arabellek boyutu kullanmak yerine gönderilecek verinin boyutuna göre bu arabellek boyutunun belirlenmesinin gerektiğini söylemişti. Veri aktarımının başarıyla sonuçlandığını anlamak için de while alinan < gonderilen gibi bir koşul altında çalışan bir döngünün kullanılabileceğini belirtmişti.

Daha sonradan @EkremDincel arkadaşımız, bu konuyla alakalı bir modül tasarladı. Yani socket modülünü kullanmak yerine bu modülü kullanabilir ve veri aktarımının daha güvenli olmasını sağlayabilirsiniz. Tabi modülü kullanırken, veri gönderme ve veri alma işlemlerinin birbirlerini bloklamayacak şekilde gerçekleşmesi için iş parçacıkları kullanmanız gerekecektir. @EkremDincel arkadaşımızın yaptığı, veri alış-verişini daha kontrollü bir şekilde yapmak için alternatif send ve recv fonksiyonlarını içeren bir yama oluşturmaktır. (Yama doğru bir kelime mi emin olamadım.)

Yapmanız gereken şey sadece https://gist.github.com/EkremDincel/a26230ddfe0dc4dd80b6fcbb56df1102 adresine giderek, bu dosyayı indirmek ve @EkremDincel’in yaptığı gibi socket programını bu microstream modülünü kullanarak yazmaktır.

İlgili mesajı incelemek için aşağıdaki bağlantıyı kullanabilirsiniz.

2 Beğeni

Veya isreadable metodu kullanılabilir, soketin okunabilecek mesajı olup olmadığını döndürüyor.

2 Beğeni