C#基于Windows服務(wù)的聊天程序(1)
本文將演示怎么通過(guò)C#開(kāi)發(fā)部署一個(gè)Windows服務(wù),該服務(wù)提供各客戶(hù)端的信息通訊,適用于局域網(wǎng)。采用TCP協(xié)議,單一服務(wù)器連接模式為一對(duì)多;多臺(tái)服務(wù)器的情況下,當(dāng)客戶(hù)端連接數(shù)超過(guò)預(yù)設(shè)值時(shí)可自動(dòng)進(jìn)行負(fù)載轉(zhuǎn)移,當(dāng)然也可手動(dòng)切換服務(wù)器,這種場(chǎng)景在實(shí)際項(xiàng)目中應(yīng)用廣泛。
簡(jiǎn)單的消息則通過(guò)服務(wù)器轉(zhuǎn)發(fā),文件類(lèi)的消息則讓客戶(hù)端自己建立連接進(jìn)行傳輸。后續(xù)功能將慢慢完善。
自定義協(xié)議:

1.新建Windows服務(wù)項(xiàng)目

2.修改配置文件添加
<appSettings> <add key="maxQueueCount" value="10"/> <add key="failoverServer" value="192.168.250.113,192.168.250.141"/> </appSettings>
說(shuō)明:maxQueueCount為最大連接數(shù),failoverServer故障轉(zhuǎn)移備用服務(wù)器(多個(gè)服務(wù)器,隔開(kāi))
3.打開(kāi)ChatService右鍵添加安裝程序,此時(shí)會(huì)自動(dòng)添加ProjectInstaller.cs文件,文件中會(huì)默認(rèn)添加serviceProcessInstaller1和serviceInstaller1兩個(gè)組件

修改serviceInstaller1和serviceProcessInstaller1的屬性信息如圖


StartType屬性說(shuō)明:
Automatic 指示服務(wù)在系統(tǒng)啟動(dòng)時(shí)將由(或已由)操作系統(tǒng)啟動(dòng)。如果某個(gè)自動(dòng)啟動(dòng)的服務(wù)依賴(lài)于某個(gè)手動(dòng)啟動(dòng)的服務(wù),則手動(dòng)啟動(dòng)的服務(wù)也會(huì)在系統(tǒng)啟動(dòng)時(shí)自動(dòng)啟動(dòng)。
Disabled 指示禁用該服務(wù),以便它無(wú)法由用戶(hù)或應(yīng)用程序啟動(dòng)。
Manual 指示服務(wù)只由用戶(hù)(使用“服務(wù)控制管理器”)或應(yīng)用程序手動(dòng)啟動(dòng)。
Account屬性說(shuō)明:
LocalService 充當(dāng)本地計(jì)算機(jī)上非特權(quán)用戶(hù)的帳戶(hù),該帳戶(hù)將匿名憑據(jù)提供給所有遠(yuǎn)程服務(wù)器。
LocalSystem 服務(wù)控制管理員使用的帳戶(hù),它具有本地計(jì)算機(jī)上的許多權(quán)限并作為網(wǎng)絡(luò)上的計(jì)算機(jī)。
NetworkService 提供廣泛的本地特權(quán)的帳戶(hù),該帳戶(hù)將計(jì)算機(jī)的憑據(jù)提供給所有遠(yuǎn)程服務(wù)器。
User 由網(wǎng)絡(luò)上特定的用戶(hù)定義的帳戶(hù)。如果為 ServiceProcessInstaller.Account 成員指定 User,則會(huì)使系統(tǒng)在安裝服務(wù)時(shí)提示輸入有效的用戶(hù)名和密碼,除非您為 ServiceProcessInstaller 實(shí)例的 Username 和 Password 這兩個(gè)屬性設(shè)置值。
4.完成以后打開(kāi)ChatService代碼,重寫(xiě)OnStart和OnStop方法(即服務(wù)的啟動(dòng)和停止方法)。若要重寫(xiě)其它方法請(qǐng)?jiān)赟erviceBase中查看。
5.在項(xiàng)目中添加服務(wù)注冊(cè)和卸載腳本文件
Install.bat @echo off path %SystemRoot%\Microsoft.NET\Framework\v4.0.30319;%path% installutil %~dp0\WindowsChat.exe %SystemRoot%\system32\sc failure "ChatService" reset= 30 actions= restart/1000 pause @echo on Uninstall.bat @echo off path %SystemRoot%\Microsoft.NET\Framework\v4.0.30319;%path% installutil -u %~dp0\WindowsChat.exe pause @echo on
說(shuō)明:%~dp0 表示bat文件所在的目錄
文件屬性選擇 始終復(fù)制-內(nèi)容,這樣才能生成到輸出文件夾中

6.回到上面的重寫(xiě)OnStart和OnStop方法
創(chuàng)建一個(gè)SocketHelper類(lèi)
namespace WindowsChat
{
public delegate void WriteInfo(string info);
public class SocketHelper
{
#region 構(gòu)造函數(shù)
public SocketHelper()
{
}
public SocketHelper(WriteInfo method)
{
this.method = method;
}
#endregion
public static Socket LocalSocket = null;
private object lockObj = new object();
public static List<Socket> Clients = new List<Socket>();
private WriteInfo method = null;
/// <summary>
/// 創(chuàng)建Socket
/// </summary>
/// <param name="port">端口默認(rèn) 11011</param>
/// <param name="backlog">The maximum length of the pending connections queue.</param>
/// <returns></returns>
public Socket Create(int port = 11011, int backlog = 100)
{
if (LocalSocket == null)
{
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, port);//本機(jī)預(yù)使用的IP和端口
LocalSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
LocalSocket.Bind(ipEndPoint);
LocalSocket.Listen(backlog);
}
return LocalSocket;
}
/// <summary>
/// 查找客戶(hù)端連接
/// </summary>
/// <param name="id">標(biāo)識(shí)</param>
/// <returns></returns>
private Socket FindLinked(string id)
{
foreach (var item in Clients)
{
if (item.RemoteEndPoint.ToString() == id)
return item;
}
return null;
}
/// <summary>
/// 接受遠(yuǎn)程連接
/// </summary>
public void Accept()
{
if (LocalSocket != null)
{
while (true)
{
Socket client = LocalSocket.Accept();
Thread thread = new Thread(new ParameterizedThreadStart(Revice));
thread.Start(client);
WriteLog("客戶(hù)端:" + client.RemoteEndPoint.ToString() + " 接入");
lock (lockObj)
{
Clients.Add(client);
}
BroadCast("ADD|" + client.RemoteEndPoint.ToString());
}
}
}
/// <summary>
/// 日志
/// </summary>
/// <param name="info">信息</param>
private void WriteLog(string info)
{
using (FileStream fs = new FileStream("C:\\chatservice.txt", FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
{
using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8))
{
sw.WriteLine(info);
}
}
if (method != null)
{
method(info);
}
}
/// <summary>
/// 廣播
/// </summary>
/// <param name="info">信息</param>
public void BroadCast(string info)
{
foreach (var item in Clients)
{
try
{
item.Send(Encoding.UTF8.GetBytes(info));
}
catch (Exception ex)
{
WriteLog(item.RemoteEndPoint.ToString() + ex.Message);
continue;
}
}
}
/// <summary>
/// 介紹信息
/// </summary>
/// <param name="client"></param>
public void Revice(object client)
{
Socket param = client as Socket;
var remoteName = param.RemoteEndPoint.ToString();
if (param != null)
{
int res = 0;
while (true)
{
byte[] buffer = new byte[10240];
int size = param.ReceiveBufferSize;
try
{
res = param.Receive(buffer);
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.ConnectionReset)
{
Clients.Remove(param);
WriteLog("客戶(hù)端:" + remoteName + "斷開(kāi)連接1");
BroadCast("REMOVE|" + remoteName);
param.Close();
return;
}
}
if (res == 0)
{
Clients.Remove(param);
WriteLog("客戶(hù)端:" + remoteName + "斷開(kāi)連接2");
BroadCast("REMOVE|" + remoteName);
param.Close();
return;
}
var clientMsg = Encoding.UTF8.GetString(buffer, 0, res);
WriteLog(string.Format("收到客戶(hù)端{(lán)0}命令:{1}", remoteName, clientMsg));
if (clientMsg == "GETALL")
{
StringBuilder sb = new StringBuilder();
foreach (var item in Clients)
{
sb.AppendFormat("{0}|", item.RemoteEndPoint.ToString());
}
param.Send(Encoding.UTF8.GetBytes("ALL|" + sb.ToString()));
}
else if (clientMsg == "OFFLINE")
{
if (Clients.Contains(param))
{
Clients.Remove(param);
WriteLog("客戶(hù)端:" + remoteName + "斷開(kāi)連接2");
BroadCast("REMOVE|" + remoteName);
param.Close();
return;
}
}
else if (clientMsg.StartsWith("TRANST|"))
{
var msgs = clientMsg.Split('|');
var toSocket = FindLinked(msgs[1]);
if (toSocket != null)
{
WriteLog(remoteName + "發(fā)給" + msgs[1] + "的消息" + msgs[2]);
toSocket.Send(Encoding.UTF8.GetBytes("TRANSF|" + remoteName + "|" + msgs[2]));
}
}
}
}
}
}
}
重寫(xiě)OnStart和OnStop方法
public partial class ChatService : ServiceBase
{
SocketHelper helper;
Thread mainThread;
public ChatService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
if (helper == null)
{
helper = new SocketHelper();
}
helper.Create();
mainThread = new Thread(new ThreadStart(helper.Accept));
mainThread.IsBackground = true;
mainThread.Start();
}
protected override void OnStop()
{
helper.BroadCast("SHUTDOWN");
}
}
至此一個(gè)簡(jiǎn)易的Windows服務(wù)的聊天服務(wù)端開(kāi)發(fā)完成,后續(xù)會(huì)在這基礎(chǔ)上進(jìn)行擴(kuò)展。
運(yùn)行install.bat(以管理員身份運(yùn)行)如圖

7.運(yùn)行 services.msc查找到ChatService服務(wù),能正常啟動(dòng)停止說(shuō)明部署成功!

當(dāng)然你也可以將InstallUtil.exe拷貝到執(zhí)行文件所在目錄,比如c:\bin\
則部署腳本為
cd c:\bin\
InstallUtil WindowsChat.exe
卸載腳本
InstallUtil -u WindowsChat.exe
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- c# 實(shí)現(xiàn)語(yǔ)音聊天的實(shí)戰(zhàn)示例
- C# Socket編程實(shí)現(xiàn)簡(jiǎn)單的局域網(wǎng)聊天器的示例代碼
- C#使用Socket實(shí)現(xiàn)服務(wù)器與多個(gè)客戶(hù)端通信(簡(jiǎn)單的聊天系統(tǒng))
- C#使用Socket實(shí)現(xiàn)局域網(wǎng)聊天
- 基于c#用Socket做一個(gè)局域網(wǎng)聊天工具
- 分享一個(gè)C#編寫(xiě)簡(jiǎn)單的聊天程序(詳細(xì)介紹)
- C#制作簡(jiǎn)單的多人在線(xiàn)即時(shí)交流聊天室
- C#基于UDP實(shí)現(xiàn)的P2P語(yǔ)音聊天工具
- C#實(shí)現(xiàn)簡(jiǎn)單聊天程序的方法
- c#實(shí)現(xiàn)多線(xiàn)程局域網(wǎng)聊天系統(tǒng)
- C#聊天程序服務(wù)端與客戶(hù)端完整實(shí)例代碼
- c#多線(xiàn)程網(wǎng)絡(luò)聊天程序代碼分享(服務(wù)器端和客戶(hù)端)
- c#基于WinForm的Socket實(shí)現(xiàn)簡(jiǎn)單的聊天室 IM
相關(guān)文章
C#環(huán)形隊(duì)列的實(shí)現(xiàn)方法詳解
這篇文章先是簡(jiǎn)單的給大家介紹了什么是環(huán)形隊(duì)列和環(huán)形隊(duì)列的優(yōu)點(diǎn),然后通過(guò)實(shí)例代碼給大家介紹C#如何實(shí)現(xiàn)環(huán)形隊(duì)列,有需要的朋友們可以參考借鑒,下面來(lái)一起看看吧。2016-09-09
C#中BitConverter.ToUInt16()和BitConverter.ToString()的簡(jiǎn)單使用
這篇文章主要介紹了C#中BitConverter.ToUInt16()和BitConverter.ToString()的簡(jiǎn)單使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
C# List 并發(fā)丟數(shù)據(jù)問(wèn)題原因及解決方案
這篇文章主要介紹了C# List 并發(fā)丟數(shù)據(jù)問(wèn)題原因及解決方案,幫助大家更好的理解和使用c#,感興趣的朋友可以了解下2021-02-02
C#.net編程創(chuàng)建Access文件和Excel文件的方法詳解
這篇文章主要介紹了C#.net編程創(chuàng)建Access文件和Excel文件的方法,結(jié)合實(shí)例形式總結(jié)分析了C#創(chuàng)建Access與Excel文件的幾種常用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06
Unity3D Shader實(shí)現(xiàn)掃描顯示效果
這篇文章主要為大家詳細(xì)介紹了Unity3D Shader實(shí)現(xiàn)掃描顯示效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03
C#判斷字符串中是否包含指定字符串及contains與indexof方法效率問(wèn)題
這篇文章主要介紹了C#判斷字符串中是否包含指定字符串及contains與indexof方法效率問(wèn)題 ,文中給大家列舉通過(guò)兩種方法來(lái)判斷,需要的朋友可以參考下2018-10-10
C#實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能
給大家分享用C#寫(xiě)出一個(gè)計(jì)算機(jī)功能的全部代碼分享,有興趣的朋友可以跟著做一下。2018-03-03
C# winform登陸框驗(yàn)證碼的實(shí)現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了C# winform登陸框驗(yàn)證碼的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
C#實(shí)現(xiàn)讀取USB轉(zhuǎn)串口參數(shù)并顯示在ComboBox
在很多應(yīng)用程序中,尤其是那些需要與外部硬件通信的程序中,自動(dòng)檢測(cè)和讀取串口參數(shù)是一個(gè)非常有用的功能,下面我們就來(lái)看看如何在C#中實(shí)現(xiàn)這一功能吧2024-01-01

