C#利用Socket实现客户端之间直接通信
2019獨角獸企業重金招聘Python工程師標準>>>
實驗功能:
?設計程序,分別構建通信的兩端:服務器端和客戶端應用程序,套接字類型為面向連接的Socket,自己構建雙方的應答模式,實現雙方的數據的發送和接收(S發給C,C發給S)。
服務端程序能響應單個或任意多個客戶端連接請求;服務端能向單個客戶發送消息,支持群發消息給所有客戶端;
通信的雙方具備異常響應功能,包括對方異常退出的處理。如果客戶端退出,服務器有響應;反之亦然。
客戶端之間直接通信,C與C之間直接通信(不是通過S傳遞)。
設計思路:
服務器設計思路:服務器的設計是這次實驗最復雜的部分,因為服務器的功能比較多。作為服務器,它要可以同時與多個客戶端連接,為每一個連接的客戶端創建一個通信Socket,自己還要有一個Socket用于監聽客戶端的連接請求;服務器要創建一個數據結構用于保存連接進來的客戶端的信息(Socket和客戶端的名字);服務器要將連接進來的客戶端顯示出來,用戶可以根據顯示出來的用戶列表來向指定的客戶端發信息;服務器要能及時地刷新客戶端列表,當有新的客戶端連接進來或是退出的時候要及時通知所有的客戶端并刷新自己的客戶端列表;服務器要能接收所有的客戶端的信息,并將信息無錯地轉發給指定的客戶端。
客戶端設計思路:客戶端的設計相對于服務器來說的話對會比較簡單一點。客戶端要有接收服務器信息的功能,但客戶端只向服務器發信息,客戶端通過服務器的轉發功能向其它的客戶端發送信息。客戶端要可以處理服務器發過來的信息,還要有數據結構用來保存所有客戶端的名字,并將所有客戶端名字列表顯示出來。可以指定客戶端列表里面的多個項來向不同的客戶端發信息。
通信數據處理:無論是服務器發給客戶端,還是客戶端發給服務器的數據,雙方都要進行處理。對于不用的類型的數據要設計不用的標志信息,當雙方收到信息后跟據標志信息進行不同的處理。數據可以分為三種?:
a)登陸信息。這類信息提示有新的客戶端連接進來。該信息由客戶端首先發給服務器,服務器收到后會更新自己的在線客戶端列表,增加與該客戶端通信的Socket和名字,并將該信息轉發給所有在線的客戶端,提醒客戶端即時更新客戶端列表。這類信息以“login,客戶端名”的形式發送。
b)退出信息。這類信息提示發信息的客戶端即將退出服務器。該信息由客戶端首先發給服務器,服務器收到后會更新自己的在線客戶端列表,刪除與該客戶端通信的Socket和名字,并將該信息轉發給所有在線的客戶端,提醒客戶端即時更新客戶端列表。這類信息以“logout,客戶端名”的形式發送。
c)通信信息。這類信息提示發送信息的客戶端向在線的某個客戶端或是服務器發起了通信,也可以是服務器與某個客戶端發起了通信。如果該信息是服務器發給客戶端或是客戶端發給服務器,則直接發送,不用經過轉發;如果是客戶端向另一個客戶端發送信息,則是先發給服務器,服務再轉發給指定的客戶端。這類信息以“talk,目的客戶端名,發送的信息”的形式發送。
?線程的設計思路:在服務器方面,需要一個程專門用于監聽客戶端的連接請求,對于連接進來的每一個客戶端,還要創建一個線程用于接收信息,程序的主線程用于向不同的客戶端發送信息,所以服務器至少需要要n+2(n>=0)個線程;在客戶端方面,需要一個線程用于接收服務的信息,還要一個線程用于向服務器發送信息,所以只需要2個線程。
信息無邊界問題:由于這里用的C#里面原始Socket套接字,所以在數據收發的過程中會出現無邊界的問題。有時服務器向客戶端發送多條不同類型的信息,客戶端會把它們合并在一起,當成一條信息處理。為了提取不同類型的信息,發送信息之前要為每一條信息加特定的結束符。
客戶端之間直接通信問題:為了實現客戶端之間的直接通信,客戶端之間必須知道其它客戶端的IP和端口,這可以通過服務器的轉發得到客戶端之間的IP和端口。客戶端也必須有一個自己可用的端口號用來和其它客戶端之間的通信,所以除了第一次的客戶端與服務器的連接以外,客戶端即是服務器也是客戶端。
服務器處理不同類型信息代碼:
?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);????//?轉發對話break;?default:?sendMessageTorichBox?("不知道什么意思!");break;?}服務器監聽客戶端代碼:
private??void?button1_Click(object?sender,?EventArgs?e){isNormalExit??=?false;buttu_richBoxDelegate??d?=?buttu_richBox;???????//?委托事件try?{myListener.Listen?(10);??????????????????????????//?開始監聽richTextBox1.Invoke(d,"成功監聽.");???????????//?成功監聽}?catch{richTextBox1.Invoke(d,"監聽失敗。");?????????}Thread?mhThread?=?new?Thread(ListenClientConnect);??//?創建新的線程mhThread.IsBackground?=?true;???????????????????????//?設置為后臺線程mhThread.Start?();button1.Enabled=false;??????????????????????????????//?開始監聽按鈕不可用button2.Enabled=?true;????????????????????????????????}服務器接受客戶端代碼:
private?void?ListenClientConnect?() {Socket?newClient?=null;While?(isNormalExit==false){ try?{newClient?=?myListener.Accept();??????????//?接受客戶端if(isNormalExit?==?true)??????????????????//?如果服務器停止監聽{?newClient.Close();?????????????????????//?關閉Socketusercount?=?0;UserCount(usercount);Break;}}Catch{break;}User?user?=?new?User(newClient);?????????????????//?保存客戶端列表Thread?threadReceive?=?new?Thread(ReceiveData);??//?創建新的線程threadReceive.IsBackground=true;?????????????????//設置為后臺線程threadReceive.Start(user);???????????????????????//?開始線程UserCount(++usercount);?????????????????????????//?客戶端人數加1}}客戶端連接服務器代碼:
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));??????//?連接服務器break;}catch{//若獲取的IP是vs6的話? }}client.Send(Encoding.UTF8.GetBytes("login,"?+?textBox1.Text));//向服務器發信息Thread?threadReceive?=?new?Thread(new?ThreadStart(ReceiveData));//創建新線程threadReceive.IsBackground?=?true;???????????????????????????//?設置為后臺線程threadReceive.Start();???????????????????????????????????????//開始線程}客戶端接受服務器信息代碼:
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,?"與服務器失去聯系。");?client.Shutdown(SocketShutdown.Both);????????????//?關閉套接字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();???????????????????????????????????????????????//關閉連接}客戶端監聽其它客戶端代碼:
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();}程序運行效果:
服務器運行界面:
有客戶端連接進服務器:
在線客戶列表顯示了連接進的客戶端的名字,在線客戶人數顯示為3人
上圖表示有3個客戶端連接進了服務器。
服務器向客戶端發送信息:
服務器向在線客戶列表里的2個客戶同時發了信息,2個客戶端收到了正確的信息。
客戶端的啟動界面:
客戶端自動生成用戶的名字。
客戶端登陸的界面:
客戶端顯示連接成功,并刷新在線用戶列表。
多個客戶端連接服務器時的界面:
當有多個客戶端與服務器連接時,客戶端會自動更新在線用戶列表。
客戶端向其它客戶端發TCP信息:
客戶端可以同時向服務器和多個客戶端發送信息。
客戶端接收來自其它客戶端的TCP信息:
接收的信息是其它客戶端直接發過來的,不經過服務器的轉發。
客戶端退出時:
客戶端退出時,服務器會知道退出的用戶,并把該客戶端移出列表,同時發信息通知其它的客戶端,使它們可以及時地更新用戶列表。
服務器退出時:
當服務器退出時,所有的客戶端會提示與服務器失去聯系,并將在線用戶列表清空。
轉載于:https://my.oschina.net/u/1540055/blog/280470
總結
以上是生活随笔為你收集整理的C#利用Socket实现客户端之间直接通信的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux总结篇 linux命令 虚拟机
- 下一篇: 【 Grey Hack 】万金油脚本:在