欢迎光临
我们一直在努力

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<string> OnMessageReceived;//接收到消息触发事件

    public event Action<string> OnStatusChange;//状态变化时触发事件

    public event Action<string> OnError;//发生错误时触发
    
   }

三、创建好Socket类的字段属性后,要想实现通信还需完善各自的通信方法

  •   首先不管是服务器还是客服端,我们都要获取本机的IP地址和端口用来连接双方(这里我选择IPV4地址,端口默认8888)

/// <summary>
/// 构造函数初始话本机IP地址
/// </summary>
public SocketManger()
{
_localIP = GetLocalIPAddress();
}

/// <summary>
/// 获取本机IP
/// </summary>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
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";//默认返回本地地址
}

  • 其次是服务器开启监听等待客服端连接的方法

/// <summary>
/// 启动监听(作为服务器端)
/// </summary>
/// <param name="port"></param>
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}");
}
}

/// <summary>
/// 监听客服端的连接的线程方法
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void ListentForClients()
{
try
{
//启动监听
_listener = new TcpListener(IPAddress.Any, _port);
_listener.Start();

OnStatusChange?.Invoke("等待连接中…\\n");//状态更新

//阻塞等待客服连接
_client = _listener.AcceptTcpClient(); //接受客服端连接
_stream = _client.GetStream();//获取网络流

OnStatusChange?.Invoke("客服端已连接\\n");//更新状态

//开始接收消息
StartReceiving();
}
catch (Exception ex)
{
OnError?.Invoke($"监听异常:{ex.Message}{Environment.NewLine}");
}
}

  • 然后是客服端连接服务器的方法

/// <summary>
/// 客服端连接服务器
/// </summary>
/// <param name="serverIP"></param>
/// <param name="port"></param>
/// <returns></returns>
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;
}
}

  • 最后是接收消息、发送消息,断开连接的方法

    /// <summary>
    /// 开始接受消息
    /// </summary>
    private void StartReceiving()
    {
    //创建接受消息线程
    _receiveThread = new Thread(new ThreadStart(RecevieMessage));
    //设置为后台线程
    _receiveThread.IsBackground = true;
    //启动线程
    _receiveThread.Start();
    }

    /// <summary>
    /// 接收消息的方法
    /// </summary>
    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
    }
    }
    }
    }

    /// <summary>
    /// 发送消息
    /// </summary>
    /// <param name="message"></param>
    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}");
    }
    }

    /// <summary>
    /// 断开连接的方法
    /// </summary>
    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,//系统消息
}

/// <summary>
/// 将消息对象进行序列化为Json字符串
/// </summary>
/// <returns></returns>
public string ToJsonSerializer(ChatMessage chat)
{
return Tool.Serialize<ChatMessage>(chat);
//return System.Text.Json.JsonSerializer.Serialize(this);
}

/// <summary>
/// 将json字符串反序列化为消息
/// </summary>
/// <param name="jsonString"></param>
/// <returns></returns>
public static ChatMessage FromJson(string jsonString)
{
return Tool.Deserializer<ChatMessage>(jsonString);
}
}

        发送消息要将消息序列化为JSON文件发送,这里我使用了Newtonsoft.Json包,为了方便大家可以使用Text.Json来序列化

  • 界面控件方法绑定

public partial class ChatForm : Form
{
private SocketManger _socketManger;
private string _currentUser = "用户" + new Random().Next(10000, 99999);
List<ChatMessage> 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;//断开按钮禁用
}

/// <summary>
/// 断开
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnClose_Click(object sender, EventArgs e)
{
_socketManger.Disconnect();//断开连接
btnTranfer.Enabled = true;//连接按钮启用
btnOpen.Enabled = true;//监听按钮启用
btnSender.Enabled = false;//发送按钮禁用
btnClose.Enabled = false;//断开按钮禁用
}

/// <summary>
/// 上线
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnOpen_Click(object sender, EventArgs e)
{
_socketManger.StartListening();//开始监听
btnTranfer.Enabled = false;//连接按钮禁止使用
btnOpen.Enabled = false;//监听按钮禁止使用
btnSender.Enabled = true;//发送按钮启用
btnClose.Enabled = true;//断开按钮启用
}

/// <summary>
/// 发送按钮
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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();
}
}

/// <summary>
/// 连接对方
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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;//断开按钮启用
}
}
/// <summary>
/// 接收消息
/// </summary>
/// <param name="jsonMessage"></param>
public void OnMessageReceived(string jsonMessage)
{
if (this.InvokeRequired)
{
//切换UI线程
this.Invoke(new Action<string>(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}");
}
}
/// <summary>
/// 状态变化
/// </summary>
/// <param name="status"></param>
private void OnStatusChanged(string status)
{
if (this.InvokeRequired)
{
this.Invoke(new Action<string>(OnStatusChanged), status);
return;
}
txtChat.AppendText($"【系统】{status}{Environment.NewLine}");
}
/// <summary>
/// 发生错误时
/// </summary>
/// <param name="error"></param>
private void OnError(string error)
{
if (this.InvokeRequired)
{
this.Invoke(new Action<string>(OnError), error);
return;
}
MessageBox.Show(error, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
/// <summary>
/// 读取本地的聊天数据
/// </summary>
/// <returns></returns>
static List<ChatMessage> ReadFile() {
if (!File.Exists(Tool.path)) {
return new List<ChatMessage>();
}
string result = Tool.ReadJsonFile();
return Tool.Deserializer<List<ChatMessage>>(result);
}
/// <summary>
/// 存储到本地
/// </summary>
/// <param name="chatMessages"></param>
/// <param name="message"></param>
static void SaveMessagesToLocal(List<ChatMessage> chatMessages, ChatMessage message)
{
chatMessages.Add(message);
Tool.WriteJsonFile(Tool.Serialize(chatMessages));//更新本地的聊天数据
}
}

        这里我建立了本地文件存储聊天记录(极限于本机的,对方发送的消息不会保存),不需要的可以去掉,也可优化升级一下,将对方的记录也存下来,到这里也就完成了TCP通信的制作。

六、效果展示

  • 作为服务器开始监听,等待客服端连接

  • 客服端连接服务器

  • 连接成功开始通信

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

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

赞(0)
未经允许不得转载:171主机测评 » C#简易winform客服端服务器一体的TCP通信程序
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址