naritoブログ

【お知らせ】
新ブログができました。今後そちらで更新し、このサイトは更新されません(ウェブサイト自体は残しておきます)
このブログの内容に関してコメントしたい場合は、新ブログのフリースペースに書き込んでください

このブログの内容を新ブログに移行中です。このブログで見つからない記事は、新ブログにありま

Kivyで、シンプルなチャット

約793日前 2017年2月18日0:16
プログラミング関連
Kivy Python
このような、シンプルなチャットを作ります。
試しに2つ立ち上げて...


入力し、エンターを押すと


それぞれのチャット欄に表示されます。


Kivy1.9.1
Python3.4
です。

Python、マルチスレッドを使い簡易チャット
https://torina.top/detail/253/
を基に作成していますが、元が結構ガバガバなのであまり実用的ではないです。

server.py
こいつを、先に実行しておく必要があります。

import socket
import threading


HOST = ''
PORT = 9998
clients = []


def remove_conection(con, address):
"""クライアントと接続を切る"""

print('[切断]{}'.format(address))
con.close()
clients.remove((con, address))


def server_start():
"""サーバをスタートする"""

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((HOST, PORT))
sock.listen(10)

while True:
con, address = sock.accept()
print("[接続]{}".format(address))
clients.append((con, address))
handle_thread = threading.Thread(target=handler,
args=(con, address),
daemon=True)
handle_thread.start()


def handler(con, address):
"""クライアントからデータを受信する"""

while True:
try:
data = con.recv(1024)
except ConnectionResetError:
remove_conection(con, address)
break
else:
if not data:
remove_conection(con, address)
break
else:
print("[受信]{} - {}".format(address, data.decode("utf-8")))
for c in clients:
c[0].sendto(data, c[1])


if __name__ == "__main__":
server_start()


実行すると、こんな感じで接続やらの情報が表示されます。



main.py

import socket
import threading

from kivy.app import App
from kivy.properties import NumericProperty, ObjectProperty, ListProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label

HOST = '127.0.0.1'
PORT = 9998


class ChatClient(BoxLayout):
msg = ObjectProperty(None)
view = ObjectProperty(None)
all_text = ListProperty()

def send_msg(self):
self.sock.send(self.msg.text.encode('utf-8'))
self.msg.text = ''

def recv_msg(self):
while True:
data = self.sock.recv(1024)
msg = data.decode('utf-8')
self.all_text.append(msg)
self.view.text = '\\n'.join(self.all_text)

def start(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((HOST, PORT))
handle_thread = threading.Thread(target=self.recv_msg, daemon=True)
handle_thread.start()


class Chat(App):

def build(self):
chat = ChatClient()
chat.start()
return chat


Chat().run()


chat.kv

<ChatClient>:
msg: msg
view: view

orientation: 'vertical'

TextInput:
id: view
size_hint: 1, .9
text: ''
readonly: True


TextInput:
id: msg
size_hint: 1, .1
text: ''
multiline: False
on_text_validate: root.send_msg()



先にkvファイルを見ましょう。
下記は受信したメッセージを表示するTextinputですね。高さは9割ほどつかっています。readonlyで、編集はできないようにしています。
そもそもTextinputが適切なウィジェットではないように思いましたが、使ってみると案外いける

TextInput:
id: view
size_hint: 1, .9
text: ''
readonly: True


こっちは入力欄。書き忘れてましたが、日本語は対応していません。
入力欄は1行、エンターでsend_msgを呼びます。

TextInput:
id: msg
size_hint: 1, .1
text: ''
multiline: False
on_text_validate: root.send_msg()



main.pyです。
ルートウィジェットを返す前に、先にstartというメソッドを呼んでいます。こいつがサーバとの接続なんかをする訳です。

class Chat(App):

def build(self):
chat = ChatClient()
chat.start()
return chat

Chat().run()


startでは、サーバと接続し、メッセージ受信用のスレッドを作成しています。self.recv_msgを、別スレッドで実行していますね。

class ChatClient(BoxLayout):
...
...
def start(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((HOST, PORT))
handle_thread = threading.Thread(target=self.recv_msg, daemon=True)
handle_thread.start()


そのrecv_msgです。
無限ループしつつ、サーバからデータを受け取り、それをall_textというListPropertyに一度格納します。
リストにすることで表示するメッセージの数なんかが変えられるかもしれません。今回はしていません。
id: viewのTextinputに、\nは改行ですね。

def recv_msg(self):
while True:
data = self.sock.recv(1024)
msg = data.decode('utf-8')
self.all_text.append(msg)
self.view.text = '\\n'.join(self.all_text)



送信用のメソッドです。入力欄からエンターでここです。
サーバに送信し、入力欄のクリアです。

def send_msg(self):
self.sock.send(self.msg.text.encode('utf-8'))
self.msg.text = ''