最近接触到socket,这个和通信有关 ,比如客户端和服务端 俩个之间交流信息就会用到。
那么socket 是如何实现通信的呢?
对于服务端是这样的
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;
using System.Threading;
namespace _01聊天室服务器
{
public partial class FormServer : Form
{
public FormServer()
{
InitializeComponent();
TextBox.CheckForIllegalCrossThreadCalls = false; //检查非法跨线程调用,关闭
}
//服务端 监听套接字
Socket sokWatch = null;
//服务端 监听线程
Thread thrWatch = null;
//字典集合:保存 通信套接字
Dictionary<string, MsgConnection> dictConn = new Dictionary<string, MsgConnection>();
#region 1.0 开始监听
/// <summary>
/// 开始监听
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnMonitor_Click(object sender, EventArgs e)
{
try
{
//1.0创建监听套接字,使用ip4协议,流式传输,tcp链接
sokWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2.0绑定端口
//2.1获取网络节点对象
IPAddress address = IPAddress.Parse(txbIp.Text);
IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txbPort.Text));
//2.2 绑定端口(其实内部 就向系统的端口表中注册了一个端口,并指定了当前程序句柄)
sokWatch.Bind(endPoint);
//2.3 设置监听队列,指限制同时处理的连接请求数,即同时处理的客户端连接请求。
sokWatch.Listen(10);
2.4开始监听:此方法会阻断当前线程,直到有其他程序连接过来,才执行完毕; 需重新开启线程
//sokWatch.Accept();
//2.4开始监听,调用监听线程 执行 监听套接字的监听方法。
thrWatch = new Thread(WatchConncting);
thrWatch.IsBackground = true;
thrWatch.Start();
ShowMsg("服务器启动!");
}
catch (SocketException soex)
{
ShowMsg("异常:" + soex);
}
catch (Exception ex)
{
ShowMsg("异常:" + ex);
}
}
#endregion
//开始监听,线程调用
bool isWatch = true;
#region 2.0 服务器监听方法 + void WatchConncting()
void WatchConncting()
{
try
{
//循环监听客户端的连接请求。
while (isWatch)
{
//2.4开始监听,返回了一个通信套接字
Socket sockMsg = sokWatch.Accept();
//2.5 创建通信管理类
MsgConnection conn = new MsgConnection(sockMsg, ShowMsg, RemoveClient);
//将当前连接成功的【与客户端通信的套接字】的标识保存起来,并显示到列表中
//将远程客户端的 ip 和 端口 字符串 存入列表
listOnline.Items.Add(sockMsg.RemoteEndPoint.ToString());
//将服务器端的通信套接字存入字典集合。
dictConn.Add(sockMsg.RemoteEndPoint.ToString(), conn);
ShowMsg("有客户端连接了!");
}
}
catch (Exception ex)
{
ShowMsg("异常" + ex);
}
}
}
还有一个负责通信的类 MsgConnection
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Threading;
namespace _01聊天室服务器
{
/// <summary>
/// 通信管理类,负责处理与某个客户端通信的过程
/// </summary>
public class MsgConnection
{
//与某个客户端通信套接字
Socket sokMsg = null;
//通信线程
Thread thrMsg = null;
//创建一个委托对象, 在窗体显示消息的方法
DGShowMsg dgShow = null;
//创建一个关闭连接的方法
DGCloseConn dgCloseConn = null;
#region 1.0 构造函数
public MsgConnection(Socket sokMsg, DGShowMsg dgShow, DGCloseConn dgCloseConn)
{
this.sokMsg = sokMsg;
this.dgShow = dgShow;
this.dgCloseConn = dgCloseConn;
//创建通信线程,负责调用通信套接字,来接收客户端消息。
thrMsg = new Thread(ReceiveMsg);
thrMsg.IsBackground = true;
thrMsg.Start(this.sokMsg);
}
#endregion
bool isReceive = true;
#region 2.0 接收客户端发送的消息
void ReceiveMsg(object obj)
{
Socket sockMsg = obj as Socket;
//3 通信套接字 监听客户端的消息,传输的是byte格式。
//3.1 开辟了一个 1M 的空间,创建的消息缓存区,接收客户端的消息。
byte[] arrMsg = new byte[1024 * 1024 * 1];
try
{
while (isReceive)
{
//注意:Receive也会阻断当前的线程。
//3.2 接收客户端的消息,并存入消息缓存区。
//并 返回 真实接收到的客户端数据的字节长度。
int realLength = sockMsg.Receive(arrMsg);
//3.3 将接收的消息转成字符串
string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg, 0, realLength);
//3.4 将消息显示到文本框
dgShow(strMsg);
}
}
catch (Exception ex)
{
//调用窗体类的关闭移除方法
dgCloseConn(sokMsg.RemoteEndPoint.ToString());
//显示消息
dgShow("客户端断开连接!");
}
}
#endregion
#region 3.0 向客户端发送文本消息 + void Send(string msg)
/// <summary>
/// 3.0 向客户端发送文本消息
/// </summary>
/// <param name="msg"></param>
public void Send(string msg)
{
byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(msg);
//通过指定的套接字将字符串发送到指定的客户端
try
{
sokMsg.Send(MakeNewByte("str",arrMsg));
}
catch (Exception ex)
{
dgShow("异常" + ex.Message);
}
}
#endregion
#region 4.0 向客户端发送文件 + void SendFile(string strPath)
/// <summary>
/// 4.0 向客户端发送文件
/// </summary>
/// <param name="strFilePath"></param>
public void SendFile(string strFilePath)
{
//4.1 读取要发送的文件
byte[] arrFile = System.IO.File.ReadAllBytes(strFilePath);
//4.2 向客户端发送文件
sokMsg.Send(MakeNewByte("file", arrFile));
}
#endregion
#region 4.1 向客户端发送抖屏命令 + void SendShake()
/// <summary>
/// 4.1 向客户端发送抖屏命令
/// </summary>
public void SendShake()
{
sokMsg.Send(new byte[1] { 2 });
}
#endregion
#region 5.0 返回带标识的新数组 + byte[] MakeNew(string type, byte[] oldArr)
/// <summary>
/// 返回带标识的新数组
/// </summary>
/// <param name="type"></param>
/// <param name="oldArr"></param>
/// <returns></returns>
public byte[] MakeNewByte(string type, byte[] oldArr)
{
//5.1 创建一个新数组(是原数组长度 +1)
byte[] newArrFile = new byte[oldArr.Length + 1];
//5.2 将原数组数据复制到新数组中(从新数组下标为1的位置开始)
oldArr.CopyTo(newArrFile, 1);
//5.3 根据内容类型为新数组第一个元素设置标识符号
switch (type.ToLower())
{
case "str":
newArrFile[0] = 0; //只能存0-255之间的数值
break;
case "file":
newArrFile[0] = 1;
break;
default:
newArrFile[0] = 2;
break;
}
return newArrFile;
}
#endregion
#region 6.0 关闭通信
/// <summary>
/// 关闭通信
/// </summary>
public void Close()
{
isReceive = false;
sokMsg.Close();
sokMsg = null;
}
#endregion
}
}
客户端的是这样的
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Net.Sockets;
using System.Net;
namespace _02聊天室客户端
{
public partial class FormClient : Form
{
public FormClient()
{
InitializeComponent();
TextBox.CheckForIllegalCrossThreadCalls = false;
}
//客户端通信套接字
Socket sokMsg = null;
//客户端通信线程
Thread thrMsg = null;
#region 1.0 发送连接服务端请求
/// <summary>
/// 连接服务器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnConnectServer_Click(object sender, EventArgs e)
{
try
{
//1.0创建连接套接字,使用ip4协议,流式传输,tcp链接
sokMsg = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2.0获取要链接的服务端节点
//2.1获取网络节点对象
IPAddress address = IPAddress.Parse(txtIp.Text);
IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text));
//3 向服务端发送链接请求。
sokMsg.Connect(endPoint);
ShowMsg("连接服务器成功!");
//4 开启通信线程
thrMsg = new Thread(ReceiveMsg);
thrMsg.IsBackground = true;
//win7, win8 需要设置客户端通信线程同步设置,才能在接收文件时打开文件选择框
thrMsg.SetApartmentState(ApartmentState.STA);
thrMsg.Start();
}
catch (Exception ex)
{
ShowMsg("连接服务器失败!" + ex.Message);
}
}
#endregion
bool isReceive = true;
#region 2.0 接收服务端消息
void ReceiveMsg()
{
//准备一个消息缓冲区域
byte[] arrMsg = new byte[1024 * 1024 * 1];
try
{
while (isReceive)
{
//接收 服务器发来的数据,因为包含了一个标示符,所以内容的真实长度应该-1
int realLength = sokMsg.Receive(arrMsg)-1;
switch (arrMsg[0])
{
//文本
case 0:
GetMsg(arrMsg,realLength);
break;
//文件
case 1:
GetFile(arrMsg,realLength);
break;
default:
ShakeWindow();
break;
}
}
}
catch (Exception ex)
{
sokMsg.Close();
sokMsg = null;
ShowMsg("服务器断开连接!");
}
}
#endregion
}
服务端需要socket 中的Bind(),Listen(); 客户端需要socket 中的Connect() ;详情见代码;