|
1 | | -# python-grpc-chat |
| 1 | +# Python gRPC Chat |
2 | 2 | Chat application created with gRPC. This was a study for bidirectional gRPC streaming. |
| 3 | + |
| 4 | +## Server |
| 5 | + |
| 6 | +```python |
| 7 | +class ChatServer(rpc.ChatServerServicer): |
| 8 | + |
| 9 | + def __init__(self): |
| 10 | + # List with all the chat history |
| 11 | + self.chats = [] |
| 12 | + |
| 13 | + # The stream which will be used to send new messages to clients |
| 14 | + def ChatStream(self, request_iterator, context): |
| 15 | + """ |
| 16 | + This is a response-stream type call. This means the server can keep sending messages |
| 17 | + Every client opens this connection and waits for server to send new messages |
| 18 | +
|
| 19 | + :param request_iterator: |
| 20 | + :param context: |
| 21 | + :return: |
| 22 | + """ |
| 23 | + lastindex = 0 |
| 24 | + # For every client a infinite loop starts (in gRPC's own managed thread) |
| 25 | + while True: |
| 26 | + # Check if there are any new messages |
| 27 | + while len(self.chats) > lastindex: |
| 28 | + n = self.chats[lastindex] |
| 29 | + lastindex += 1 |
| 30 | + yield n |
| 31 | + |
| 32 | + def SendNote(self, request: chat.Note, context): |
| 33 | + """ |
| 34 | + This method is called when a clients sends a Note to the server. |
| 35 | +
|
| 36 | + :param request: |
| 37 | + :param context: |
| 38 | + :return: |
| 39 | + """ |
| 40 | + print("[{}] {}".format(request.name, request.message)) |
| 41 | + # Add it to the chat history |
| 42 | + self.chats.append(request) |
| 43 | + return chat.Empty() |
| 44 | +``` |
| 45 | + |
| 46 | +## Client |
| 47 | + |
| 48 | +```python |
| 49 | +address = 'localhost' |
| 50 | +port = 11912 |
| 51 | + |
| 52 | + |
| 53 | +class Client: |
| 54 | + |
| 55 | + def __init__(self, u: str, window): |
| 56 | + # the frame to put ui components on |
| 57 | + self.window = window |
| 58 | + self.username = u |
| 59 | + # create a gRPC channel + stub |
| 60 | + channel = grpc.insecure_channel(address + ':' + str(port)) |
| 61 | + self.conn = rpc.ChatServerStub(channel) |
| 62 | + # create new listening thread for when new message streams come in |
| 63 | + threading.Thread(target=self.__listen_for_messages, daemon=True).start() |
| 64 | + self.__setup_ui() |
| 65 | + self.window.mainloop() |
| 66 | + |
| 67 | + def __listen_for_messages(self): |
| 68 | + """ |
| 69 | + This method will be ran in a separate thread as the main/ui thread, because the for-in call is blocking |
| 70 | + when waiting for new messages |
| 71 | + """ |
| 72 | + for note in self.conn.ChatStream(chat.Empty()): |
| 73 | + print("R[{}] {}".format(note.name, note.message)) |
| 74 | + self.chat_list.insert(END, "[{}] {}\n".format(note.name, note.message)) |
| 75 | + |
| 76 | + def send_message(self, event): |
| 77 | + """ |
| 78 | + This method is called when user enters something into the textbox |
| 79 | + """ |
| 80 | + message = self.entry_message.get() |
| 81 | + if message is not '': |
| 82 | + n = chat.Note() |
| 83 | + n.name = self.username |
| 84 | + n.message = message |
| 85 | + print("S[{}] {}".format(n.name, n.message)) |
| 86 | + self.conn.SendNote(n) |
| 87 | + |
| 88 | + ... |
| 89 | +``` |
| 90 | + |
| 91 | +## The proto file |
| 92 | + |
| 93 | +```proto |
| 94 | +syntax = "proto3"; |
| 95 | +
|
| 96 | +package grpc; |
| 97 | +
|
| 98 | +message Empty {} |
| 99 | +
|
| 100 | +// I called it Note because message Message is annoying to work with |
| 101 | +message Note { |
| 102 | + string name = 1; |
| 103 | + string message = 2; |
| 104 | +} |
| 105 | +
|
| 106 | +service ChatServer { |
| 107 | + // This bi-directional stream makes it possible to send and receive Notes between 2 persons |
| 108 | + rpc ChatStream (Empty) returns (stream Note); |
| 109 | + rpc SendNote (Note) returns (Empty); |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +# DEMO |
| 114 | + |
| 115 | + |
| 116 | + |
| 117 | + |
0 commit comments