本文将介绍利用基于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()等,该方法可靠性好,但由于协议复杂,通信效率不高。
创建服务器
1.程序框架
Server窗体负责消息交互,Sign窗体负责创建服务器,UsersDataBase负责存储注册的用户信息。
2.窗体设计
可输入端口,显示端口有效范围,点击按键即可创建服务器。
3.控件选择
根据窗口设计选择相应的控件,包含按键以及提示文字标签等。
其中fix为控制程序焦点的TextBox控件,保证启动程序时聚焦到该控件上,具体设置如下:
step1 将控制程序焦点的TextBox控件缩小到不可见,将TabIndex修改为0
step2 将输入端口的TextBox控件中的TabIndex修改为大于0
4.程序设计
点击创建服务器按键后,打开服务器通讯窗口,并将端口号传入该窗口。
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("端口号无效!");
}
}
/* 聚焦到portTextBox控件时运行 */
private void portTextBox_Enter(object sender, EventArgs e)
{
if (portTextBox.Text != "")
{
portTextBox.Text = ""; //清空默认文字
}
portTextBox.ForeColor = Color.Black; //设置文本颜色
}
}
}
注:
1.启动时输入端口的TextBox控件中显示默认文字,发生聚焦事件时清空默认文字并设置颜色;
2.点击创建客户端按键后,只能隐藏该窗体,不能关闭该窗体,显示的窗体是子窗口,该窗口为主窗口,若关闭主窗口,所有窗口都会被关闭;
服务器通信
1.程序框架
Server窗体负责消息交互,Sign窗体负责创建服务器,UsersDataBase负责存储注册的用户信息。
2.窗体设计
可控制监听的启停,显示服务器的状态与监听的端口号,显示收发的消息,点击按键可发送消息。
3.控件选择
根据窗体设计选择相应的控件,包含按键、显示状态、端口号以及消息等。
4.程序设计
step1 跨窗口传入参数,传入上级窗口以及端口号
public ServerForm(IPEndPoint tempPort,Form tempForm)
{
/* 跨窗口传入参数 */
port = tempPort; //端口号
signForm = tempForm; //上级窗体
InitializeComponent();
}
step2 更新窗体信息,创建监听套接字(socketWatch),与本机地址及端口进行绑定,创建监听线程(thListen),启动函数是Listen()传入参数为监听套接字(socketWatch)
/* 当时点击开始监听按键时运行 */
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("正在监听!");
}
}
step3 Listen()函数中利用Accept()函数阻塞线程直至有客户端连接,创建与该客户端对应的套接字(socket),创建接受线程(thReceive),启动函数是Receive()传入参数为此次连接的套接字(socket)
/* 监听客户端连接 */
void Listen(object obj)
{
Socket socket = obj as Socket;
while (true)
{
try
{
/* 阻塞线程直到有客户端连接,传输层完成了TCP三次握手 */
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()函数阻塞,这段代码并不是一个死循环>
step4 此时服务器(Server)与客户端(Client)已建立了连接,并用接受线程(thReceive)来接受客户端(Clinet)所发送的二进制数据流,将Stream流数据解码成字符串,并在窗体中显示消息
/* 服务器Server接收客户端Client发来的消息 */
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;
}
}
}
<ShowMsg为自定义函数用于显示消息在窗体中>
step5 此时服务器(Server)与客户端(Client)已建立了连接,利用socket类中的Send()函数发送消息给客户端(Client),将字符串编码成Stream流数据,并在窗体中显示消息
/* 当时点击发送按键时运行 */
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++;
}
}
/* 服务器Server给客户端Client发送消息 */
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("客户端未连接!");
}
}
5.源码
按照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
{
/* 变量定义
* port:服务器的网络终结点,即IP:端口号
* socketWatch:监听客户端的套接字
* watchFlag:监听哨兵
* socketList:存放套接字的列表
* thListen:监听客户端线程
* thReceive:接受消息线程
*/
IPEndPoint port;
Form signForm = new Form();
Socket socketWatch;
int watchFlag = 0;
List<Socket> socketList = new List<Socket>();
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;
/* 关闭Socket和Thread */
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
{
/* 阻塞线程直到有客户端连接,传输层完成了TCP三次握手 */
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;
}
}
}
/* 服务器Server接收客户端Client发来的消息 */
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++;
}
}
/* 服务器Server给客户端Client发送消息 */
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); //显示异常信息
}
}
}
}
创建客户端
1.程序框架
Client窗体负责消息交互,Connect窗体负责连接服务器,Login窗体负责登录账户。
2.窗体设计
可输入服务器IP与端口,点击按键即可连接服务器。
3.控件选择
根据窗体设计选择相应的控件,包含按键以及提示文字标签等。
4.程序设计
点击连接服务器按键后,打开客户端通讯窗口,并将端口号传入该窗口。
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
{
/* 变量定义
* name:登录的用户名
* connStr:连接数据库标识
*/
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可以在使用完该对象后,自动释放资源 */
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
{
/* 申请socket连接指定服务器的地址和端口 */
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); //显示异常信息
}
}
}
}
客户端通信
1.程序框架
Client窗体负责消息交互,Connect窗体负责连接服务器,Login窗体负责登录账户。
2.窗体设计
可控制连接的通断,显示客户端的状态与连接的服务器IP与端口号、客户端端口号,显示收发的消息,点击按键可发送消息。
3.控件选择
根据窗体设计选择相应的控件,包含按键、显示状态、端口号以及消息等。
4.程序设计
step1 跨窗口传入参数,传入上级窗口以及端口号
public ClientForm(IPEndPoint tempPort,string tempStr)
{
/* 跨窗口传入参数 */
port = tempPort; //端口号
name = tempStr; //用户名
InitializeComponent();
}
step2 更新窗体信息,创建此次连接套接字(socket),根据服务器的IP与端口号连接,创建接受线程(thReceive),启动函数是Receive()传入参数为此次连接的套接字(socket)
/* 当时点击连接按键时运行 */
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("服务器未上线!无法连接");
}
}
step3 此时服务器(Server)与客户端(Client)已建立了连接,并用接受线程(thReceive)来接受服务器(Server)所发送的二进制数据流,将Stream流数据解码成字符串,并在窗体中显示消息
/* 客户端Client接收服务器Server发来的消息 */
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;
}
}
}
<ShowMsg为自定义函数用于显示消息在窗体中>
step4 此时服务器(Server)与客户端(Client)已建立了连接,利用socket类中的Send()函数发送消息给服务器(Server),将字符串编码成Stream流数据,并在窗体中显示消息
/* 当时点击发送按键时运行 */
private void btnSend_Click(object sender, EventArgs e)
{
if (socket == null)
{
MessageBox.Show("没有连接服务器!");
sendMsgTextBox.Text = ""; //清除消息
return;
}
else
{
Send(socket);
}
}
/* 客户端Client给服务器Server发送窗口消息 */
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("服务器未上线!");
}
}
5.源码
按照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
{
/* 变量定义
* name:登录的用户名
* port:服务器的网络终结点,即IP:端口号
* socket:此次连接的套接字
* connFlag:连接哨兵
* thReceive:接受消息线程
* connStr:连接数据库标识
*/
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可以在使用完该对象后,自动释放资源 */
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;
/* 关闭Socket和Thread */
if (socket != null)
{
socket.Shutdown(SocketShutdown.Both);
socket.Close();
socket.Dispose();
//socket = null;
connFlag = 0;
}
}
/* 客户端Client接收服务器Server发来的消息 */
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);
}
}
/* 客户端Client给服务器Server发送窗口消息 */
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); //显示异常信息
}
}
}
}