Cấp bậc tác giả:

DOTNET

Xây dựng Chương trình Socket Chat bằng C#

Được viết bởi webmaster ngày 13/05/2013 lúc 12:36 AM
Thực hiện chương trình Chat sử dụng Socket. Ngoài chức năng trò chuyện cơ bản, nó có khả năng gửi tin nhắn và quản lý đăng nhập Chat đang diễn ra. Có hai ứng dụng: ChatServer và ChatClient
  • 0
  • 10725
Tải tệp tin: Click ở đây

Xây dựng Chương trình Socket Chat bằng C#


Mô tả:

Thực hiện chương trình Chat sử dụng Socket. Ngoài chức năng trò chuyện cơ bản, nó có khả năng gửi tin nhắn và quản lý đăng nhập Chat đang diễn ra. Có hai ứng dụng: ChatServer và ChatClient

ChatServer quản lý phiên. Nó duy trì danh sách các chatters hoạt động và tin nhắn chuyển tiếp đến. ChatServer được mã hoá trên cổng 5555 trong trường hợp bạn phải thay đổi nó. Chat Server là đa luồng - giao tiếp với từng Client thông qua luồng riêng biệt. ChatServer khi được khởi động, nó truy cập vào thread StartListening () để đợi kết nối client: 

private void StartListening()
{
listener = new TcpListener(listenport);
listener.Start();
while (true) 
{
try
{
Socket s = listener.AcceptSocket();
clientsocket = s;
clientservice = new Thread(new ThreadStart(ServiceClient));
clientservice.Start();
}
catch(Exception e)
{
Console.WriteLine(e.ToString() );
}
}
}

Thread nàytiếp tục lắng nghe phía client. Khi yêu cầu kết nối được nhận, máy chủ bắt đầu Thread gọi là ServiceClient() để phục vụ Client. Lưu ý rằng khi socket được thiết lập, bản sao của socket được thực hiện. Điều này là cần thiết cho client để có socket riêng. Khi các Thread được bắt đầu, đối tượng client tạo ra tất cả các thông tin có liên quan về từng client và lưu trong ArrayList. Lớp Client sẽ được hiển thị dưới đây. Lưu ý lớp Client có chứa tham chiếu đến nó là của riêng thread ServiceClient() và socket. Điều này giúp đơn giản hoá công việc của ChatServer và làm cho chương trình logic dễ dàng hơn vì ChatServer liên lạc trực tiếp với Client và có thể kill thread của chúng trực tiếp thay vì phải sử dụng danh sách riêng biệt để theo dõi, trong đó Socket và Thread đó thuộc về phía Client. 

namespace ChatServer
{
using System.Net.Sockets;
using System.Net;
public class Client
{
private Thread clthread;
private EndPoint endpoint;
private string name;
private Socket sock;
public Client(string _name, EndPoint _endpoint, Thread _thread, Socket _sock)
{
clthread = _thread;
endpoint = _endpoint;
name = _name;
sock = _sock;
}
public override string ToString()
{
return endpoint.ToString()+ " : " + name;
}
public Thread CLThread
{
get{return clthread;}
set{clthread = value;}
}
public EndPoint Host
{
get{return endpoint;}
set{endpoint = value;}
}
public string Name
{
get{return name;}
set{name = value;}
}
public Socket Sock
{
get{return sock;}
set{sock = value;}
}
}
}

Thread ServiceClient(), tất cả công việc được thực hiện. ServiceClient() xác định việc thực hiện phía máy chủ giao thức ứng dụng. Giao thức ứng dụng nghĩa là giao thức tin nhắn trao đổi giữa máy chủ và client. Giao thức mà tôi đã sử dụng có hai đặc điểm. 
Đầu tiên, nó là phi trạng thái, có nghĩa là các thông báo ở ngữ cảnh tự do. Việc xử lý các tin nhắn không phụ thuộc vào thông báo trước đó hoặc bất kỳ ngữ cảnh nào khác - Các tin nhắn có thể được nhận bất cứ lúc nào và trong bất kỳ thứ tự nào không gây nhầm lẫn ứng dụng. 
Thứ hai, thông điệp là hoàn toàn tự mô tả, có nghĩa là tất cả các dữ liệu cần thiết để xử lý tin nhắn được chứa trong danh sách tin nhắn. Máy chủ nhận bốn lệnh, CONN, CHAT, PRIV, và GONE. 
CONN thiết lập một client mới, sẽ gửi danh sách các chatter hiện tại, và thông báo cho chatter khác biết rằng người mới đã tham gia nhóm.
CHAT chỉ đơn giản là chuyển tiếp trò chuyện đến tất cả người nhận.
PRIV gửi thông điệp tới những người được chỉ định. 
GONE loại bỏ tài khoản hoạt động trò chuyện và thông báo cho tất cả các thành viên khác trong cuộc trò chuyện. GONE cũng sẽ gây chết Thread client bằng cách thiết lập bool keepalive là false. Đây là cách để diệt các thread, tuy nhiên ChatServer có thể giết Thread chính nó nếu nó phát hiện các kết nối bị chấm dứt. 

Máy chủ nhận được các tin nhắn đến như chuỗi ASCII. Ký tự '|' được sử dụng như các dấu phân cách giữa các phần của tin nhắn. Thông báo bao gồm lệnh, một hoặc nhiều tham số khác cần thiết để xử lý tin nhắn. 

private void ServiceClient()
{
Socket client = clientsocket;
bool keepalive = true;
while (keepalive)
{
Byte[] buffer = new Byte[1024];
client.Receive(buffer);
string clientcommand = System.Text.Encoding.ASCII.GetString(buffer);
string[] tokens = clientcommand.Split(new Char[]{'|'});
Console.WriteLine(clientcommand);
if (tokens[0] == "CONN")
{
for(int n=0; n<clients.Count; n++) {
Client cl = (Client)clients[n];
SendToClient(cl, "JOIN|" + tokens[1]);
}
EndPoint ep = client.RemoteEndPoint;
Client c = new Client(tokens[1], ep, clientservice, client);
clients.Add(c);
string message = "LIST|" + GetChatterList() +"\r\n";
SendToClient(c, message);
lbClients.Items.Add(c);
}
if (tokens[0] == "CHAT")
{
for(int n=0; n<clients.Count; n++)
{
Client cl = (Client)clients[n];
SendToClient(cl, clientcommand);
}
}
if (tokens[0] == "PRIV") {
string destclient = tokens[3];
for(int n=0; n<clients.Count; n++) {
Client cl = (Client)clients[n];
if(cl.Name.CompareTo(tokens[3]) == 0)
SendToClient(cl, clientcommand);
if(cl.Name.CompareTo(tokens[1]) == 0)
SendToClient(cl, clientcommand);
}
}
if (tokens[0] == "GONE")
{
int remove = 0;
bool found = false;
int c = clients.Count;
for(int n=0; n<c; n++)
{
Client cl = (Client)clients[n];
SendToClient(cl, clientcommand);
if(cl.Name.CompareTo(tokens[1]) == 0)
{
remove = n;
found = true;
lbClients.Items.Remove(cl);
}
}
if(found)
clients.RemoveAt(remove);
client.Close();
keepalive = false;
}
}

ChatServer hiển thị dưới đây. Trực quan, nó chỉ hiển thị IP máy chủ Chat, socket và tên người dùng.

chatserver.jpg

ChatClient cho phép người dùng đăng nhập vào Chat, gửi và nhận tin nhắn. Nếu ChatClient được bắt đầu mà không có bất kỳ tham số dòng lệnh, nó giả định ChatServer là trên localhost. Nếu không, bạn phải cung cấp địa chỉ IP cho ChatServer trên dòng lệnh. Sau khi bắt đầu, đặt tên Chat của bạn trong hộp trò chuyện nhập và nhấp vào Connect. ChatServer sẽ phản ứng với danh sách các chatter hiện tại mà ChatClient đặt trong điều khiển ListBox. Sau đó bạn có thể gửi tin nhắn bằng cách gõ chúng, để gửi nhấp Send. Nếu bạn muốn gửi tin nhắn, bấm private và chọn tên từ ListBox. Chỉ có một tên có thể được lựa chọn tại một thời điểm. Sau đó gửi tin nhắn. Người nhận sẽ nhận được thông báo với cụm chuỗi "Private from" vào trước nó. 

Khi Client cố gắng kết nối, kết nối được thiết lập và đăng ký ChatServer. EstablishConnection() sử dụng TcpClient để kết nối với ChatServer. NetworkStream được thiết lập sẽ sử dụng để gửi tin nhắn. Một lần nữa cổng ChatServer được code cứng là 5555. 

private void EstablishConnection()
{
statusBar1.Text = "Connecting to Server";
try 
{
clientsocket = new TcpClient(serveraddress,serverport);
ns = clientsocket.GetStream();
sr = new StreamReader(ns);
connected = true;
}
catch (Exception e)
{
MessageBox.Show("Could not connect to Server","Error",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
statusBar1.Text = "Disconnected";
}
}

Phương thức constructs RegisterWithServer() và gửi lệnh CONN đến ChatServer. Lệnh CONN chứa tên chatters. Lệnh được xây dựng bằng cách sử dụng ký tự '|' như một dấu phân cách. ChatClient sau đó nhận được danh sách các chatter và thêm chúng vào ListBox. 

private void RegisterWithServer()
{
try 
{
string command = "CONN|" + ChatOut.Text;
Byte[] outbytes = System.Text.Encoding.ASCII.GetBytes(command.ToCharArray());
ns.Write(outbytes,0,outbytes.Length);
string serverresponse = sr.ReadLine();
serverresponse.Trim();
string[] tokens = serverresponse.Split(new Char[]{'|'});
if(tokens[0] == "LIST")
{
statusBar1.Text = "Connected";
btnDisconnect.Enabled = true;
}
for(int n=1; n<tokens.Length-1; n++)
lbChatters.Items.Add(tokens[n].Trim(new char[]{'\r','\n'}));
this.Text = clientname + ": Connected to Chat Server";
}
catch (Exception e)
{
MessageBox.Show("Error Registering","Error",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}

Sau khi ChatClient đã kết nối, nó sẽ thread ReceiveChat() để xử lý tất cả các ins và outs. ReceiveChat() thực hiện các item của giao thức ứng dụng. 

private void ReceiveChat()
{
bool keepalive = true;
while (keepalive) 
{
try
{
Byte[] buffer = new Byte[2048];
ns.Read(buffer,0,buffer.Length);
string chatter = System.Text.Encoding.ASCII.GetString(buffer);
string[] tokens = chatter.Split(new Char[]{'|'});
if (tokens[0] == "CHAT")
{
rtbChatIn.AppendText(tokens[1]);
if(logging)
logwriter.WriteLine(tokens[1]);
}
if (tokens[0] == "PRIV") {
rtbChatIn.AppendText("Private from ");
rtbChatIn.AppendText(tokens[1].Trim() );
rtbChatIn.AppendText(tokens[2] + "\r\n");
if(logging){
logwriter.Write("Private from ");
logwriter.Write(tokens[1].Trim() );
logwriter.WriteLine(tokens[2] + "\r\n");
}
}
if (tokens[0] == "JOIN")
{
rtbChatIn.AppendText(tokens[1].Trim() );
rtbChatIn.AppendText(" has joined the Chat\r\n");
if(logging)
logwriter.WriteLine(tokens[1]+" has joined the Chat");
string newguy = tokens[1].Trim(new char[]{'\r','\n'});
lbChatters.Items.Add(newguy);
}
if (tokens[0] == "GONE")
{
rtbChatIn.AppendText(tokens[1].Trim() );
rtbChatIn.AppendText(" has left the Chat\r\n");
if(logging)
logwriter.WriteLine(tokens[1]+" has left the Chat");
lbChatters.Items.Remove(tokens[1].Trim(new char[]{'\r','\n'}));
}
if (tokens[0] == "QUIT")
{
ns.Close();
clientsocket.Close();
keepalive = false;
statusBar1.Text = "Server has stopped";
connected= false;
btnSend.Enabled = false;
btnDisconnect.Enabled = false;
}
}
catch(Exception e){}
}
}

Nhấp vào nút Start Logging sẽ bắt đầu ghi tất cả các cuộc trò chuyện và tin nhắn đến tập tin văn bản. Tên tập tin được xây dựng từ ngày hiện tại và thời gian. Một thư mục phụ được gọi là "logs" sẽ tạo ra để lưu trữ các tập tin đăng nhập. 

ChatClient được hiển thị dưới đây:

chatclient_01.jpg

chatclient_02.jpg

VẤN ĐỀ: 
Truy cập vào danh sách Client nên được đồng bộ. Đừng cố gắng để sử dụng tính năng đăng nhập văn bản với nhiều Client trên cùng một máy hoặc nó có thể sẽ bị phá vỡ nếu được giao các tên cùng một tập tin. Đôi khi danh sách Client của chatters hoạt động không cập nhật đúng khi ai đó ra khỏi phòng. Tôi đã không tìm ra lý do tại sao. Nếu bạn ngắt kết nối và đăng nhập lại nó sẽ được sửa chữa. Một số ký tự đôi khi bị mất từ tập tin đăng nhập.

Nguồn bài viết: DOTNET.VN

BÌNH LUẬN BÀI VIẾT

Bài viết mới nhất

LIKE BOX

Bài viết được xem nhiều nhất

HỌC HTML