【總結】學習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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...