服务器端:
using System; using System.Windows.Forms; using System.Net.Sockets; using System.Net;//IPAddress,IPEndPoint(ip和端口)类 using System.Threading; using System.Collections.Generic; using System.IO; namespace MyChatRoomServer { public partial class Server : Form { public Server() { InitializeComponent(); //一个线程正在访问当前UI线程,要关闭微软设置的对文本框操作的检查 TextBox.CheckForIllegalCrossThreadCalls = false; } Thread threadWatch = null;//负责监听客户端连接请求的线程 Socket socketWatch = null;//负责监听的套接字 private void btnBeginListen_Click(object sender, EventArgs e) { //创建服务端负责监听的套接字,参数(IPV4寻址协议,使用流格式,使用Tcp传输协议) socketWatch = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); //获得文本框中IP地址对象 IPAddress address = IPAddress.Parse(txtIP.Text.Trim()); //获取端口号 IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim())); //将负责监听的套接字绑定到唯一的IP和端口上 socketWatch.Bind(endpoint); //设置监听队列的长度,意思就是同时发送请求只能10个 socketWatch.Listen(10); //创建一个新的套接字专门负责跟客户端通信,注意:Accept方法会阻塞当前程序 //Socket sockConnection = socketWatch.Accept(); //创建负责监听的线程,并传入监听的方法 threadWatch = new Thread(WatchConnecting); threadWatch.IsBackground = true;//设置为后台线程,只要前台的一结束,那么程序就结束 threadWatch.Start();//启动线程 ShowMsg("服务器启动监听成功!"); } //保存了服务器端所有负责和客户端通信的套接字 Dictionary<string, Socket> dict = new Dictionary<string, Socket>(); //保存了服务器端所有负责调用通信套接字Recive方法的线程 Dictionary<string, Thread> dictThread = new Dictionary<string, Thread>(); //Socket sokConnection = null;//负责通信的套接字 /// <summary> /// 监听客户端请求的方法 /// </summary> void WatchConnecting() { while (true) //加上循环持续不断的监听新的客户端的连接 { //开始监听客户端连接请求,注意:Accept方法会阻断当时线程 Socket sokConnection = socketWatch.Accept(); //将列表控件中追加一个客户端的ip端口字符串,作为客户端的唯一标识 lblOnline.Items.Add(sokConnection.RemoteEndPoint.ToString()); //将与客户端通信的套接字对象sokConnection添加到键值对集合中,并以客户端IP端口作为键 dict.Add(sokConnection.RemoteEndPoint.ToString(), sokConnection); //创建一个委托 ParameterizedThreadStart pts = new ParameterizedThreadStart(RecMsg); //创建通信线程 Thread thr = new Thread(pts); thr.IsBackground = true; //设置为后台 thr.Start(sokConnection); //将线程的参数传入 dictThread.Add(sokConnection.RemoteEndPoint.ToString(),thr); ShowMsg("客户端连接成功!"+sokConnection.RemoteEndPoint.ToString()); } } /// <summary> /// 服务端负责监听客户端发来的数据方法 /// </summary> void RecMsg(object socketClientPara) { Socket socketClient = socketClientPara as Socket; while (true) { //定义一个2M的接受数据的缓存区 byte[] arrMsgRec = new byte[1024 * 1024 * 2];//手动准备2M空间 //将接受到的数据存入arrMsgRec数组,并返回真正接收到的数据长度 int length = socketClient.Receive(arrMsgRec); try { length = socketClient.Receive(arrMsgRec); } catch (SocketException ex) { ShowMsg("异常" + ex.Message+",RemoteEnd=" + socketClient.RemoteEndPoint.ToString()); //从通信套接字集合中删除被中断连接的通信套接字 dict.Remove(socketClient.RemoteEndPoint.ToString());//删除异常的端口和IP //从通信线程中删除被中断的连接通信线程对象 dictThread.Remove(socketClient.RemoteEndPoint.ToString());//删除异常的线程 //有异常则跳出,不执行后面的 //从列表中删除被中断的连接IP:port lblOnline.Items.Remove(socketClient.RemoteEndPoint.ToString()); break; //return; } catch (Exception ex) { ShowMsg("异常" + ex.Message); //有异常则跳出,不执行后面的 return; } if (arrMsgRec[0] == 0)//判断传过来的第一个数据是0,则代表是文本数据 { //将数组转成字符串,此时是将数组所有的元素都转成字符串,而真正接收到的只是服务器端传来的一些字符 string strMsgRec = System.Text.Encoding.UTF8.GetString(arrMsgRec, 1, length-1); ShowMsg(strMsgRec); } //如果是1,则代表发送过来的是文件数据(文件/图片...) else if (arrMsgRec[0] == 1) { //保存文件选择框对象 SaveFileDialog sfd = new SaveFileDialog(); if(sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { string fileSavePath = sfd.FileName;//获得文件保存路径 //创建文件流,让文件流来根据路径创建一个文件 using (FileStream fs = new FileStream(fileSavePath, FileMode.Create)) { fs.Write(arrMsgRec,1,length-1); ShowMsg("文件保存成功:"+fileSavePath); } } } } } void ShowMsg(string msg) { txtMsg.AppendText(msg + "\r\n"); } private void btnCloseServer_Click(object sender, EventArgs e) { //threadWatch.Abort();//关闭线程 //ShowMsg("服务器启动监听成功!"); } //发送消息到客户端 private void btnSend_Click(object sender, EventArgs e) { if (string.IsNullOrEmpty(lblOnline.Text)) { MessageBox.Show("请在左侧选择要发送的好友"); } else { string strMsg = txtMsgSend.Text.Trim(); //将要发送的字符串转成utf8对应的字节数组 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); string strClientKey = lblOnline.Text; //通过key找到字典集合中对应的与某个用户客户端通信的套接字的send方法,发数据给对方 try { dict[strClientKey].Send(arrMsg); //一旦上面的出现异常,下面的就不执行 ShowMsg("发送了数据出去:" + strMsg); } //sokConnection.Send(arrMsg); catch (SocketException ex) { ShowMsg("发送时异常:"+ex.Message); return; } catch (Exception ex) { ShowMsg("发送时异常:" + ex.Message); return; } } } //服务器群发消息 private void btnSendToAll_Click(object sender, EventArgs e) { string strMsg = txtMsgSend.Text.Trim(); //将要发送的字符串转成utf8对应的字节数组 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); //便利当前字典里面所有的通信套接字 foreach (Socket s in dict.Values) { s.Send(arrMsg); } ShowMsg("群发完毕!:)"); } } }
客户端:
using System; using System.Windows.Forms; using System.Net; using System.Net.Sockets; using System.Threading; using System.IO; namespace MyChatRoomClient { public partial class FChatClient : Form { public FChatClient() { InitializeComponent(); //一个线程正在访问当前UI线程,要关闭微软设置的对文本框操作的检查 //关闭跨线程检查 TextBox.CheckForIllegalCrossThreadCalls = false; } IPAddress address = null; IPEndPoint endpoint = null; Socket socketClient = null; //客户端套接字 Thread threadClient = null;//客户端负责接受服务端发来的消息的线程 //客户端发送请求到服务器 private void btnConnect_Click(object sender, EventArgs e) { address = IPAddress.Parse(txtIP.Text.Trim()); endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim())); socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { socketClient.Connect(endpoint); //创建线程,监听服务器端发来的消息 threadClient = new Thread(RecMsg); threadClient.IsBackground = true; threadClient.Start(); } catch (Exception ee) { MessageBox.Show(ee.ToString()); } } /// <summary> /// 监听服务器端发来的消息 /// </summary> void RecMsg() { while (true) { //定义一个2M的接受数据的缓存区 byte[] arrMsgRec = new byte[1024 * 1024 * 2];//手动准备2M空间 //将接受到的数据存入arrMsgRec数组,并返回真正接收到的数据长度 int length = socketClient.Receive(arrMsgRec); //将数组转成字符串,此时是将数组所有的元素都转成字符串,而真正接收到的只是服务器端传来的一些字符 string strMsgRec = System.Text.Encoding.UTF8.GetString(arrMsgRec,0,length); ShowMsg(strMsgRec); } } #region 在窗体文本框中显示消息-void ShowMsg(string msg) /// <summary> /// 在窗体文本框中显示消息 /// </summary> /// <param name="msg">消息</param> void ShowMsg(string msg) { txtMsg.AppendText(msg + "\r\n"); } #endregion #region 选择要发送的文件-btnChooseFile_Click //选择要发送的文件 private void btnChooseFile_Click(object sender, EventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { txtFilePath.Text = ofd.FileName; } } #endregion //向服务器发送文本消息 private void btnSendMsg_Click(object sender, EventArgs e) { string strMsg = txtMsgSend.Text.Trim(); byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); byte[] arrMsgSend = new byte[arrMsg.Length + 1]; arrMsgSend[0] = 0;//设置标识位,0代表是文字 Buffer.BlockCopy(arrMsg, 0, arrMsgSend, 1, arrMsg.Length); socketClient.Send(arrMsgSend); ShowMsg("我发送了:" + strMsg); //向服务端发送文件 private void btnSendFile_Click(object sender, EventArgs e) { //用文件流打开用户选择的文件 using (FileStream fs = new FileStream(txtFilePath.Text, FileMode.Open)) { byte[] arrFile = new byte[1024*1024*2];//定义一个2M缓存区 //将文件数据读到数组arrFile中,并获得读取的真是数据长度 int length = fs.Read(arrFile,0,arrFile.Length); byte[] arrFileSend = new byte[length + 1]; arrFileSend[0] = 1;//代表发送的是文件数据 //for (int i = 0; i < length; i++) //{ // arrFileSend[i + 1] = arrFile[i]; //} //数据块的拷贝,将arrFile从第0个开始拷贝,拷贝到arrFileSend,从第一个开始存放 Buffer.BlockCopy(arrFile,0,arrFileSend,1,length); //arrFile.CopyTo(arrFileSend,length);只能从0开始拷贝 //发送了包含了标识位的新数据到服务端 socketClient.Send(arrFileSend); } } } }
源码文件: http://download.csdn.net/detail/s10141303/5892933