画像ファイル
画像ファイル   C#、VB2005 でSocket通信 (複数クライアント&非同期処理)
 VB2005のコード 
VB2005のコード
Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Data
Imports System.Drawing
Imports System.Text
Imports System.Net
Imports System.Net.Sockets
Imports System.Windows.Forms
Imports System.Threading

'別スレッドからClientHandlerを持つListの保守
Public Delegate Sub dlgsetList(ByVal ch As ClientHandler)

'別スレッドからメインスレッドのテキストボックスに書き込むデリゲート
Public Delegate Sub dlgWriteText(ByVal ch As ClientHandler, ByVal text As String)

'別スレッドからログを書き込むデリゲート
Public Delegate Sub dlgWriteLog(ByVal text As String)


Public Class FomServer

'受送信は必ずshift-jisと仮定している
'他の文字の場合はここを変える事
Public ecUni As Encoding = Encoding.GetEncoding("utf-16")
Public ecSjis As Encoding = Encoding.GetEncoding("shift-jis")


'サーバーのリスナー設定
Private Listener As TcpListener = Nothing

'サーバーのセカンドスレッドの設定
Private threadServer As Thread = Nothing

'クライアントの参照を保持するListクラス
Private lstClientHandler As New List(Of ClientHandler)()

'サーバースタートボタン押下
Private Sub butStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles butStart.Click
StartSock()
End Sub

'ソケット通信開始
Private Sub StartSock()
'サーバーが開始したら
If ServerStart() Then
'ボタンのenableを変える
butStart.Enabled = False
butStop.Enabled = True
'LEDインジケータ風の色を変える
picIndicator.BackColor = Color.LightGreen
End If
End Sub

'** セカンドスレッドの作成とサーバーのスタート**
Private Function ServerStart() As Boolean
'TcpListenerを使用してサーバーの接続の確立
Try
'ログの書き込み
WriteLog("サーバを開始しました。")
'スレッドの作成と開始
threadServer = New Thread(New ThreadStart(AddressOf ServerListen))
threadServer.Start()
Return (True)

Catch ex As Exception
'エラーが起きた
WriteLog("サーバ接続エラー:" & ex.Message.ToString())
Listener.[Stop]()
picIndicator.BackColor = Color.Navy
Return (False)
End Try
End Function



'/** セカンドスレッドせで実行されるサーバーのListen **"
'サーバーのListen
Private Sub ServerListen()
'TcpListenerを作成
Listener = New TcpListener(IPAddress.Any, Integer.Parse(textBoxPortNo.Text))
Listener.Start()
' クライアントの接続を受けるための永久ループ
Try
Do
' Listener.AcceptSocketは同期メソドで接続要求が有るまで
'値を返さずここで待機します
' 新しい接続要求ががあると接続を許可して
' 新しいソケットを返します

Dim socketForClient As Socket = Listener.AcceptSocket()

'クライアント毎の接続とフォームのインスタンスを渡す
Dim handler As New ClientHandler(socketForClient, Me)

'ListクラスにClientHandlerのインスタンスを保持します
Me.BeginInvoke(New dlgsetList(AddressOf Me.addNewClient), New Object() {handler})

'読み込みを開始
handler.StartRead()
Loop While True

Catch generatedExceptionName As System.Threading.ThreadAbortException
Exit Sub
'スレッドが閉じられた時に発生
Catch generatedExceptionName As System.Net.Sockets.SocketException
Exit Sub
'スレッドが閉じられた時に発生
Catch ex As Exception
'匿名デリゲートでエラーを書き込む
Me.textBoxLog.BeginInvoke(New dlgWriteLog(AddressOf WriteLog), New Object() {"受信エラー " & ex.ToString()})
Exit Sub
End Try
End Sub

'/**** 接続、切断、受信処理****/"
'** 受信文字の表示***
'受信があった時に呼ばれて
'受信文字をテキストボックスに書き込みます
Public Sub WriteReadText(ByVal cl As ClientHandler, ByVal text As String)
'Listクラスが保持しているClientHandlerの中のSocketクラスの
'Handleを比較し、クライアントを識別します。
Dim no As Integer = 0
For i As Integer = 0 To lstClientHandler.Count - 1
If lstClientHandler(i) Is cl Then
'クライアントのハンドルが一致した
no = CInt(lstClientHandler(i).ClientHandle)
Exit For
End If
Next
'送られたメッセージをクライアントの情報と時間と共に書き込む
Me.textBoxRead.AppendText((("[" & no.ToString() & "さんからのメッセージ] ") + DateTime.Now.ToString() & vbCr & vbLf) + text & vbCr & vbLf)
End Sub

'** ClientHandleクラスのListクラスへの登録**
'新しい接続が有った時Delegateから呼ばれて、
'新しいClientHandlerクラスのインスタンスをListクラスに登録します。
Private Sub addNewClient(ByVal cl As ClientHandler)
lstClientHandler.Add(cl)
resetClientListBox()
WriteLog(cl.ClientHandle.ToString() & " さんが接続しました。")
End Sub

'** 切断が生じた時のClientHandleクラスのListクラスからの削除**
'切断があった時にDelegateから呼ばれて
'切断されたクライアントのClientHandleクラスのインスタンスをを
'Listクラスから削除します。
Public Sub deleteClient(ByVal cl As ClientHandler)
'Listクラスを総なめ
For i As Integer = 0 To lstClientHandler.Count - 1
If lstClientHandler(i) Is cl Then
WriteLog(lstClientHandler(i).ClientHandle.ToString() & " さんが切断しました。")
lstClientHandler.RemoveAt(i)
resetClientListBox()
Exit Sub
End If
Next
End Sub


'** クライアント表示の更新**
'接続、切断が有った時にListを総なめして
'接続しているクライアント表示のListBoxwoを新しく書き直す
Private Sub resetClientListBox()
listBoxClient.Items.Clear()
For i As Integer = 0 To lstClientHandler.Count - 1
listBoxClient.Items.Add(lstClientHandler(i).ClientHandle.ToString())
Next
End Sub



'**************************************
'サーバーの終了処理
'**************************************
'ストップボタン押下
Private Sub butStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles butStop.Click
StopSock()

End Sub


Private Sub FomServer_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
StopSock()
End Sub
Private Sub StopSock()
CloseServer()
'ボタンのenableを変える
butStart.Enabled = True
butStop.Enabled = False
End Sub

'サーバーのクローズ
Private Sub CloseServer()
If Listener IsNot Nothing Then
Listener.Stop()
Thread.Sleep(20)
Listener = Nothing
End If

'スレッドは必ず終了させること
If threadServer IsNot Nothing Then
threadServer.Abort()
threadServer = Nothing
End If
'インディケータの色を変える
picIndicator.BackColor = Color.Navy
'ログを書き込む
WriteLog("サーバーが閉じられました。")

End Sub


'ListBoxでクライアントのシリアル番号を選択して
'メッセージを送信する

'送信ボタン押下

Private Sub butSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles butSend.Click
Dim clientHandler As ClientHandler = Nothing

'リストボックスでクライアントのハンドルが選択されていない。
If listBoxClient.SelectedItem Is Nothing Then
Exit Sub
End If

'ListBoxの選択された番号を取得
Dim no As Integer = Integer.Parse(listBoxClient.SelectedItem.ToString())


'ListからハンドルをキーににをKeyに該当のclientHandlerを取得する
For i As Integer = 0 To lstClientHandler.Count - 1
If CInt(lstClientHandler(i).ClientHandle) = no Then
clientHandler = lstClientHandler(i)
Exit For
End If
Next

'sift-jisに変換して送る
Dim data As Byte() = ecSjis.GetBytes(textBoxWrite.Text)

Try
clientHandler.WriteString(data)
Catch ex As Exception
MessageBox.Show(ex.ToString(), "送信エラー")
End Try

End Sub

'各テキストボックスのクリア

Private Sub butClsWrite_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles butClsWrite.Click
textBoxWrite.Clear()
End Sub

Private Sub butClsRead_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles butClsRead.Click
textBoxWrite.Clear()
End Sub

Private Sub butClsLog_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles butClsLog.Click
textBoxLog.Clear()
End Sub

'Logを書き込む
Public Sub WriteLog(ByVal strlog As String)
Me.textBoxLog.AppendText((DateTime.Now.ToString() & " ") + strlog & vbCr & vbLf)
End Sub
End Class

'/**** 複数コネクション、非同期I/O クラス****/"
Public Class ClientHandler
Private buffer As Byte()
'受信データ
Private socket As Socket
Private networkStream As NetworkStream
Private callbackRead As AsyncCallback
Private callbackWrite As AsyncCallback

'FomServerの参照を保持する
Private fomServer As FomServer = Nothing

'受送信は必ずshift-jisと仮定している
'他の文字の場合はここを変える事
Private ecUni As Encoding = Encoding.GetEncoding("utf-16")
Private ecSjis As Encoding = Encoding.GetEncoding("shift-jis")

'クラスのコンストラクタ
Public Sub New(ByVal socketForClient As Socket, ByVal _FomServer As FomServer)
'呼び出し側のSocketを保持
socket = socketForClient

'呼び出し側のフォームのインスタンスを保持
fomServer = _FomServer

'読み込み用のバッファ
buffer = New Byte(255) {}

'socketの読み書き用のstreamを作成します
networkStream = New NetworkStream(socketForClient)

'読み込み完了時にCLRから呼ばれるメソド
callbackRead = New AsyncCallback(AddressOf Me.OnReadComplete)

'書き込み完了時にCLRから呼ばれるメソド
callbackWrite = New AsyncCallback(AddressOf Me.OnWriteComplete)
End Sub

'Socketクラスのハンドルを返します
Public ReadOnly Property ClientHandle() As IntPtr
Get
Return socket.Handle
End Get
End Property

' クライアントからの文字列読み出し開始
'別スレッドで行われ、読み込み終了時にcallbackReadがCLRによって
'呼び出されます。
Public Sub StartRead()
networkStream.BeginRead(buffer, 0, buffer.Length, callbackRead, Nothing)
End Sub


'networkStream.BeginReadの別スレッドから、読み込み完了時
'又はクライアント切断時にコールバックされます。
Private Sub OnReadComplete(ByVal ar As IAsyncResult)
Try
'受信文字をStreamから読み込みます
If networkStream Is Nothing Then
Exit Sub
End If

'受信バイト数が返る
Dim bytesRead As Integer = networkStream.EndRead(ar)


If bytesRead > 0 Then
'受信文字が有った
'受信部分だけ切り出す
Dim getByte As Byte() = New Byte(bytesRead - 1) {}
For i As Integer = 0 To bytesRead - 1
getByte(i) = buffer(i)
Next

Dim uniBytes As Byte()
''S-Jisからユニコードに変換
uniBytes = Encoding.Convert(ecSjis, ecUni, getByte)

'バイト配列から文字列に変換する
Dim strGetText As String = ecUni.GetString(uniBytes)
'受信文字を切り出す

'メインスレッドのテキストボックスに書き込む
fomServer.Invoke(New dlgWriteText(AddressOf fomServer.WriteReadText), New Object() {Me, strGetText})

'次の受信を待つ
networkStream.BeginRead(buffer, 0, buffer.Length, callbackRead, Nothing)
Else
'終了ボタンが押され場合はここに落ちる
'クライアントのListからの削除
fomServer.Invoke(New dlgsetList(AddressOf fomServer.deleteClient), New Object() {Me})
networkStream.Close()
socket.Close()
networkStream = Nothing
Thread.Sleep(20) 'これを入れないとNullReferenceExceptionが起きる
socket = Nothing
End If

Catch generatedExceptionName As System.Net.Sockets.SocketException
Exit Sub
'スレッドが閉じられた時に発生
'何もしません;
Catch generatedExceptionName As System.IO.IOException
Exit Sub
'スレッドが閉じられた時に発生
Catch ex As Exception
'エラーログの書き込み
fomServer.Invoke(New dlgWriteLog(AddressOf fomServer.WriteLog), New Object() {"受信エラーが起こりました" & ex.ToString()})
End Try
End Sub

'クライアントに送信
'別スレッドで送信し、送信が終了すると
'OnWriteCompleteがコールバックされます。
Public Sub WriteString(ByVal buffer As Byte())
networkStream.BeginWrite(buffer, 0, buffer.Length, callbackWrite, Nothing)
End Sub

' 文字列の書き込みが完了したときにメッセージを出力して読み取りを続けます
Private Sub OnWriteComplete(ByVal ar As IAsyncResult)
networkStream.EndWrite(ar)
'networkStream.BeginRead(buffer, 0, buffer.Length, callbackRead, Nothing)
End Sub
End Class

画像 上記VB2005コードのダウンロード
 C#、Vb2005 でSocket通信 (複数クライアント&非同期処理)本文に戻る   


画像ファイル