一:什么是套接字
在網絡編程中最常用的方案便是Client/Server (客戶機/服務器)模型。在這種方案中客戶應用程序向服務器程序請求服務。一個服務程序通常在一個眾所周知的地址監聽對服務的請求,也就是說,服務進程一直處於休眠狀態,直到一個客戶向這個服務的地址提出了連接請求。在這個時刻,服務程序被"驚醒"並且為客戶提供服務-對客戶的請求作出適當的反應。為了方便這種Client/Server模型的網絡編程,90年代初,由Microsoft聯合了其他幾家公司共同制定了一套WINDOWS下的網絡編程接口,即Windows Sockets規范,它不是一種網絡協議,而是一套開放的、支持多種協議的Windows下的網絡編程接口。現在的Winsock已經基本上實現了與協議無關,你可以使用Winsock來調用多種協議的功能,但較常使用的是TCP/IP協議。Socket實際在計算機中提供了一個通信端口,可以通過這個端 口與任何一個具有Socket接口的計算機通信。應用程序在網絡上傳輸,接收的信息都通過這個Socket接口來實現。
簡單的來說,socket非常類似於電話插座。以一個電話網為例。電話的通話雙方相當於相互通信的2個程序,電話號碼就是IP地址。任何用戶在通話之前,首先要占有一部電話機,相當於申請一個socket;同時要知道對方的號碼,相當於對方有一個固定的socket。然后向對方撥號呼叫,相當於發出連接請求。對方假如在場並空閑,拿起電話話筒,雙方就可以正式通話,相當於連接成功。雙方通話的過程,是一方向電話機發出信號和對方從電話機接收信號的過程,相當於向socket發送數據和從socket接收數據。通話結束后,一方掛起電話機相當於關閉socket,撤消連接。
二:Socket的一般應用模式(Server-Client)
客戶端Client Socket連接服務端指定的端口(負責接收和發送服務端消息)
- 必須指定要連接的服務端地址和斷口。
- 通過創建一個Socket對象來初始化一個到服務端的連接。
服務端Welcoming Socket監聽到客戶端連接,創建Connection Socket(負責和客戶端通訊)
- 一個負責接監聽客戶端連接的套接字
- 每成功接收到一個客戶端連接便在服務端產生一個對應Socket
Socket的通訊過程:
客戶端:
- 申請一個Socket
- 連接到指定服務器(指明了IP地址和端口號)
服務器端:
- 申請一個Socket
- 綁定到一個IP地址和端口上
- 開啟偵聽,等待接受連接
socket通信的基本流程圖:
三:網絡聊天室原理與實現-服務端:
- 開始監聽客戶連接 -WatchConnection()
Thread threadWatchPort = null;//監聽端口線程 Socket socketWatchPort = null; //存儲客戶端連接的信息 Dictionary<string, ClientConnection> dictConnections = new Dictionary<string, ClientConnection>(); //IP地址 IPAddress address = null; //IP節點 IPEndPoint endpoint = null; /// <summary> /// 開始監聽用戶連接 /// </summary> /// 函數原型是int PASCAL listen(SOCKET,int); ///其中第二參數的含義樓主理解錯誤,並非最大可連接數,而是最多可緩存的監聽個數。 ///這里listen()維護一個隊列,每一個請求監聽,但尚未被accept()的請求都放在隊列里,而一旦監聽被accept()之后,該監聽就從隊列移走了。 private void WatchConnection() { try { //創建IP地址 address = IPAddress.Parse(txtIP.Text); //創建IP節點(包含IP和端口) endpoint = new IPEndPoint(address, int.Parse(txtPort.Text)); //創建一個監聽套接字(基於TCP的流式套接字) socketWatchPort = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //將套接字綁定到主機上某個端口 socketWatchPort.Bind(endpoint); //能同時處理的連接數 socketWatchPort.Listen(10); threadWatchPort = new Thread(WatchPort); threadWatchPort.Name = "threadWatchPort"; //設為后台線程,當所有前台線程停止后會自動關閉 threadWatchPort.IsBackground = true; threadWatchPort.Start(); ShowMsg("服務器啟動完畢,等待客戶端連接"); } catch (Exception ex) { ShowErrorMsg("",ex); } }
- 在另一線程監聽指定端口- WatchPort()
private void WatchPort() { while (true) { try { //cSok和客戶端通信套接字 Socket cSok = socketWatchPort.Accept(); ClientConnection conn = new ClientConnection(this, cSok); ShowMsg("客戶端" + cSok.RemoteEndPoint.ToString() + "連接成功"); dictConnections.Add(cSok.RemoteEndPoint.ToString(), conn); AddClientToList(cSok.RemoteEndPoint.ToString()); } catch (Exception ex) { ShowErrorMsg("",ex); //break; } } }
- 和客戶端連接的通道類:
public class ClientConnection { Thread threadClient = null; Socket socket = null; FrmMain frmMain = null; bool doesClose = false; public ClientConnection(FrmMain frmMain,Socket socket) { this.frmMain = frmMain; this.socket = socket; threadClient = new Thread(WatchClientMsg); threadClient.IsBackground = true; threadClient.Start(); } #region 監聽客戶端消息 -WatchClientMsg(); /// <summary> /// 監聽客戶端消息 /// </summary> private void WatchClientMsg() { while (!doesClose) { try { byte[] byteMsgRec = new byte[1024 * 1024 * 4]; int length = socket.Receive(byteMsgRec, byteMsgRec.Length, SocketFlags.None); if (length > 0) { string strMsgRec = Encoding.UTF8.GetString(byteMsgRec, 1, length - 1); ShowMsg(socket.RemoteEndPoint.ToString() + "說:" + strMsgRec); } } catch (Exception ex) { if (socket!=null) { ShowErr("客戶端"+socket.RemoteEndPoint.ToString()+"斷開連接:",ex); frmMain.RemoveListItem(socket.RemoteEndPoint.ToString()); break; } // } } } #endregion #region 發送窗口抖動 SendShake() public void SendShake() { try { byte[] finalByte = new byte[1]; finalByte[0] = 2; socket.Send(finalByte); } catch (Exception ex) { ShowErr("SendShake()",ex); } } #endregion #region 發送消息 -SendMsg(string msg) /// <summary> /// 發送消息 標志:第一位是:0 /// </summary> /// <param name="msg"></param> public void SendMsg(string msg) { try { byte[] msgSendByte = Encoding.UTF8.GetBytes(msg); byte[] finalByte = new byte[msgSendByte.Length + 1]; finalByte[0] = 0; Buffer.BlockCopy(msgSendByte, 0, finalByte, 1, msgSendByte.Length); socket.Send(finalByte); } catch (Exception ex) { ShowErr("SendMsg(string msg)",ex); throw; } } #endregion #region 發送文件 -SendFile(string fileName) /// <summary> /// 發送文件 標記:第一位為1 /// </summary> /// <param name="fileName">文件路徑</param> public void SendFile(string fileName) { FileStream fs = null; try { fs = new FileStream(fileName, FileMode.Open); byte[] byteFile = new byte[1024 * 1024 * 5]; int length = fs.Read(byteFile, 0, byteFile.Length); if (length > 0) { byte[] byteFinalFile = new byte[length + 1]; byteFinalFile[0] = 1; Buffer.BlockCopy(byteFile, 0, byteFinalFile, 1, length); socket.Send(byteFinalFile); } } catch (Exception ex) { ShowErr("SendFile(string fileName)", ex); } finally { fs.Close(); } } #endregion #region 關閉與客戶端連接-Close() public void Close() { doesClose = true; threadClient.Abort(); socket.Shutdown(SocketShutdown.Both); socket.Close(); socket = null; } #endregion #region 在面板上顯示消息 -ShowMsg() private void ShowMsg(string msg) { this.frmMain.ShowMsg(msg); } private void ShowErr(string errMsg,Exception ex) { this.frmMain.ShowErrorMsg(errMsg, ex); } #endregion }
三:網絡聊天室原理與實現-客戶端:
- 初始化客戶端
//初始化客戶端Socket用於連接服務器端 private void InitSocketAndConnect() { try { dgShowMsg = new DGShowMsg(DoShowMsg); //創建一個客戶端Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); address = IPAddress.Parse(txtIp.Text.Trim()); endP = new IPEndPoint(address, int.Parse(txtPort.Text.Trim())); //連接到指定服務器的指定端口 clientSocket.Connect(endP); ShowMsg("連接成功!"); } catch (Exception ex) { ShowErr("InitSocketAndConnect()",ex); } }
- 接收消息
private void WatchMsg() { while (true) { byte[] msgByte = new byte[1024 * 1024 * 2]; int length = 0; try { length = clientSocket.Receive(msgByte,msgByte.Length,SocketFlags.None); if (length>0) { if (msgByte[0]==0)//接受文字 { ShowMsg("對方說:"+Encoding.UTF8.GetString(msgByte,1,length-1)); } else if (msgByte[0]==1)//接受文件 { SaveFileDialog sfd = new SaveFileDialog(); if (sfd.ShowDialog()==DialogResult.OK) { string savePath = sfd.FileName; using (FileStream fs=new FileStream (savePath,FileMode.Create) ) { fs.Write(msgByte,1,length-1); } ShowMsg("文件保存成功:"+savePath); } } else//抖動窗體 { ShakeWindow(); } } }
- 發送消息到服務端
/// <summary> /// 發送消息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSendMsg_Click(object sender, EventArgs e) { if (clientSocket!=null) { try { string msgSend = txtInput.Text.Trim(); byte[] orgByte = Encoding.UTF8.GetBytes(msgSend); byte[] finalByte=new byte[orgByte.Length+1]; finalByte[0] = 0; Buffer.BlockCopy(orgByte,0,finalByte,1,orgByte.Length); clientSocket.Send(finalByte); ShowMsg("我說:"+msgSend); } catch (SocketException ex) { ShowErr("發送消息時",ex); } } }
- 抖動窗體:
/// <summary> /// 抖動窗體 /// </summary> private void doShakeWin() { Random ran = new Random(); System.Drawing.Point point = this.Location; for (int i = 0; i < 30; i++) { this.Location = new System.Drawing.Point(point.X + ran.Next(8), point.Y + ran.Next(8)); System.Threading.Thread.Sleep(15); this.Location = point; System.Threading.Thread.Sleep(15); } }
程序參考出處:
源代碼下載:
http://files.cnblogs.com/OceanEyes/Ocean.Eyes.SocketWork.Solution.rar