您的位置:首页 > 理论基础 > 计算机网络

小兵传奇三:scoket网络编程传输图片

2013-07-15 10:06 405 查看
做一个传输图片的的scoket,遇到了各种各样的问题(附录中给出)

后来用一个比较稳定的框架后得到解决

网络框架服务端:http://download.csdn.net/detail/q383965374/5744159

这里记录该网络框架的用法:

整个传输图片的流程:

打开客户端时 客户端中用新线程打开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)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: