?? listener.cs
字號:
?using System;
using System.Collections;
using System.Collections.Specialized;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Net;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
namespace EasyChat_Server
{
class Listener
{
#region 字段定義
/// <summary>
/// 服務器程序使用的端口,默認為8888
/// </summary>
private int _port = 8888;
/// <summary>
/// 接收數據緩沖區大小64K
/// </summary>
private const int _maxPacket = 64 * 1024;
/// <summary>
/// 服務器端的監聽器
/// </summary>
private TcpListener _tcpl = null;
/// <summary>
/// 保存所有客戶端會話的哈希表
/// </summary>
private Hashtable _transmit_tb = new Hashtable();
#endregion
#region 服務器方法
/// <summary>
/// 關閉監聽器并釋放資源
/// </summary>
public void Close()
{
if (_tcpl != null)
{
_tcpl.Stop();
}
//關閉客戶端連接并清理資源
if (_transmit_tb.Count != 0)
{
foreach (Socket session in _transmit_tb.Values)
{
session.Shutdown(SocketShutdown.Both);
}
_transmit_tb.Clear();
_transmit_tb = null;
}
}
/// <summary>
/// 配置監聽端口號
/// </summary>
public void GetConfig()
{
string portParam;
Console.Write("請輸入監聽端口,直接回車則接受默認端口8888: ");
portParam = Console.ReadLine();
if (portParam != string.Empty)
{
if (!int.TryParse(portParam, out _port) || _port < 1023 || _port > 65535)
{
_port = 8888;
Console.WriteLine("端口號不合法,默認端口號被接受!");
}
}
}
/// <summary>
/// 序列化在線列表,向客戶端返回序列化后的字節數組
/// </summary>
/// <returns>序列化后的字節數組</returns>
private byte[] SerializeOnlineList()
{
StringCollection onlineList = new StringCollection();
foreach (object o in _transmit_tb.Keys)
{
onlineList.Add(o as string);
}
IFormatter format = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
format.Serialize(stream, onlineList);
byte[] ret = stream.ToArray();
stream.Close();
return ret;
}
/// <summary>
/// 提取命令
/// 格式為兩個一位整數拼接成的字符串。
/// 第一位為0表示客戶機向服務器發送的命令,為1表示服務器向客戶機發送的命令。
/// 第二位表示命令的含義,具體如下:
/// "01"-離線
/// "02"-請求在線列表
/// "03"-請求對所有人閃屏振動
/// "04"-請求對指定用戶閃屏振動
/// "05"-請求廣播消息
/// default-轉發給指定用戶
/// </summary>
/// <param name="s">要解析的包含命令的byte數組,只提取前兩個字節</param>
/// <returns>拼接成的命令</returns>
private string DecodingBytes(byte[] s)
{
return string.Concat(s[0].ToString(), s[1].ToString());
}
/// <summary>
/// 線程執行體,轉發消息
/// </summary>
/// <param name="obj">傳遞給線程執行體的用戶名,用以與用戶通信</param>
private void ThreadFunc(object obj)
{
//通過轉發表得到當前用戶套接字
Socket clientSkt = _transmit_tb[obj] as Socket;
while (true)
{
try
{
//接受第一個數據包。
//僅有兩種情況,一為可以識別的命令格式,否則為要求轉發給指定用戶的用戶名。
//這里的實現不夠優雅,但不失為此簡單模型的一個解決之道。
byte[] _cmdBuff = new byte[128];
clientSkt.Receive(_cmdBuff);
string _cmd = DecodingBytes(_cmdBuff);
/// "01"-離線
/// "02"-請求在線列表
/// "03"-請求對所有人閃屏振動
/// "04"-請求對指定用戶閃屏振動
/// "05"-請求廣播消息
/// default-轉發給指定用戶
switch (_cmd)
{
case "01":
{
_transmit_tb.Remove(obj);
string svrlog = string.Format("[系統消息]用戶 {0} 在 {1} 已斷開... 當前在線人數: {2}\r\n\r\n", obj, DateTime.Now, _transmit_tb.Count);
Console.WriteLine(svrlog);
//向所有客戶機發送系統消息
foreach (DictionaryEntry de in _transmit_tb)
{
string _clientName = de.Key as string;
Socket _clientSkt = de.Value as Socket;
_clientSkt.Send(Encoding.Unicode.GetBytes(svrlog));
}
Thread.CurrentThread.Abort();
break;
}
case "02":
{
byte[] onlineBuff = SerializeOnlineList();
//先發送響應信號,用于客戶機的判斷,"11"表示服務發給客戶機的更新在線列表的命令
clientSkt.Send(new byte[] { 1, 1 });
clientSkt.Send(onlineBuff);
break;
}
case "03":
{
string displayTxt = string.Format("[系統提示]用戶 {0} 向您發送了一個閃屏振動。\r\n\r\n", obj);
foreach (DictionaryEntry de in _transmit_tb)
{
string _clientName = de.Key as string;
Socket _clientSkt = de.Value as Socket;
if (!_clientName.Equals(obj))
{
//先發送響應信號,用于客戶機的判斷,"12"表示服務發給客戶機的閃屏振動的命令
_clientSkt.Send(new byte[] { 1, 2 });
_clientSkt.Send(Encoding.Unicode.GetBytes(displayTxt));
}
}
break;
}
case "04":
{
string _receiver = null;
byte[] _receiverBuff = new byte[128];
clientSkt.Receive(_receiverBuff);
_receiver = Encoding.Unicode.GetString(_receiverBuff).TrimEnd('\0');
string displayTxt = string.Format("[系統提示]用戶 {0} 向您發送了一個閃屏振動。\r\n\r\n", obj);
//通過轉發表查找接收方的套接字
if (_transmit_tb.ContainsKey(_receiver))
{
Socket receiverSkt = _transmit_tb[_receiver] as Socket;
receiverSkt.Send(new byte[] { 1, 2 });
receiverSkt.Send(Encoding.Unicode.GetBytes(displayTxt));
}
else
{
string sysMessage = string.Format("[系統消息]您剛才的閃屏振動沒有發送成功。\r\n可能原因:用戶 {0} 已離線或者網絡阻塞。\r\n\r\n", _receiver);
clientSkt.Send(Encoding.Unicode.GetBytes(sysMessage));
}
break;
}
case "05":
{
byte[] _msgBuff = new byte[_maxPacket];
clientSkt.Receive(_msgBuff);
foreach (DictionaryEntry de in _transmit_tb)
{
string _clientName = de.Key as string;
Socket _clientSkt = de.Value as Socket;
if (!_clientName.Equals(obj))
{
_clientSkt.Send(_msgBuff);
}
}
break;
}
default:
{
//以上都不是則表明第一個包是接收方用戶名,繼續接受第二個消息正文數據包
string _receiver = Encoding.Unicode.GetString(_cmdBuff).TrimEnd('\0');
byte[] _packetBuff = new byte[_maxPacket];
clientSkt.Receive(_packetBuff);
if (_transmit_tb.ContainsKey(_receiver))
{
//通過轉發表查找接收方的套接字
Socket receiverSkt = _transmit_tb[_receiver] as Socket;
receiverSkt.Send(_packetBuff);
}
else
{
string sysMessage = string.Format("[系統消息]您剛才的內容沒有發送成功。\r\n可能原因:用戶 {0} 已離線或者網絡阻塞。\r\n\r\n", _receiver);
clientSkt.Send(Encoding.Unicode.GetBytes(sysMessage));
}
break;
}
}
}
catch (SocketException)
{
_transmit_tb.Remove(obj);
string svrlog = string.Format("[系統消息]用戶 {0} 的客戶端在 {1} 意外終止!當前在線人數:{2}\r\n\r\n", obj, DateTime.Now, _transmit_tb.Count);
Console.WriteLine(svrlog);
//向所有客戶機發送系統消息
foreach (DictionaryEntry de in _transmit_tb)
{
string _clientName = de.Key as string;
Socket _clientSkt = de.Value as Socket;
_clientSkt.Send(Encoding.Unicode.GetBytes(svrlog));
}
Console.WriteLine();
Thread.CurrentThread.Abort();
}
}
}
/// <summary>
/// 啟動監聽,輪詢監聽客戶機請求并將客戶端套接字存入轉發表
/// </summary>
public void StartUp()
{
IPAddress _ip = Dns.GetHostAddresses(Dns.GetHostName())[0];
_tcpl = new TcpListener(_ip, _port);
_tcpl.Start();
Console.WriteLine("服務器已啟動,正在監聽...\n");
Console.WriteLine(string.Format("服務器IP:{0}\t端口號:{1}\n", _ip, _port));
while (true)
{
byte[] packetBuff = new byte[_maxPacket];
Socket newClient = _tcpl.AcceptSocket();
newClient.Receive(packetBuff);
string userName = Encoding.Unicode.GetString(packetBuff).TrimEnd('\0');
//驗證是否為唯一用戶
if (_transmit_tb.Count != 0 && _transmit_tb.ContainsKey(userName))
{
newClient.Send(Encoding.Unicode.GetBytes("cmd::Failed"));
continue;
}
else
{
newClient.Send(Encoding.Unicode.GetBytes("cmd::Successful"));
}
//將新連接加入轉發表并創建線程為其服務
_transmit_tb.Add(userName, newClient);
string svrlog = string.Format("[系統消息]新用戶 {0} 在 {1} 已連接... 當前在線人數: {2}\r\n\r\n", userName, DateTime.Now, _transmit_tb.Count);
Console.WriteLine(svrlog);
Thread clientThread = new Thread(new ParameterizedThreadStart(ThreadFunc));
clientThread.Start(userName);
//向所有客戶機發送系統消息
foreach (DictionaryEntry de in _transmit_tb)
{
string _clientName = de.Key as string;
Socket _clientSkt = de.Value as Socket;
if (!_clientName.Equals(userName))
{
_clientSkt.Send(Encoding.Unicode.GetBytes(svrlog));
}
}
}
}
#endregion
}
}
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -