【總結】學習Socket編寫的聊天室小程式

来源:http://www.cnblogs.com/fenglingyi/archive/2016/08/27/4754785.html
-Advertisement-
Play Games

1.前言 在學習Socket之前,先來學習點網路相關的知識吧,自己學習過程中的一些總結,Socket是一門很高深的學問,本文只是Socket一些最基礎的東西,大神請自覺繞路。 傳輸協議 TCP:Transmission Control Protocol 傳輸控制協議TCP是一種面向連接(連接導向)的 ...


1.前言

在學習Socket之前,先來學習點網路相關的知識吧,自己學習過程中的一些總結,Socket是一門很高深的學問,本文只是Socket一些最基礎的東西大神請自覺繞路。

傳輸協議

TCP:Transmission Control Protocol 傳輸控制協議TCP是一種面向連接(連接導向)的、可靠的、基於位元組流的運輸層(Transport layer)通信協議。 特點: 面向連接的協議,數據傳輸必須要建立連接,所以在TCP中需要連接時間。 傳輸數據大小限制,一旦連接建立,雙方可以按統一的格式傳輸大的數據。 一個可靠的協議,確保接收方完全正確地獲取發送方所發送的全部數據。 說到TCP就不得不說經典的三次握手。 在TCP/IP協議中,TCP協議通過三次握手建立一個可靠的連接

第一次握手:客戶端嘗試連接伺服器,向伺服器發送syn包(同步序列編號Synchronize Sequence Numbers),syn=j,客戶端進入SYN_SEND狀態等待伺服器確認

第二次握手:伺服器接收客戶端syn包並確認(ack=j+1),同時向客戶端發送一個SYN包(syn=k),即SYN+ACK包,此時伺服器進入SYN_RECV狀態

第三次握手:第三次握手:客戶端收到伺服器的SYN+ACK包,向伺服器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和伺服器進入ESTABLISHED狀態,完成三次握手

  UDP: User Datagram Protocol的簡稱, 中文名是用戶數據包協議,是 OSI 參考模型中一種無連接的傳輸層協議,提供面向事務的簡單不可靠信息傳送服務。 特點: 每個數據報中都給出了完整的地址信息,因此無需要建立發送方和接收方的連接。 UDP傳輸數據時是有大小限制的,每個被傳輸的數據報必須限定在64KB之內。 UDP是一個不可靠的協議,發送方所發送的數據報並不一定以相同的次序到達接收方。    

TCP協議:就好比兩個電話機 通過電話線進行數據交互的格式約定

HTTP協議:就好比兩個人 通過電話機 說話的語法。

(1)公認埠(WellKnownPorts):從0到1023,它們緊密綁定(binding)於一些服務。通常這些埠的通訊明確表明瞭某種服務的協議。例如:80埠實際上總是HTTP通訊。

(2)註冊埠(RegisteredPorts):從1024到49151。它們鬆散地綁定於一些服務。也就是說有許多服務綁定於這些埠,這些埠同樣用於許多其它目的。例如:許多系統處理動態埠從1024左右開始。

(3)動態和/或私有埠(Dynamicand/orPrivatePorts):從49152到65535。理論上,不應為服務分配這些埠。實際上,機器通常從1024起分配動態埠。

 

OSI網路7層模型

TCP/IP(Transmission Control Protocol/Internet Protocol)即傳輸控制協議/網間協議,是一個工業標準的協議集,它是為廣域網(WANs)設計的。 UDP(User Data Protocol,用戶數據報協議)是與TCP相對應的協議。它是屬於TCP/IP協議族中的一種。 應用層 (Application):應用層是個很廣泛的概念,有一些基本相同的系統級 TCP/IP 應用以及應用協議,也有許多的企業商業應用和互聯網應用。 傳輸層 (Transport):傳輸層包括 UDP 和 TCP,UDP 幾乎不對報文進行檢查,而 TCP 提供傳輸保證。 網路層 (Network):網路層協議由一系列協議組成,包括 ICMP、IGMP、RIP、OSPF、IP(v4,v6) 等。 鏈路層 (Link):又稱為物理數據網路介面層,負責報文傳輸。   IP地址 每台聯網的電腦都有一個唯一的IP地址。 長度32位,分為四段,每段8位,用十進位數字表示,每段範圍 0 ~ 255 特殊IP:127.0.0.1 用戶本地網卡測試 版本:V4(32位) 和 V6(128位,分為8段,每段16位)   在網路上有很多電腦,這些電腦一般運行了多個網路程式。每種網路程式都打開一個Socket,並綁定到一個埠上,不同的埠對應於不同的網路程式。 常用埠:21 FTP  ,25 SMTP  ,110 POP3  ,80 HTTP , 443 HTTPS   有兩種常用Socket類型: 流式Socket(STREAM):
是一種面向連接的Socket,針對於面向連接的TCP服務應用,安全,但是效率低   數據報式Socket(DATAGRAM):
是一種無連接的Socket,對應於無連接的UDP服務應用.不安全(丟失,順序混亂,在接收端要分析重排及要求重發),但效率高.   說了那麼多,讓我們來看看socket在網路7層協議中的位置。如下圖所示

2.聊天室原理

 Socket 流式(伺服器端和客戶端 伺服器端的Socket(至少需要兩個) 一個負責接收客戶端連接請求(但不負責與客戶端通信) 每成功接收到一個客戶端的連接便在服務端產生一個對應的負責通信的Socket 在接收到客戶端連接時創建. 為每個連接成功的客戶端請求在服務端都創建一個對應的Socket(負責和客戶端通信).   客戶端的Socket 客戶端Socket 必須指定要連接的服務端IP地址和埠。 通過創建一個Socket對象來初始化一個到伺服器端的TCP連接    Socket的通訊過程 伺服器端: 申請一個socket 綁定到一個IP地址和一個埠上 開啟偵聽,等待接授連接   客戶端: 申請一個socket 連接伺服器(指明IP地址和埠號) l伺服器端接到連接請求後,產生一個新的socket(埠大於1024)與客戶端建立連接併進行通訊,原監聽socket繼續監聽。   Socket常用的一些類和方法 IPAddress類:包含了一個IP地址 IPEndPoint類:包含了一對IP地址和埠號 Socket (): 創建一個Socket Bind(): 綁定一個本地的IP和埠號(IPEndPoint) Listen(): 讓Socket偵聽傳入的連接嘗試,並指定偵聽隊列容量 Connect(): 初始化與另一個Socket的連接 Accept(): 接收連接並返回一個新的socket Send(): 輸出數據到Socket Receive(): 從Socket中讀取數據 Close(): 關閉Socket (銷毀連接)  

3.聊天室代碼

伺服器端代碼:

using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Server
{
    using System.Net.Sockets;
    using System.Net;
    using System.Threading;
    public partial class Form1 : Form
    {

        public Form1()
        {
            InitializeComponent();
            TextBox.CheckForIllegalCrossThreadCalls = false;
        } 


        //服務端 監聽套接字
        Socket socketWatch = null;
        //服務端 監聽線程
        Thread threadWatch = null;
        //字典集合:保存 通信套接字
        Dictionary<string, Socket> dictCon = new Dictionary<string, Socket>(); 
        private void btnStartListen_Click(object sender, EventArgs e)
        {

            try
            {
                //1.創建監聽套接字 使用 ip4協議,流式傳輸,TCP連接
                socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //2.綁定埠
                //2.1獲取網路節點對象
                IPAddress address = IPAddress.Parse(txtIP.Text);
                IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text));
                //2.2綁定埠(其實內部 就向系統的 埠表中 註冊 了一個埠,並指定了當前程式句柄)
                socketWatch.Bind(endPoint);
                //2.3設置監聽隊列
                socketWatch.Listen(10);
                //2.4開始監聽,調用監聽線程 執行 監聽套接字的 監聽方法
                threadWatch = new Thread(WatchConnecting);
                threadWatch.IsBackground = true;
                threadWatch.Start();
                ShowMsg("楓伶憶,伺服器啟動啦!");
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                throw;
            }


        } 
        void WatchConnecting()
        {
            //2.4開始監聽:此方法會阻斷當前線程,直到有 其它程式 連接過來,才執行完畢
            Socket sokMsg = socketWatch.Accept();
            //將當前連接成功的 【與客戶端通信的套接字】 的 標識 保存起來,並顯示到 列表中
            //將 遠程客戶端的 ip和埠 字元串 存入 列表
            this.lbOnline.Items.Add(sokMsg.RemoteEndPoint.ToString());
            //將 服務端的通信套接字 存入 字典集合
            dictCon.Add(sokMsg.RemoteEndPoint.ToString(), sokMsg);

            ShowMsg("有客戶端連接了!");
            //2.5創建 通信線程
            Thread thrMsg = new Thread(ReceiveMsg);
            thrMsg.IsBackground = true;
            thrMsg.Start(sokMsg);
        }
        void ReceiveMsg(object obj)
        {
            try
            {
                Socket sokMsg = obj as Socket;
                //3.通信套接字 監聽 客戶端的 消息
                //3.1創建 消息緩存區
                byte[] arrMsg = new byte[1024 * 1024 * 1];
                while (isReceive)
                {
                    //3.2接收客戶端的消息 並存入 緩存區,註意:Receive方法也會阻斷當前的線程
                    sokMsg.Receive(arrMsg);
                    //3.3將接收到的消息 轉成 字元串
                    string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg);
                    //3.4將消息 顯示到 文本框
                    ShowMsg(strMsg);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                throw;
            }
        }
        void ShowMsg(string strmsg)
        {
            this.txtShow.AppendText(strmsg + "\r\n");
        } 
        private void btnSend_Click_1(object sender, EventArgs e)
        {

            string strClient = this.lbOnline.Text;
            if (string.IsNullOrEmpty(strClient))
            {
                MessageBox.Show("請選擇你要發送消息的客戶端");
                return;
            }
            if (dictCon.ContainsKey(strClient))
            {
                string strMsg = this.txtInput.Text.Trim();
                ShowMsg("\r\n向客戶端【" + strClient + "】說:" + strMsg);

                //使用 指定的 通信套接字 將 字元串 發送到 指定的客戶端
                byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
                dictCon[strClient].Send(arrMsg);
            }
            this.txtInput.Text = "";
        }
     }

}

 客戶端代碼:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Client
{
    using System.Net.Sockets;
    using System.Net;
    using System.Threading;
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            TextBox.CheckForIllegalCrossThreadCalls = false;
        } 


       //客戶端 通信套接字
        Socket socketMsg = null;
        //客戶端 通信線程
        Thread threadMsg = null;

        bool isRec = true;//標記任務
        private void btnConnect_Click(object sender, EventArgs e)
        {
            try
            {
                //1.創建監聽套接字 使用 ip4協議,流式傳輸,TCP連接
                socketMsg = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //2.獲取要連接的服務端 節點
                //2.1獲取網路節點對象
                IPAddress address = IPAddress.Parse(txtIP.Text);
                IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text));
                //3.向服務端 發送鏈接請求
                socketMsg.Connect(endPoint);
                ShowMsg("連接伺服器成功~~!");
                //4.開啟通信線程
                threadMsg = new Thread(RecevieMsg);
                threadMsg.IsBackground = true;
                threadMsg.Start();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                throw;
            }

        } 
        void RecevieMsg()
        {
            try
            {
                //3.1創建 消息緩存區
                byte[] arrMsg = new byte[1024 * 1024 * 1];
                while (isRec)
                {
                    socketMsg.Receive(arrMsg);
                    string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg);
                    ShowMsg("\r\n伺服器說:" + strMsg);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                throw;
            }
        } 
        private void btnSend_Click_1(object sender, EventArgs e)
        {
            string strMsg = this.txtInput.Text.Trim();
            byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
            socketMsg.Send(arrMsg);
            this.txtInput.Text = "";
        }
        void ShowMsg(string strmsg)
        {
            this.txtShow.AppendText(strmsg + "\r\n");
        } 

}

}

 最終的效果圖如下:

 

4.註意

至少要定義一個要連接的遠程主機的IP和埠號。

埠號必須在 1 和 65535之間,最好在1024以後。 要連接的遠程主機必須正在監聽指定埠,也就是說你無法隨意連接遠程主機。 如: IPAddress addr = IPAddress.Parse("127.0.0.1"); IPEndPoint endp = new IPEndPoint(addr, 8989);

  服務端先綁定:serverWelcomeSocket.Bind(endp)

  客戶端再連接:clientSocket.Connect(endp)

  一個Socket一次只能連接一臺主機。 Socket關閉後無法再次使用。 每個Socket對象只能一臺遠程主機連接. 如果你想連接到多台遠程主機, 你必須創建多個Socket對象

 5.擴展

l實現傳送文件 如果接收數據是文件還是文字? 設計"協議": 把要傳遞的位元組數組前面都加上一個位元組做為標識。0:表示文字  1:表示文件 即:文字:  0+文字(位元組數組表示) 文件:1+文件的二進位信息  

 比如Socket的分包,黏包問題,非同步編程在後續的文章繼續討論

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • linux的基本原則: 1、有目的單一的小程式組成,組合小程式完成複雜任務。 2、一切皆文件 3、儘量避免捕獲用戶介面 4、配置文件保存為純文本格式 CLI介面: 命令提示符,prompt,bash #:root $:普通用戶 命令格式: 命令 選項 參數 選項: [] :可省略 <>:必選 ... ...
  • 在Linux系統當中,如何搜、索查找文件裡面的內容呢? 這個應該是系統維護、管理當中遇到最常見的需求。那麼下麵介紹,總結一下如何搜索、查找文件當中的內容。 搜索、查找文件當中的內容,一般最常用的是grep命令,另外還有egrep, vi命令也能搜索文件裡面內容 1:搜索某個文件裡面是否包含字元串,使... ...
  • 1.定義 進程是具有一定獨立功能的程式關於某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位。 線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能夠獨立運行的基本單位。線程自己不擁有系統資源,與和它同在一個進程中的其他線程共用進程所擁有的系統資源。 2.關係 ...
  • 1. 安裝一個小程式:Add_Open_Command_Window_Here.reg 2. 選中一個文件夾,按住“Shift”+右鍵,出來 3. 自動跳出命令行視窗 ...
  • 安裝軟體時遇到這樣的情況:我就是管理員許可權啊,怎麼會安裝有問題呢? 後來知道,用戶名即使分配了你是管理員許可權,有些文件還是有限制的(特別是C盤) 昨天遇到一個問題,有個文件夾里的隱藏文件就是無法顯示,做瞭如下操作: 選中文件夾右鍵“屬性”—“安全”—添加“Everyone”,把所有許可權勾上。 ...
  • 1. 點擊“組織”,再選擇“文件夾和搜索選項”命令。 2. 接下來在打開的“文件夾選項”對話框中,單擊“查看”,切換到“查看”選項卡中。 3. 然後在下麵的“高級設置”區域,取消“隱藏受保護的操作系統文件”前面的覆選框;再選擇下麵的“顯示隱藏的文件、文件夾和驅動器”單選項。 4. 最後單擊“確定”按 ...
  • 一、線程屬性 可以使用pthread_attr_t結構修改線程預設屬性,並這些屬性和創建的線程練習起來,可以使用pthread_att_init函數初始化pthread_attr_t結構,調用pthread_attr_init後,pthread_attr_t結構所包含的就是操作系統實現支持的所有線程 ...
  • ELF:可執行二進位文件的存儲格式 可執行的,可鏈接的文件 文件系統: rootfs :根文件系統 ls / /boot:系統啟動相關的文件,如內核、initrd、以及grub(引導載入器bootloader) vmlinux-2.6.18-308.el5 initrd-2.6.18-308.el5 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...