網絡編程之及時通信程序(聊天室)--------(二)服務器端搭建


       在上一節中已經給大家講述了即時通信程序的通信流程,以及相應的通信格式,在這一節中我會帶領大家搭建即時通信程序的服務器端。

      在這一節中我們用到的知識有TCPListener、套接字(Socket)多線程(Thread)、文件流(FileStream)、、Dictionary<T,T>集合。

      首先新建一個WinForm應用程序,頁面布局如下:

頁面布局:兩個單行文本框分別為服務器監聽的IP(txtIP)和端口(txtPort),

          一個多行文本框(txtServer)來顯示服務器運行情況,

          一個ListBox(onLineListServer)用來顯示在線用戶

兩個按鈕分別負責啟動服務(btnqd)和退出(btnexit)

首先需要創建一些全局變量如下:

#region  Members

             private const int MAX=100//設置最大的監聽隊列,超過該隊列之后服務器不再接收請求        
       //創建全局的TCPListener的實例,專門負責客戶端的連接請求   
        private TcpListener tcpListener;
        //臨時套接字,臨時存儲由tcpListener創建的負責通信的套接字
        private Socket lsSocket;
        //用來存儲負責和客戶端通信的套接字鍵為客戶端登錄名,值為負責與客戶端通信的套接字,所有的鍵即為在線用戶,和onLineListServer的Item一一對應
        public Dictionary<string, Socket> socketDictionary = new Dictionary<string, Socket>();

  

接下來就是需要在構造函數里做一些處理了,在構造函數里,我們主要是加載一個皮膚文件,同時獲取本機的IP,指定監聽端口,這里要注意盡量把指定端口設置大一點,防止和特定端口進行沖突。

代碼如下:

public ChatServer()
        {
            InitializeComponent();
            skinEngine1.SkinFile = "SteelBlack.ssk";//加載皮膚文件
            txtServerIP.Text = Dns.GetHostByName(Dns.GetHostName()).AddressList[0].ToString();//動態獲取本機IP
            txtServerPort.Text = "11615";
            TextBox.CheckForIllegalCrossThreadCalls = false;//取消TextBox的跨線程操作檢驗,即允許在非UI線程中訪問TextBox控件
            ListBox.CheckForIllegalCrossThreadCalls = false;//和上面TextBox效果一樣
        }

  

以上這些准備工作做好之后,我們就可以啟動服務了,所以在啟動服務按鈕的Click事件里我們需要將tcpListener給實例化,同時要打開監聽服務,開始監聽客戶端的請求。這里就遇到了一個問題,因為我們不知道客戶端什么時候請求,所以這個時候我們就需要新建一個線程該線程專門負責監聽客戶端的請求,我們稱之為監聽線程。

代碼如下:

private void btnqd_Click(object sender, EventArgs e)
        {
            tcpListener=new TcpListener(IPAddress.Parse(txtServerIP.Text),Convert.ToInt32(txtServerPort.Text));
            tcpListener.Start();
            Thread listenThread=new Thread(new ThreadStart(Listening));
            listenThread.Start();
            listenThread.IsBackground=true;
            txtServer.Text += "服務器已啟動!\r\n";
        }

  在我們創建的監聽線程里執行了Listening函數,在Listening函數里我們會用tcpListener創建一個專門負責監聽的套接字,並將該套接字存儲在臨時變量lsScoket中,這個時候我們會再創建一個線程,專門來負責跟客戶端的通信,我們稱之為通信線程。每監聽到一個客戶端的請求,我們都會創建一個相應的通信套接字,和一個專門負責通信的通信線程。

代碼如下:

private void Listening()
        {
            while (true)
            {
                Socket comminuteSocket = tcpListener.AcceptSocket();
                this.lsSocket = comminuteSocket;
                if (socketDictionary.Count < 100)//如果客戶端請求數量大於100,則將該請求的連接關閉,即服務端拒絕為其服務
                {
                    Thread serviceThread = new Thread(new ThreadStart(Service));
                    serviceThread.IsBackground = true;
                    serviceThread.Start();
                }
                else
                {
                    this.lsSocket.Close();
                }
            }
   
     

  在通信線程里,我們才真正的涉及到跟客戶端的通信,會將客戶端發送的消息進行解析和處理,我們在上一節中定制了特定的通信格式,在這里我再重申一下。

1、首先對接收過來的字節數組進行判斷,如果接收過來的字節數組的首字節buffer[0]==0

則說明客戶端發送過來的是文件,這個時候我們只需要遍歷在線人員名單,也就是遍歷socketDictionary的鍵,並且通過與鍵對應的Socket類型的通信套接字將該文件字節數組轉發給每一個在線的客戶端。

2、接受過來的字節數組的首字節buffer[0]!=0,即說明客戶端發送過來的是消息字符串,這個時候我們就要對該消息字符串做進一步的解析:

    2.1、"On|上線者名稱|":如果接受的消息是這樣的格式的話,則服務器需要對該字符串進行分割,取上線者名稱,並對其進行判斷,如何socketDictionary字典中沒有這個鍵值,則將其加入onLineListServer,將其對應的通信套接字加入socketDictionary字典中,如果有這樣的鍵存在,則自動在將鍵增加一個字符“1”,並向所有的在線人員發送通知,格式內容如下:"OnLine|'上線者名稱'上線啦!|",如果要通知所有已在線的人員更新在線人員名單,格式內容如下:"Off|name1、name2、name3、...|

    2.2、"MSGALL|發送者名字|發送內容|",服務器遍歷在線人員名單並向所有在線人員發送信息"'發送者名字'說:'發送內容'"

    2.3、"MSG|接受者名字|發送者名字|發送內容|",服務器先從socketDictionary字典中找到與接受者名字一樣的key所對應的通信套接字,並利用該通信套接字向客戶端發送信息,格式內容如下:"'發送者名字'說:'發送內容'"

    2.4、"Off|發送者名字|",服務器首先從socketDictionary字典中找到與接受者名字一樣的key所對應的通信套接字,將其從中刪除,並且將其名字從在線列表中移除,並向所有在線人員發送新的在線人員名單,進行在線人員更新,格式內容如下:"Off|nam1、name2、name3......|"

代碼如下:

 private void Service()
        {
            byte[] buffer = new byte[1024 * 1024 * 10];
            Socket commSocket = this.lsSocket;
            bool connected = true;
            while (connected)
            {
                //接受傳輸過來的字節流
                int length = commSocket.Receive(buffer);
                if (buffer[0] == 0)//如果緩沖區字節數組的首字節值為0,則為文件,直接保存
                {
                    for (int i = 0; i < onlineListServer.Items.Count; i++)
                    {
                        socketDictionary[onlineListServer.Items[i].ToString()].Send(buffer);
                    }
                }
                else
                {
                    string Msg = Encoding.Default.GetString(buffer);
                    string[] msg = Msg.Split(new char[] { '|' });
                    //當有新的成員上線的時候,在登錄連接時向服務器發送一個信息,告訴服務器****上線了,然后服務器會像所有在線人員發送信息:
//“*****上線了”,並且發送新的在線人員名單,讓所有在線人員更新各自的在線人員名單。 if (msg[0] == "ON") { string online=null; if (!socketDictionary.ContainsKey(msg[1]))//如果Socket字典中不存在該鍵,則立刻添加 { online= msg[1] + "上線啦!\r\n"; commSocket.Send(Encoding.Default.GetBytes("0")); socketDictionary.Add(msg[1], lsSocket); onlineListServer.Items.Add(msg[1]); txtServer.Text += msg[1]; } else { commSocket.Send(Encoding.Default.GetBytes("1"));//如果該Socket字典中存在該鍵,則將該鍵增加一個字符‘1’之后再添加,防止重名沖突 socketDictionary.Add(msg[1]+"1",lsSocket); onlineListServer.Items.Add(msg[1]+"1"); txtServer.Text+=msg[1]+"1"; online=msg[1] + "1上線啦!\r\n"; } txtServer.Text += "上線啦!\r\n"; string names = ""; for (int i = 0; i < onlineListServer.Items.Count; i++) { names += onlineListServer.Items[i].ToString() + "、"; } names += "|"; //向所有在線人員發送“***上線了”消息,並且發送最新在線人員名單,通知其更新在線人員信息。 for (int i = 0; i < onlineListServer.Items.Count; i++) { socketDictionary[onlineListServer.Items[i].ToString()].Send(Encoding.Default.GetBytes("OnLine|" + online)); Thread.Sleep(100); socketDictionary[onlineListServer.Items[i].ToString()].Send(Encoding.Default.GetBytes("Off|" + names)); } } //向所有在線人員群發信息 else if (msg[0] == "MSGALL") { foreach (string receiever in socketDictionary.Keys) { socketDictionary[receiever].Send(Encoding.Default.GetBytes(msg[1] + "說:" + msg[2])); } } //向指定的某個人發送信息 else if (msg[0] == "MSG") { socketDictionary[msg[1]].Send(Encoding.Default.GetBytes(msg[2] + "說:" + msg[3])); } else if (msg[0] == "Off") { string names = ""; socketDictionary.Remove(msg[1]); onlineListServer.Items.Remove(msg[1]); for (int i = 0; i < onlineListServer.Items.Count; i++) { names += onlineListServer.Items[i].ToString() + "、"; } names += "|"; for (int i = 0; i < onlineListServer.Items.Count; i++) { socketDictionary[onlineListServer.Items[i].ToString()].Send(Encoding.Default.GetBytes("Off|" + names)); } } } } }

  這一節到這里,不知不覺間我們已經把即時通信程序的服務端給搭建好了。在這一節里你需要明白的是服務器端的工作流程,即我們常說的"三次握手",第一次客戶端向服務器端發請求建立連接,第二次服務端創建專門負責通信的套接字,第三次專門負責通信的套接字跟客戶端進行通信。同時我們還要理解掌握它是如何利用多線程進行客戶端監聽、通信的。在這里我們自己定義了一些通信格式,這些通信格式就跟HTTP協議一樣,只不過我們定義的格式僅在我們小范圍內適用。

好了這一節就到這里了,希望可以對大家有所幫助,也還請大家多多指點。在下一節中我會帶領大家把客戶端給實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM