聊天程序开发

 

开发前小笔记:

Socket的通信过程

服务器端:

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

客户端:

  • l 申请一个socket
  • l 连接服务器(指明IP地址和端口号)

 

服务器端接到连接请求后,产生一个新的socket(端口大于1024)与客户端建立连接并

进行通讯,原监听socket继续监听。

 

Socket方法

 

-------------------相关类------------------

 

分析结构图:

 

服务端:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.Linq;
  7 using System.Text;
  8 using System.Windows.Forms;
  9 
 10 using System.Net;//包含IP,IPAdress,IPEndPoint(ip和端口)类
 11 using System.Net.Sockets;//导入套接字
 12 using System.Threading;
 13 using System.IO;
 14 
 15 namespace MyCharRoomServer
 16 {
 17     public partial class FChatServer : Form
 18     {
 19         public FChatServer()
 20         {
 21             InitializeComponent();
 22             TextBox.CheckForIllegalCrossThreadCalls = false;//关闭对文本框的跨线程操作检查
 23             
 24         }
 25         Thread threadWatch = null;//负责监听客户端连接请求的线程
 26         Socket socketWatch = null;//负责监听的套接字
 27        
 28        
 29         private void btnBeginListem_Click(object sender, EventArgs e)
 30         {
 31             //创建服务端负责监听的套接字,参数(使用IP4寻址协议,使用流式连接,使用TCP协议传输数据)
 32           
 33             socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 34             //获得文本框中的IP地址对象
 35             IPAddress address = IPAddress.Parse(txtIP.Text.Trim());
 36             //创建包含ip和port的网络节点对象
 37             IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));
 38             //将负责监听的套接字 绑定到唯一的IP和端口上
 39             socketWatch.Bind(endpoint);
 40             //设置监听队列的长度
 41             socketWatch.Listen(10);
 42             //创建负责监听的线程,并传入监听方法
 43             threadWatch = new Thread(WatchConnecting);
 44             threadWatch.IsBackground = true;//设置为后台线程
 45             threadWatch.Start();//开启线程
 46 
 47 
 48             ShowMsg("服务器启动监听成功!");
 49             //socketWatch.Bind
 50             //IPEndPoint
 51         }
 52         //保存了服务器端 所有负责和客户端通信的套接字
 53         Dictionary<string, Socket> dict = new Dictionary<string, Socket>();
 54         //保存了服务器端所有负责调用通信套接字.Recive方法的线程
 55         Dictionary<string, Thread> dictThread = new Dictionary<string, Thread>();
 56         /// <summary>
 57         /// 监听客户端请求的方法
 58         /// </summary>
 59         void WatchConnecting()
 60         {
 61             while (true) //持续不断监听新的客户端的连接请求
 62             {
 63                 
 64                 //开始监听客户端连接请求,注意:Accept方法,会阻断当前的线程!
 65                 Socket sokConnetion = socketWatch.Accept();//一旦监听到客户端的请求,就返回一个负责和该客户端通信的套接字 sokConnetion
 66                 //向列表控件中 添加一个客户端的ip端口字符串 作为客户端的唯一标识
 67                 lbOnline.Items.Add(sokConnetion.RemoteEndPoint.ToString());
 68                 //将与客户端通信的套接字对象 sokConnection添加到键值对集合中,并与客户端IP端口作为键值
 69                 dict.Add(sokConnetion.RemoteEndPoint.ToString(),sokConnetion);
 70                 
 71                 //创建通信线程
 72                 ParameterizedThreadStart pts = new ParameterizedThreadStart(RecMsg);
 73                 Thread thr = new Thread(pts);
 74                 thr.IsBackground = true;//设置为 q
 75                 thr.Start(sokConnetion);
 76                 dictThread.Add(sokConnetion.RemoteEndPoint.ToString(), thr);
 77 
 78                 ShowMsg("客户端连接成功!"+sokConnetion.RemoteEndPoint.ToString());// 79                 //sokConnection.RemoteEndPoint中保存的是当前连接客户端的Ip和端口
 80             }
 81         }
 82         /// <summary>
 83         /// 服务端负责监听客户端发送来的数据的方法
 84         /// </summary>
 85         void RecMsg(object socketClientPara)
 86         {
 87             Socket socketClient = socketClientPara as Socket;
 88             while (true)
 89             {
 90                 //定义一个接收用的缓存区(2M字节数组)
 91                 byte[] arrMsgRec = new byte[1024 * 1024 * 2];
 92                 //将接收到的数据存入arrMsgRec数组,并返回真正接收到的数据长度
 93                 int length = -1;
 94                 try
 95                 {
 96                     length = socketClient.Receive(arrMsgRec);
 97                 }
 98                 catch (SocketException ex)
 99                 {
100                     ShowMsg("异常:" + ex.Message);
101                     //从通信套接字集合中删除被中断连接的通信套接字
102                     dict.Remove(socketClient.RemoteEndPoint.ToString());
103                     //从通信线程集合中删除被中断连接的线程对象
104                     dictThread.Remove(socketClient.RemoteEndPoint.ToString());
105                     //从列表中移除被中断的连接ip:Port
106                     lbOnline.Items.Remove(socketClient.RemoteEndPoint.ToString());
107                     break;
108                     
109                     //dict.Remove(
110                 }
111                 catch (Exception ex)
112                 {
113                     ShowMsg("异常:" + ex.Message);
114                     break;
115                 }
116                 if (arrMsgRec[0] == 0)//判断发送过来的数组的第一个元素是0 则代表发送来的是文本文字数据
117                 { 
118                 //此时是将数组所有的元素都转成字符串,而真正接收到的只有服务端的几个字符
119                 string strMsgRec = System.Text.Encoding.UTF8.GetString(arrMsgRec, 1, length-1);
120                 ShowMsg(strMsgRec);
121 
122                 }
123                 else if (arrMsgRec[0] == 1)//如果是1,则代表发送过来的文件数据(图片/视频/文件……)
124                 {
125                     SaveFileDialog sfd = new SaveFileDialog();//保存文件选择框对象
126                     if (sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK)//用户选择文件路径后
127                     {
128                         string fileSavePath = sfd.FileName;//获得要保存的文件路径
129                         //创建文件流,然后让文件流来根据路径创建一个文件
130                         using (FileStream fs = new FileStream(fileSavePath, FileMode.Create))
131                         {
132                             fs.Write(arrMsgRec, 1, length-1);
133                             ShowMsg("文件保存成功:" + fileSavePath);
134                         }
135                     }
136 
137                 }
138             }
139         }
140 
141         //发送消息到客户端
142         private void btnSend_Click(object sender, EventArgs e)
143         {
144             if (string.IsNullOrEmpty(lbOnline.Text))
145             {
146                 MessageBox.Show("请选择要发送的好友");
147             }
148             else
149             {
150                 string strMsg = txtMsgSend.Text.Trim();
151                 //将要发送的字符串转成utf8对应的字节数组
152                 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
153                 //获得列表中 选中的Key
154                 string strClientKey = lbOnline.Text;
155                 //通过key,找到字典集合中对应的与某个客户端通信的套接字send方法,发送数据给对方
156                 try
157                 {
158                     dict[strClientKey].Send(arrMsg);
159                 }
160                 catch (SocketException ex)
161                 {
162 
163                     ShowMsg("发送时异常:" + ex.Message);
164                     return;
165                 }
166                 catch (Exception ex)
167                 {
168                     ShowMsg("发送时异常:" + ex.Message);
169                     
170                 }
171                 //sokConnetion.Send(arrMsg);
172                 ShowMsg("发送了数据出去:" + strMsg);
173             }
174         }
175         //服务器端群发消息
176         private void btnSendToAll_Click(object sender, EventArgs e)
177         {
178             string strMsg = txtMsgSend.Text.Trim();
179             //将要发送的字符串转成utf8对应的字节数组
180             byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
181             foreach(Socket s in dict.Values)
182             {
183                 s.Send(arrMsg);
184             }
185             ShowMsg("群发完毕::)");
186         }
187 
188 
189         void ShowMsg(string msg)
190         {
191             txtMsg.AppendText(msg + "\r\n");
192         }
193        
194        
195         
196     }
197 }

 

客户端:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.Linq;
  7 using System.Text;
  8 using System.Windows.Forms;
  9 
 10 
 11 using System.Net.Sockets;
 12 using System.Net;
 13 using System.Threading;
 14 using System.IO;
 15 
 16 
 17 namespace MyChatClient
 18 {
 19     public partial class FChatClient : Form
 20     {
 21         public FChatClient()
 22         {
 23             InitializeComponent();
 24             TextBox.CheckForIllegalCrossThreadCalls = false;
 25         }
 26         Thread threadClient = null;//客户端负责接收服务端发来的数据消息的线程
 27         Socket socketClient = null;//客户端套接字
 28         //客户端发送连接请求到服务器
 29         private void btnConnect_Click(object sender, EventArgs e)
 30         {
 31             //获得IP
 32             IPAddress address= IPAddress.Parse(txtIP.Text.Trim());
 33             //网络节点
 34             IPEndPoint endpoint =new IPEndPoint(address,int.Parse(txtPort.Text.Trim()));
 35             //创建客户端套接字
 36             socketClient=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
 37             //向指定的IP和端口发送连接请求
 38             socketClient.Connect(endpoint);
 39             //创建线程 监听服务端发来的消息
 40             threadClient = new Thread(RecMsg);
 41             threadClient.IsBackground = true;
 42             threadClient.Start();
 43           
 44         }
 45         void RecMsg()
 46         {
 47             while (true)
 48             {
 49                 //定义一个接收用的缓存区(2M字节数组)
 50                 byte[] arrMsgRec = new byte[1024 * 1024 * 2];
 51                 //将接收到的数据存入arrMsgRec数组,并返回真正接收到的数据长度
 52                 int length = socketClient.Receive(arrMsgRec);
 53                 
 54                 //此时是将数组所有的元素都转成字符串,而真正接收到的只有服务端的几个字符
 55                 string strMsgRec = System.Text.Encoding.UTF8.GetString(arrMsgRec,0,length);
 56                 ShowMsg(strMsgRec);
 57             }
 58         }
 59 
 60         //向服务器发送文本消息
 61         private void btnSendMsg_Click(object sender, EventArgs e)
 62         {
 63             string strMsg = txtMsgSend.Text.Trim();
 64             //将字符串转成方便网络传送的二进制数据
 65             byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
 66             byte[] arrMsgSend = new byte[arrMsg.Length + 1];
 67             arrMsgSend[0] = 0;//设置标识位,0代表发送的是文字
 68             Buffer.BlockCopy(arrMsg, 0, arrMsgSend, 1, arrMsg.Length);
 69             socketClient.Send(arrMsgSend);
 70             ShowMsg("我说:" + strMsg);
 71         }
 72         #region 选择要发送的文件- void btnChooseFile_Click
 73         //选择要发送的文件
 74         private void btnChooseFile_Click(object sender, EventArgs e)
 75         {
 76             OpenFileDialog ofd = new OpenFileDialog();
 77             if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
 78             {
 79                 txtFilePath.Text = ofd.FileName;
 80             }
 81         } 
 82         #endregion
 83         
 84         
 85         //向服务端发送文件
 86         private void btnSendFile_Click(object sender, EventArgs e)
 87         {
 88             //用文件流打开用户选择的文件
 89             if (string.IsNullOrEmpty(txtFilePath.Text))
 90             {
 91                 MessageBox.Show("请选择要发送的文件路径");
 92             }
 93             else
 94             {
 95                 using (FileStream fs = new FileStream(txtFilePath.Text, FileMode.Open))
 96                 {
 97                     byte[] arrFile = new byte[1024 * 1024 * 2];//定义一个2M的数组(缓存区)
 98                     //将文件数据读到数组arrFile中,并获取读取的真实数组长度length
 99                     int length = fs.Read(arrFile, 0, arrFile.Length);
100                     byte[] arrFileSend = new byte[length + 1];
101                     arrFileSend[0] = 1;//代表发送的是文件数据
102                     //for (int i = 0; i < length; i++)
103                     //{
104                     //    arrFileSend[i + 1] = arrFile[i];
105                     //}
106                     //将arrFile数组里的元素从第0个开始拷贝,拷贝到arrFileSend数组里,从第一个位置开始存放,一共存放length个数组
107                     Buffer.BlockCopy(arrFile, 0, arrFileSend, 1, length);
108                     //发送包含标识位的新数组到服务端
109                     socketClient.Send(arrFileSend);
110                     //arrFile.CopyTo(arrFileSend, length);
111                 }
112             }
113         }
114       
115         #region 在窗体文本框中显示消息-void ShowMsg(string msg)
116         /// <summary>
117         /// 在窗体文本框中显示消息
118         /// </summary>
119         /// <param name="msg">消息</param>
120         void ShowMsg(string msg)
121         {
122             txtMsg.AppendText(msg + "\r\n");
123         } 
124         #endregion
125        
126        
127        
128     }
129 }

 

 

 

 

posted @ 2013-06-20 15:37  -112  阅读(409)  评论(0编辑  收藏  举报