Socket 客户端实现#
对应源码:
D:\study\C#1\WinForm\stu0814\stu0814\Form1.cs(原生 UI 版客户端)。 基础概念见 Socket 基础与 TCP 通信流程。
功能概述#
聊天客户端,包含四个操作:连接服务器 → 接收消息 → 发送消息 → 断开连接。
完整代码#
public partial class Form1 : Form
{
// 通过 Socket 类实现 TCP/IP 通信
Socket lianSocket = null;
Thread t1;
// ── 1. 连接服务器 ──
private void button1_Click(object sender, EventArgs e)
{
// 实例化 Socket:协议类型、双向数据流、TCP
lianSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
// 绑定 IP 地址
IPAddress ip = IPAddress.Parse(textBox1.Text);
// 获取端口号
int port = Convert.ToInt32(textBox2.Text);
// 形成连接点
IPEndPoint iPEndPoint = new IPEndPoint(ip, port);
// 连接服务器
lianSocket.Connect(iPEndPoint);
this.button4.BackColor = Color.Green;
this.button4.Text = "已连接";
this.button1.Enabled = false; // 连接后禁用连接按钮
this.button2.Enabled = true; // 启用断开按钮
// 开一个后台线程监听消息
t1 = new Thread(Receive);
t1.IsBackground = true;
t1.Start(lianSocket); // 开启线程时传入 Socket 对象
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
// ── 2. 接收消息(后台线程) ──
private void Receive(object o)
{
Socket socket = o as Socket;
while (true)
{
byte[] buffer = new byte[1024];
// r 是接收到的字节个数
int r = socket.Receive(buffer);
if (r == 0)
{
UpdateState(false); // 服务器断开
break;
}
// 字节数组 → 字符串
string msg = Encoding.ASCII.GetString(buffer);
AddMsg(msg, false);
}
}
// ── 3. 发送消息 ──
private void button3_Click(object sender, EventArgs e)
{
string str = richTextBox2.Text;
if (string.IsNullOrEmpty(str))
{
return;
}
try
{
byte[] buffer = Encoding.UTF8.GetBytes(str); // 字符串 → 字节数组
lianSocket.Send(buffer);
AddMsg(str); // 自己的消息显示在右侧
}
catch (Exception ex)
{
// TODO: 记录日志
}
}
// ── 4. 断开连接 ──
private void button2_Click(object sender, EventArgs e)
{
if (lianSocket != null)
{
lianSocket.Shutdown(SocketShutdown.Both); // 禁止收发
lianSocket.Close(); // 关闭连接
t1.Abort(); // 结束接收线程
button4.BackColor = Color.Red;
button4.Text = "连接已断开";
button2.Enabled = false;
button1.Enabled = true;
}
}
// ── 封装显示消息(跨线程更新 UI) ──
private void AddMsg(string str, bool a = true)
{
string str1 = a ? "= >" + str + "\r\n" : "< =" + str + "\r\n";
this.richTextBox1.BeginInvoke(
new Action<string>((msg) => this.richTextBox1.Text += msg), str1);
}
}
核心流程解析#
1. 连接服务器(Connect)#
Connect是客户端主动发起连接的动作,服务器地址由IP + 端口组成- 连接成功后立即开启后台接收线程,防止
Receive()阻塞界面 - 界面状态切换:按钮
Enabled互斥(连接后禁用连接、启用断开)
2. 接收消息(Receive + 线程)#
t1 = new Thread(Receive);
t1.IsBackground = true; // 后台线程:主程序退出时自动结束
t1.Start(lianSocket); // 把 Socket 传给线程方法
接收线程中 socket.Receive(buffer) 是阻塞的:
- 收到数据 → 返回字节数
r - 对方关闭连接 → 返回
0,跳出循环
3. 发送消息(Send)#
发送前必须把字符串编码为字节数组。
4. 断开连接(Shutdown + Close)#
断开三步:先 Shutdown(停止收发)→ 再 Close(释放 Socket)→ 最后结束线程。
代码中的问题(详见 Socket 代码问题分析)#
| 问题 | 位置 | 说明 |
|---|---|---|
| 编码不一致 | Receive 用 ASCII,发送用 UTF8 |
中文会乱码,应统一用 UTF8 |
| 未按实际长度解码 | GetString(buffer) 未用 r 截断 |
缓冲区剩余空字节会拼进消息 |
UpdateState 空实现 |
Receive 中调用 |
服务器断开时界面无反馈 |
t1.Abort() |
断开连接 | 强制杀线程不推荐,建议用标志位 |
相关笔记#
- Socket 基础与 TCP 通信流程 — 基础概念
- Socket 服务端实现 — 服务端对应实现
- Socket 代码问题分析 — 代码问题汇总
- Thread 线程 — 线程基础
- 跨线程调用控件 BeginInvoke — BeginInvoke 跨线程更新 UI