Socket的通信方式:
Socket有TCP和UDP两种通信方式,我们可以根据具体的情况来选择。一般情况下,如果需要数据准确传输、不丢失,则选择TCP;反之,则选择UDP。
参考文章:
Socket的TCP和UDP连接
需要提前了解的Socket知识点:
- Client端输入的IP都是Server所在电脑的IP
- Server最好设置0.0.0.0这样无论迁移到哪个电脑上,都是那台电脑的IP
- Client和Server必须在同一个局域网之下,否则不能通信;如果要实现跨局域网通信,需要使用公网IP(通过阿里云等)
整个聊天室的代码分为两个模块:
- Server服务器模块:只能有一个,用来存储监听用户发送的内容
- Client客户模块:可以有多个,各个用户直接进行收发信息
以下是Server代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace Server
{class Program{static Socket socket = null;static void Main(string[] args){socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);socket.Bind(new IPEndPoint(IPAddress.Parse("0.0.0.0"), 60000));socket.Listen(10);Console.WriteLine("已启动侦听");Task.Run(Connect);Console.ReadLine();}static List<Socket> socketPool = new List<Socket>();static Socket acceptSocket = null;static void Connect(){while (true){try{acceptSocket = socket.Accept();var receiveSocket = acceptSocket;Console.WriteLine($"已接受连接:{receiveSocket.RemoteEndPoint}");socketPool.Add(receiveSocket);Task.Run(Receive);}catch (Exception ex){Console.WriteLine($"连接失败:{ex.Message}");}}}static void Receive(){var receiveSocket = acceptSocket;while (true){if (receiveSocket == null) continue;if (!receiveSocket.Connected) continue;try{byte[] buffer = new byte[1024];var len = receiveSocket.Receive(buffer);if (len < 1) continue;var msg = Encoding.UTF8.GetString(buffer, 0, len);Console.WriteLine($"来自 {receiveSocket.RemoteEndPoint} 的消息 {msg}");var responseBuffer = Encoding.UTF8.GetBytes($"来自{receiveSocket.RemoteEndPoint} 的消息:{msg}");// receiveSocket.Send(responseBuffer);//清理失效的连接for (int i = socketPool.Count - 1; i >= 0; i--){if (socketPool[i] == null || !socketPool[i].Connected){socketPool.RemoveAt(i);}}//广播消息(除了发送消息方之外的,全部广播)foreach (var s in socketPool){if (s != receiveSocket){s.Send(responseBuffer);}}buffer = null;}catch (Exception ex){Console.WriteLine(ex.Message + "\n");}}}}
}
以下是Client代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;/// <summary>
/// 客户端
/// </summary>
namespace WpfApp1
{/// <summary>/// MainWindow.xaml 的交互逻辑/// </summary>public partial class MainWindow : Window{Socket socket = null;public MainWindow(){InitializeComponent();btnConnect.Click += btnConnect_Click;btnSend.Click += btnSend_Click;Task.Run(Receive);}private void btnConnect_Click(object sender, RoutedEventArgs e){if (socket != null && socket.Connected){ShowMessage($"无需重复连接");return;}try{socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);ShowMessage($"LocalEndPoint={socket.LocalEndPoint},RemoteEndPoint={socket.RemoteEndPoint}");var ipEndPoint = new IPEndPoint(IPAddress.Parse(txtIP.Text), int.Parse(txtPort.Text));socket.Connect(ipEndPoint);if (socket.Connected){ShowMessage($"成功连接到:{socket.RemoteEndPoint}");}}catch (Exception ex){ShowMessage($"连接失败:{ex.Message}");socket = null;}}private void btnSend_Click(object sender, RoutedEventArgs e){if (!socket.Connected){ShowMessage($"未连接,无法发送");return;}try{if (rtxtSend.Text != ""){var buffer = Encoding.UTF8.GetBytes(rtxtSend.Text);socket.Send(buffer);ShowMessage($"发送到:{socket.RemoteEndPoint},消息:{rtxtSend.Text}");}rtxtSend.Text = null;}catch (Exception ex){ShowMessage($"发送失败:{socket.RemoteEndPoint},{ex.Message}");socket = null;}}private void Receive(){while (true){if (socket == null) continue;if (!socket.Connected) continue;try{byte[] buffer = new byte[1024];if (buffer != null){int len = socket.Receive(buffer);if (len < 1) continue;string msg = Encoding.UTF8.GetString(buffer, 0, len);ShowMessage(msg);}buffer = null;}catch (Exception ex){ShowMessage(ex.Message + "\n");socket = null;}}}private void ShowMessage(string msg){///这是Winform的用法//子线程调用 //if (rtxtLog.InvokeRequired) //c#中禁止跨线程直接访问控件,InvokeRequired是为了解决这个问题而产生的//{// rtxtLog.Invoke(new Action(() => rtxtLog.Text += msg + "\n"));//}//主线程调用//else//{// rtxtLog.Text += msg + "\n";//}if (!CheckAccess()) //WPF使用Dispatcher控制对消息泵的访问,而不是让每个控件负责访问UI线程。{Dispatcher.Invoke(new Action(() => rtxtLog.Text += msg + "\n"));}//主线程调用else{rtxtLog.Text += msg + "\n";}}}
}