2024年5月30日 星期四

9.VB.NET 精進篇 筆記 - Socket

VB.NET Socket 筆記(精進篇)

VB.NET Socket 筆記(精進篇)

Socket 是程式進行網路通訊時使用的端點。伺服器端負責等待連線,客戶端負責主動連線;連線建立後,雙方就可以透過 Socket 傳送與接收位元組資料。

Socket 程式容易卡住的地方,不只是「能不能連上」。更重要的是:資料是不是完整、接收動作會不會卡住畫面、連線中斷時能不能正常結束,以及 TCP 串流要如何切出一筆一筆訊息。

先理解 Socket 在做什麼

Socket 是兩端溝通的出入口

Socket 可以理解成程式和網路之間的出入口。伺服器端先開好門等待連線,客戶端主動連進來。連線建立後,雙方都可以送資料,也都可以收資料。

  • 伺服器 Server:綁定 IP 與 Port,呼叫 Listen 等待客戶端連線。
  • 客戶端 Client:知道伺服器 IP 與 Port,呼叫 Connect 建立連線。
  • Send:把資料轉成位元組後送出去。
  • Receive:從 Socket 接收一段位元組資料。

Socket 程式設計要一起考慮四件事:

  1. 連線:IP、Port、伺服器是否啟動、通訊埠是否被占用。
  2. 接收:Receive 只代表收到一段資料,不保證剛好是一筆完整訊息。
  3. 執行緒:等待連線或等待資料時,不應讓 Windows Forms 畫面卡住。
  4. 關閉:Socket、背景工作與客戶端清單都需要明確結束流程。
項目 意思 常見問題
Bind把伺服器 Socket 綁到指定 IP 與 Port。Port 被占用、權限不足、埠號錯誤。
Listen讓伺服器開始等待連線。只 Listen 但沒有 Accept,客戶端仍無法正常交換資料。
Accept接受一個客戶端連線。同步 Accept 會等待,不能直接放在 UI 執行緒。
Connect客戶端連到伺服器。IP 錯誤、Port 錯誤、伺服器未啟動。
Send / Receive送出與接收位元組資料。文字編碼錯誤、訊息不完整、粘包與拆包。

Windows Forms 測試配置

建議測試方式

Socket 範例建議用兩個 Windows Forms 專案測試:一個當伺服器,一個當客戶端。若只想快速體驗,可先用本機位址 127.0.0.1 與固定埠號 9000

  • Server 專案:負責啟動監聽、顯示收到的訊息。
  • Client 專案:負責輸入訊息、連線、送出資料、顯示回應。
  • 測試順序:先啟動 Server,再按 Client 的送出按鈕。

基礎連線:Server 與 Client

場景一:建立本機留言伺服器

這個範例建立一個簡單的本機伺服器。Server 監聽 9000,Client 送出一段文字後,Server 顯示收到內容並回傳 OK。這個情境容易測試,也能清楚看出 Server 的基本流程。

需要的主控項
  • ButtonStartServer:啟動伺服器。
  • ButtonStopServer:停止伺服器。
  • LabelServerStatus:顯示伺服器狀態。
  • TextBoxLog:顯示收到的訊息,建議設定 Multiline=TrueScrollBars=Vertical
範例程式碼
VB.NET / Windows Forms - Server
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading

Public Class Form1
    Private listener As Socket
    Private serverThread As Thread
    Private isServerRunning As Boolean = False

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        TextBoxLog.Multiline = True
        TextBoxLog.ScrollBars = ScrollBars.Vertical
        LabelServerStatus.Text = "伺服器狀態:未啟動"
    End Sub

    Private Sub ButtonStartServer_Click(sender As Object, e As EventArgs) Handles ButtonStartServer.Click
        If isServerRunning Then
            LabelServerStatus.Text = "伺服器狀態:已經啟動"
            Return
        End If

        isServerRunning = True
        serverThread = New Thread(AddressOf RunMessageServer)
        serverThread.IsBackground = True
        serverThread.Start()
        LabelServerStatus.Text = "伺服器狀態:啟動中"
    End Sub

    Private Sub ButtonStopServer_Click(sender As Object, e As EventArgs) Handles ButtonStopServer.Click
        StopServer()
    End Sub

    Private Sub RunMessageServer()
        Try
            listener = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
            listener.Bind(New IPEndPoint(IPAddress.Loopback, 9000))
            listener.Listen(10)
            SetStatusText("伺服器狀態:監聽中 127.0.0.1:9000")

            While isServerRunning
                Dim client As Socket = listener.Accept()
                HandleClient(client)
            End While

        Catch ex As SocketException
            If isServerRunning Then
                SetStatusText("伺服器狀態:Socket 錯誤 - " & ex.SocketErrorCode.ToString())
            End If
        Catch ex As ObjectDisposedException
            ' 關閉 listener 時可能發生,屬於停止流程的一部分。
        Catch ex As Exception
            If isServerRunning Then
                SetStatusText("伺服器狀態:錯誤 - " & ex.Message)
            End If
        End Try
    End Sub

    Private Sub HandleClient(ByVal client As Socket)
        Using client
            Dim buffer(1023) As Byte
            Dim received As Integer = client.Receive(buffer)

            If received > 0 Then
                Dim message As String = Encoding.UTF8.GetString(buffer, 0, received)
                AppendLog("收到:" & message)

                Dim responseBytes() As Byte = Encoding.UTF8.GetBytes("OK")
                client.Send(responseBytes)
            End If
        End Using
    End Sub

    Private Sub StopServer()
        isServerRunning = False

        If listener IsNot Nothing Then
            listener.Close()
            listener = Nothing
        End If

        LabelServerStatus.Text = "伺服器狀態:已停止"
    End Sub

    Private Sub SetStatusText(ByVal message As String)
        If Me.IsHandleCreated AndAlso Not Me.IsDisposed Then
            Me.BeginInvoke(New MethodInvoker(Sub()
                                                 LabelServerStatus.Text = message
                                             End Sub))
        End If
    End Sub

    Private Sub AppendLog(ByVal message As String)
        If Me.IsHandleCreated AndAlso Not Me.IsDisposed Then
            Me.BeginInvoke(New MethodInvoker(Sub()
                                                 TextBoxLog.AppendText(message & Environment.NewLine)
                                             End Sub))
        End If
    End Sub

    Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
        StopServer()
    End Sub
End Class
畫面輸出結果(TextBoxLog)
收到:今天 15:00 開會 收到:報表已放到共享資料夾
邏輯解析
  • Bind 把伺服器綁到本機 127.0.0.1:9000
  • Accept 會等待客戶端連線,因此放在背景執行緒中。
  • BeginInvoke 讓背景執行緒安全更新 LabelServerStatusTextBoxLog
  • StopServer 會關閉 listener,讓等待中的 Accept 結束。

場景二:建立本機留言客戶端

Client 負責連到本機伺服器,送出 TextBoxMessage 的內容,並顯示伺服器回應。這個範例搭配場景一測試。

需要的主控項
  • TextBoxMessage:輸入要送出的文字。
  • ButtonSend:連線並送出。
  • LabelClientStatus:顯示連線狀態。
  • LabelResponse:顯示伺服器回應。
範例程式碼
VB.NET / Windows Forms - Client
Imports System.Net
Imports System.Net.Sockets
Imports System.Text

Public Class Form1
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        TextBoxMessage.Text = "今天 15:00 開會"
        LabelClientStatus.Text = "連線狀態:尚未送出"
        LabelResponse.Text = "伺服器回應:無"
    End Sub

    Private Sub ButtonSend_Click(sender As Object, e As EventArgs) Handles ButtonSend.Click
        Dim message As String = TextBoxMessage.Text.Trim()

        If message = String.Empty Then
            LabelClientStatus.Text = "連線狀態:訊息不可空白"
            Return
        End If

        Try
            Using client As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
                client.Connect(New IPEndPoint(IPAddress.Loopback, 9000))
                LabelClientStatus.Text = "連線狀態:已連線"

                Dim sendBytes() As Byte = Encoding.UTF8.GetBytes(message)
                client.Send(sendBytes)

                Dim buffer(1023) As Byte
                Dim received As Integer = client.Receive(buffer)
                Dim response As String = Encoding.UTF8.GetString(buffer, 0, received)

                LabelResponse.Text = "伺服器回應:" & response
            End Using

        Catch ex As SocketException
            LabelClientStatus.Text = "連線狀態:Socket 錯誤 - " & ex.SocketErrorCode.ToString()
        Catch ex As Exception
            LabelClientStatus.Text = "連線狀態:錯誤 - " & ex.Message
        End Try
    End Sub
End Class
畫面輸出結果
LabelClientStatus: 連線狀態:已連線 LabelResponse: 伺服器回應:OK
邏輯解析
  • IPAddress.Loopback 代表本機位址,也就是 127.0.0.1
  • Encoding.UTF8.GetBytes 把文字轉成 Socket 可送出的位元組。
  • Using client 可確保連線結束後釋放 Socket。

TCP 訊息邊界

TCP 最容易誤會的地方

TCP 是串流,不是「一個 Send 對一個 Receive」。一次 Send 的資料可能被拆成多次接收;多次 Send 的資料也可能在接收端黏在一起。因此正式程式不能只靠一次 Receive 判斷一筆完整訊息。

場景三:使用長度前綴建立訊息封包

常見做法是每筆訊息前面放 4 個位元組的長度。接收端先讀長度,再依照長度把內容收滿。這樣可避免粘包與拆包造成資料判斷錯誤。

封包格式
區段 長度 用途
Length4 bytes表示後方內容有幾個位元組。
Body可變實際訊息內容,範例使用 UTF-8 文字。
範例程式碼
VB.NET / Packet Helper
Imports System.Net.Sockets
Imports System.Text

Public Class PacketHelper
    Public Shared Function BuildTextPacket(ByVal message As String) As Byte()
        Dim body() As Byte = Encoding.UTF8.GetBytes(message)
        Dim lengthBytes() As Byte = BitConverter.GetBytes(body.Length)
        Dim packet(4 + body.Length - 1) As Byte

        Array.Copy(lengthBytes, 0, packet, 0, 4)
        Array.Copy(body, 0, packet, 4, body.Length)

        Return packet
    End Function

    Public Shared Function ReceiveTextPacket(ByVal socket As Socket) As String
        Dim lengthBytes() As Byte = ReceiveExact(socket, 4)
        Dim bodyLength As Integer = BitConverter.ToInt32(lengthBytes, 0)

        If bodyLength < 0 OrElse bodyLength > 1024 * 1024 Then
            Throw New InvalidOperationException("訊息長度不合理。")
        End If

        Dim body() As Byte = ReceiveExact(socket, bodyLength)
        Return Encoding.UTF8.GetString(body)
    End Function

    Private Shared Function ReceiveExact(ByVal socket As Socket, ByVal size As Integer) As Byte()
        Dim buffer(size - 1) As Byte
        Dim offset As Integer = 0

        While offset < size
            Dim readCount As Integer = socket.Receive(buffer, offset, size - offset, SocketFlags.None)

            If readCount = 0 Then
                Throw New SocketException(CInt(SocketError.ConnectionReset))
            End If

            offset += readCount
        End While

        Return buffer
    End Function
End Class
邏輯解析
  • BuildTextPacket 會把文字變成「長度 + 內容」格式。
  • ReceiveExact 會反覆呼叫 Receive,直到收滿指定長度。
  • 收到長度後再收內容,可清楚判斷一筆訊息的邊界。

多客戶端與非同步接收

場景四:非同步接收多個客戶端留言

同步範例適合理解流程,但一次只處理一個連線。若要讓多個客戶端同時連線,可以使用非同步接收。此範例保留簡化結構,重點放在 AcceptTcpClientAsync、背景接收與 UI 更新。

需要的主控項
  • ButtonStartAsync:啟動非同步伺服器。
  • ButtonStopAsync:停止非同步伺服器。
  • LabelAsyncStatus:顯示伺服器狀態。
  • ListBoxMessages:顯示多個客戶端送來的訊息。
範例程式碼
VB.NET / Windows Forms - Async Server
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
Imports System.Threading.Tasks

Public Class Form1
    Private tcpListener As TcpListener
    Private cancelSource As CancellationTokenSource

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        LabelAsyncStatus.Text = "非同步伺服器:未啟動"
    End Sub

    Private Async Sub ButtonStartAsync_Click(sender As Object, e As EventArgs) Handles ButtonStartAsync.Click
        If tcpListener IsNot Nothing Then
            LabelAsyncStatus.Text = "非同步伺服器:已經啟動"
            Return
        End If

        cancelSource = New CancellationTokenSource()
        tcpListener = New TcpListener(IPAddress.Loopback, 9100)
        tcpListener.Start()
        LabelAsyncStatus.Text = "非同步伺服器:監聽中 127.0.0.1:9100"

        Try
            Await AcceptClientsAsync(cancelSource.Token)
        Catch ex As ObjectDisposedException
            ' Stop 時可能發生,屬於正常停止流程。
        Catch ex As Exception
            LabelAsyncStatus.Text = "非同步伺服器:錯誤 - " & ex.Message
        End Try
    End Sub

    Private Sub ButtonStopAsync_Click(sender As Object, e As EventArgs) Handles ButtonStopAsync.Click
        StopAsyncServer()
    End Sub

    Private Async Function AcceptClientsAsync(ByVal token As CancellationToken) As Task
        While Not token.IsCancellationRequested
            Dim client As TcpClient = Await tcpListener.AcceptTcpClientAsync()
            _ = HandleClientAsync(client, token)
        End While
    End Function

    Private Async Function HandleClientAsync(ByVal client As TcpClient,
                                             ByVal token As CancellationToken) As Task
        Using client
            Dim stream As NetworkStream = client.GetStream()
            Dim buffer(1023) As Byte
            Dim readCount As Integer = Await stream.ReadAsync(buffer, 0, buffer.Length, token)

            If readCount > 0 Then
                Dim message As String = Encoding.UTF8.GetString(buffer, 0, readCount)
                ListBoxMessages.Items.Add("收到:" & message)

                Dim response() As Byte = Encoding.UTF8.GetBytes("OK")
                Await stream.WriteAsync(response, 0, response.Length, token)
            End If
        End Using
    End Function

    Private Sub StopAsyncServer()
        If cancelSource IsNot Nothing Then
            cancelSource.Cancel()
            cancelSource.Dispose()
            cancelSource = Nothing
        End If

        If tcpListener IsNot Nothing Then
            tcpListener.Stop()
            tcpListener = Nothing
        End If

        LabelAsyncStatus.Text = "非同步伺服器:已停止"
    End Sub

    Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
        StopAsyncServer()
    End Sub
End Class
畫面輸出結果(ListBoxMessages)
收到:A 客戶端上線 收到:B 客戶端上線 收到:A 客戶端送出確認
邏輯解析
  • TcpListener 是較高階的 TCP 伺服器封裝,適合簡化教學範例。
  • AcceptTcpClientAsync 等待連線時不會阻塞 UI 流程。
  • _ = HandleClientAsync(...) 讓每個客戶端獨立處理。
  • 正式高併發服務通常還需要連線清單、錯誤紀錄與封包解析。

檔案傳輸

場景五:傳送小型備份檔案

檔案是二進位資料,不適合先轉成文字再傳。基本做法是先送出檔案大小,再分段送出內容,接收端依照大小判斷何時收完。

需要的主控項
  • ButtonPickFile:選擇檔案。
  • ButtonSendFile:送出檔案。
  • LabelFileName:顯示檔案名稱。
  • LabelSendStatus:顯示傳送狀態。
  • ProgressBar1:顯示傳送進度。
範例程式碼
VB.NET / Windows Forms - File Sender
Imports System.IO
Imports System.Net
Imports System.Net.Sockets
Imports System.Threading.Tasks

Public Class Form1
    Private selectedFilePath As String = String.Empty

    Private Sub ButtonPickFile_Click(sender As Object, e As EventArgs) Handles ButtonPickFile.Click
        Using dialog As New OpenFileDialog()
            dialog.Title = "選擇要傳送的檔案"
            dialog.Filter = "所有檔案 (*.*)|*.*"

            If dialog.ShowDialog() = DialogResult.OK Then
                selectedFilePath = dialog.FileName
                LabelFileName.Text = "檔案:" & Path.GetFileName(selectedFilePath)
            End If
        End Using
    End Sub

    Private Async Sub ButtonSendFile_Click(sender As Object, e As EventArgs) Handles ButtonSendFile.Click
        If selectedFilePath = String.Empty OrElse Not File.Exists(selectedFilePath) Then
            LabelSendStatus.Text = "傳送狀態:尚未選擇檔案"
            Return
        End If

        ButtonSendFile.Enabled = False
        ProgressBar1.Value = 0
        LabelSendStatus.Text = "傳送狀態:傳送中"

        Try
            Await SendFileAsync(selectedFilePath)
            LabelSendStatus.Text = "傳送狀態:完成"
        Catch ex As Exception
            LabelSendStatus.Text = "傳送狀態:失敗 - " & ex.Message
        Finally
            ButtonSendFile.Enabled = True
        End Try
    End Sub

    Private Async Function SendFileAsync(ByVal filePath As String) As Task
        Using client As New TcpClient()
            Await client.ConnectAsync(IPAddress.Loopback, 9200)

            Using stream As NetworkStream = client.GetStream()
                Dim fileInfo As New FileInfo(filePath)
                Dim lengthBytes() As Byte = BitConverter.GetBytes(fileInfo.Length)
                Await stream.WriteAsync(lengthBytes, 0, lengthBytes.Length)

                Dim buffer(8191) As Byte
                Dim sentTotal As Long = 0

                Using fileStream As New FileStream(filePath, FileMode.Open, FileAccess.Read)
                    Dim readCount As Integer

                    Do
                        readCount = Await fileStream.ReadAsync(buffer, 0, buffer.Length)

                        If readCount > 0 Then
                            Await stream.WriteAsync(buffer, 0, readCount)
                            sentTotal += readCount

                            Dim percent As Integer = CInt((sentTotal * 100) / fileInfo.Length)
                            ProgressBar1.Value = Math.Min(100, percent)
                        End If
                    Loop While readCount > 0
                End Using
            End Using
        End Using
    End Function
End Class
畫面輸出結果
LabelFileName: 檔案:backup-demo.zip LabelSendStatus: 傳送狀態:完成 ProgressBar1: 100
邏輯解析
  • 先送出 8 bytes 的檔案長度,接收端才知道要收多少資料。
  • FileStream.ReadAsyncNetworkStream.WriteAsync 可避免 UI 被長時間阻塞。
  • 大型檔案不建議一次 ReadAllBytes,分段讀取較穩定。

場景六:接收備份檔案並存到本機

檔案傳輸只寫傳送端還不夠完整。接收端需要先讀取 8 bytes 的檔案長度,再持續接收內容,直到累積位元組數等於檔案大小。這樣才知道檔案是否真的收完。

需要的主控項
  • ButtonStartFileReceiver:啟動檔案接收端。
  • LabelReceiveStatus:顯示接收狀態。
  • ProgressBar1:顯示接收進度。
範例程式碼
VB.NET / Windows Forms - File Receiver
Imports System.IO
Imports System.Net
Imports System.Net.Sockets
Imports System.Threading.Tasks

Public Class Form1
    Private fileListener As TcpListener

    Private Async Sub ButtonStartFileReceiver_Click(sender As Object,
                                                    e As EventArgs) Handles ButtonStartFileReceiver.Click
        ButtonStartFileReceiver.Enabled = False
        ProgressBar1.Value = 0
        LabelReceiveStatus.Text = "接收端狀態:等待檔案"

        Try
            fileListener = New TcpListener(IPAddress.Loopback, 9200)
            fileListener.Start()

            Using client As TcpClient = Await fileListener.AcceptTcpClientAsync()
                Await ReceiveFileAsync(client)
            End Using

            LabelReceiveStatus.Text = "接收端狀態:檔案接收完成"

        Catch ex As Exception
            LabelReceiveStatus.Text = "接收端狀態:失敗 - " & ex.Message
        Finally
            If fileListener IsNot Nothing Then
                fileListener.Stop()
                fileListener = Nothing
            End If

            ButtonStartFileReceiver.Enabled = True
        End Try
    End Sub

    Private Async Function ReceiveFileAsync(ByVal client As TcpClient) As Task
        Using stream As NetworkStream = client.GetStream()
            Dim lengthBytes(7) As Byte
            Await ReadExactAsync(stream, lengthBytes, lengthBytes.Length)

            Dim fileLength As Long = BitConverter.ToInt64(lengthBytes, 0)

            If fileLength <= 0 OrElse fileLength > 1024L * 1024L * 200L Then
                Throw New InvalidOperationException("檔案大小不合理。")
            End If

            Dim savePath As String = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
                "received-backup.bin")

            Dim buffer(8191) As Byte
            Dim receivedTotal As Long = 0

            Using fileStream As New FileStream(savePath, FileMode.Create, FileAccess.Write)
                While receivedTotal < fileLength
                    Dim needRead As Integer = CInt(Math.Min(buffer.Length, fileLength - receivedTotal))
                    Dim readCount As Integer = Await stream.ReadAsync(buffer, 0, needRead)

                    If readCount = 0 Then
                        Throw New IOException("連線已中斷,檔案尚未收完。")
                    End If

                    Await fileStream.WriteAsync(buffer, 0, readCount)
                    receivedTotal += readCount

                    Dim percent As Integer = CInt((receivedTotal * 100) / fileLength)
                    ProgressBar1.Value = Math.Min(100, percent)
                End While
            End Using
        End Using
    End Function

    Private Async Function ReadExactAsync(ByVal stream As NetworkStream,
                                          ByVal buffer() As Byte,
                                          ByVal size As Integer) As Task
        Dim offset As Integer = 0

        While offset < size
            Dim readCount As Integer = Await stream.ReadAsync(buffer, offset, size - offset)

            If readCount = 0 Then
                Throw New IOException("連線已中斷。")
            End If

            offset += readCount
        End While
    End Function
End Class
畫面輸出結果
LabelReceiveStatus: 接收端狀態:檔案接收完成 ProgressBar1: 100 桌面產生檔案: received-backup.bin
邏輯解析
  • 接收端先用 ReadExactAsync 收滿 8 bytes 長度欄位。
  • 接著依檔案長度分段接收內容,避免一次吃下整個檔案。
  • ReadAsync 回傳 0,代表連線中斷,需要停止並回報錯誤。
  • 此範例固定存成 received-backup.bin,正式程式可再加入檔名欄位或檔案類型欄位。

實務補充:Socket 完整度檢查

Socket 程式不只檢查能不能連線,還需要檢查下列面向:

  1. 連線流程:Server 是否先啟動,Port 是否可用,Client 是否連到正確位置。
  2. 訊息格式:文字、命令、檔案是否有明確格式,不混在同一段資料裡猜測。
  3. 訊息邊界:是否能處理粘包、拆包與半包資料。
  4. 背景處理:等待連線、等待資料、檔案傳輸是否避免卡住 UI。
  5. 例外處理:SocketException、連線中斷、超時、資料不完整是否有處理。
  6. 資源釋放:SocketTcpClientNetworkStreamFileStream 是否確實關閉。

文章定位

這篇屬於 Socket 精進篇,適合建立完整觀念與 Windows Forms 實作骨架。若要再往大型正式系統延伸,通常會再補通訊協定版本、心跳封包、斷線重連、傳輸加密、連線池、日誌追蹤與壓力測試。

常見錯誤與排查方向

連線失敗

  • Server 尚未啟動,Client 就先呼叫 Connect
  • IP 或 Port 寫錯。
  • Port 已被其他程式占用。
  • 防火牆阻擋外部連線。

資料不完整或黏在一起

  • TCP 是串流,Receive 不等於完整一筆訊息。
  • 沒有設計訊息結尾或長度欄位。
  • 文字與二進位資料混在一起,沒有明確格式。
  • 接收端沒有累積緩衝區。

Windows Forms 畫面卡住

  • AcceptReceive 或檔案傳輸直接寫在 UI 執行緒。
  • 大量傳輸中直接更新控制項太頻繁。
  • 背景執行緒直接修改控制項,造成跨執行緒錯誤。

重點整理

  1. Socket 是網路通訊的端點,Server 與 Client 都透過它收發資料。
  2. Server 的基本流程是 BindListenAcceptReceiveSend
  3. Client 的基本流程是 ConnectSendReceive、關閉連線。
  4. TCP 是串流,正式程式需要設計訊息邊界,例如長度前綴。
  5. Windows Forms 中不要把等待式 Socket 操作直接放在 UI 執行緒。
  6. 背景接收或非同步流程更新 UI 時,需要注意跨執行緒問題。
  7. 檔案傳輸應分段處理,並先告知接收端總長度。
  8. Socket 程式需要明確的停止與釋放流程,避免 Port 被占用或程式無法正常關閉。