C#简易winform客服端服务器一体的TCP通信程序
一、核心要点
winform做到TCP协议通信的客服端和服务器的要点是建立好各自的线程,核心建立Socket类用来管控客服端与服务器之间通信的核心。
二、Socket类的建立
public class SocketManager
{
//TCP监听器,用户接收连接
private TcpListener _listener; //作为服务器使用
//TCP客服端,用于通信
private TcpClient _client;//作为客服端使用
//网络流,用于读写数据
private NetworkStream _stream;//简历数据流,服务器与客服端通信的桥梁
//监听线程
private Thread _listenerThread;//服务器监听线程,用于连接客服端
//监听接收消息线程
private Thread _receiveThread;//服务器与客服端之间接收消息的线程
//运行状态标志
private bool _isRunning = false;
//默认端口
private int _port = 8888;
//本机IP
private string _localIP;
//封装调用
public string LocalIP { get => _localIP; }
//事件声明
public event Action OnMessageReceived;//接收到消息触发事件
public event Action OnStatusChange;//状态变化时触发事件
public event Action OnError;//发生错误时触发
}
三、创建好Socket类的字段属性后,要想实现通信还需完善各自的通信方法
- 首先不管是服务器还是客服端,我们都要获取本机的IP地址和端口用来连接双方(这里我选择IPV4地址,端口默认8888)
///
/// 构造函数初始话本机IP地址
///
public SocketManger()
{
_localIP = GetLocalIPAddress();
}
///
/// 获取本机IP
///
///
///
private string GetLocalIPAddress()
{
var host = Dns.GetHostEntry(Dns.GetHostName());//获取主机信息
foreach (IPAddress ip in host.AddressList)
{
//判断是否为IPV4
if (ip.AddressFamily == AddressFamily.InterNetwork)//AddressFamily枚举类型
{
return ip.ToString();
}
}
return "127.0.0.1";//默认返回本地地址
}
- 其次是服务器开启监听等待客服端连接的方法
///
/// 启动监听(作为服务器端)
///
///
public void StartListening(int port = 8888)
{
try
{
_port = port;//设置端口号
_isRunning = true;//设置运行状态为true
//创建并启动监听线程
_listenerThread = new Thread(new ThreadStart(ListentForClients));
_listenerThread.IsBackground = true;
_listenerThread.Start();
//触发状态发生变化事件,通知界面更新
OnStatusChange?.Invoke($"🐕监听已启动...{Environment.NewLine}IP:{_localIP}{Environment.NewLine}端口:{_port}{Environment.NewLine}");
}
catch (Exception ex)
{
OnError?.Invoke($"启动监听失败:{ex.Message}{Environment.NewLine}");
}
}
///
/// 监听客服端的连接的线程方法
///
///
private void ListentForClients()
{
try
{
//启动监听
_listener = new TcpListener(IPAddress.Any, _port);
_listener.Start();
OnStatusChange?.Invoke("等待连接中...
");//状态更新
//阻塞等待客服连接
_client = _listener.AcceptTcpClient(); //接受客服端连接
_stream = _client.GetStream();//获取网络流
OnStatusChange?.Invoke("客服端已连接
");//更新状态
//开始接收消息
StartReceiving();
}
catch (Exception ex)
{
OnError?.Invoke($"监听异常:{ex.Message}{Environment.NewLine}");
}
}
- 然后是客服端连接服务器的方法
///
/// 客服端连接服务器
///
///
///
///
public bool ConnectionToServer(string serverIP, int port = 8888)
{
try
{
_client = new TcpClient();//创建 TCP客服端
_client.Connect(serverIP, port);//连接到指定服务器
_stream = _client.GetStream();//获取网络流
_isRunning = true; //设置运行状态
OnStatusChange?.Invoke($"🐕已连接到服务器:{serverIP}{Environment.NewLine}");
StartReceiving();//开始接收消息
return true;//连接成功
}
catch (Exception ex)
{
OnError?.Invoke($"连接失败:{ex.Message}{Environment.NewLine}");
return false;
}
}
- 最后是接收消息、发送消息,断开连接的方法
////// 开始接受消息 /// private void StartReceiving() { //创建接受消息线程 _receiveThread = new Thread(new ThreadStart(RecevieMessage)); //设置为后台线程 _receiveThread.IsBackground = true; //启动线程 _receiveThread.Start(); } ////// 接收消息的方法 /// private void RecevieMessage() { byte[] buffer = new byte[4096];//设置一个4kb缓冲区 while (_isRunning && _client != null && _client.Connected) { try { /** 第一个参数是读取的数据存储的位置是一个byte数组,一个将从内存中读取到的数据存入到byte[]数组buffer中; 第二个参数是int offset,是btye数组buffer开始存储的位置 第三个参数是int size,存储的长度,这里选择将所有的数据全部存储到buffer数组中 //返回值类型是一个int,表示从NetWorkStream中读取的字节数 */ int bytesRead = _stream.Read(buffer, 0, buffer.Length); //如果返回值bytesRead大于0表示,从网络数据流中读取到了数据 if (bytesRead > 0) { //将字节数组转换为字符串 string message = Encoding.UTF8.GetString(buffer, 0, bytesRead); OnMessageReceived?.Invoke(message);//触发接收消息事件 } } catch (Exception ex) { if (_isRunning) { OnStatusChange?.Invoke($"❗ 连接已断开{Environment.NewLine}");//更新状态 _isRunning = false;//设置状态为false } } } } ////// 发送消息 /// /// public void SendMessage(string message) { if (!_isRunning || _stream == null) { OnError?.Invoke($"未建立连接,无法发送消息{Environment.NewLine}"); return; } try { byte[] data = Encoding.UTF8.GetBytes(message);//将字符串转换为字节数组 _stream.Write(data, 0, data.Length);//写入网络流 _stream.Flush();//刷新流 } catch (Exception ex) { OnError?.Invoke($"发送失败:{ex.Message}{Environment.NewLine}"); } } ////// 断开连接的方法 /// public void Disconnect() { _isRunning = false;//关闭运行状态 _stream?.Dispose();//断开网络流 _client?.Close();//关闭客服端 _listener?.Stop();//服务器停止监听 OnStatusChange?.Invoke($"连接已关闭{Environment.NewLine}"); }完成以上操作后,恭喜您已经成功了90%,剩下的10%只需见了winformUI界面
四、winformUI界面制作
- 这里简易做一个winformUI界面

五、界面建好后还需以下操作
- 创建用户数据模型(用户信息、发送的消息、发送的时间)
public class ChatMessage
{
//发送者
public string Sender { set; get; }
//发送内容
public string Content { set; get; }
//发送时间(时间戳)
public DateTime TimeTemp { set; get; }
//消息类型(文本,文件,系统)
public MessageType Type { set; get; }
public enum MessageType
{
Text,//文本信息
File,//文件消息
System,//系统消息
}
///
/// 将消息对象进行序列化为Json字符串
///
///
public string ToJsonSerializer(ChatMessage chat)
{
return Tool.Serialize(chat);
//return System.Text.Json.JsonSerializer.Serialize(this);
}
///
/// 将json字符串反序列化为消息
///
///
///
public static ChatMessage FromJson(string jsonString)
{
return Tool.Deserializer(jsonString);
}
}
发送消息要将消息序列化为JSON文件发送,这里我使用了Newtonsoft.Json包,为了方便大家可以使用Text.Json来序列化
- 界面控件方法绑定
public partial class ChatForm : Form
{
private SocketManger _socketManger;
private string _currentUser = "用户" + new Random().Next(10000, 99999);
List chatMessages = ReadFile();//获取本地的聊天数据
public ChatForm()
{
InitializeComponent();
_socketManger = new SocketManger();
_socketManger.OnMessageReceived += OnMessageReceived;
_socketManger.OnStatusChange += OnStatusChanged;
_socketManger.OnError += OnError;
}
private void ChatForm_Load(object sender, EventArgs e)
{
labMyIP.Text = $"我的IP地址:{_socketManger.LocalIP}";
btnTranfer.Enabled = true;//连接按钮启用
btnOpen.Enabled = true;//监听按钮启用
btnSender.Enabled = false;//发送按钮禁用
btnClose.Enabled = false;//断开按钮禁用
}
///
/// 断开
///
///
///
private void btnClose_Click(object sender, EventArgs e)
{
_socketManger.Disconnect();//断开连接
btnTranfer.Enabled = true;//连接按钮启用
btnOpen.Enabled = true;//监听按钮启用
btnSender.Enabled = false;//发送按钮禁用
btnClose.Enabled = false;//断开按钮禁用
}
///
/// 上线
///
///
///
private void btnOpen_Click(object sender, EventArgs e)
{
_socketManger.StartListening();//开始监听
btnTranfer.Enabled = false;//连接按钮禁止使用
btnOpen.Enabled = false;//监听按钮禁止使用
btnSender.Enabled = true;//发送按钮启用
btnClose.Enabled = true;//断开按钮启用
}
///
/// 发送按钮
///
///
///
private void btnSender_Click(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(txtContent.Text.Trim()))
{
var message = new ChatMessage
{
Sender = _currentUser,//用户
Content = txtContent.Text.Trim(),//发送消息
TimeTemp = DateTime.Now,//发送时间
Type = ChatMessage.MessageType.Text//类型文本
};
SaveMessagesToLocal(chatMessages, message);
string messageJsonString = Tool.Serialize(message);//序列化消息
//发送消息
_socketManger.SendMessage(messageJsonString);//发送消息
//在聊天框中显示自己发送过的消息
this.txtChat.AppendText($"[{DateTime.Now:HH:mm}] {_currentUser}:{txtContent.Text.Trim()}{Environment.NewLine}");
//清空输入框并设置焦点
this.txtContent.Clear();
this.txtContent.Focus();
}
}
///
/// 连接对方
///
///
///
private void btnTranfer_Click(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(this.txtFriendIP.Text.Trim()))
{
_socketManger.ConnectionToServer(txtFriendIP.Text.Trim());
btnTranfer.Enabled = false;//连接按钮禁止使用
btnOpen.Enabled = false;//监听按钮禁止使用
btnSender.Enabled = true;//发送按钮启用
btnClose.Enabled = true;//断开按钮启用
}
}
///
/// 接收消息
///
///
public void OnMessageReceived(string jsonMessage)
{
if (this.InvokeRequired)
{
//切换UI线程
this.Invoke(new Action(OnMessageReceived), jsonMessage);
return;
}
try
{
//反序列化
var message = ChatMessage.FromJson(jsonMessage);
SaveMessagesToLocal(chatMessages, message);
//在聊天框中显示自己发送过的消息
txtChat.AppendText($"[{DateTime.Now:HH:mm}] {message.Sender}:{message.Content}{Environment.NewLine}");
//播放声音
System.Media.SystemSounds.Beep.Play(); //播放系统
}
catch (Exception)
{
//如果转换不成json格式,就使用原来的消息
txtChat.AppendText($"[{DateTime.Now:HH:mm}] 收到:{jsonMessage} {Environment.NewLine}");
}
}
///
/// 状态变化
///
///
private void OnStatusChanged(string status)
{
if (this.InvokeRequired)
{
this.Invoke(new Action(OnStatusChanged), status);
return;
}
txtChat.AppendText($"【系统】{status}{Environment.NewLine}");
}
///
/// 发生错误时
///
///
private void OnError(string error)
{
if (this.InvokeRequired)
{
this.Invoke(new Action(OnError), error);
return;
}
MessageBox.Show(error, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
///
/// 读取本地的聊天数据
///
///
static List ReadFile() {
if (!File.Exists(Tool.path)) {
return new List();
}
string result = Tool.ReadJsonFile();
return Tool.Deserializer>(result);
}
///
/// 存储到本地
///
///
///
static void SaveMessagesToLocal(List chatMessages, ChatMessage message)
{
chatMessages.Add(message);
Tool.WriteJsonFile(Tool.Serialize(chatMessages));//更新本地的聊天数据
}
}
这里我建立了本地文件存储聊天记录(极限于本机的,对方发送的消息不会保存),不需要的可以去掉,也可优化升级一下,将对方的记录也存下来,到这里也就完成了TCP通信的制作。
六、效果展示
- 作为服务器开始监听,等待客服端连接

- 客服端连接服务器

- 连接成功开始通信


- 最后给大家看看保存的聊天记录吧(我忘记我完善过了)

感谢观看。需要源码看我主页更新,谢谢。









