模拟聊天室
Posted 楚弋川
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模拟聊天室相关的知识,希望对你有一定的参考价值。
在学习了多线程和套接字之后,跟着老师把聊天室程序完整的做了下来,以下是我的总结:
1. 首先:为大家展示一下这个聊天程序的基本样子,如图:
2. 先来看服务器端,在启动服务按钮下完成设置网络通信的IP和端口号,在这里我们就用本地电脑地址127.0.0.1,以后如果联网,将IP地址改为外网IPV4地址即可,端口号可以任意设置。
public Server()
InitializeComponent();
TextBox.CheckForIllegalCrossThreadCalls = false;//关闭对文本框跨线程检查
Thread thread = null;//负责监听客户端连接请求的线程
Socket socketWatch = null;//负责服务器的监听功能
private void btnBeginListen_Click(object sender, EventArgs e)
//获得文本框中的IP地址
IPAddress address = IPAddress.Parse(txtIP.Text.Trim());
//创建包含IP和端口号的网络节点对象
IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtProt.Text.Trim()));
//创建服务器负责监听的套接字,参数(使用IP4寻址协议,使用流式连接,使用TCP协议传出数据)
socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//将负责监听的套接字绑定到唯一的Ip和端口号上
socketWatch.Bind(endpoint);
//设置监听队列长度
socketWatch.Listen(10);
//创建线程解决Accept()带来的长时间占用当前线程
Thread thread = new Thread(WatchConnection);
//设置后台线程
thread.IsBackground = true;
//启动线程
thread.Start();
ShowMsg("服务器启动监听成功!");
void ShowMsg(string msg)
txtMsg.AppendText(msg + "\\n");
3. 这个时候因为没有任何客户端与之通信,可以使用微软提供的命令提示符的方式对其进行下一步操作:打开cmd,在里面输入IP和端口号,如127.0.0.1空格800,这样就可以测试是否可以连接成功
//保存了服务器端所有负责和客户端通信的套接字
Dictionary<string, Socket> dict = new Dictionary<string, Socket>();
//保存了服务器端所有页面调用通信套接字receive方法的线程
Dictionary<string, Thread> dictThread = new Dictionary<string, Thread>();
// 监听客户端请求的方法
void WatchConnection()
while (true)//持续不断地监听新的客户端的连接请求
//创建一个新的套接字,开始监听客户端的连接请求,注意:Accept()会阻断当前线程
Socket socketConnection = socketWatch.Accept();
//像列表控件中添加客户端Ip端口字符串,作为客户端唯一标志
lbOnline.Items.Add(socketConnection.RemoteEndPoint.ToString());
//将与客户端通信的套接字对象socketConnection添加到键值集合中,并以客户端IP端口作为键
dict.Add(socketConnection.RemoteEndPoint.ToString(), socketConnection);
//创建一个新的线程,即通信线程
ParameterizedThreadStart pts = new ParameterizedThreadStart(ReciveMsg);
Thread thr = new Thread(pts);
thr.IsBackground = true;
thr.SetApartmentState(ApartmentState.STA);//创建并进入一个单线程单元
thr.Start(socketConnection);
dictThread.Add(socketConnection.RemoteEndPoint.ToString(), thr);
//socketConnection.RemoteEndPoint中保存的是当前连接客户端的IP和端口
ShowMsg("客户端连接成功!" +socketConnection.RemoteEndPoint.ToString());
4. 接下来是服务器向客服端发送数据和接收数据,接收的数据要进行特殊处理,判断数据的是消息还是文件。
//发送消息到客户端
private void btnsend_Click(object sender, EventArgs e)
if (string.IsNullOrEmpty(lbOnline.Text))
MessageBox.Show("请选择要发送的对象!");
else
string str = txtsend.Text.Trim();
//将 要发送的字符串转成utf8对应的字节数组
byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(str);
//获得列表中选中的Key
string strClientKey =lbOnline.SelectedItem.ToString();
//通过Key,找到字典集合中对应的某个客户端通信的套接字的send方法,发送给对方
try
dict[strClientKey].Send(arrMsg);
ShowMsg("发送了:" + str);
catch (SocketException ex)
ShowMsg("发送数据时异常:" + ex);
catch (Exception ex)
ShowMsg("发送数据时异常:" + ex);
//接收客户端消息
void ReciveMsg(object socketClientPara)
Socket socketClient = socketClientPara as Socket;
while (true)
//定义一个2M的接收缓存区
byte[] arrMsg = new byte[1024 * 1024 * 2];
//将接收的数据存入arrMsg数组,并返回真正接收到的数据长度
int length = -1;
try
length =socketClient.Receive(arrMsg);
catch (SocketException ex)
ShowMsg("异常:" + ex);
//从通信套接字删除被中断连接的套接字对象
dict.Remove(socketClient.RemoteEndPoint.ToString());
//从通信线程中删除被中断连接的套接字对象
dictThread.Remove(socketClient.RemoteEndPoint.ToString());
//从列表中移除被中断的Ip和端口号
lbOnline.Items.Remove(socketClient.RemoteEndPoint.ToString());
break;
catch (Exception ex)
ShowMsg("异常:" + ex);
break;
if (arrMsg[0] == 0)//判断接收的数据文本第一个字符是0,代表文字;1,代表文件
//此时是将数组所有元素装成字符串,而真正接收到的只有服务端发过来的字符
string str = System.Text.Encoding.UTF8.GetString(arrMsg, 1,length - 1);
ShowMsg("接收到:" + str);
else if (arrMsg[0] == 1)
//在win7中,一个线程调用SaveFileDialog()方法时,要把该线程设置为thr.SetApartmentState(ApartmentState.STA);
SaveFileDialog sfd = new SaveFileDialog();//文件保存对象
if (sfd.ShowDialog() ==System.Windows.Forms.DialogResult.OK) //用户选择好路径后
string fileSave = sfd.FileName;//文件的保存路径
using (FileStream fs = new FileStream(fileSave, FileMode.Create)) //新建一个文件,保存文件流
fs.Write(arrMsg, 1,length - 1);//去掉标识符
ShowMsg("文件保存成功" + fileSave);
5. 使用群发消息时,我们可以这样做
private void btnSendToAll_Click(object sender, EventArgs e)
string str = txtsend.Text.Trim();
byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(str);
foreach (Socket s in dict.Values)
s.Send(arrMsg);
ShowMsg("群发完毕!");
6. 接下来看客户端如何进行操作的,服务器打开后,客户端需要与服务器的IP和端口一致才可以进行通信,这部分和服务器端代码很类似,首先是判断从文本中后的socket通信地址,然后与服务器连接即可。
public Cilent()
InitializeComponent();
TextBox.CheckForIllegalCrossThreadCalls = false;
Socket socketConnect = null;//负责通信的套接字
private void btnConnect_Click(object sender, EventArgs e)
IPAddress address = IPAddress.Parse(txtIP.Text.Trim());
IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtProt.Text.Trim()));
socketConnect = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
socketConnect.Connect(endPoint);//建立与远程主机连接
catch (Exception ex)
ShowMsg("异常:" + ex);
Thread thread = new Thread(ReciveMsg);
thread.IsBackground = true;
thread.Start();
7. 剩下就是客户端向服务端发送数据和从服务端接收数据。在这里我们将这些数据先转换成二进制流,便于进行TCP数据传输,然后再转换成原来的格式。
//客户端接收数据
void ReciveMsg()
while (true)
//定义一个2M的接收缓存区
byte[] arrMsg = new byte[1024 * 1024 * 2];
//将接收的数据存入arrMsg数组,并返回真正接收到的数据长度
int length = -1;
try
length =socketConnect.Receive(arrMsg);
catch (SocketException ex)
ShowMsg("异常:" + ex);
break;
catch (Exception ex)
ShowMsg("异常:" + ex);
break;
//此时是将数组所有元素装成字符串,而真正接收到的只有服务端发过来的字符
string str = System.Text.Encoding.UTF8.GetString(arrMsg, 0,length);
ShowMsg("接收到:" + str);
void ShowMsg(string msg)
txtMsg.AppendText(msg + "\\n");
//向服务器端发送数据
private void btnsend_Click(object sender, EventArgs e)
string str = txtsend.Text.Trim();
if (!string.IsNullOrEmpty(str))
//将 要发送的字符串转成utf8对应的字节数组
byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(str);
//通过Key,找到字典集合中对应的某个客户端通信的套接字的send方法,发送给对方
byte[] arrMsgSend = new byte[arrMsg.Length + 1];
arrMsgSend[0] = 0;
Buffer.BlockCopy(arrMsg, 0, arrMsgSend, 1,arrMsg.Length);
socketConnect.Send(arrMsgSend);
ShowMsg("发送了:" + str);
8. 为了扩展聊天室功能,为它添加了文件传输功能,这里主要是对文件选择和文件处理进行介绍。
//选择发送的文件
private void btnCheckFile_Click(object sender, EventArgs e)
OpenFileDialog ofd = new OpenFileDialog();
if (ofd.ShowDialog() ==System.Windows.Forms.DialogResult.OK)
txtFile.Text = ofd.FileName;
//向服务器发送文件
private void btnSendFile_Click(object sender, EventArgs e)
//用文件流打开用户选择的文件
using (FileStream fs = new FileStream(txtFile.Text, FileMode.Open))
byte[] arrMsg = new byte[1024 * 1024 * 2];
//将文件数据存入arrMsg数组,并返回真正接收到的数据长度
int length = fs.Read(arrMsg, 0,arrMsg.Length);
//新建一个比原来数组长度加1的数组
byte[] arrFileSend = new byte[length + 1];
//将数组的第一个值设为标识符,1代表是文件类型
arrFileSend[0] = 1;
//数组复制1
//for (int i = 0; i < length; i++)
//
// arrFileSend[i + 1] = arrMsg[i];
//
//数组复制2
//arrMsg.CopyTo(arrFileSend, length);
//数组复制3,将arrMsg数组里的元素,从第0个开始拷贝,拷贝到arrFileSend数组里,从第1个位置开始存放,一共拷贝length个长度
Buffer.BlockCopy(arrMsg, 0, arrFileSend, 1,length);
//发送包含了标识符的新数组到服务器
socketConnect.Send(arrFileSend);
9. 经过这个小项目,使我在委托,线程,套接字,文件处理等方面都有了新的认识,也对现有的知识进行了巩固,感谢传智老师认真详细的见解。
以上是关于模拟聊天室的主要内容,如果未能解决你的问题,请参考以下文章