Socket是网络编程必须要了解的一块,C#对Socket的封装还是蛮好的,用起来非常方便。下面是一个非常简单的实例代码。
客户端 主要有下面几个关键点:
1. 用Socket对象连接server, 连接成功后用Thread启动receive的轮询,如果接收到新的数据到达就解码然后更新。
2. 如果有发送,直接调用Send()函数发送编码好的字符。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
TextBox.CheckForIllegalCrossThreadCalls = false;
}
Socket sokClient = null;//负责与服务端通信的套接字
Thread threadClient = null;//负责 监听 服务端发送来的消息的线程
bool isRec = true;//是否循环接收服务端数据
private byte[] GetKeepAliveData()
{
uint dummy = 0;
byte[] inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);
BitConverter.GetBytes((uint)3000).CopyTo(inOptionValues, Marshal.SizeOf(dummy));//3s keep-alive间隔
BitConverter.GetBytes((uint)500).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);//0.5s 尝试间隔
return inOptionValues;
}
private void btnConnect_Click(object sender, EventArgs e)
{
//实例化 套接字
sokClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//创建 ip对象
IPAddress address = IPAddress.Parse(txtIP.Text.Trim());
//创建网络节点对象 包含 ip和port
IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));
//连接 服务端监听套接字
sokClient.Connect(endpoint);
//创建负责接收 服务端发送来数据的 线程
threadClient = new Thread(ReceiveMsg);
threadClient.IsBackground = true;
//如果在win7下要通过 某个线程 来调用 文件选择框的代码,就需要设置如下
threadClient.SetApartmentState(ApartmentState.STA);
threadClient.Start();
}
/// <summary>
/// 接收服务端发送来的消息数据
/// </summary>
void ReceiveMsg()
{
while (isRec)
{
byte[] msgArr = new byte[1024 * 1024 * 1];//接收到的消息的缓冲区
int length = 0;
//接收服务端发送来的消息数据
length = sokClient.Receive(msgArr);//Receive会阻断线程
string strMsg = System.Text.Encoding.UTF8.GetString(msgArr, 1, length - 1);
txtShow.AppendText(strMsg + "\r\n");
}
}
//发送消息
private void btnSend_Click(object sender, EventArgs e)
{
byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(txtInput.Text.Trim());
sokClient.Send(arrMsg);
}
}
服务器端的代码和注意点
1. 需要一个socket对象用来做Listening (监听),使用Thread启动轮询
2. 每次有客户端连接请求之后,都要启动一个Socket实例去handle和这个客户端的通信,
3. 这个Socket对象相当于一个完整的客户端Socket,代码也类似于客户端的socket代码
public partial class Form1 : Form
{
Socket sokWatch = null;//负责监听 客户端段 连接请求的 套接字
Thread threadWatch = null;//负责 调用套接字, 执行 监听请求的线程
public Form()
{
InitializeComponent();
TextBox.CheckForIllegalCrossThreadCalls = false;
}
private void start_Click(object sender, EventArgs e)
{
//实例化 套接字 (ip4寻址协议,流式传输,TCP协议)
sokWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//创建 ip对象
IPAddress address = IPAddress.Parse(txtIP.Text.Trim());
//创建网络节点对象 包含 ip和port
IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));
//将 监听套接字 绑定到 对应的IP和端口
sokWatch.Bind(endpoint);
//设置 监听队列 长度为10(同时能够处理 10个连接请求)
sokWatch.Listen(10);
threadWatch = new Thread(StartWatch);
threadWatch.IsBackground = true;
threadWatch.Start();
txtShow.AppendText("启动服务器成功......\r\n");
}
Dictionary<string, ConnectionClient> dictConn = new Dictionary<string, ConnectionClient>();
bool isWatch = true;
//监听函数
void StartWatch()
{
while (isWatch)
{
//threadWatch.SetApartmentState(ApartmentState.STA);
//监听 客户端 连接请求,但是,Accept会阻断当前线程
Socket sokMsg = sokWatch.Accept();//监听到请求,立即创建负责与该客户端套接字通信的套接字
ConnectionClient connection = new ConnectionClient(sokMsg, ShowMsg, RemoveClientConnection);
//将负责与当前连接请求客户端 通信的套接字所在的连接通信类 对象 装入集合
dictConn.Add(sokMsg.RemoteEndPoint.ToString(), connection);
//将 通信套接字 加入 集合,并以通信套接字的远程IpPort作为键
//dictSocket.Add(sokMsg.RemoteEndPoint.ToString(), sokMsg);
//将 通信套接字的 客户端IP端口保存在下拉框里
cboClient.Items.Add(sokMsg.RemoteEndPoint.ToString());
ShowMsg("接收连接成功......");
//启动一个新线程,负责监听该客户端发来的数据
//Thread threadConnection = new Thread(ReciveMsg);
//threadConnection.IsBackground = true;
//threadConnection.Start(sokMsg);
}
}
bool isRec = true;//与客户端通信的套接字 是否 监听消息
//发送消息 到指定的客户端
private void btnSend_Click(object sender, EventArgs e)
{
//byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(txtInput.Text.Trim());
//从下拉框中 获得 要哪个客户端发送数据
string connectionSokKey = cboClient.Text;
if (!string.IsNullOrEmpty(connectionSokKey))
{
//从字典集合中根据键获得 负责与该客户端通信的套接字,并调用send方法发送数据过去
dictConn[connectionSokKey].Send(txtInput.Text.Trim());
//sokMsg.Send(arrMsg);
}
else
{
MessageBox.Show("请选择要发送的客户端~~");
}
}
//向文本框显示消息 -void ShowMsg(string msgStr)
public void ShowMsg(string msgStr)
{
txtShow.AppendText(msgStr + "\r\n");
}
private void btnSendMsgAll_Click(object sender, EventArgs e)
{
foreach (ConnectionClient conn in dictConn.Values)
{
conn.Send(txtInput.Text.Trim());
}
}
}
负责和每个客户端通信的Socket对象
public delegate void DGShowMsg(string strMsg);
public class ConnectionClient
{
Socket sokMsg;
DGShowMsg dgShowMsg;//负责 向主窗体文本框显示消息的方法委托
DGShowMsg dgRemoveConnection;// 负责 从主窗体 中移除 当前连接
Thread threadMsg;
public ConnectionClient(Socket sokMsg, DGShowMsg dgShowMsg, DGShowMsg dgRemoveConnection)
{
this.sokMsg = sokMsg;
this.dgShowMsg = dgShowMsg;
this.dgRemoveConnection = dgRemoveConnection;
this.threadMsg = new Thread(RecMsg);
this.threadMsg.IsBackground = true;
this.threadMsg.Start();
}
bool isRec = true;
// 02负责监听客户端发送来的消息
void RecMsg()
{
while (isRec)
{
try
{
byte[] arrMsg = new byte[1024 * 1024 * 2];
//接收 对应 客户端发来的消息
int length = sokMsg.Receive(arrMsg);
//将接收到的消息数组里真实消息转成字符串
string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg, 0, length);
//通过委托 显示消息到 窗体的文本框
dgShowMsg(strMsg);
}
catch (Exception ex)
{
isRec = false;
//从主窗体中 移除 下拉框中对应的客户端选择项,同时 移除 集合中对应的 ConnectionClient对象
dgRemoveConnection(sokMsg.RemoteEndPoint.ToString());
}
}
}
// 03向客户端发送消息
public void Send(string strMsg)
{
byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
byte[] arrMsgFinal = new byte[arrMsg.Length + 1];
arrMsgFinal[0] = 0;//设置 数据标识位等于0,代表 发送的是 文字
arrMsg.CopyTo(arrMsgFinal, 1);
sokMsg.Send(arrMsgFinal);
}
// 04向客户端发送文件数据 +void SendFile(string strPath)
public void SendFile(string strPath)
{
//通过文件流 读取文件内容
using (FileStream fs = new FileStream(strPath, FileMode.OpenOrCreate))
{
byte[] arrFile = new byte[1024 * 1024 * 2];
//读取文件内容到字节数组,并 获得 实际文件大小
int length = fs.Read(arrFile, 0, arrFile.Length);
//定义一个 新数组,长度为文件实际长度 +1
byte[] arrFileFina = new byte[length + 1];
arrFileFina[0] = 1;//设置 数据标识位等于1,代表 发送的是文件
//将 文件数据数组 复制到 新数组中,下标从1开始
//arrFile.CopyTo(arrFileFina, 1);
Buffer.BlockCopy(arrFile, 0, arrFileFina, 1, length);
//发送文件数据
sokMsg.Send(arrFileFina);//, 0, length + 1, SocketFlags.None);
}
}
public void CloseConnection()
{
isRec = false;
}
}