如何在不重置TCP Client的情况下通过按钮实现客户端消息发送?
搞定Windows Forms聊天室发送按钮的正确姿势
嘿,我来帮你解决这个发送消息的问题!先看看你现有代码里的几个明显小问题:
- 每次点击按钮都新建
TcpClient,但压根没调用Connect连服务器,这肯定发不出去啊 - 编码类拼写错了:
ASCIIEncoding asii应该是ASCIIEncoding ascii(少了个c哦) - 没正确处理流的资源释放,容易造成内存泄漏
- 如果是持续聊天的场景,重复创建连接效率很低,最好复用一个全局的连接实例
下面给你两种方案,你可以根据自己的场景选:
方案一:短连接(每次发送新建连接,适合简单场景)
修改后的发送按钮代码,直接能用:
private void sendButton_Click(object sender, EventArgs e) { // 先检查输入框有没有内容,别发空消息 if (string.IsNullOrWhiteSpace(sendBox.Text)) { MessageBox.Show("先输入要发的消息呀!"); return; } try { // 这里替换成你的服务器IP和端口哈 string serverIp = "127.0.0.1"; int serverPort = 8888; // using语句会自动释放资源,不用手动关啦 using (TcpClient tcpClient = new TcpClient()) { // 先连服务器 tcpClient.Connect(serverIp, serverPort); using (NetworkStream stream = tcpClient.GetStream()) { ASCIIEncoding ascii = new ASCIIEncoding(); // 把输入框的文字转成字节数组 byte[] messageBytes = ascii.GetBytes(sendBox.Text); // 先发送消息长度(可选,但服务器端能更准确接收,避免粘包) byte[] lengthBytes = BitConverter.GetBytes(messageBytes.Length); stream.Write(lengthBytes, 0, lengthBytes.Length); // 发送消息内容 stream.Write(messageBytes, 0, messageBytes.Length); // 发完清空输入框,方便下次输入 sendBox.Text = string.Empty; } } } catch (Exception ex) { MessageBox.Show($"发送失败啦:{ex.Message}"); } }
这个方案的好处:
- 用
using自动释放资源,不用操心内存泄漏 - 加了空输入判断,用户体验更好
- 可选的长度发送,解决服务器端可能的粘包问题
- 发完自动清空输入框,很贴心
方案二:长连接(复用连接,适合持续聊天)
如果是要一直聊天,频繁发消息,长连接更高效,不会每次都重新握手。步骤如下:
第一步:在窗体类里定义全局的连接和流
private TcpClient _tcpClient; private NetworkStream _networkStream; private ASCIIEncoding _asciiEncoder = new ASCIIEncoding();
第二步:窗体加载时先连上服务器
private void ChatForm_Load(object sender, EventArgs e) { try { string serverIp = "127.0.0.1"; int serverPort = 8888; _tcpClient = new TcpClient(); _tcpClient.Connect(serverIp, serverPort); _networkStream = _tcpClient.GetStream(); // 顺便启动个后台线程收服务器消息,实现双向聊天 Task.Run(() => ReceiveMessages()); } catch (Exception ex) { MessageBox.Show($"连不上服务器:{ex.Message}"); } }
第三步:修改发送按钮的代码
private void sendButton_Click(object sender, EventArgs e) { if (string.IsNullOrWhiteSpace(sendBox.Text)) { MessageBox.Show("先输入消息哦!"); return; } // 先检查连接是不是正常的 if (_tcpClient == null || !_tcpClient.Connected) { MessageBox.Show("和服务器断连啦,重新连一下吧!"); return; } try { byte[] messageBytes = _asciiEncoder.GetBytes(sendBox.Text); // 先发送消息长度 byte[] lengthBytes = BitConverter.GetBytes(messageBytes.Length); _networkStream.Write(lengthBytes, 0, lengthBytes.Length); // 发送消息内容 _networkStream.Write(messageBytes, 0, messageBytes.Length); // 清空输入框 sendBox.Text = string.Empty; } catch (Exception ex) { MessageBox.Show($"发送失败:{ex.Message}"); // 连接异常就清理资源 CleanupConnection(); } }
第四步:加个收消息的方法和资源清理方法
private void ReceiveMessages() { byte[] lengthBuffer = new byte[4]; while (_tcpClient != null && _tcpClient.Connected) { try { // 先读消息长度 int bytesRead = _networkStream.Read(lengthBuffer, 0, lengthBuffer.Length); if (bytesRead == 0) { // 服务器断开了 Invoke((Action)(() => MessageBox.Show("服务器跑啦!"))); CleanupConnection(); break; } int messageLength = BitConverter.ToInt32(lengthBuffer, 0); byte[] messageBuffer = new byte[messageLength]; bytesRead = 0; // 确保把完整消息读出来 while (bytesRead < messageLength) { int remaining = messageLength - bytesRead; int read = _networkStream.Read(messageBuffer, bytesRead, remaining); if (read == 0) { CleanupConnection(); break; } bytesRead += read; } if (bytesRead == messageLength) { string receivedMessage = _asciiEncoder.GetString(messageBuffer); // 跨线程更新UI,因为收消息是后台线程,不能直接改UI Invoke((Action)(() => { chatHistoryBox.AppendText($"对方说:{receivedMessage}{Environment.NewLine}"); })); } } catch (Exception ex) { Invoke((Action)(() => MessageBox.Show($"收消息失败:{ex.Message}"))); CleanupConnection(); break; } } } private void CleanupConnection() { _networkStream?.Close(); _tcpClient?.Close(); _networkStream = null; _tcpClient = null; }
这个方案的好处:
- 一次连接重复用,效率高
- 后台线程自动收消息,实现真正的双向聊天
- 处理了连接断开的情况,自动清理资源
- 用
Invoke保证UI更新的线程安全,不会报错
最后提醒你哦:服务器端也要对应处理,比如如果用了发送长度的方式,服务器端也要先读长度再读内容,这样才能正确拿到完整的消息~
内容的提问来源于stack exchange,提问作者user9714286
相关产品推荐
相关产品推荐

