基於Socket的網絡聊天室編程(第一版)


一:什么是套接字

在網絡編程中最常用的方案便是Client/Server (客戶機/服務器)模型。在這種方案中客戶應用程序向服務器程序請求服務。一個服務程序通常在一個眾所周知的地址監聽對服務的請求,也就是說,服務進程一直處於休眠狀態,直到一個客戶向這個服務的地址提出了連接請求。在這個時刻,服務程序被"驚醒"並且為客戶提供服務-對客戶的請求作出適當的反應。為了方便這種Client/Server模型的網絡編程,90年代初,由Microsoft聯合了其他幾家公司共同制定了一套WINDOWS下的網絡編程接口,即Windows Sockets規范,它不是一種網絡協議,而是一套開放的、支持多種協議的Windows下的網絡編程接口。現在的Winsock已經基本上實現了與協議無關,你可以使用Winsock來調用多種協議的功能,但較常使用的是TCP/IP協議。Socket實際在計算機中提供了一個通信端口,可以通過這個端 口與任何一個具有Socket接口的計算機通信。應用程序在網絡上傳輸,接收的信息都通過這個Socket接口來實現。

簡單的來說,socket非常類似於電話插座。以一個電話網為例。電話的通話雙方相當於相互通信的2個程序,電話號碼就是IP地址。任何用戶在通話之前,首先要占有一部電話機,相當於申請一個socket;同時要知道對方的號碼,相當於對方有一個固定的socket。然后向對方撥號呼叫,相當於發出連接請求。對方假如在場並空閑,拿起電話話筒,雙方就可以正式通話,相當於連接成功。雙方通話的過程,是一方向電話機發出信號和對方從電話機接收信號的過程,相當於向socket發送數據和從socket接收數據。通話結束后,一方掛起電話機相當於關閉socket,撤消連接。

二:Socket的一般應用模式(Server-Client)

客戶端Client Socket連接服務端指定的端口(負責接收和發送服務端消息)

  • 必須指定要連接的服務端地址和斷口。
  • 通過創建一個Socket對象來初始化一個到服務端的連接。

服務端Welcoming Socket監聽到客戶端連接,創建Connection Socket(負責和客戶端通訊)

  • 一個負責接監聽客戶端連接的套接字
  • 每成功接收到一個客戶端連接便在服務端產生一個對應Socket

Socket的通訊過程:

客戶端:

  1. 申請一個Socket
  2. 連接到指定服務器(指明了IP地址和端口號)

服務器端:

  1. 申請一個Socket
  2. 綁定到一個IP地址和端口上
  3. 開啟偵聽,等待接受連接

 

socket通信的基本流程圖:

 三:網絡聊天室原理與實現-服務端:

  • 開始監聽客戶連接 -WatchConnection()
        Thread threadWatchPort = null;//監聽端口線程
        Socket socketWatchPort = null;
        //存儲客戶端連接的信息
        Dictionary<string, ClientConnection> dictConnections = new Dictionary<string, ClientConnection>();
        //IP地址
        IPAddress address = null;
        //IP節點
        IPEndPoint endpoint = null;
        /// <summary>
        /// 開始監聽用戶連接
        /// </summary>
        /// 函數原型是int PASCAL listen(SOCKET,int);
        ///其中第二參數的含義樓主理解錯誤,並非最大可連接數,而是最多可緩存的監聽個數。
        ///這里listen()維護一個隊列,每一個請求監聽,但尚未被accept()的請求都放在隊列里,而一旦監聽被accept()之后,該監聽就從隊列移走了。
        private void WatchConnection()
        {
            try
            {
                //創建IP地址
                address = IPAddress.Parse(txtIP.Text);
                //創建IP節點(包含IP和端口)
                endpoint = new IPEndPoint(address, int.Parse(txtPort.Text));
                //創建一個監聽套接字(基於TCP的流式套接字)
                socketWatchPort = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //將套接字綁定到主機上某個端口
                socketWatchPort.Bind(endpoint);
                //能同時處理的連接數
                socketWatchPort.Listen(10);
                threadWatchPort = new Thread(WatchPort);
                threadWatchPort.Name = "threadWatchPort";
                //設為后台線程,當所有前台線程停止后會自動關閉
                threadWatchPort.IsBackground = true;
                threadWatchPort.Start();
                ShowMsg("服務器啟動完畢,等待客戶端連接");
            }
            catch (Exception ex)
            {
                ShowErrorMsg("",ex);
            }
        }
  • 在另一線程監聽指定端口- WatchPort()

 

private void WatchPort()
        {
            while (true)
            {
                try
                {
                    //cSok和客戶端通信套接字
                    Socket cSok = socketWatchPort.Accept();
                    ClientConnection conn = new ClientConnection(this, cSok);
                    ShowMsg("客戶端" + cSok.RemoteEndPoint.ToString() + "連接成功");
                    dictConnections.Add(cSok.RemoteEndPoint.ToString(), conn);
                    AddClientToList(cSok.RemoteEndPoint.ToString());
                }
                catch (Exception ex)
                {
                    ShowErrorMsg("",ex);
                    //break;
                }
            }
        }

 

  • 和客戶端連接的通道類:

 

public class ClientConnection
    {
        Thread threadClient = null;
        Socket socket = null;
        FrmMain frmMain = null;
        bool doesClose = false;
        public ClientConnection(FrmMain frmMain,Socket socket)
        {
            this.frmMain = frmMain;
            this.socket = socket;
            threadClient = new Thread(WatchClientMsg);
            threadClient.IsBackground = true;
            threadClient.Start();   
        }
        #region 監聽客戶端消息 -WatchClientMsg();
        /// <summary>
        /// 監聽客戶端消息
        /// </summary>
        private void WatchClientMsg()
        {
            while (!doesClose)
            {
                try
                {
                    byte[] byteMsgRec = new byte[1024 * 1024 * 4];
                    int length = socket.Receive(byteMsgRec, byteMsgRec.Length, SocketFlags.None);
                    if (length > 0)
                    {
                        string strMsgRec = Encoding.UTF8.GetString(byteMsgRec, 1, length - 1);
                        ShowMsg(socket.RemoteEndPoint.ToString() + "說:" + strMsgRec);
                    }
                }
                catch (Exception ex)
                {
                    if (socket!=null)
                    {
                        ShowErr("客戶端"+socket.RemoteEndPoint.ToString()+"斷開連接:",ex);
                        frmMain.RemoveListItem(socket.RemoteEndPoint.ToString());
                        break;
                    }
                    //
                }
            }
        }
        #endregion

        #region 發送窗口抖動 SendShake()
        public void SendShake()
        {
            try
            {
                byte[] finalByte = new byte[1];
                finalByte[0] = 2;
                socket.Send(finalByte);
            }
            catch (Exception ex)
            {
                ShowErr("SendShake()",ex);
            }
        }
        #endregion

        #region 發送消息 -SendMsg(string msg)
        /// <summary>
        /// 發送消息 標志:第一位是:0
        /// </summary>
        /// <param name="msg"></param>
        public void SendMsg(string msg)
        {
            try
            {
                byte[] msgSendByte = Encoding.UTF8.GetBytes(msg);
                byte[] finalByte = new byte[msgSendByte.Length + 1];
                finalByte[0] = 0;
                Buffer.BlockCopy(msgSendByte, 0, finalByte, 1, msgSendByte.Length);
                socket.Send(finalByte);
            }
            catch (Exception ex)
            {
                ShowErr("SendMsg(string msg)",ex);
                throw;
            }

        }
        #endregion

        #region 發送文件 -SendFile(string fileName)
        /// <summary>
        /// 發送文件 標記:第一位為1
        /// </summary>
        /// <param name="fileName">文件路徑</param>
        public void SendFile(string fileName)
        {
            FileStream fs = null;
            try
            {
                fs = new FileStream(fileName, FileMode.Open);
                byte[] byteFile = new byte[1024 * 1024 * 5];
                int length = fs.Read(byteFile, 0, byteFile.Length);
                if (length > 0)
                {
                    byte[] byteFinalFile = new byte[length + 1];
                    byteFinalFile[0] = 1;
                    Buffer.BlockCopy(byteFile, 0, byteFinalFile, 1, length);
                    socket.Send(byteFinalFile);
                }
            }
            catch (Exception ex)
            {
                ShowErr("SendFile(string fileName)", ex);
            }
            finally
            {
                fs.Close();
            }
        }
        #endregion

        #region 關閉與客戶端連接-Close()
        public void Close()
        {
            doesClose = true;
            threadClient.Abort();
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
            socket = null;
        }
        #endregion

        #region 在面板上顯示消息 -ShowMsg()
        private void ShowMsg(string msg)
        {
            this.frmMain.ShowMsg(msg);
        }
        private void ShowErr(string errMsg,Exception ex)
        {
            this.frmMain.ShowErrorMsg(errMsg, ex);
        }
        #endregion

    }

 

三:網絡聊天室原理與實現-客戶端:

  • 初始化客戶端
  //初始化客戶端Socket用於連接服務器端
        private void InitSocketAndConnect()
        {
            try
            {
                dgShowMsg = new DGShowMsg(DoShowMsg);
                //創建一個客戶端Socket
                clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                address = IPAddress.Parse(txtIp.Text.Trim());
                endP = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));
                //連接到指定服務器的指定端口
                clientSocket.Connect(endP);
                ShowMsg("連接成功!");
            }
            catch (Exception ex)
            {
                ShowErr("InitSocketAndConnect()",ex);
            }
        }
  • 接收消息
 private void WatchMsg()
        {
            while (true)
            {
                byte[] msgByte = new byte[1024 * 1024 * 2];
                int length = 0;
                try
                {
                    length = clientSocket.Receive(msgByte,msgByte.Length,SocketFlags.None);
                    if (length>0)
                    {
                        if (msgByte[0]==0)//接受文字
                        {
                            ShowMsg("對方說:"+Encoding.UTF8.GetString(msgByte,1,length-1));
                        }
                        else if (msgByte[0]==1)//接受文件
                        {
                            SaveFileDialog sfd = new SaveFileDialog();
                            if (sfd.ShowDialog()==DialogResult.OK)
                            {
                                string savePath = sfd.FileName;
                                using (FileStream fs=new FileStream (savePath,FileMode.Create) )
                                {
                                    fs.Write(msgByte,1,length-1);
                                }
                                ShowMsg("文件保存成功:"+savePath);
                            }
                        }
                        else//抖動窗體
                        {
                            ShakeWindow();
                        }
                    }
                }
  • 發送消息到服務端
       /// <summary>
       /// 發送消息
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
        private void btnSendMsg_Click(object sender, EventArgs e)
        {
            if (clientSocket!=null)
            {
                try
                {
                    string msgSend = txtInput.Text.Trim();
                    byte[] orgByte = Encoding.UTF8.GetBytes(msgSend);
                    byte[] finalByte=new byte[orgByte.Length+1];
                    finalByte[0] = 0;
                    Buffer.BlockCopy(orgByte,0,finalByte,1,orgByte.Length);
                    clientSocket.Send(finalByte);
                    ShowMsg("我說:"+msgSend);

                }
                catch (SocketException ex)
                {
                    ShowErr("發送消息時",ex);
                }
            }
        }
  • 抖動窗體:
 /// <summary>
  /// 抖動窗體
  /// </summary>
        private void doShakeWin()
        {
             Random ran = new Random();
             System.Drawing.Point point = this.Location;
            for (int i = 0; i < 30; i++)
            {
                this.Location = new System.Drawing.Point(point.X + ran.Next(8), point.Y + ran.Next(8));
                System.Threading.Thread.Sleep(15);
                this.Location = point;
                System.Threading.Thread.Sleep(15);
            }
        }

程序參考出處:

http://jameszou.blog.51cto.com/2173852/641032

 

源代碼下載:

http://files.cnblogs.com/OceanEyes/Ocean.Eyes.SocketWork.Solution.rar

 

 

 

 

 


免責聲明!

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



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