문서 편집 권한이 없습니다. 다음 이유를 확인해주세요: 요청한 명령은 다음 권한을 가진 사용자에게 제한됩니다: 사용자. 문서의 원본을 보거나 복사할 수 있습니다. [[분류:프로그래밍]] ==서버== 처음에는 사랑해 마지않는 Rust로 하려 했으나... 비동기 지원이 까다롭고, 채팅 프로토콜을 JSON으로 구성할 생각이여서 Typescript로 결정. ===v0.1?=== 첫 단계에서는 일단 간단하게 메시지를 받고 접속한 클라이언트에게 재전송하는 역할을 수행하는 서버 코드를 작성했다. 한 포트에서 연결을 받으면서 클라이언트가 보내는 첫 메시지에 따라서 해당 연결이 클라이언트의 리시버인지 혹은 센더인지를 파악한다. 일단은 단순한 int형만을 송수신하면서 Sender와 Receiver를 구분하지만 좀 더 개발을 진행하면 첫 수신 객체에 따라서 Sender와 Receiver를 구분하게 만들 수 있을 것이다... <source lang="typescript"> import net = require("net"); import Socket = net.Socket; import { Buffer } from "buffer"; const EndofTransmissionBlock = 0x17; class Receiver{ public constructor(socket:Socket){ this.socket = socket; this.buffer = new Buffer(0); this.socket.on("data",(data:Buffer)=> this.PollMessage(data)); } /** * PollMessage */ public PollMessage(data: Buffer) { let oldBuffer = this.buffer; if (oldBuffer.byteLength + data.byteLength > 4096) { this.buffer = Buffer.alloc(0, 0); return; } let newBuffer = new Buffer(oldBuffer.byteLength + data.byteLength); newBuffer.set(oldBuffer, 0); newBuffer.set(data, oldBuffer.byteLength); //var s = data. //var s = data.toString("utf-8"); let lastETBIndex = -1; let msgs = Array<string>(); for (let i = 0; i < newBuffer.byteLength; i++){ let ch = newBuffer.readUInt8(i); if(ch == EndofTransmissionBlock) { msgs.push(newBuffer.toString("utf8", lastETBIndex + 1, i)); lastETBIndex = i; } } this.buffer = newBuffer.slice(lastETBIndex); let ETB = new Buffer(1); ETB.writeUInt8(EndofTransmissionBlock, 0); for (let msg of msgs) { for (let sender of senders.values()) { sender.write(msg); sender.write(ETB); } } } public buffer:Buffer; public socket:Socket; } let last_index = 0; let senders = new Map<number, Socket>(); let receivers = new Map<number, Receiver>(); let server = net.createServer((socket) => { socket.on("data", (data: Buffer) => { socket.removeAllListeners("data"); let index: number = data.readInt32LE(0); if (index == 0) { last_index++; let bu = new Buffer(4); bu.writeInt32LE(last_index, 0); socket.write(bu); senders.set(last_index, socket); socket.on("close", () => { senders.delete(index); }); socket.on("error", () => { senders.delete(index); }); return; } if (senders.has(index) && !receivers.has(index)) { socket.write(data); let receiver = new Receiver( socket); receivers.set(index, receiver); socket.on("close", () => { receivers.delete(index); }); socket.on("error", () => { receivers.delete(index); }); } else { socket.destroy(); } }); }); server.listen(8080); </source> ==클라이언트== 클라이언트는 역시 사랑해 마지않는 최근에 계속 만지작거리고 있는 Xamarin.Forms (feat. C#)으로 결정. Android, UWP, IOS, WPF, mac os등 리눅스 뺀 메이저한 OS를 원소스로 애플리케이션을 제작할 수 있다. ===v0.1?=== 모든 코드는 보여줄 필요 없이 단순하게 모델만 모여줘도 서버와의 통신을 확인할 수 있다. View는 어차피 Model의 property를 바인딩 한 것뿐이니까. <source lang="csharp"> namespace SimpleChattingClient { public class ChatListModels : INotifyPropertyChanged { private const string HOST = "localhost"; private const int PORT = 8080; private ObservableCollection<string> chatList; private string message; private Socket senderSocket; private Socket receiveSocket; private int uid; public event PropertyChangedEventHandler PropertyChanged; private Task sendTask = null; private Task receiverTask = null; public ChatListModels() { chatList = new ObservableCollection<string>(); ConnectToServer(); } private async void ConnectToServer() { receiveSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); await receiveSocket.ConnectAsync(HOST, PORT); var uidBytes = BitConverter.GetBytes(uid); int s = 0; do { s += receiveSocket.Send(uidBytes, s, uidBytes.Length - s, SocketFlags.None); } while (s != uidBytes.Length); s = 0; do { s += receiveSocket.Receive(uidBytes, s, uidBytes.Length - s, SocketFlags.None); } while (s != uidBytes.Length); uid = BitConverter.ToInt32(uidBytes, 0); senderSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); await senderSocket.ConnectAsync(HOST, PORT); s = 0; do { s += senderSocket.Send(uidBytes, s, uidBytes.Length - s, SocketFlags.None); } while (s != uidBytes.Length); s = 0; do { s += senderSocket.Receive(uidBytes, s, uidBytes.Length - s, SocketFlags.None); } while (s != uidBytes.Length); if (uid != BitConverter.ToInt32(uidBytes, 0)) { throw new Exception("Could not connect to server!"); } else { } receiverTask = Task.Run(() => { var stream = new NetworkStream(receiveSocket); var chunk = new byte[4096]; var buffer = new List<byte>(); int ETB = 0; while (true) { int readByte = stream.Read(chunk, 0, 4096); if (readByte == 0) continue; buffer.AddRange(chunk.Take(readByte)); while (true) { ETB = buffer.FindIndex((byte it) => it == 0x17); if (ETB == -1) { break; } if(ETB != 0) { string text = Encoding.UTF8.GetString(buffer.ToArray(), 0, ETB); chatList.Add(text); } buffer.RemoveRange(0, ETB + 1); //PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ChatList")); } } //receiveSocket.Receive() }); } public async void SendMessageToServer() { if (message.Trim().Length == 0) return; message = message.Trim(); if (sendTask != null) { await sendTask; } sendTask = Task.Run(() => { var bytes = Encoding.UTF8.GetBytes(message); Message = ""; int s = 0; do { s += senderSocket.Send(bytes, s, bytes.Length - s, SocketFlags.None); } while (s != bytes.Length); bytes[0] = 0x17; while (senderSocket.Send(bytes, 0, 1, SocketFlags.None) != 1) ; sendTask = null; }); //sendTask.Start(); } public Command SendCommand { get => new Command(() => SendMessageToServer()); } public string Message { get => message; set { message = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Message")); } } public ObservableCollection<string> ChatList { get => chatList; } } } </source> 크로스플랫폼 채팅 애플리케이션 문서로 돌아갑니다.