winform客户端、服务器异步通信
winform客户端、服务器异步通信 视频演示
主要代码
#要发送的数据主体
class PersonPackage
{
public bool IsMale { get; set; }
public ushort Age { get; set; }
public string Name { get; set; }
public PersonPackage(bool male, ushort age, string name)
{
IsMale = male;
Age = age;
Name = name;
}
/// <summary>
/// 反序列化接收到的字节数组
/// </summary>
public PersonPackage(byte[] data)
{//字节数组data 反序列化为 性别bool ,年龄uint16,名称长度int32。共需要1+2+4=7字节。data后面的是名字
IsMale = BitConverter.ToBoolean(data, 0);
Age = BitConverter.ToUInt16(data, 1);
int nameLength = BitConverter.ToInt32(data, 3);
Name = Encoding.ASCII.GetString(data, 7, nameLength);
}
/// <summary>
/// 将此人信息序列化为字节数组。
/// </summary>
/// <remarks>
/// Use the <see cref="Buffer"/> or <see cref="Array"/> class for better performance.
/// </remarks>
public byte[] ToByteArray()
{
List<byte> byteList = new List<byte>();//字节列表
byteList.AddRange(BitConverter.GetBytes(IsMale));//bool->字节
byteList.AddRange(BitConverter.GetBytes(Age));//ushort / uint16->字节
byteList.AddRange(BitConverter.GetBytes(Name.Length));//int32->字节
byteList.AddRange(Encoding.ASCII.GetBytes(Name));//string->字节
return byteList.ToArray();//字节列表->字节数组
}
}
#客户端窗体类代码
public partial class ClientForm : Form
{
private Socket clientSocket;
private byte[] buffer;
public ClientForm()
{
InitializeComponent();
}
//显示错误对话框
private static void ShowErrorDialog(string message)
{
MessageBox.Show(message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
//接收回调
private void ReceiveCallback(IAsyncResult AR)
{
try
{
int received = clientSocket.EndReceive(AR);
if (received == 0)
{
return;
}
string message = Encoding.ASCII.GetString(buffer);
Invoke((Action) delegate
{
Text = "Server says: " + message;
});
// 再次开始接收数据。
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, null);
}
//在这种情况下避免 Pokemon 异常处理。
catch (SocketException ex)
{
ShowErrorDialog(ex.Message);
}
catch (ObjectDisposedException ex)
{
ShowErrorDialog(ex.Message);
}
}
//连接服务器回调
private void ConnectCallback(IAsyncResult AR)
{
try
{
clientSocket.EndConnect(AR);//结束挂起的异步连接请求。
UpdateControlStates(true);//发送按钮可用,连接按钮不可用,label不可见
buffer = new byte[clientSocket.ReceiveBufferSize];
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, null);
}
catch (SocketException ex)
{
ShowErrorDialog(ex.Message);
}
catch (ObjectDisposedException ex)
{
ShowErrorDialog(ex.Message);
}
}
//发送字节数组给服务器的回调函数
private void SendCallback(IAsyncResult AR)
{
try
{
clientSocket.EndSend(AR);//结束挂起的异步发送
}
catch (SocketException ex)
{
ShowErrorDialog(ex.Message);
}
catch (ObjectDisposedException ex)
{
ShowErrorDialog(ex.Message);
}
}
/// <summary>
/// 启用发送按钮的线程安全方式。
/// </summary>
private void UpdateControlStates(bool toggle)//toggle==fasle时
{
Invoke((Action)delegate
{
buttonSend.Enabled = toggle;//发送按钮不可用
buttonConnect.Enabled = !toggle;//连接按钮可用
labelIP.Visible = textBoxAddress.Visible = !toggle;//label可见
});
}
//发送人的信息 字节数据
private void buttonSend_Click(object sender, EventArgs e)
{
try
{
// 在发送之前序列化 textBoxes 文本。
PersonPackage person = new PersonPackage(checkBoxMale.Checked, (ushort)numberBoxAge.Value, textBoxEmployee.Text);
byte[] buffer = person.ToByteArray();//序列化为字节数组
clientSocket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, SendCallback, null);//发送字节数组给服务器
}
catch (SocketException ex)//主机断开后,再次发送引发异常
{
ShowErrorDialog(ex.Message);
UpdateControlStates(false);//IP可见
}
catch (ObjectDisposedException ex)
{
ShowErrorDialog(ex.Message);
UpdateControlStates(false);
}
}
//连接服务器
private void buttonConnect_Click(object sender, EventArgs e)
{
try
{//使用指定的地址族、套接字类型和协议初始化 System.Net.Sockets.Socket 类的新实例
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//连接到指定的主机。
var endPoint = new IPEndPoint(IPAddress.Parse(textBoxAddress.Text), 3333);
clientSocket.BeginConnect(endPoint, ConnectCallback, null);//开始一个对远程主机连接的异步请求
}
catch (SocketException ex)//发生套接字错误时引发的异常。
{
ShowErrorDialog(ex.Message);
}
catch (ObjectDisposedException ex)//对已释放的对象执行操作时所引发的异常。
{
ShowErrorDialog(ex.Message);
}
}
}
#服务器端窗体类代码
public partial class ServerForm : Form
{
private Socket serverSocket; //服务端套接字
private Socket clientSocket; // 我们将只接收一个客户端
private byte[] buffer;//接收缓存
public ServerForm()
{
InitializeComponent();
StartServer();
}
//显示错误对话框
private static void ShowErrorDialog(string message)
{
MessageBox.Show(message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
/// <summary>
/// Construct server socket and bind socket to all local network interfaces, then listen for connections
/// with a backlog of 10. Which means there can only be 10 pending connections lined up in the TCP stack
/// at a time. This does not mean the server can handle only 10 connections. The we begin accepting connections.
/// Meaning if there are connections queued, then we should process them.
/// 构造服务器套接字并将套接字绑定到所有本地网络接口,然后侦听积压为 10 的连接。这意味着 TCP 堆栈中一次只能有 10 个未决连接排队。
/// 这并不意味着服务器只能处理 10 个连接。我们开始接受连接。这意味着如果有连接排队,那么我们应该处理它们。
/// </summary>
private void StartServer()
{
try
{
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(IPAddress.Any, 3333));
serverSocket.Listen(10);//将 System.Net.Sockets.Socket 置于侦听状态。
serverSocket.BeginAccept(AcceptCallback, null);//开始一个异步操作来接受一个传入的连接尝试。
}
catch (SocketException ex)
{
ShowErrorDialog(ex.Message);
}
catch (ObjectDisposedException ex)
{
ShowErrorDialog(ex.Message);
}
}
private void AcceptCallback(IAsyncResult AR)
{
try
{
clientSocket = serverSocket.EndAccept(AR);// 异步接受传入的连接尝试,并创建新的 System.Net.Sockets.Socket 来处理远程主机通信。
buffer = new byte[clientSocket.ReceiveBufferSize];
// 向新连接的客户端发送消息。
var sendData = Encoding.ASCII.GetBytes("Hello");
clientSocket.BeginSend(sendData, 0, sendData.Length, SocketFlags.None, SendCallback, null);
// 监听客户端数据。
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, null);
// 继续倾听客户。
serverSocket.BeginAccept(AcceptCallback, null);
}
catch (SocketException ex)
{
ShowErrorDialog(ex.Message);
}
catch (ObjectDisposedException ex)
{
ShowErrorDialog(ex.Message);
}
}
//发送回调
private void SendCallback(IAsyncResult AR)
{
try
{
clientSocket.EndSend(AR);
}
catch (SocketException ex)
{
ShowErrorDialog(ex.Message);
}
catch (ObjectDisposedException ex)
{
ShowErrorDialog(ex.Message);
}
}
//接收回调
private void ReceiveCallback(IAsyncResult AR)
{
try
{
// Socket exception will raise here when client closes, as this sample does not
// demonstrate graceful disconnects for the sake of simplicity.
//当客户端关闭时,此处会引发套接字异常,因为为了简单起见,此示例没有演示优雅的断开连接。
int received = clientSocket.EndReceive(AR);
if (received == 0)
{
return;
}
// 接收到的数据在 PersonPackage ctor 中反序列化。
PersonPackage person = new PersonPackage(buffer);
SubmitPersonToDataGrid(person);
//再次开始接收数据。
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, null);
}
// 在这种情况下避免 Pokemon 异常处理。
catch (SocketException ex)
{
ShowErrorDialog(ex.Message);
}
catch (ObjectDisposedException ex)
{
ShowErrorDialog(ex.Message);
}
}
/// <summary>
/// 提供一种线程安全的方式将行添加到datagrid。
/// </summary>
private void SubmitPersonToDataGrid(PersonPackage person)
{
Invoke((Action)delegate
{
dataGridView.Rows.Add(person.Name, person.Age, person.IsMale);
});
}
}
多客户端 异步通信
多客户端 异步通信 视频演示
主要代码
#多客户端实例-客户端代码
class Program
{
private static readonly Socket ClientSocket = new Socket
(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //客户端套接字
private const int PORT = 100;
static void Main()
{
Console.Title = "Client";
ConnectToServer(); //连接服务器
RequestLoop();//请求循环
Exit();//退出
}
private static void ConnectToServer()//
{
int attempts = 0;//尝试连接次数
while (!ClientSocket.Connected)//未连接
{
try
{
attempts++;//尝试连接次数++
Console.WriteLine("Connection attempt " + attempts);
// 将 IPAddress.Loopback 更改为远程 IP 以连接到远程主机。
ClientSocket.Connect(IPAddress.Loopback, PORT);
}
catch (SocketException)
{
Console.Clear();
}
}
Console.Clear();
Console.WriteLine("Connected");//连接成功
}
private static void RequestLoop()
{
Console.WriteLine(@"<Type ""exit"" to properly disconnect client>");
while (true)
{
SendRequest(); //发送请求
ReceiveResponse();//接收响应
}
}
/// <summary>
///关闭套接字并退出程序。
/// </summary>
private static void Exit()
{
SendString("exit"); // 告诉服务器我们正在退出
ClientSocket.Shutdown(SocketShutdown.Both);//禁用客户端套接字上的接收与发送
ClientSocket.Close();//关闭客户端套接字
Environment.Exit(0);//关闭程序
}
private static void SendRequest()
{
Console.Write("Send a request: ");
string request = Console.ReadLine();//输入要发送的请求内容
SendString(request);
if (request.ToLower() == "exit")//如果要发送给服务器的字符串是 exit 命令
{
Exit();//退出
}
}
/// <summary>
/// 使用 ASCII 编码向服务器发送一个字符串。
/// </summary>
private static void SendString(string text)
{
byte[] buffer = Encoding.ASCII.GetBytes(text);
ClientSocket.Send(buffer, 0, buffer.Length, SocketFlags.None);
}
private static void ReceiveResponse()
{
var buffer = new byte[2048];//接收缓存
int received = ClientSocket.Receive(buffer, SocketFlags.None);//接收服务器响应
if (received == 0) return;
var data = new byte[received];
Array.Copy(buffer, data, received);//去除空字符
string text = Encoding.ASCII.GetString(data);
Console.WriteLine(text);//显示接收的响应文本
}
}
#多客户端实例-服务器端代码
class Program
{
private static readonly Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
private static readonly List<Socket> clientSockets = new List<Socket>(); //介入的客户端套接字列表
private const int BUFFER_SIZE = 2048;
private const int PORT = 100;
private static readonly byte[] buffer = new byte[BUFFER_SIZE];
static void Main()
{
Console.Title = "Server";
SetupServer();//设置服务器套接字 开始侦听
Console.ReadLine(); //当我们按下回车关闭一切
CloseAllSockets();
}
private static void SetupServer()
{
Console.WriteLine("Setting up server...");
serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT));
serverSocket.Listen(0);
serverSocket.BeginAccept(AcceptCallback, null);//开启异步操作来接受一个传入的连接尝试
Console.WriteLine("Server setup complete");
}
/// <summary>
/// Close all connected client (we do not need to shutdown the server socket as its connections
/// are already closed with the clients).
/// 关闭所有连接的客户端(我们不需要关闭服务器套接字,因为它的连接已经与客户端 关闭)。
/// </summary>
private static void CloseAllSockets()
{
foreach (Socket socket in clientSockets)
{
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
serverSocket.Close();
}
private static void AcceptCallback(IAsyncResult AR)
{
Socket socket;
try
{
socket = serverSocket.EndAccept(AR);
}
catch (ObjectDisposedException) //我似乎无法避免这种情况(在正确关闭套接字时退出)
{
return;
}
clientSockets.Add(socket);
socket.BeginReceive(buffer, 0, BUFFER_SIZE, SocketFlags.None, ReceiveCallback, socket);//开始异步接收数据
Console.WriteLine("Client connected, waiting for request...");
serverSocket.BeginAccept(AcceptCallback, null);
}
private static void ReceiveCallback(IAsyncResult AR)
{
Socket current = (Socket)AR.AsyncState;
int received;
try
{
received = current.EndReceive(AR);//结束挂起的异步读取
}
catch (SocketException)
{
Console.WriteLine("客户端强行断开");
current.Close(); //关闭该套接字
clientSockets.Remove(current);//从套接字列表移除
return;
}
byte[] recBuf = new byte[received];
Array.Copy(buffer, recBuf, received);//去除空格
string text = Encoding.ASCII.GetString(recBuf);
Console.WriteLine("Received Text: " + text);
if (text.ToLower() == "get time") // 客户端请求时间
{
Console.WriteLine("Text is a get time request");
byte[] data = Encoding.ASCII.GetBytes(DateTime.Now.ToLongTimeString());
current.Send(data);//发送时间给客户端
Console.WriteLine("Time sent to client");
}
else if (text.ToLower() == "exit") // 客户端想要优雅地退出
{
// 关闭前总是关机
current.Shutdown(SocketShutdown.Both);//禁用current套接字的发送和接收
current.Close();//关闭current套接字
clientSockets.Remove(current);//列表中移除套接字
Console.WriteLine("Client disconnected");
return;
}
else
{
Console.WriteLine("Text is an invalid request");
byte[] data = Encoding.ASCII.GetBytes("Invalid request");//无效请求
current.Send(data);
Console.WriteLine("Warning Sent");
}
current.BeginReceive(buffer, 0, BUFFER_SIZE, SocketFlags.None, ReceiveCallback, current);//继续从客户端套接字异步接收数据
}
}
同步socket通信
#主程序
class Program
{
public const int Port = 3333;
static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("No arguments provided.");
Console.ReadLine();
return;
}
// 参数 "/c" 将以客户端模式启动该程序。
// 参数“/s”将以服务器模式启动该程序。
switch (args[0].ToLower())
{
case "/c":
Client client= new Client();
client.StartSendLoop();
break;
case "/s":
// 也以客户端模式启动此应用程序。
string fileName = System.Reflection.Assembly.GetExecutingAssembly().Location;
System.Diagnostics.Process.Start(fileName, "/c");
Server server = new Server();
server.Start();
break;
}
}
}
/// <summary>
/// 表示客户端应用程序。
/// </summary>
class Client
{
private Socket clientSocket; //客户端套接字
public void StartSendLoop()
{
try
{
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 将 IPAddress.Loopback 更改为远程 IP 以连接到远程主机。
clientSocket.Connect(IPAddress.Loopback, Program.Port);
while (true)
{
Console.Write("Enter text to send: ");
string input = Console.ReadLine();
if (input == "exit")
{
clientSocket.Close(); //关闭客户端套接字
Environment.Exit(0); //退出程序
}
clientSocket.Send(Encoding.ASCII.GetBytes(input));
Console.Clear();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadLine();
}
}
}
#服务器类
class Server
{
private Socket serverSocket, clientSocket;
private byte[] buffer;
public void Start()
{
// Create server socket and listen on any local interface.
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(IPAddress.Any, 3333));
serverSocket.Listen(0);//服务端套接字处于监听状态
// 后跟...表示工作中
Console.WriteLine("Listening for connections on port " + Program.Port + "...");
clientSocket = serverSocket.Accept(); //阻塞,等待客户端连接
Console.WriteLine("Client Connected");
Console.WriteLine("Waiting for data...");
buffer = new byte[clientSocket.ReceiveBufferSize];
ReceiveData();
Console.WriteLine("Server loop ended. Press enter to exit.");
Console.ReadLine();
serverSocket.Close();
}
private void ReceiveData()
{
while (clientSocket.Connected)
{
int received = clientSocket.Receive(buffer);//接收数据写入缓存
if (received == 0) //假设客户端已断开连接。
{
Console.WriteLine("Client Disconnected");
break;
}
// 缩小缓冲区,这样我们就不会在文本中得到空字符。
Array.Resize(ref buffer, received);
string receivedMsg = Encoding.ASCII.GetString(buffer);
//重置缓冲区。
Array.Resize(ref buffer, clientSocket.ReceiveBufferSize);
Console.WriteLine("Message received: " + receivedMsg);
}
//假设客户端已断开连接并再次开始侦听连接。
Console.WriteLine("\nListening again...");
clientSocket = serverSocket.Accept();
Console.WriteLine("Client Connected");
Console.WriteLine("Waiting for data...");
ReceiveData();
}
}
结语:
以上实例仅用于对网络编程概念的理解。工程上一般会使用第三方成熟的通信库。有开发通信库能力的除外。 在专业领域同样也存在这样一个问题,在学校里学了很多理论也做出过很多demo,用到工程上会发现各种问题.做工程和做学术研究最大的区别在于工程上使用的技术要求成熟、稳定、可靠。学术研究的demo可能成功了一次,失败了99次,就可以算作好的研究成果了,这些都是在非常理想的情况下做出的成果。但是在工程上你就是成功99次,有一次失败都不行,工程上有很多现实问题要面对。所以学术上的研究成果要进行工程化是有很长的路要走的。
The End