C# 소켓 프로그래밍 - 비동기 채팅 프로그램 만들기 (1:n 통신)
지난번에 1:1 소켓프로그래밍으로 채팅 프로그램을 만들어보았습니다.
참고 : http://yeolco.tistory.com/31
이번 시간에는 1:n 비동기 방식 채팅프로그램을 만들어보겠습니다.
각 클라이언트 소켓마다 쓰레드를 만들어서 통신하는 방법입니다.
◎ 실행화면
3명의 사용자가 동시에 사용했을때
잘 작동하는 화면입니다.
사용자 1명이 나가고 새로운 사용자가 서버에 접속하는 화면입니다.
◎ Server쪽 소스코드입니다.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace SIMT_CHATTING_SERVER
{
public partial class Form1 : Form
{
TcpListener server = null; // 서버
TcpClient clientSocket = null; // 소켓
static int counter = 0; // 사용자 수
string date; // 날짜
// 각 클라이언트 마다 리스트에 추가
public Dictionary<TcpClient, string> clientList = new Dictionary<TcpClient, string>();
public Form1()
{
InitializeComponent();
// 쓰레드 생성
Thread t = new Thread(InitSocket);
t.IsBackground = true;
t.Start();
}
private void InitSocket()
{
server = new TcpListener(IPAddress.Any, 9999); // 서버 접속 IP, 포트
clientSocket = default(TcpClient); // 소켓 설정
server.Start(); // 서버 시작
DisplayText(">> Server Started");
while (true)
{
try
{
counter++; // Client 수 증가
clientSocket = server.AcceptTcpClient(); // client 소켓 접속 허용
DisplayText(">> Accept connection from client");
NetworkStream stream = clientSocket.GetStream();
byte[] buffer = new byte[1024]; // 버퍼
int bytes = stream.Read(buffer, 0, buffer.Length);
string user_name = Encoding.Unicode.GetString(buffer, 0, bytes);
user_name = user_name.Substring(0, user_name.IndexOf("$")); // client 사용자 명
clientList.Add(clientSocket, user_name); // cleint 리스트에 추가
SendMessageAll(user_name + " 님이 입장하셨습니다.", "", false); // 모든 client에게 메세지 전송
handleClient h_client = new handleClient(); // 클라이언트 추가
h_client.OnReceived += new handleClient.MessageDisplayHandler(OnReceived);
h_client.OnDisconnected += new handleClient.DisconnectedHandler(h_client_OnDisconnected);
h_client.startClient(clientSocket, clientList);
}
catch (SocketException se) { break; }
catch (Exception ex) { break; }
}
clientSocket.Close(); // client 소켓 닫기
server.Stop(); // 서버 종료
}
void h_client_OnDisconnected(TcpClient clientSocket) // cleint 접속 해제 핸들러
{
if (clientList.ContainsKey(clientSocket))
clientList.Remove(clientSocket);
}
private void OnReceived(string message, string user_name) // cleint로 부터 받은 데이터
{
if(message.Equals("leaveChat")) {
string displayMessage = "leave user : " + user_name;
DisplayText(displayMessage);
SendMessageAll("leaveChat", user_name, true);
}
else {
string displayMessage = "From client : " + user_name + " : " + message;
DisplayText(displayMessage); // Server단에 출력
SendMessageAll(message, user_name, true); // 모든 Client에게 전송
}
}
public void SendMessageAll(string message, string user_name, bool flag)
{
foreach (var pair in clientList)
{
date = DateTime.Now.ToString("yyyy.MM.dd. HH:mm:ss"); // 현재 날짜 받기
TcpClient client = pair.Key as TcpClient;
NetworkStream stream = client.GetStream();
byte[] buffer = null;
if (flag)
{
if(message.Equals("leaveChat"))
buffer = Encoding.Unicode.GetBytes(user_name + " 님이 대화방을 나갔습니다.");
else
buffer = Encoding.Unicode.GetBytes("[ " + date + " ] " + user_name + " : " + message);
}
else
{
buffer = Encoding.Unicode.GetBytes(message);
}
stream.Write(buffer, 0, buffer.Length); // 버퍼 쓰기
stream.Flush();
}
}
private void DisplayText(string text) // Server 화면에 출력
{
if (textBox1.InvokeRequired)
{
textBox1.BeginInvoke(new MethodInvoker(delegate
{
textBox1.AppendText(text + Environment.NewLine);
}));
}
else
textBox1.AppendText(text + Environment.NewLine);
}
}
}
◎ Client쪽 소스코드입니다.
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace Chatting
{
public partial class Form2 : Form
{
TcpClient clientSocket = new TcpClient(); // 소켓
NetworkStream stream = default(NetworkStream);
string message = string.Empty;
public Form2()
{
InitializeComponent();
}
private void Form2_Load(object sender, EventArgs e)
{
try
{
clientSocket.Connect("192.168.0.31", 9999); // 접속 IP 및 포트
stream = clientSocket.GetStream();
}
catch (Exception e2)
{
MessageBox.Show("서버가 실행중이 아닙니다.", "연결 실패!");
Application.Exit();
}
message = "채팅 서버에 연결 되었습니다.";
DisplayText(message);
byte[] buffer = Encoding.Unicode.GetBytes(Form1.nickname + "$");
stream.Write(buffer, 0, buffer.Length);
stream.Flush();
Thread t_handler = new Thread(GetMessage);
t_handler.IsBackground = true;
t_handler.Start();
}
private void button1_Click(object sender, EventArgs e) // 메세지 보내기
{
textBox2.Focus();
byte[] buffer = Encoding.Unicode.GetBytes(textBox2.Text + "$");
stream.Write(buffer, 0, buffer.Length);
stream.Flush();
textBox2.Text = "";
}
private void GetMessage() // 메세지 받기
{
while (true)
{
stream = clientSocket.GetStream();
int BUFFERSIZE = clientSocket.ReceiveBufferSize;
byte[] buffer = new byte[BUFFERSIZE];
int bytes = stream.Read(buffer, 0, buffer.Length);
string message = Encoding.Unicode.GetString(buffer, 0, bytes);
DisplayText(message);
}
}
private void DisplayText(string text) // Server에 메세지 출력
{
if (textBox1.InvokeRequired)
{
textBox1.BeginInvoke(new MethodInvoker(delegate
{
textBox1.AppendText(text + Environment.NewLine);
}));
}
else
textBox1.AppendText(text + Environment.NewLine);
}
private void textBox2_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter) // 엔터키 눌렀을 때
button1_Click(this, e);
}
private void Form2_FormClosing(object sender, FormClosingEventArgs e) // 폼 닫을 때 실행
{
byte[] buffer = Encoding.Unicode.GetBytes("leaveChat" + "$");
stream.Write(buffer, 0, buffer.Length);
stream.Flush();
Application.ExitThread();
Environment.Exit(0);
}
}
}
정보가 유익하셨다면 아래 공감버튼 눌러주시면 감사하겠습니다.
질문사항은 댓글로 달아주시면 성의껏 답변해드리겠습니다.
'Program Development' 카테고리의 다른 글
[C#] 자동차 번호판 인식 프로그램 with Tesseract-OCR, OpenALPR (27) | 2018.08.22 |
---|---|
[TCP/IP] C# 소켓 프로그래밍 - 채팅 프로그램 만들기 (11) | 2018.08.20 |
C# 바코드 인식 프로그램 (9) | 2018.08.18 |