做一个传输图片的的scoket,遇到了各种各样的问题(附录中给出)
后来用一个比较稳定的框架后得到解决
网络框架服务端:
这里记录该网络框架的用法:
整个传输图片的流程:
打开客户端时 客户端中用新线程打开netinputwork方法监听服务端的信号。
在客户端发送图片时,用startsend方法创建文件流打开文件流,方法内调用了ProcSendMsg方法把图片大小和要在服务端要存的路径,图片保存的名字这三个信息发送给服务端,并用10信号说明是一张图片的首次发送。
向服务端发射10信号,服务端接到10信号后创建相应的文件夹及文件流,并返回11信号
客户端监听到11信号后
先判断当前图片位置加上4096位移是不是已经超过图片大小,如果是的话就说明这是一张图片的最后一次发送,把信号设为30,并发送最后的字节.服务端接到30信号后就知道这已经是一张图片的末尾了,处理完后对客户端发送21信号。客户端收到21信号后,把线程的堵塞打开,则开始传一下张图片
如果不是最后一张图片的末尾 则继续发送 用20信号,服务端接受到20信号只做写入文件流的操作
服务端中的接收类:
FileServer.cs代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SANetworkEngine;
using System.IO;
using System.Runtime.InteropServices;
namespace FileTransServer
{
public class FileServer
{
private ServerHost m_host;
public void Start()
{
m_host = new ServerHost(true, CallbackThreadType.IOThread,
new FileService(), 2048, 15000, 180000, 2000);
m_host.AddListener(new System.Net.IPEndPoint(System.Net.IPAddress.Any,8500), 100, 3);
try
{
m_host.Start();
Console.WriteLine(DateTime.Now.ToString() + ">服务启动成功\r\n");
}
catch (Exception ex)
{
Console.WriteLine(DateTime.Now.ToString() + ">服务启动失败:" + ex.Message + "\r\n");
}
}
}
// 服务器处理逻辑
public class FileService : BaseService
{
// private string save = AppDomain.CurrentDomain.BaseDirectory;
private string save = @"D:\TEST";
class FileClientInfo
{
public string FileName;
public int FileSize;
public int FileOffset;
public string FileDir;
public FileStream FS;
}
private Dictionary<int, FileClientInfo> fileList = new Dictionary<int, FileClientInfo>();
public override void OnConnected(ConnectionEventArgs e)
{
Console.WriteLine("有客户上线:"+e.Connection.HashId);
fileList.Add(e.Connection.HashId, new FileClientInfo());
base.OnConnected(e);
}
public override void OnDisconnected(ConnectionEventArgs e)
{
base.OnDisconnected(e);
}
/// <summary>
/// 服务器的逻辑处理
/// 第1阶段设置的服务器 只包含主要的功能
/// 1:验证登陆 2:接受发送的预约成功的消息并记录
/// 下来,准备的得到信息就是钱
/// 2:阶段添加更多的可能的控制功能
/// </summary>
/// <param name="e"></param>
int count = 0;
public override void OnReceived(MessageEventArgs e)
{
// 0xff, 0xaa, 0xff
if (e.Buffer.Length == 3 && e.Buffer[0] == 0xff && e.Buffer[1] == 0xaa && e.Buffer[2] == 0xff) // 心跳包不压缩
{
base.OnReceived(e);
return; //
}
try
{
byte[] info = CommonLib.Util.SecTool.SecDecode(e.Buffer);
byte[] copyBuf=new byte[info.Length-1];
int cmd = info[0];
List<byte> rtData = new List<byte>();
switch (cmd)
{
case 10: // 获得文件信息
string data= Encoding.UTF8.GetString(info, 1, info.Length - 1);
string[] datasp = data.Split(new char[] { ',' });
fileList[e.Connection.HashId].FileName =datasp[0];
fileList[e.Connection.HashId].FileSize = int.Parse(datasp[1]);
fileList[e.Connection.HashId].FileDir = datasp[2];
fileList[e.Connection.HashId].FileOffset = 0;
string dir=Path.Combine(save,fileList[e.Connection.HashId].FileDir);
if(!Directory.Exists(dir))
{
try{
Directory.CreateDirectory(dir);
}catch{}
}
fileList[e.Connection.HashId].FS = new FileStream(Path.Combine(dir,
fileList[e.Connection.HashId].FileName), FileMode.Create);
rtData.Add((byte)11);
e.Connection.BeginSend(CommonLib.Util.SecTool.SecCode(rtData.ToArray()));
break;
case 20:
try
{
Buffer.BlockCopy(info, 1, copyBuf, 0, copyBuf.Length);
fileList[e.Connection.HashId].FS.Write(copyBuf,
0
, copyBuf.Length);
//fileList[e.Connection.HashId].FileOffset += copyBuf.Length;
}
catch
{
Console.Write("写入错误");
}
rtData.Add((byte)11);
e.Connection.BeginSend(CommonLib.Util.SecTool.SecCode(rtData.ToArray()));
break;
case 30:
try
{
Buffer.BlockCopy(info, 1, copyBuf, 0, copyBuf.Length);
fileList[e.Connection.HashId].FS.Write(copyBuf,
0
, copyBuf.Length);
//fileList[e.Connection.HashId].FileOffset += copyBuf.Length;
}
catch
{
Console.Write("写入错误");
}
rtData.Add((byte)21);
e.Connection.BeginSend(CommonLib.Util.SecTool.SecCode(rtData.ToArray()));
fileList[e.Connection.HashId].FS.Close();
count++;
Console.Write("传完一张\t" + DateTime.Now+"\t"+count+"\n");
break;
}
}
catch
{
Console.Write("接受错误");
}
base.OnReceived(e);
}
public override void OnException(ExceptionEventArgs e)
{
if (e.Exception != null)
{
}
base.OnException(e);
}
public override void OnSend(MessageEventArgs e)
{
base.OnSend(e);
}
}
}
根据服务端的需求的操作 只需要修改 服务端的逻辑处理中的 代码就行了 这里实现的 是 接收客户端发送的信息 把图片写入 相应的 文件夹。
客户端要引入服务端中的两个project-------CommonLib和SANetworkEngine
然后新建一个发送类
客户端中的发送类:
using System.Threading;
using Import_Advtisement;
using System.IO;
using System.Collections.Generic;
using System.Text;
public class FileClient: SANetworkEngine.BaseDisposable
{
static FileClient m_inst;
private string m_connectIP;
public string ConnectIP
{
get { return m_connectIP; }
set { m_connectIP = value; }
}
private int m_connectPort;
public int ConnectPort
{
get { return m_connectPort; }
set { m_connectPort = value; }
}
private SANetworkEngine.SimpleClient m_net;
public SANetworkEngine.SimpleClient Net
{
get { return m_net; }
set { m_net = value; }
}
Thread m_netInputWork;
bool m_netInputFlag;
private Form1 m_uiHandler;
public Form1 UiHandler
{
get { return m_uiHandler; }
set { m_uiHandler = value; }
}
private FileClient()
{
m_netInputWork = new Thread(new ThreadStart(NetInputWork));
m_netInputWork.IsBackground = true;
}
public static FileClient Instance
{
get
{
if (m_inst == null)
{
lock (typeof(FileClient))
{
if (m_inst == null)
{
m_inst = new FileClient();
}
}
}
return m_inst;
}
}
private void NetInputWork()
{
while (m_netInputFlag)
{
// 目前只有一个回应消息处理
byte[] info = m_net.Read();
if (info != null)
{
try
{
// 读一个字节的指令
// 解码
info = CommonLib.Util.SecTool.SecDecode(info);
int cmd = info[0];
int tran_size=4096;
byte cmdd = 20;
switch (cmd)
{
case 11:
if (file_offer + 4096 >= file_size)
{
tran_size = file_size - file_offer;
cmdd = 30;
}
byte[] buffer = new byte[tran_size];
file_stream.Read(buffer, 0,tran_size);
file_offer += tran_size;
ProcSendMsg1(cmdd,buffer);
break;
case 21:
file_stream.Close();
reset_event.Set();
break;
}
}
catch
{
int a = 4;
}
}
else if (m_net.LastError != null)
{
int a = 4;
}
else
{
int a = 4;
}
Thread.Sleep(10);
}
}
private void ProcSendMsg1(byte cmd, byte[] data_pic)
{
List<byte> data = new List<byte>();
data.Add(cmd);
data.AddRange(data_pic);
byte[] msg = data.ToArray();
msg = CommonLib.Util.SecTool.SecCode(msg);
m_net.Write(msg); // 发送网络信息
}
/// <summary>
/// 启动网络获取器
/// </summary>
public void StartNetGet()
{
m_netInputFlag = true;
m_netInputWork.Start();
}
public void ResetClient()
{
Free(true);
m_netInputWork = new Thread(new ThreadStart(NetInputWork));
m_netInputWork.IsBackground = true;
}
protected override void Free(bool canAccessFinalizable)
{
m_net.Disconnect();
m_net.Dispose(); // 释放网络资源
m_netInputFlag = false;
//try
//{
// m_netInputWork.Abort();
//}
//catch { }
base.Free(canAccessFinalizable);
}
int file_offer;
int file_size;
FileStream file_stream;
public void StartSend(string strFileName, string strDictory, string strSaveFileName)
{
//创建一个文件对象
file_offer = 0;
FileInfo EzoneFile = new FileInfo(strFileName);
//打开文件流
file_stream = EzoneFile.OpenRead();
file_size = (int)file_stream.Length;
ProcSendMsg(10, strSaveFileName+","+file_size+","+strDictory);
reset_event.WaitOne();
}
private AutoResetEvent reset_event=new AutoResetEvent(false); //false为激活状态
// 就采用一个字节作为指令有0-255 种
private void ProcSendMsg(byte cmd, string info)
{
List<byte> data = new List<byte>();
data.Add(cmd);
data.AddRange(Encoding.UTF8.GetBytes(info));
byte[] msg = data.ToArray();
msg = CommonLib.Util.SecTool.SecCode(msg);
m_net.Write(msg); // 发送网络信息
}
//public void SendCodeBitmap(string codemark, byte[] imgData)
//{
// List<byte> data = new List<byte>();
// data.Add((byte)80);
// byte[] mark = Encoding.UTF8.GetBytes(codemark);
// short marklen = (short)mark.Length;
// data.AddRange(BitConverter.GetBytes(marklen));
// data.AddRange(mark);
// data.AddRange(imgData);
// byte[] msg = data.ToArray();
// msg = CommonLib.Util.SecTool.SecCode(msg);
// m_net.Write(msg); // 发送网络信息
//}
登陆信息发送
//public void SendLoginInfo(string id, string logname,string proxysn)
//{
// ProcSendMsg((byte)10, id + "," + logname + "," + proxysn);
//}
//public void SendAcctInfo(string pid, string pwd, string proxysn)
//{
// ProcSendMsg((byte)20, pid + "," + pwd + "," + proxysn);
//}
//public void SendSuccessInfo(string pid, string proxysn)
//{
// ProcSendMsg((byte)30, pid + "," + proxysn);
//}
}
客户端要修改操作时,主要是修改NetInputWork() ,Startsend() 还有ProSendMsg() ProSendMsg1()这几个函数。
在需要发送的地方调用发送类中的方法:
FileClient.Instance.StartSend(directoryName + "\\" + dt.Rows[i]["月"].ToString() + "." + dt.Rows[i]["日"].ToString() + "\\" + dt.Rows[i]["媒体"].ToString() + "\\" + sValue, Convert.ToDateTime(dt.Rows[i]["发布日期"].ToString()).ToString("yyyy-MM"), fileName + sValue.Substring(sValue.LastIndexOf(".")));
在主窗体Form的登录函数添加连接:
Form1_Load(object sender, EventArgs e) 函数的相应位置添加:
using System.Net;
using System.Net.Sockets;
FileClient.Instance.UiHandler = this;
FileClient.Instance.Net = new SANetworkEngine.SimpleClient(true, SANetworkEngine.CallbackThreadType.IOThread,
new IPEndPoint(IPAddress.Parse("192.168.0.79"), 8500), 2048, 3, 3000, true, 15000);
// new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8500), 2048, 3, 3000, true, 15000);
FileClient.Instance.Net.OnHeart += new EventHandler<SANetworkEngine.HeartEvent>(Net_OnHeart);
FileClient.Instance.Net.OnException += new EventHandler<SANetworkEngine.ClientExceptionEvent>(Net_OnException);
FileClient.Instance.Net.OnDisconnect += new EventHandler(Net_OnDisconnect);
FileClient.Instance.Net.ConnectServer();
if (FileClient.Instance.Net.Connected)
{
FileClient.Instance.StartNetGet();
}
else
{
MessageBox.Show("无法连接");
}
并且增加以下函数:
void Net_OnDisconnect(object sender, EventArgs e)
{
FileClient.Instance.ResetClient();
FileClient.Instance.Net = new SANetworkEngine.SimpleClient(true, SANetworkEngine.CallbackThreadType.IOThread,
new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8500), 2048, 3, 3000, true, 15000);
FileClient.Instance.Net.OnHeart += new EventHandler<SANetworkEngine.HeartEvent>(Net_OnHeart);
FileClient.Instance.Net.OnException += new EventHandler<SANetworkEngine.ClientExceptionEvent>(Net_OnException);
FileClient.Instance.Net.OnDisconnect += new EventHandler(Net_OnDisconnect);
FileClient.Instance.Net.ConnectServer();
if (FileClient.Instance.Net.Connected)
{
FileClient.Instance.StartNetGet();
}
else
{
}
}
void Net_OnException(object sender, SANetworkEngine.ClientExceptionEvent e)
{
throw new NotImplementedException();
}
private byte[] heartdata = new byte[] { 0xff, 0xaa, 0xff };
void Net_OnHeart(object sender, SANetworkEngine.HeartEvent e)
{
e.HeartData = heartdata;
}
附录:
现象:服务端打开但是过了一会就关闭了一个连接,或者远程主机关闭了一个连接
原因1:
接收的
监听的队列不够
解决方法:
1.增加监听的队列
2.把监听放到新线程中
原因2:
服务端在接受数据时,客户端突然关闭或者 因为网络原因连接关闭
解决方法:
接收数据应该放在try catch中,使其不影响到下面的传输 ,不应作为一个异常处理
解决方法3:
用阻塞单线程,一个传完了再运行另一个(框架中的方法)
现象:接受完后有些文件或者图片不全
原因:
Arithmetic operation resulted in an overflo scoket发生算术溢出或者算术异常
soket缓冲区只有8k字节,当每次发送的字节太多而网速又不够时 就会发生上面这两种错误
解决方法有两种
1.将发送后的线程等待时间 thread.sleep设得长一点.
2.比较安全的方法:不要一次把整个文件发送,而是分段发送,而且每段的字节不超过8字节 也就是 8x1024 byte (一般设置为2x1024byte或者 4x1024byte)