项目大概方案是一个服务器端,多个客户端,服务端监听客户端的请求,回应客户端请求向客户端发送文件,服务端还要控制客户端连接最大数。客户端接收服务器端返回的文件。
一、服务端实现代码
//连接的用户
private List<ClientUser> userList = new List<ClientUser>();
//监听对象
private TcpListener myListener;
//服务器端IP地址
private string localaddr = string.Empty;
//端口
private int localport;
/// <summary>
/// 是否正在监听
/// </summary>
public bool IsListen = false;
/// <summary>
/// 开始监听
/// </summary>
public void StartListener()
{
try
{
logmsg.Title = "启动监听";
logmsg.Content = string.Format("启动[{0}:{1}]监听,等待客户端连接请求。", localaddr, localport);
log.Info(logmsg);
AddItem(logmsg.Content);
//IP地址
IPAddress localAddress = IPAddress.Parse(localaddr);
myListener = new TcpListener(localAddress, localport);
myListener.Start(this.maxClientUsers);
IsListen = true;
//线程池
ThreadPool.SetMaxThreads(maxClientUsers, maxClientUsers);
//创建一个线程监听客户端连接请求
ThreadStart ts = new ThreadStart(ListenClientConnect);
Thread myThread = new Thread(ts);
myThread.Start();
}
catch (Exception ex)
{
logmsg.Title = "启动监听出错";
logmsg.Content = string.Format("启动[{0}:{1}]监听出错,", localaddr, localport) + string.Format("错误:{0}", ex.Message);
log.Error(logmsg);
AddItem(logmsg.Content);
IsListen = false;
}
}
/// <summary>
/// 关闭监听
/// </summary>
public void CloseListener()
{
try
{
myListener.Stop();
IsListen =false;
AddItem(string.Format("关闭[{0}:{1}]监听。", localaddr, localport));
logmsg.Title = "关闭监听";
logmsg.Content = string.Format("关闭[{0}:{1}]监听。", localaddr, localport);
log.Info(logmsg);
}
catch (Exception ex)
{
IsListen = true;
logmsg.Title = "关闭监听出错";
logmsg.Content = string.Format("关闭[{0}:{1}]监听出错", localaddr, localport) + string.Format("错误:{0}", ex.Message);
log.Error(logmsg);
AddItem(logmsg.Content);
}
}
/// <summary>
/// 接收客户端连接
/// </summary>
private void ListenClientConnect()
{
while (true)
{
TcpClient newClient = null;
try
{
//等待用户进入
newClient = myListener.AcceptTcpClient();
}
catch (Exception ex)
{
//当单击“停止监听”或者退出此窗体时AcceptTcpClient()会产生异常
//因此可以利用此异常退出循环
break;
}
if (newClient != null)
{
string text = string.Empty;
string sendString = string.Empty;
//接收消息,超时时间
//newClient.ReceiveTimeout = 1 * 60 * 1000;
ClientUser user = new ClientUser(newClient);
user.userName = newClient.Client.RemoteEndPoint.ToString();
string[] _NewEndPointArr = user.userName.Split(':');
foreach (ClientUser item in userList)
{
string[] _OldEndPointArr = item.userName.Split(':');
if (_OldEndPointArr[0].Trim().Equals(_NewEndPointArr[0].Trim()) && _OldEndPointArr[1].Trim().Equals(_NewEndPointArr[1].Trim()))
{
item.client.Close();
logmsg.Title = logmsg.Content = string.Format("[{0}]关闭上次连接。", item.userName);
log.Info(logmsg);
}
}
userList.Add(user);
Count(userList.Count);
//线程池
ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveData), user);
//通知客户端连接成功
SendTextToOne(user, "0|ok|连接成功", "", "");
}
else
{
//连接失败
AddItem("接收客户端连接失败。");
}
}
}
/// <summary>
/// 接收、处理客户端信息,每个客户1个线程,参数用户区分客户端
/// </summary>
/// <param name="obj"></param>
private void ReceiveData(object obj)
{
ClientUser user = (ClientUser)obj;
TcpClient client = user.client;
int timeOut=30 * 1000;
client.ReceiveTimeout = timeOut;
string userNameStr = user.userName;
//是否正常退出接收线程
bool normalExit = false;
//用于控制是否退出循环
bool exitWhile = false;
string text = string.Empty;
string factorycodeStr = string.Empty;
string factoryName = string.Empty;
FactoryInfo model = new FactoryInfo();
int sleepTime = 0;
string nomalText = "";
while (exitWhile == false)
{
#region 接收客户端信息
string receiveString = string.Empty;
string sendString = string.Empty;//发送信息
try
{
if (user.netStream.DataAvailable)
{
receiveString = user.sr.ReadLine();
if (userList.Count > maxClientUsers && !string.IsNullOrEmpty(receiveString))
{
#region 超过最大连接数
//向客户端返回信息
//格式:指令序号|状态State|消息Message|备用数据项
text = string.Format("服务器已达到最大连接数[{0}]", maxClientUsers);
sendString = "2|error|" + text + ",请稍后重新请求";
SendTextToOne(user, sendString, factorycodeStr, factoryName);
text += string.Format(",拒绝[{0}]连接请求。", user.userName);
AddItem(text);
#endregion
nomalText = "超过最大连接数" + maxClientUsers;
normalExit = true;
break;
}
}
else
{
Thread.Sleep(3000);
sleepTime += 1;
if (sleepTime>0 && sleepTime <= 10)
{
continue;
}
else
{
break;//一直接收不到消息就退出
}
}
}
catch (Exception ex)
{
//该客户底层套接字不存在时会出现异常
//AddItem(string.Format("接收{1}数据失败,原因:{0}", ex.Message, user.userName));
logmsg.Title = "接收客户端信息出错,";
logmsg.Content = "接收" + user.userName + "信息出错,原因:" + ex.ToString();
log.Error(logmsg);
break;//退出循环
}
#endregion
//已接收到消息,重置等待次数
sleepTime = 0;
#region 检查TcpClient 对象是否关闭
//TcpClient 对象将套接字进行了封装,如果TcpClient对象关闭了,
//但是底层套接字未关闭,并不产生异常,但是读取的结果为 null
if (string.IsNullOrEmpty(receiveString))
{
if (normalExit == false)
{
//如果停止了监听,Connected 为 false
if (!client.Connected)
{
AddItem(string.Format("与[{0}]失去联系,已停止接收该客户端信息", client.Client.RemoteEndPoint));
logmsg.Title = "与客户端失去联系";
logmsg.Content = string.Format("已停止接收[{0}]客户端信息。", client.Client.RemoteEndPoint);
log.Warn(logmsg);
}
}
//退出循环
break;
}
else if (!receiveString.Contains("|"))
{
sendString = "请求拒绝,原因:请求信息格式错误";
SendTextToOne(user, sendString, factorycodeStr, factoryName);
logmsg.Title = "拒绝客户端请求";
logmsg.Content = string.Format("拒绝来自[{0}]的请求,原因:请求信息[{1}]格式错误。", userNameStr, receiveString);
//log.Warn(logmsg);
AddItem(logmsg.Content);
break;//退出循环
}
#endregion
#region 对客户端信息进行分割
text = string.Format("接收[{0}]的信息:{1}。", userNameStr, receiveString);
AddItem(text);
logmsg.Title = "接收到客户端信息";
logmsg.Content = text;
log.Info(logmsg);
string[] splitString = receiveString.Split('|');
string command = splitString[0];//请求指令
#endregion
#region 处理接收到的消息
switch (command.ToString())
{
case "1":
//向客户端发送连接成功消息
break;
case "3":
//向客户端发送文件,在获取文件时要避免重复发送某一个文件,需要对代码加锁
break;
}
}
//获取一个文件名,文件大小等参数
private string GetAPacketFullFileName(参数省略)
{
string fullFileNameBackup = string.Empty;
//加锁 防止文件重发
lock (obj)
{
//获取文件部分,省略...
//要保证文件是不重名的,获取一个文件名后,要记录文件名已发送过
}
return fullFileName;
}
二、客户端请求和接收消息实现
client = new TcpClient();
//client.ReceiveTimeout = 1 * 60 * 1000;
try
{
client.Connect(IPAddress.Parse(serverIP), port);
//获取网络流
NetworkStream netStream = client.GetStream();
//hanym 添加网络流判断
if (netStream != null)
{
//添加读取数据缓存大小设置
sr = new StreamReader(netStream, System.Text.Encoding.UTF8, true, 1024*100);
sw = new StreamWriter(netStream, System.Text.Encoding.UTF8);
//向线程池添加接受消息任务
//ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveData), DateTime.Now.ToString());
ReceiveData(DateTime.Now.ToString());
}
else
{
logmsg.Title = "[" + factorycode + "]与服务器连接失败,获取网络流失败";
logmsg.Content = string.Format("与服务器{0}连接失败," , serverIP + ":" + port);
log.Error(logmsg);
return;
}
}
catch (Exception ex)
{
logmsg.Title = "["+factorycode+"]与服务器建立连接出错";
logmsg.Content = string.Format("与服务器{0}连接出错,错误:"+ex.ToString(), serverIP + ":" + port);
log.Error(logmsg);
AddItem(string.Format("与服务器{0}连接失败,错误:"+ex.Message, serverIP + ":" + port)) ;
return;
}
/// <summary>
/// 接收服务器端信息
/// </summary>
/// <param name="FileName"></param>
/// <param name="errmessage"></param>
private void ReceiveData(object obj)
{
FileSize = 0;
//FileName = string.Empty;
string errmessage = string.Empty;
bool exitWhile = false;
while (exitWhile == false)
{
string receiveString = string.Empty;
#region 接收消息
try
{
if (client.Available <= 0)
{
continue;
}
{
receiveString = sr.ReadLine();
}
}
catch (Exception ex)
{
logmsg.Title = "接收服务器端信息失败";
logmsg.Content = "原因:" + ex.Message;
log.Error(logmsg);
normalExit = true;
break;
}
#endregion
#region 验证接收到的消息
if (string.IsNullOrEmpty(receiveString))
{
if (normalExit == false)
{
errmessage = "与服务器端失去联系。";
AddItem("与服务器端失去联系。");
}
normalExit = true;
//结束线程
break;
}
#endregion
string[] splitString = receiveString.Split('|');
string command = splitString[0].ToLower();
int intcmd=-1;
if (int.TryParse(command, out intcmd))
{
logmsg.Title = "接收服务器端信息";
logmsg.Content = "信息:" + receiveString;
log.Info(logmsg);
}
string sendMsg = string.Empty;
#region 处理接收的消息
switch (command)
{
case "0":
#region 接收到服务器连接结果信息, 如果连接成功向服务器请求
if (splitString[1] == "error")
{
if (splitString.Length > 2)
{
errmessage = "向服务器端发送连接请求失败,原因:" + splitString[2].ToString();
}
else
{
errmessage = "向服务器端发送连接请求失败。";
}
AddItem(errmessage);
exitWhile = true;
break;
}
sendMsg = "1|参数1|参数2|参数3";
SendToServer(sendMsg);//向服务器端发送文件请求
#endregion
break;
case "4":
#region 接收服务端发送文件参数消息
if (splitString[1] == "error")
{//如果请求的文件在服务器里没有了,则退出。
errmessage = splitString[2];
break;
}
string filefullname = string.Empty;//全名称(包含路径)
string filename = splitString[3];//文件名
string filesize = splitString[4];//文件大小
#endregion
#region 开始接收文件
try
{
NetworkStream ns = client.GetStream();
byte[] bufferRec = new byte[1024 * 1024 * 2];
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
filefullname = path + @"\" + filename;
FileStream fs = File.Open(filefullname, FileMode.OpenOrCreate, FileAccess.Write);
int Len = 0;
while (Len < int.Parse(filesize))
{
int readLen = ns.Read(bufferRec, 0, bufferRec.Length);
fs.Write(bufferRec, 0, readLen);
Len += readLen;
}
fs.Dispose();
fs.Close();
/*MemoryStream ms = new System.IO.MemoryStream();
byte[] resBytes = new byte[1024 * 1024 * 2];//定义一个2MB缓存区
int resSize;
do
{
resSize = ns.Read(resBytes, 0, resBytes.Length);
if (resSize == 0) break;
ms.Write(resBytes, 0, resSize);
} while (ns.DataAvailable);
if (ms.Length > 0)
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
filefullname = path + @"\" + filename;
FileStream fs = File.Open(filefullname, FileMode.OpenOrCreate, FileAccess.Write);
fs.Write(ms.ToArray(), 0, (int)ms.Length);
fs.Flush();
fs.Close(); //关闭文件流
}
*/
if (File.Exists(filefullname))
{
FileInfo info = new FileInfo(filefullname);
FileSize = info.Length;
}
AddItem("发送大小:" + filesize + "实际接收大小:" + FileSize);
logmsg.Title = "[" + FactoryCode + "]接收文件大小信息";
logmsg.Content = "发送大小:" + filesize + ",收到大小:" + FileSize + ",文件:" + filename;
if (FileSize < long.Parse(filesize))
{
logmsg.Title = "[" + FactoryCode + "]接收文件大小出错";
log.Error(logmsg);
}
else
{
log.Info(logmsg);
}
#region 文件接收完成后向服务器端发送指令,通知接收完成。
SendToServer("5|ok|文件接收成功);
#endregion
}
catch (Exception ex)
{
logmsg.Title = "[" + FactoryCode + "]接收服务器文件出错";
logmsg.Content = "错误:" + ex.ToString();
log.Error(logmsg);
SendToServer("5|error|文件接收失败);
}
#endregion
Thread.Sleep(3000);
exitWhile = true;//文件接收结束,当前线程不再接收消息
break;
}//end switch
#endregion
}//end while
}
/// <summary>
/// 向服务器发送数据
/// </summary>
/// <param name="str">信息内容</param>
private void SendToServer(string str)
{
try
{
sw.WriteLine(str);
sw.Flush();
}
catch (Exception ex)
{
logmsg.Title =向服务器发送信息出错";
logmsg.Content = "信息:" + str + "原因:" + ex.Message;
log.Error(logmsg);
}
}