基于Socket的网络聊天室编程(第一版)
基于Socket的网络聊天室编程(第一版)
木宛城主 发表于3年前
基于Socket的网络聊天室编程(第一版)
  • 发表于 3年前
  • 阅读 33
  • 收藏 1
  • 点赞 0
  • 评论 0

标题:腾讯云 新注册用户域名抢购1元起>>>   

一:什么是套接字

在网络编程中最常用的方案便是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的通讯过程:

客户端:

  1. 申请一个Socket
  2. 连接到指定服务器(指明了IP地址和端口号)

服务器端:

  1. 申请一个Socket
  2. 绑定到一个IP地址和端口上
  3. 开启侦听,等待接受连接

 

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://jameszou.blog.51cto.com/2173852/641032

 

源代码下载:

http://files.cnblogs.com/OceanEyes/Ocean.Eyes.SocketWork.Solution.rar

 

 

 

 

 

共有 人打赏支持
粉丝 1
博文 222
码字总数 199010
×
木宛城主
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: