本文将介绍利用基于tcp通信协议的Socket实现服务器与客户端之间的数据传输。 目录 前言 计算机通信 创建服务器 服务器通信 创建客户端 客户端通信 前言 TCP/IP(Transmission Control Pr
本文将介绍利用基于tcp通信协议的Socket实现服务器与客户端之间的数据传输。
前言
计算机通信
创建服务器
服务器通信
创建客户端
客户端通信
TCP/IP(Transmission Control Protocol/Internet Protocol)是一种传输控制协议/网间协议,TCP属于传输层、IP属于网络层,而套接字(Socket)是应用层和传输层之间的一个抽象类,基于传输层暴露的接口进行应用层开发,例如连接Connect()、监听Listen()、发送Send()等等。
可见Socket与TCP/IP没有必然的联系,实际上Socket不仅限于支持TCP/IP还支持在Http、UDP等协议,只不过TCP/IP是使用最广泛的协议之一,提供了可靠的传输服务。
因此本文选择介绍TCP通信协议的Socket实现服务器与客户端之间的数据传输。
本地通信:一个进程对应一个标示PID,本地进程通信中PID唯一表示本地中的一个进程;
网络通信:然而PID只在本地唯一,网络中的两个进程PID冲突几率很大,也就诞生了一个主机对应一个IP地址,网络进程通信中IP地址+协议+端口号唯一表示网络中的一个进程;
只有一个进程对应一个唯一的标示,即该标示唯一表示一个进程,才能保证端与端之间能够可靠的传输数据,本文介绍的TCP通信协议属于网络通信,也就可以利用一个云端搭建服务器实现我们现实生活中几乎每天都在使用的上网聊天功能,下图是实现TCP通信协议的Socket的过程。
套接字(Socket):Socket = IP地址:端口号,在C#中利用Socket类中的LocalEndPoint服务器的终结点和RemoteEndPoint客户端的终结点来表示唯一的套接字对象,可通过调用成员函数来监听Listen()、发送Send()和接受Receive()等,该方法可靠性好,但由于协议复杂,通信效率不高。
Server窗体负责消息交互,Sign窗体负责创建服务器,UsersDataBase负责存储注册的用户信息。
可输入端口,显示端口有效范围,点击按键即可创建服务器。
根据窗口设计选择相应的控件,包含按键以及提示文字标签等。
其中fix为控制程序焦点的TextBox控件,保证启动程序时聚焦到该控件上,具体设置如下:
点击创建服务器按键后,打开服务器通讯窗口,并将端口号传入该窗口。
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading.Tasks;using System.Windows.Forms;namespace Server{ public partial class CreatForm : Form { public CreatForm() { InitializeComponent(); } private void CreatForm_Load(object sender, EventArgs e) { Control.CheckForIllegalCrossThreadCalls = false; //取消对跨线程访问的检查 portTextBox.ForeColor = Color.Gray; //设置文本颜色 portTextBox.Text = "0-65535"; //显示默认文字 } private void btnCreat_Click(object sender, EventArgs e) { IPAddress ip = IPAddress.Any; //监听所有活动网卡 if (Convert.ToInt32(portTextBox.Text) > 0 && Convert.ToInt32(portTextBox.Text) < 65535) //端口号有效范围为0-65535 { IPEndPoint port = new IPEndPoint(ip, Convert.ToInt32(portTextBox.Text)); //创建监听对象 ServerFORM serverForm = new ServerForm(port, this); //创建并初始化服务器窗体实例 //参数1:监听的端口号,参数2:本窗体 this.Hide(); //隐藏当前窗体 serverForm.ShowDialog(); //显示服务器窗体 } else { MessageBox.Show("端口号无效!"); } } private void portTextBox_Enter(object sender, EventArgs e) { if (portTextBox.Text != "") { portTextBox.Text = "";//清空默认文字 } portTextBox.ForeColor = Color.Black; //设置文本颜色 } }}
注:
1.启动时输入端口的TextBox控件中显示默认文字,发生聚焦事件时清空默认文字并设置颜色;
2.点击创建客户端按键后,只能隐藏该窗体,不能关闭该窗体,显示的窗体是子窗口,该窗口为主窗口,若关闭主窗口,所有窗口都会被关闭;
Server窗体负责消息交互,Sign窗体负责创建服务器,UsersDataBase负责存储注册的用户信息。
可控制监听的启停,显示服务器的状态与监听的端口号,显示收发的消息,点击按键可发送消息。
根据窗体设计选择相应的控件,包含按键、显示状态、端口号以及消息等。
public ServerForm(IPEndPoint tempPort,Form tempForm) { port = tempPort; //端口号 signForm = tempForm; //上级窗体 InitializeComponent(); }
private void btnStartListen_Click(object sender, EventArgs e) { try { statusLabel.Text = "已启动"; btnStartListen.Enabled = false; btnStopListen.Enabled = true; socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //创建TCP通信协议Socket if(watchFlag == 0) { socketWatch.Bind(port); //开始监听 watchFlag++; socketWatch.Listen(10); //socketWatch为被动连接,最大连接数为10 thListen = new Thread(Listen); //设置thListen线程的启动函数为监听函数Listen() thListen.IsBackground = true; //设置thListen线程为后台线程 thListen.Start(socketWatch); //启动thListen线程并传值给Listen() } } catch(Exception ex) //异常捕获 { MessageBox.Show("正在监听!"); } }
void Listen(object obj) { Socket socket = obj as Socket; while (true) { try { socket = socketWatch.Accept(); //接受到客户端Client的连接请求,返回一个负责和客户端通讯的socket /// 在没有接受到连接请求前,位于Accept()下面的代码是不会被执行的,也就是线程阻塞 socketList.Add(socket); //添加到套接字列表 thReceive = new Thread(Receive); //设置thReceive线程的启动函数为接受函数Receive() thReceive.IsBackground = true; //设置thReceive线程为后台线程 thReceive.Start(socket); //启动thReceive线程并传socket给Receive() /// 客户端连接成功后,服务端应该收到客户端发来的消息,该消息是位传输的 } catch(Exception ex) //异常捕获 { MessageBox.Show(ex.Message); //显示异常信息 break; } } }
<在没有收到客户端连接之前会被Accept()函数阻塞,这段代码并不是一个死循环>
void Receive(object obj) { Socket socket = obj as Socket; while (true) { try { byte[] buffer = new byte[1024 * 1024 * 3]; //约定缓存长度解决粘包问题 int r = socket.Receive(buffer); //接受客户端Client缓存 if (r == 0) //接受到空消息 { break; } else { string str = Encoding.UTF8.GetString(buffer, 0, r); //缓存解码为字符串 ShowMsg(socket.RemoteEndPoint.ToString() + ":" + str); //显示接受到的消息 } } catch (Exception ex) //异常捕获 { MessageBox.Show(ex.Message); //显示异常信息 break; } } }
private void btnSend_Click(object sender, EventArgs e) { if (socketList.Count == 0) { MessageBox.Show("没有客户端连接!"); sendMsgTextBox.Text = ""; //清除消息 return; } int index = 0; while (index < socketList.Count) { Send(socketList[index]); index++; } } void Send(object obj) { try { Socket socket = obj as Socket; byte[] buffer = System.Text.Encoding.UTF8.GetBytes(sendMsgTextBox.Text); //将泛型转换为数组 socket.Send(buffer); //发送缓存至客户端Client ShowMsg(socket.LocalEndPoint.ToString() + ":" + sendMsgTextBox.Text); //显示发送的消息 sendMsgTextBox.Text = ""; //清除消息} } catch { MessageBox.Show("客户端未连接!"); } }
按照step1-step5完成程序设计,源码如下:
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.net;using System.Net.Sockets;using System.Text;using System.Threading;using System.Threading.Tasks;using System.windows.Forms;namespace Server{ public partial class ServerForm : Form { IPEndPoint port; Form signForm = new Form(); Socket socketWatch; int watchFlag = 0; List socketList = new List(); Thread thListen; Thread thReceive; public ServerForm(IPEndPoint tempPort,Form tempForm) { port = tempPort; //端口号 signForm = tempForm; //上级窗口 InitializeComponent(); } private void ServerForm_Load(object sender, EventArgs e) { Control.CheckForIllegalCrossThreadCalls = false; //取消对跨线程访问的检查 portLabel.Text = "端口号:" + port.Port.ToString(); //显示端口号 btnStartListen.Enabled = true; btnStopListen.Enabled = false; } private void ServerForm_Closed(object sender, FormClosedEventArgs e) { System.Environment.Exit(0); } private void btnStartListen_Click(object sender, EventArgs e) { try { statusLabel.Text = "已启动"; btnStartListen.Enabled = false; btnStopListen.Enabled = true; socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //创建TCP通信协议Socket if(watchFlag == 0) { socketWatch.Bind(port); //开始监听 watchFlag++; socketWatch.Listen(10); //socketWatch为被动连接,最大连接数为10 thListen = new Thread(Listen); //设置thListen线程的启动函数为监听函数Listen() thListen.IsBackground = true; //设置thListen线程为后台线程 thListen.Start(socketWatch); //启动thListen线程并传值给Listen() } } catch(Exception ex) //异常捕获 { MessageBox.Show("正在监听!"); } } private void btnStopListen_Click(object sender, EventArgs e) { statusLabel.Text = "未启动"; btnStartListen.Enabled = true; btnStopListen.Enabled = false; if (socketList.Count > 0) { socketWatch.Shutdown(SocketShutdown.Both); socketWatch.Close(); socketWatch.Dispose(); //socketWatch = null; socketList.Clear(); watchFlag = 0; } } void Listen(object obj) { Socket socket = obj as Socket; while (true) { try { socket = socketWatch.Accept(); //接受到客户端Client的连接请求,返回一个负责和客户端通讯的socket /// 在没有接受到连接请求前,位于Accept()下面的代码是不会被执行的,也就是线程阻塞 socketList.Add(socket); //添加到套接字列表 thReceive = new Thread(Receive); //设置thReceive线程的启动函数为接受函数Receive() thReceive.IsBackground = true; //设置thReceive线程为后台线程 thReceive.Start(socket); //启动thReceive线程并传socket给Receive() /// 客户端连接成功后,服务端应该收到客户端发来的消息,该消息是位传输的 } catch(Exception ex) //异常捕获 { MessageBox.Show(ex.Message); //显示异常信息 break; } } } void Receive(object obj) { Socket socket = obj as Socket; while (true) { try { byte[] buffer = new byte[1024 * 1024 * 3]; //约定缓存长度解决粘包问题 int r = socket.Receive(buffer); //接受客户端Client缓存 if (r == 0) //接受到空消息 { break; } else { string str = Encoding.UTF8.GetString(buffer, 0, r); //缓存解码为字符串 ShowMsg(socket.RemoteEndPoint.ToString() + ":" + str); //显示接受到的消息 } } catch (Exception ex) //异常捕获 { MessageBox.Show(ex.Message); //显示异常信息 break; } } } private void btnSend_Click(object sender, EventArgs e) { if (socketList.Count == 0) { MessageBox.Show("没有客户端连接!"); sendMsgTextBox.Text = ""; //清除消息 return; } int index = 0; while (index < socketList.Count) { Send(socketList[index]); index++; } } void Send(object obj) { try { Socket socket = obj as Socket; byte[] buffer = System.Text.Encoding.UTF8.GetBytes(sendMsgTextBox.Text); //将泛型转换为数组 socket.Send(buffer); //发送缓存至客户端Client ShowMsg(socket.LocalEndPoint.ToString() + ":" + sendMsgTextBox.Text); //显示发送的消息 sendMsgTextBox.Text = ""; //清除消息} } catch { MessageBox.Show("客户端未连接!"); } } void ShowMsg(string str) { try { showMsgTextBox.AppendText(str + "\r\n"); } catch (Exception ex) //异常捕获 { MessageBox.Show(ex.Message); //显示异常信息 } } }}
Client窗体负责消息交互,Connect窗体负责连接服务器,Login窗体负责登录账户。
可输入服务器IP与端口,点击按键即可连接服务器。
根据窗体设计选择相应的控件,包含按键以及提示文字标签等。
点击连接服务器按键后,打开客户端通讯窗口,并将端口号传入该窗口。
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Data.sqlClient;using System.Drawing;using System.Linq;using System.Net;using System.Text;using System.Threading.Tasks;using System.Windows.Forms;namespace Client{ public partial class ConnectForm : Form { static string name; //用户名 static string connStr = @"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=D:\Code\TCP\Server\UsersDataBase.mdf;Integrated Security=True"; //连接数据库标识 public ConnectForm(string tempStr) { name = tempStr; InitializeComponent(); } private void Connect_Load(object sender, EventArgs e) { Control.CheckForIllegalCrossThreadCalls = false; //取消对跨线程访问的检查 } private void ConnectForm_Closing(object sender, FormClosingEventArgs e) { /// 更新IsOnline为下线状态 string sqlUpdate = string.Format("update [User] set IsOnline='{0}' where Name='{1}'", 0, name); //SQL语句,更新IsOnline为下线状态 using (SqlConnection conn = new SqlConnection(connStr)) //创建数据库连接类 { using (SqlCommand cmdUpdate = new SqlCommand(sqlUpdate, conn)) //创建数据库命令类 { conn.Open(); //打开数据库连接 cmdUpdate.ExecuteNonQuery(); //执行SQL语句 ///执行非查询命令时使用ExecuteNonQuery,会返回影响的行数 conn.Close(); //关闭数据库连接 this.Hide(); //隐藏当前窗体 } } } private void ConnectForm_FormClosed(object sender, FormClosedEventArgs e) { System.Environment.Exit(0); } private void btnConnect_Click(object sender, EventArgs e) { try { IPAddress ip = IPAddress.Parse(IPTextBox.Text); //IPAddress包含了一个IP地址,IPEndPoin包含了一对IP地址和端口 IPEndPoint port = new IPEndPoint(ip, Convert.ToInt32(portTextBox.Text)); //创建监听对象 ClientForm clientForm = new ClientForm(port, name); //创建并初始化服务器窗体实例 //参数1:监听的端口号,参数2:登录的用户名 this.Hide(); //隐藏当前窗体 clientForm.ShowDialog(); //显示客户端窗体 } catch(Exception ex) //异常捕获 { MessageBox.Show(ex.Message); //显示异常信息 } } }}
Client窗体负责消息交互,Connect窗体负责连接服务器,Login窗体负责登录账户。
可控制连接的通断,显示客户端的状态与连接的服务器IP与端口号、客户端端口号,显示收发的消息,点击按键可发送消息。
根据窗体设计选择相应的控件,包含按键、显示状态、端口号以及消息等。
public ClientForm(IPEndPoint tempPort,string tempStr) { port = tempPort; //端口号 name = tempStr; //用户名 InitializeComponent(); }
private void btnConnect_Click(object sender, EventArgs e) { try { statusLabel.Text = "已连接"; btnConnect.Enabled = false; btnDisconnect.Enabled = true; socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //创建TCP通信协议Socket if(connFlag == 0) { socket.Connect(port); connFlag++; clientPortLabel.Text = "客户端端口:" + (socket.LocalEndPoint as IPEndPoint).Port.ToString(); thReceive = new Thread(Receive); //设置thReceive线程的启动函数为接受函数Receive() thReceive.IsBackground = true; //设置thReceive线程为后台线程 thReceive.Start(socket); //启动thReceive线程并传socket给Receive() } } catch { statusLabel.Text = "未连接"; btnConnect.Enabled = true; btnDisconnect.Enabled = false; socket.Close(); socket = null; MessageBox.Show("服务器未上线!无法连接"); } }
void Receive(object obj) { Socket socket = obj as Socket; while (true) { try { //约定缓存长度解决粘包问题 byte[] buffer = new byte[1024 * 1024 * 5]; int r = socket.Receive(buffer); if (r == 0) //没有发送消息 { break; } else { string str = Encoding.UTF8.GetString(buffer, 0, r); //缓存解码为字符串 ShowMsg(socket.RemoteEndPoint.ToString() + ":" + str); //显示接受到的消息 } } catch //异常捕获 { this.btnDisconnect.Click += new System.EventHandler(this.btnDisconnect_Click); //触发断开按键事件 break; } } }
private void btnSend_Click(object sender, EventArgs e) { if (socket == null) { MessageBox.Show("没有连接服务器!"); sendMsgTextBox.Text = ""; //清除消息 return; } else { Send(socket); } } void Send(object obj) { try { Socket socket = obj as Socket; byte[] buffer = System.Text.Encoding.UTF8.GetBytes(sendMsgTextBox.Text); //将泛型转换为数组 socket.Send(buffer); //发送缓存至服务器Server ShowMsg(socket.LocalEndPoint.ToString() + ":" + sendMsgTextBox.Text); //显示发送的消息 sendMsgTextBox.Text = ""; //清除消息 } catch //异常捕获 { MessageBox.Show("服务器未上线!"); } }
按照step1-step4完成程序设计,源码如下:
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Data.SqlClient;using System.Drawing;using System.Linq;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;using System.Threading.Tasks;using System.Windows.Forms;namespace Client{ public partial class ClientForm : Form { static string name; static IPEndPoint port; Socket socket; int connFlag = 0; Thread thReceive; static string connStr = @"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=D:\Code\TCP\Server\UsersDataBase.mdf;Integrated Security=True"; //连接数据库标识 public ClientForm(IPEndPoint tempPort,string tempStr) { port = tempPort; //端口号 name = tempStr; //用户名 InitializeComponent(); } private void ClientForm_Load(object sender, EventArgs e) { Control.CheckForIllegalCrossThreadCalls = false; //取消对跨线程访问的检查 serverIPLabel.Text = "服务器IP:" + port.Address.ToString(); serverPortLabel.Text = "服务器端口:" + port.Port.ToString(); btnConnect.Enabled = true; btnDisconnect.Enabled = false; } private void ClientForm_Closing(object sender, FormClosingEventArgs e) { /// 更新IsOnline为下线状态 string sqlUpdate = string.Format("update [User] set IsOnline='{0}' where Name='{1}'", 0, name); //SQL语句,更新IsOnline为下线状态 using (SqlConnection conn = new SqlConnection(connStr)) //创建数据库连接类 { using (SqlCommand cmdUpdate = new SqlCommand(sqlUpdate, conn)) //创建数据库命令类 { conn.Open(); //打开连接 //执行非查询命令时使用ExecuteNonQuery,会返回影响的行数 cmdUpdate.ExecuteNonQuery(); //执行SQL语句 conn.Close(); //关闭数据库连接 this.Hide(); //隐藏当前窗体 } } } private void ClientForm_Closed(object sender, FormClosedEventArgs e) { System.Environment.Exit(0); } private void btnConnect_Click(object sender, EventArgs e) { try { statusLabel.Text = "已连接"; btnConnect.Enabled = false; btnDisconnect.Enabled = true; socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //创建TCP通信协议Socket if(connFlag == 0) { socket.Connect(port); connFlag++; clientPortLabel.Text = "客户端端口:" + (socket.LocalEndPoint as IPEndPoint).Port.ToString(); thReceive = new Thread(Receive); //设置thReceive线程的启动函数为接受函数Receive() thReceive.IsBackground = true; //设置thReceive线程为后台线程 thReceive.Start(socket); //启动thReceive线程并传socket给Receive() } } catch { statusLabel.Text = "未连接"; btnConnect.Enabled = true; btnDisconnect.Enabled = false; socket.Close(); socket = null; MessageBox.Show("服务器未上线!无法连接"); } } private void btnDisconnect_Click(object sender, EventArgs e) { statusLabel.Text = "未连接"; btnConnect.Enabled = true; btnDisconnect.Enabled = false; if (socket != null) { socket.Shutdown(SocketShutdown.Both); socket.Close(); socket.Dispose(); //socket = null; connFlag = 0; } } void Receive(object obj) { Socket socket = obj as Socket; while (true) { try { //约定缓存长度解决粘包问题 byte[] buffer = new byte[1024 * 1024 * 5]; int r = socket.Receive(buffer); if (r == 0) //没有发送消息 { break; } else { string str = Encoding.UTF8.GetString(buffer, 0, r); //缓存解码为字符串 ShowMsg(socket.RemoteEndPoint.ToString() + ":" + str); //显示接受到的消息 } } catch //异常捕获 { this.btnDisconnect.Click += new System.EventHandler(this.btnDisconnect_Click); //触发断开按键事件 break; } } } private void btnSend_Click(object sender, EventArgs e) { if (socket == null) { MessageBox.Show("没有连接服务器!"); sendMsgTextBox.Text = ""; //清除消息 return; } else { Send(socket); } } void Send(object obj) { try { Socket socket = obj as Socket; byte[] buffer = System.Text.Encoding.UTF8.GetBytes(sendMsgTextBox.Text); //将泛型转换为数组 socket.Send(buffer); //发送缓存至服务器Server ShowMsg(socket.LocalEndPoint.ToString() + ":" + sendMsgTextBox.Text); //显示发送的消息 sendMsgTextBox.Text = ""; //清除消息 } catch //异常捕获 { MessageBox.Show("服务器未上线!"); } } void ShowMsg(string str) { try { showMsgTextBox.AppendText(str + "\r\n"); } catch (Exception ex) //异常捕获 { MessageBox.Show(ex.Message); //显示异常信息 } } }}
来源地址:https://blog.csdn.net/qq_44732054/article/details/126577654
--结束END--
本文标题: .NET编程——利用C#实现TCP协议的异步通信Socket套接字(WinForm)
本文链接: https://www.lsjlt.com/news/394097.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
下载Word文档到电脑,方便收藏和打印~
2024-05-16
2024-05-16
2024-05-16
2024-05-15
2024-05-15
2024-05-15
2024-05-15
2024-05-15
2024-05-15
2024-05-15
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0