聊天程序开发
开发前小笔记:
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 }