C#利用Socket实现客户端之间直接通信
2019獨角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
實驗功能:
?設(shè)計程序,分別構(gòu)建通信的兩端:服務(wù)器端和客戶端應(yīng)用程序,套接字類型為面向連接的Socket,自己構(gòu)建雙方的應(yīng)答模式,實現(xiàn)雙方的數(shù)據(jù)的發(fā)送和接收(S發(fā)給C,C發(fā)給S)。
服務(wù)端程序能響應(yīng)單個或任意多個客戶端連接請求;服務(wù)端能向單個客戶發(fā)送消息,支持群發(fā)消息給所有客戶端;
通信的雙方具備異常響應(yīng)功能,包括對方異常退出的處理。如果客戶端退出,服務(wù)器有響應(yīng);反之亦然。
客戶端之間直接通信,C與C之間直接通信(不是通過S傳遞)。
設(shè)計思路:
服務(wù)器設(shè)計思路:服務(wù)器的設(shè)計是這次實驗最復(fù)雜的部分,因為服務(wù)器的功能比較多。作為服務(wù)器,它要可以同時與多個客戶端連接,為每一個連接的客戶端創(chuàng)建一個通信Socket,自己還要有一個Socket用于監(jiān)聽客戶端的連接請求;服務(wù)器要創(chuàng)建一個數(shù)據(jù)結(jié)構(gòu)用于保存連接進來的客戶端的信息(Socket和客戶端的名字);服務(wù)器要將連接進來的客戶端顯示出來,用戶可以根據(jù)顯示出來的用戶列表來向指定的客戶端發(fā)信息;服務(wù)器要能及時地刷新客戶端列表,當(dāng)有新的客戶端連接進來或是退出的時候要及時通知所有的客戶端并刷新自己的客戶端列表;服務(wù)器要能接收所有的客戶端的信息,并將信息無錯地轉(zhuǎn)發(fā)給指定的客戶端。
客戶端設(shè)計思路:客戶端的設(shè)計相對于服務(wù)器來說的話對會比較簡單一點。客戶端要有接收服務(wù)器信息的功能,但客戶端只向服務(wù)器發(fā)信息,客戶端通過服務(wù)器的轉(zhuǎn)發(fā)功能向其它的客戶端發(fā)送信息。客戶端要可以處理服務(wù)器發(fā)過來的信息,還要有數(shù)據(jù)結(jié)構(gòu)用來保存所有客戶端的名字,并將所有客戶端名字列表顯示出來。可以指定客戶端列表里面的多個項來向不同的客戶端發(fā)信息。
通信數(shù)據(jù)處理:無論是服務(wù)器發(fā)給客戶端,還是客戶端發(fā)給服務(wù)器的數(shù)據(jù),雙方都要進行處理。對于不用的類型的數(shù)據(jù)要設(shè)計不用的標(biāo)志信息,當(dāng)雙方收到信息后跟據(jù)標(biāo)志信息進行不同的處理。數(shù)據(jù)可以分為三種?:
a)登陸信息。這類信息提示有新的客戶端連接進來。該信息由客戶端首先發(fā)給服務(wù)器,服務(wù)器收到后會更新自己的在線客戶端列表,增加與該客戶端通信的Socket和名字,并將該信息轉(zhuǎn)發(fā)給所有在線的客戶端,提醒客戶端即時更新客戶端列表。這類信息以“login,客戶端名”的形式發(fā)送。
b)退出信息。這類信息提示發(fā)信息的客戶端即將退出服務(wù)器。該信息由客戶端首先發(fā)給服務(wù)器,服務(wù)器收到后會更新自己的在線客戶端列表,刪除與該客戶端通信的Socket和名字,并將該信息轉(zhuǎn)發(fā)給所有在線的客戶端,提醒客戶端即時更新客戶端列表。這類信息以“logout,客戶端名”的形式發(fā)送。
c)通信信息。這類信息提示發(fā)送信息的客戶端向在線的某個客戶端或是服務(wù)器發(fā)起了通信,也可以是服務(wù)器與某個客戶端發(fā)起了通信。如果該信息是服務(wù)器發(fā)給客戶端或是客戶端發(fā)給服務(wù)器,則直接發(fā)送,不用經(jīng)過轉(zhuǎn)發(fā);如果是客戶端向另一個客戶端發(fā)送信息,則是先發(fā)給服務(wù)器,服務(wù)再轉(zhuǎn)發(fā)給指定的客戶端。這類信息以“talk,目的客戶端名,發(fā)送的信息”的形式發(fā)送。
?線程的設(shè)計思路:在服務(wù)器方面,需要一個程專門用于監(jiān)聽客戶端的連接請求,對于連接進來的每一個客戶端,還要創(chuàng)建一個線程用于接收信息,程序的主線程用于向不同的客戶端發(fā)送信息,所以服務(wù)器至少需要要n+2(n>=0)個線程;在客戶端方面,需要一個線程用于接收服務(wù)的信息,還要一個線程用于向服務(wù)器發(fā)送信息,所以只需要2個線程。
信息無邊界問題:由于這里用的C#里面原始Socket套接字,所以在數(shù)據(jù)收發(fā)的過程中會出現(xiàn)無邊界的問題。有時服務(wù)器向客戶端發(fā)送多條不同類型的信息,客戶端會把它們合并在一起,當(dāng)成一條信息處理。為了提取不同類型的信息,發(fā)送信息之前要為每一條信息加特定的結(jié)束符。
客戶端之間直接通信問題:為了實現(xiàn)客戶端之間的直接通信,客戶端之間必須知道其它客戶端的IP和端口,這可以通過服務(wù)器的轉(zhuǎn)發(fā)得到客戶端之間的IP和端口。客戶端也必須有一個自己可用的端口號用來和其它客戶端之間的通信,所以除了第一次的客戶端與服務(wù)器的連接以外,客戶端即是服務(wù)器也是客戶端。
服務(wù)器處理不同類型信息代碼:
?string[]?splitString?=?receiveString.Split(',');?????????????//分割字符switch?(splitString[0].ToLower()){case??"login":????????????????????????????//?登陸信息user.username?=?splitString[1];userList.Add?(user);??????????????????//?增加用戶列表AddItemToListBox?(user.username);?????//?刷新用戶列表sendToAllClient?(user,receiveString);?//?通知所有在線用戶FirstLogin?(user);break;?case??"logout":???????????????????????????//?退出信息DeletItemInListBox?(user.username);??sendToAllClient?(user,receiveString);//?通知所有在線用戶?RemoveUser?(user);???????????????????//?刪除用戶信息UserCount?(--usercount);?????????????//?刷新用戶列表break;?case??"talk":????????????????????????????//?對話信息multMessage?(user,receiveString);????//?轉(zhuǎn)發(fā)對話break;?default:?sendMessageTorichBox?("不知道什么意思!");break;?}服務(wù)器監(jiān)聽客戶端代碼:
private??void?button1_Click(object?sender,?EventArgs?e){isNormalExit??=?false;buttu_richBoxDelegate??d?=?buttu_richBox;???????//?委托事件try?{myListener.Listen?(10);??????????????????????????//?開始監(jiān)聽richTextBox1.Invoke(d,"成功監(jiān)聽.");???????????//?成功監(jiān)聽}?catch{richTextBox1.Invoke(d,"監(jiān)聽失敗。");?????????}Thread?mhThread?=?new?Thread(ListenClientConnect);??//?創(chuàng)建新的線程mhThread.IsBackground?=?true;???????????????????????//?設(shè)置為后臺線程mhThread.Start?();button1.Enabled=false;??????????????????????????????//?開始監(jiān)聽按鈕不可用button2.Enabled=?true;????????????????????????????????}服務(wù)器接受客戶端代碼:
private?void?ListenClientConnect?() {Socket?newClient?=null;While?(isNormalExit==false){ try?{newClient?=?myListener.Accept();??????????//?接受客戶端if(isNormalExit?==?true)??????????????????//?如果服務(wù)器停止監(jiān)聽{?newClient.Close();?????????????????????//?關(guān)閉Socketusercount?=?0;UserCount(usercount);Break;}}Catch{break;}User?user?=?new?User(newClient);?????????????????//?保存客戶端列表Thread?threadReceive?=?new?Thread(ReceiveData);??//?創(chuàng)建新的線程threadReceive.IsBackground=true;?????????????????//設(shè)置為后臺線程threadReceive.Start(user);???????????????????????//?開始線程UserCount(++usercount);?????????????????????????//?客戶端人數(shù)加1}}客戶端連接服務(wù)器代碼:
Private??void?button1_Click(object?sender,?EventArgs?e) {??button1.Enabled?=?false;client??=?new?Socket(AddressFamily.InterNetwork,?SocketType.Stream,?ProtocolType.Tcp);??????????????????????????????//新建套接字AddrichTextBox1Massage?d?=?sendrichTextBox1Massage;Try?{String??name?=?Dns.GetHostName();?????????????????????//?獲得計算機的名字IPHostEntry?me?=?Dns.GetHostEntry?(name);?????????????//獲得計算機IPforeach(IPAddress?ips?in?me.AddressList){Try?{?IPEndPoint?ep?=?new??IPEndPoint(ips,?8889);??client.Connect(new?IPEndPoint(ips,?8889));??????//?連接服務(wù)器break;}catch{//若獲取的IP是vs6的話? }}client.Send(Encoding.UTF8.GetBytes("login,"?+?textBox1.Text));//向服務(wù)器發(fā)信息Thread?threadReceive?=?new?Thread(new?ThreadStart(ReceiveData));//創(chuàng)建新線程threadReceive.IsBackground?=?true;???????????????????????????//?設(shè)置為后臺線程threadReceive.Start();???????????????????????????????????????//開始線程}客戶端接受服務(wù)器信息代碼:
private?void?ReceiveData(){AddrichTextBox1Massage?d?=?sendrichTextBox1Massage;int?receiveLength;while(isExit==false){try{receiveLength?=?client.Receive(result);?????????????//開始接收信息recieveMessage=Encoding.UTF8.GetString(result,0,receiveLength);}catch{if?(isExit?==?false){richTextBox1.Invoke(d,?"與服務(wù)器失去聯(lián)系。");?client.Shutdown(SocketShutdown.Both);????????????//?關(guān)閉套接字client.Close();}break;}string[]?splitString?=?recieveMessage.Split(',');?????????//處理信息string?command?=?splitString[0].ToLower();switch(command)?{case?"login":AddOnline(recieveMessage);???????????????//?登陸信息break;case?"logout":?RemoveUserName(splitString[1]);????????//?退出信息break;case?"talk":?richTextBox1.Invoke(d,?"["+splitString[1]?+?"]對我說:?"?+?splitString[2]);?????????????????????????//?對話信息break;default:?richTextBox1.Invoke(d,"不知什么意思。");?break;}?}LostConnect();???????????????????????????????????????????????//關(guān)閉連接}客戶端監(jiān)聽其它客戶端代碼:
private?void?ServerReceive(Object?client) {AddrichTextBox1Massage?d?=?sendrichTextBox1Massage;Socket?myClientSocket?=?(Socket)client;byte[]?str?=new?byte[1024];while?(true)?{try{int?n?=?myClientSocket.Receive(str);richTextBox1.Invoke(d,?Encoding.UTF8.GetString(str,?0,?n));break;}catch?{myClientSocket.Close();//richTextBox1.Invoke(d,?"接收消息失敗!");break;}}myClientSocket.Close();}程序運行效果:
服務(wù)器運行界面:
有客戶端連接進服務(wù)器:
在線客戶列表顯示了連接進的客戶端的名字,在線客戶人數(shù)顯示為3人
上圖表示有3個客戶端連接進了服務(wù)器。
服務(wù)器向客戶端發(fā)送信息:
服務(wù)器向在線客戶列表里的2個客戶同時發(fā)了信息,2個客戶端收到了正確的信息。
客戶端的啟動界面:
客戶端自動生成用戶的名字。
客戶端登陸的界面:
客戶端顯示連接成功,并刷新在線用戶列表。
多個客戶端連接服務(wù)器時的界面:
當(dāng)有多個客戶端與服務(wù)器連接時,客戶端會自動更新在線用戶列表。
客戶端向其它客戶端發(fā)TCP信息:
客戶端可以同時向服務(wù)器和多個客戶端發(fā)送信息。
客戶端接收來自其它客戶端的TCP信息:
接收的信息是其它客戶端直接發(fā)過來的,不經(jīng)過服務(wù)器的轉(zhuǎn)發(fā)。
客戶端退出時:
客戶端退出時,服務(wù)器會知道退出的用戶,并把該客戶端移出列表,同時發(fā)信息通知其它的客戶端,使它們可以及時地更新用戶列表。
服務(wù)器退出時:
當(dāng)服務(wù)器退出時,所有的客戶端會提示與服務(wù)器失去聯(lián)系,并將在線用戶列表清空。
轉(zhuǎn)載于:https://my.oschina.net/u/1540055/blog/280470
總結(jié)
以上是生活随笔為你收集整理的C#利用Socket实现客户端之间直接通信的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux总结篇 linux命令 虚拟机
- 下一篇: C# snap7