using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
namespace Masuit.Tools.Net
{
///
/// Socket客户端操作类
///
public static class SocketClient
{
#region 私有字段
///
/// 设置数据缓冲区大小 默认1024
///
private static readonly int m_maxpacket = 1024 * 4;
#endregion
#region 服务器侦听
///
/// 服务器侦听方法 返回null则说明没有链接上
///
/// 返回一个套接字(Socket)
public static Socket ListenerSocket(this TcpListener listener)
{
try
{
return listener.AcceptSocket();
}
catch (Exception)
{
return null;
}
}
///
/// 服务器侦听方法 返回null则说明没有链接上
///
/// TCP监听对象
/// 返回一个网络流
public static NetworkStream ListenerStream(this TcpListener listener)
{
try
{
return listener.AcceptTcpClient().GetStream();
}
catch (Exception)
{
return null;
}
}
#endregion
#region 客户端连接
///
/// 从客户端连接获取socket对象
///
/// TCP客户端
/// 客户端节点
/// 客户端socket
public static Socket ConnectSocket(this TcpClient tcpclient, IPEndPoint ipendpoint)
{
try
{
tcpclient.Connect(ipendpoint);
return tcpclient.Client;
}
catch (Exception)
{
return null;
}
}
///
/// 从客户端连接获取socket对象
///
/// TCP客户端
/// IP地址
/// 端口号
/// 客户端socket
public static Socket ConnectSocket(this TcpClient tcpclient, IPAddress ipadd, int port)
{
try
{
tcpclient.Connect(ipadd, port);
return tcpclient.Client;
}
catch (Exception)
{
return null;
}
}
///
/// 从客户端获取网络流对象
///
/// TCP客户端
/// 客户端节点
/// 客户端的网络流
public static NetworkStream ConnectStream(this TcpClient tcpclient, IPEndPoint ipendpoint)
{
try
{
tcpclient.Connect(ipendpoint);
return tcpclient.GetStream();
}
catch (Exception)
{
return null;
}
}
///
/// 从客户端获取网络流对象
///
/// TCP客户端
/// IP地址
/// 端口号
/// 客户端网络流对象
public static NetworkStream ConnectStream(this TcpClient tcpclient, IPAddress ipadd, int port)
{
try
{
tcpclient.Connect(ipadd, port);
return tcpclient.GetStream();
}
catch (Exception)
{
return null;
}
}
#endregion
#region Socket接收数据
///
/// 接受固定长度字符串
///
/// socket对象
/// 字符串长度
/// 字节数据
public static byte[] ReceiveFixData(this Socket socket, int size)
{
int offset = 0;
int dataleft = size;
byte[] msg = new byte[size];
while (dataleft > 0)
{
var recv = socket.Receive(msg, offset, dataleft, 0);
if (recv == 0)
{
break;
}
offset += recv;
dataleft -= recv;
}
return msg;
}
///
/// 接收变长字符串
/// 为了处理粘包问题 ,每次发送数据时 包头(数据字节长度) + 正文
/// 这个发送小数据
/// 设置包头的字节为8,不能超过8位数的字节数组
///
/// 客户端socket
/// byte[]数组
public static byte[] ReceiveVarData(this Socket socket)
{
//每次接受数据时,接收固定长度的包头,包头长度为8
byte[] lengthbyte = ReceiveFixData(socket, 8);
//length得到字符长度 然后加工处理得到数字
int length = GetPacketLength(lengthbyte);
//得到正文
return ReceiveFixData(socket, length);
}
///
/// 接收T类对象,反序列化
///
/// 接收T类对象,T类必须是一个可序列化类
/// 客户端socket
/// 强类型对象
public static T ReceiveVarData(this Socket socket)
{
//先接收包头长度 固定8个字节
byte[] lengthbyte = ReceiveFixData(socket, 8);
//得到字节长度
int length = GetPacketLength(lengthbyte);
byte[] bytecoll = new byte[m_maxpacket];
IFormatter format = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
int offset = 0; //接收字节个数
int lastdata = length; //还剩下多少没有接收,初始大小等于实际大小
int receivedata = m_maxpacket; //每次接收大小
//循环接收
int mark = 0; //标记几次接收到的数据为0长度
while (true)
{
//剩下的字节数是否小于缓存大小
if (lastdata < m_maxpacket)
{
receivedata = lastdata; //就只接收剩下的字节数
}
int count = socket.Receive(bytecoll, 0, receivedata, 0);
if (count > 0)
{
stream.Write(bytecoll, 0, count);
offset += count;
lastdata -= count;
mark = 0;
}
else
{
mark++;
if (mark == 10)
{
break;
}
}
if (offset == length)
{
break;
}
}
stream.Seek(0, SeekOrigin.Begin); //必须要这个 或者stream.Position = 0;
T t = (T)format.Deserialize(stream);
return t;
}
///
/// 在预先得到文件的文件名和大小
/// 调用此方法接收文件
///
/// socket服务端
/// 路径必须存在
/// 文件名
/// 预先知道的文件大小
/// 处理过程
public static bool ReceiveFile(this Socket socket, string path, string filename, long size, Action progress)
{
if (!Directory.Exists(path))
{
return false;
}
//主要是防止有重名文件
string savepath = GetPath(path, filename); //得到文件路径
//缓冲区
byte[] file = new byte[m_maxpacket];
int receivedata = m_maxpacket; //每次要接收的长度
long offset = 0; //循环接收的总长度
long lastdata = size; //剩余多少还没接收
int mark = 0;
using var fs = new FileStream(savepath, FileMode.OpenOrCreate, FileAccess.Write);
if (size <= 0)
{
return false;
}
bool ret = false;
while (true)
{
if (lastdata < receivedata)
{
receivedata = Convert.ToInt32(lastdata);
}
var count = socket.Receive(file, 0, receivedata, SocketFlags.None); //每次接收的实际长度
if (count > 0)
{
fs.Write(file, 0, count);
offset += count;
lastdata -= count;
mark = 0;
}
else
{
mark++; //连续5次接收为0字节 则跳出循环
if (mark == 10)
{
break;
}
}
//接收进度
progress(Convert.ToInt32(Convert.ToDouble(offset) / Convert.ToDouble(size) * 100));
//接收完毕
if (offset == size)
{
ret = true;
break;
}
}
return ret;
}
///
/// 从socket服务端接收文件
///
/// socket服务端
/// 文件保存路径(必须存在)
/// 文件名
/// 预先知道的文件大小
/// 处理结果
public static bool ReceiveFile(this Socket socket, string path, string filename, long size)
{
return ReceiveFile(socket, path, filename, size, null);
}
///
/// 预先不知道文件名和文件大小 用此方法接收
/// 此方法对于的发送方法是SendFile()
///
/// socket服务端
/// 要保存的目录
public static void ReceiveFile(this Socket socket, string path)
{
//得到包头信息字节数组 (文件名 + 文件大小 的字符串长度)
//取前8位
byte[] info_bt = ReceiveFixData(socket, 8);
//得到包头信息字符长度
int info_length = GetPacketLength(info_bt);
//提取包头信息,(文件名 + 文件大小 的字符串长度)
byte[] info = ReceiveFixData(socket, info_length);
//得到文件信息字符串 (文件名 + 文件大小)
string info_str = Encoding.UTF8.GetString(info);
string[] strs = info_str.Split('|');
string filename = strs[0]; //文件名
long length = Convert.ToInt64(strs[1]); //文件大小
//开始接收文件
ReceiveFile(socket, path, filename, length);
}
private static int GetPacketLength(byte[] length)
{
string str = Encoding.UTF8.GetString(length);
str = str.TrimEnd('*');
return int.TryParse(str, out var _length) ? _length : 0;
}
private static int i;
private static string markPath = string.Empty;
///
/// 得到文件路径(防止有文件名重复)
/// 如:aaa.txt已经在directory目录下存在,则会得到文件aaa(1).txt
///
/// 目录名
/// 文件名
/// 文件路径
public static string GetPath(string directory, string file)
{
if (markPath == string.Empty)
{
markPath = Path.Combine(directory, file);
}
string path = Path.Combine(directory, file);
if (File.Exists(path))
{
i++;
string filename = Path.GetFileNameWithoutExtension(markPath) + "(" + i + ")";
string extension = Path.GetExtension(markPath);
return GetPath(directory, filename + extension);
}
i = 0;
markPath = string.Empty;
return path;
}
#endregion
#region Socket发送数据
///
/// 发送固定长度消息
/// 发送字节数不能大于int型最大值
///
/// 源socket
/// 消息的字节数组
/// 返回发送字节个数
public static int SendFixData(this Socket socket, byte[] msg)
{
int size = msg.Length; //要发送字节长度
int offset = 0; //已经发送长度
int dataleft = size; //剩下字符
int senddata = m_maxpacket; //每次发送大小
while (true)
{
//如过剩下的字节数 小于 每次发送字节数
if (dataleft < senddata)
{
senddata = dataleft;
}
int count = socket.Send(msg, offset, senddata, SocketFlags.None);
offset += count;
dataleft -= count;
if (offset == size)
{
break;
}
}
return offset;
}
///
/// 发送变长信息 格式 包头(包头占8位) + 正文
///
/// 发送方socket对象
/// 发送文本
/// 发送的数据内容长度
public static int SendVarData(this Socket socket, string contact)
{
//得到字符长度
int size = Encoding.UTF8.GetBytes(contact).Length;
//包头字符
string length = GetSendPacketLengthStr(size);
//包头 + 正文
byte[] sendbyte = Encoding.UTF8.GetBytes(length + contact);
//发送
return SendFixData(socket, sendbyte);
}
///
/// 发送变成信息
///
/// 发送方socket对象
/// 消息的 字节数组
/// 消息长度
public static int SendVarData(this Socket socket, byte[] bytes)
{
//得到包头字节
int size = bytes.Length;
string length = GetSendPacketLengthStr(size);
byte[] lengthbyte = Encoding.UTF8.GetBytes(length);
//发送包头
SendFixData(socket, lengthbyte); //因为不知道正文是什么编码所以没有合并
//发送正文
return SendFixData(socket, bytes);
}
///
/// 发送T类型对象,序列化
///
/// T类型
/// 发送方的socket对象
/// T类型对象,必须是可序列化的
/// 消息长度
public static int SendSerializeObject(this Socket socket, T obj)
{
byte[] bytes = SerializeObject(obj);
return SendVarData(socket, bytes);
}
///
/// 发送文件
///
/// socket对象
/// 文件路径
/// 是否发送文件(头)信息,如果当前知道文件[大小,名称]则为false
/// 处理过程
/// 处理结果
public static bool SendFile(this Socket socket, string path, bool issend, Action progress)
{
if (!File.Exists(path))
{
return false;
}
var fileinfo = new FileInfo(path);
string filename = fileinfo.Name;
long length = fileinfo.Length;
//发送文件信息
if (issend)
{
SendVarData(socket, filename + "|" + length);
}
//发送文件
long offset = 0;
byte[] b = new byte[m_maxpacket];
int mark = 0;
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
int senddata = b.Length;
//循环读取发送
while (true)
{
int count = fs.Read(b, 0, senddata);
if (count > 0)
{
socket.Send(b, 0, count, SocketFlags.None);
offset += count;
mark = 0;
}
else
{
mark++;
if (mark == 10)
{
break;
}
}
progress(Convert.ToInt32(Convert.ToDouble(offset) / Convert.ToDouble(length) * 100));
if (offset == length)
{
return true;
}
Thread.Sleep(50); //设置等待时间,以免粘包
}
return false;
}
///
/// 发送文件,不需要进度信息
///
/// socket对象
/// 文件路径
/// 是否发生(头)信息
/// 处理结果
public static bool SendFile(this Socket socket, string path, bool issend)
{
return SendFile(socket, path, issend, null);
}
///
/// 发送文件,不需要进度信息和(头)信息
///
/// socket对象
/// 文件路径
/// 处理结果
public static bool SendFile(this Socket socket, string path)
{
return SendFile(socket, path, false, null);
}
private static byte[] SerializeObject(object obj)
{
IFormatter format = new BinaryFormatter();
using var stream = new MemoryStream();
format.Serialize(stream, obj);
return stream.ToArray();
}
private static string GetSendPacketLengthStr(int size)
{
string length = size + "********"; //得到size的长度
return length.Substring(0, 8); //截取前前8位
}
#endregion
}
}