关于SOCKET的使用,原理等,网上早已是一大堆,为什么我还要写这么一篇呢。其实和很多人一样,一是做个备忘,另外一方面也是让需要的人能看到。为什么要这样说呢?
网上很多例子,但是都只是发送一个字符串,接收一个字符串,然后对于按长度接收数据,数据转换,递归接收数据等就没有然后了,只有理论上的说明,对于小白,怎样让代码能实用,用起来才是关键。所有我就觉得我这篇还是可以放上来的,至于写的好与不好请各位指正。多讨论总比自己想的主义多。
这里也先说一下,我以下提供的代码本人已投入使用,但范围有限,如果使用请根据项目另行修改。一切仅做参考!
代码如下
服务器端
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace WaterClassLibrary
{
/// <summary>
/// Socket 服务器端类
/// 客户端尽可能连接一次只能做一次发送
/// </summary>
public class Cls_SocketServer
{
private IPAddress ServerIP;
private int Port;
private Socket SocketServer;
public ArrayList Clients;
public delegate void ServiceWork(Socket forClient, String strMessage);
public event ServiceWork DoServiceWork;
#region 该定义用于处理接收数据分包
/// <summary>
/// 当前客户端状态
/// </summary>
class Cls_ClientStata
{
/// <summary>
/// 当前连接客户端
/// </summary>
public Socket client;
/// <summary>
/// 当前接收到的数据
/// </summary>
public string data;
/// <summary>
/// 用于存储接收的数据,最后转换为 string 赋值 data
/// </summary>
public List<byte> byteSource = new List<byte>();
/// <summary>
/// 每次接收的数据长度,实际可能没有这么长,需要根据实际情况处理
/// </summary>
public byte[] buffer=new byte[1024];
/// <summary>
/// 需要接收的数据长度
/// </summary>
public int ReceiveDataLength;
/// <summary>
///
/// </summary>
/// <param name="s"></param>
/// <param name="d"></param>
public Cls_ClientStata(Socket s)
{
client = s;
}
}
#endregion
/// <summary>
/// 实例服务器端Socket
/// </summary>
///<param name="P">端口号</param>
///<param name="ip">这里请使用Any,否则可能服务无法从外网访问</param>
public Cls_SocketServer(IPAddress ip, int P)
{
ServerIP = ip;
Port = P;
Clients = new ArrayList();
}
/// <summary>
/// 开始服务,可以创建客户连接
/// </summary>
/// <returns></returns>
public bool StartServer()
{
try
{
//获取IP地址
IPEndPoint ipep = new IPEndPoint(ServerIP, Port);
//实例化Socket
SocketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//将Socket与指定IP绑定
SocketServer.Bind(ipep);
//Socket 开始监听,设置监听队列的长度
SocketServer.Listen(10000);
SocketServer.BeginAccept(new AsyncCallback(ClientAccepted), SocketServer);
return true;
}
catch(Exception ex)
{
return false;
}
}
/// <summary>
/// 接受客户连接
/// </summary>
/// <param name="ar"></param>
private void ClientAccepted(IAsyncResult ar)
{
try
{
var socket = ar.AsyncState as Socket;//这里应该是SocketServer
//这就是客户端的Socket实例,我们后续可以将其保存起来
var client = socket.EndAccept(ar);//连接到的客户端
Clients.Add(client);//加入当前连接客户端列表
Cls_ClientStata ccs = new Cls_ClientStata(client);
//接收客户端的消息(这个和在客户端实现的方式是一样的)
client.BeginReceive(ccs.buffer, 0, ccs.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), ccs);
//准备接受下一个客户端请求
socket.BeginAccept(new AsyncCallback(ClientAccepted), socket);
}
catch
{
}
}
/// <summary>
/// 接收收到的信息
/// </summary>
/// <param name="ar"></param>
private void ReceiveMessage(IAsyncResult ar)
{
try
{
//使用自定义类来存储数据,以便于区分是哪个客户端发来的信息
Cls_ClientStata ccs = (Cls_ClientStata)ar.AsyncState;
Socket client = ccs.client;
int length = client.EndReceive(ar);
if (length == 0)
{//如果接收不到数据
client.Close();
return;
}
/// 虽然设置了 css.buffer 的长度为1024,但实际接收数据时,不一定能
/// 接收到1024的数据,如果此时直接认为是接收了1024,那么收到的数据
/// 就会多出空数据来,这样最终的收到的数据就会与发送的不一致,由其
/// 是在发送文件时,收到的文件将是不可用,不可以打开的。
byte[] tmpbyte = new byte[length];
Array.Copy(ccs.buffer, 0, tmpbyte, 0, length);
ccs.byteSource.AddRange(tmpbyte);//接收数据
#region 使用长度来标记是否已接收结束
if (ccs.ReceiveDataLength <= 0
&& ccs.byteSource.Count>=4)
{//还未计算接收长度,同时,接收到的长度已经大于或等于长度位长度
byte[] byteLengthg = new byte[4];
Array.Copy(ccs.byteSource.ToArray(), 0, byteLengthg, 0, 4);
ccs.ReceiveDataLength = Cls_WaterSocketMessage.byteArrayToInt(byteLengthg);//使转换方式与JAVA一致
//ccs.ReceiveDataLength = BitConverter.ToInt32(ccs.byteSource.ToArray(),0);
}
if (ccs.byteSource.Count >= ccs.ReceiveDataLength + 4)
{//数据已全部接收到
if (DoServiceWork != null)
{//返回方法不为空
ccs.data = Encoding.UTF8.GetString(ccs.byteSource.ToArray(), 4, ccs.ReceiveDataLength);
//实际使用中发现会出现数据传输错误,故加入哈希验证,减少错误数据读取率
int indexf = ccs.data.LastIndexOf('\f');
string resultData = ccs.data.Substring(0, indexf);
string hashData = ccs.data.Substring(indexf + 1, ccs.data.Length - indexf - 1);
if (Cls_WaterSocketMessage.GetHashValue(resultData) == hashData)
{//哈希值字符串比较
DoServiceWork(ccs.client, resultData);
}
else
{
DoServiceWork(ccs.client, "FALSE");
}
}
ccs.client.Close();//结束连接
return;
}
//接收下一个消息(因为这是一个递归的调用,所以这样就可以一直接收消息了)
client.BeginReceive(ccs.buffer, 0, ccs.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), ccs);
#endregion
}
catch(Exception ex)
{
}
}
/// <summary>
/// 结束服务
/// </summary>
public void Stop()
{
try
{
SocketServer.Shutdown(SocketShutdown.Both);
SocketServer.Close();
foreach (Socket s in Clients)
{
s.Shutdown(SocketShutdown.Both);
s.Close();
}
}
catch (Exception ex)
{
//throw new Exception(ex.Message);
}
}
/// <summary>
/// 发送网络信息
/// </summary>
/// <param name="s">要发送的客户端</param>
/// <param name="ms">要发送的信息</param>
public void SendMessage(Socket s, string ms)
{
if (!s.Poll(500, SelectMode.SelectRead))
{
byte[] sendData = Encoding.UTF8.GetBytes(ms); //报文内容转 byte
byte[] hashcode = Encoding.UTF8.GetBytes("\f" + Cls_WaterSocketMessage.GetHashValue(ms));//报文内容的哈希值
byte[] sendHead = Cls_WaterSocketMessage.intToByteArray(sendData.Length + hashcode.Length);//报文头,使转换方式与JAVA一致
//byte[] sendHead = BitConverter.GetBytes(sendData.Length + hashcode.Length);//报文头
//发送报文长度,int 占4个byte
s.Send(sendHead); //发送报文头
s.Send(sendData); //发送报文信息
s.Send(hashcode); //发送信息哈希验证码
}
}
}
}
客户端
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace WaterClassLibrary
{
/// <summary>
/// Socket 客户端类
/// </summary>
public class Cls_SocketClient
{
/// <summary>
/// 连接服务器的Socket
/// </summary>
public Socket client;
/// <summary>
/// 接收到的数据
/// </summary>
//private string ReciveData;
private StringBuilder strBuilder = new StringBuilder();
/// <summary>
/// SOCKET 状态
/// </summary>
ManualResetEvent timeoutObject = new ManualResetEvent(false);//通知一个或多个正在等待的线程已发生事件
/// <summary>
/// 接收服务器发来的数据后执行的方法
/// </summary>
/// <param name="ServerBackBack">服务器返回的数据</param>
public delegate void DoServerBack(string ServerBackBack);
DoServerBack doServerBack;
/// <summary>
/// 获取或设置客户端等待获取服务器回应的最长时间(毫秒)
/// 默认30000(30秒)
/// </summary>
public int millisecondsTimeout = 30000;
/// <summary>
/// 接收数据缓存
/// </summary>
private byte[] receiveBuffer = new byte[1024];
/// <summary>
/// 用于存储接收的数据
/// </summary>
List<byte> byteSource = new List<byte>();
/// <summary>
/// 接收数据长度
/// </summary>
private int ReceiveDataLength;
#region 连接并接收数据
/// 连接时应当处理超时问题,否则可能在连接时过长时间等待
/// 最终造成连接失败,或是无反应假像。
/// <summary>
/// 连接到服务器
/// </summary>
/// <param name="conIP">连接IP</param>
/// <param name="conPort">连接端品</param>
/// <param name="ServerBackDeel"></param>
/// <param name="csmt"></param>
public void Connect(string conIP, int conPort, DoServerBack ServerBackDeel, Cls_WaterSocketMessage csmt)
{
//创建套接字
client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
doServerBack = ServerBackDeel;
//开始连接到服务器
try
{
#region Old code,without timeout
//连接指定服务器
client.Connect(conIP, conPort);
if (client.Connected)
{//连接成功
//向服务器发送服务请求
string s = csmt.GetSendMessageString();
SendMessage(s);
//开始接收服务器返回结果
client.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, 0, new AsyncCallback(ReceiveCallBack), null);
}
else
{//连接失败
client.Close();//释放资源
}
#endregion
}
catch (Exception ex)
{
client.Close();
//如果显示提示,那么在后台静默等待服务器开启时
//就会出现显示即程序暂停的情况
//System.Windows.Forms.MessageBox.Show(ex.Message);
}
}
/// <summary>
/// 连接到服务器
/// </summary>
/// <param name="conIP">连接IP</param>
/// <param name="conPort">连接端口</param>
/// <param name="ServerBackDeel"></param>
/// <param name="strData"></param>
public void Connect(string conIP, int conPort, DoServerBack ServerBackDeel, string strData)
{
//创建套接字
client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
doServerBack = ServerBackDeel;
//开始连接到服务器
try
{
//连接指定服务器
client.Connect(conIP, conPort);
if (client.Connected)
{//连接成功
//向服务器发送服务请求
SendMessage(strData);
//开始接收服务器返回结果
client.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, 0, new AsyncCallback(ReceiveCallBack), null);
}
else
{//连接失败
client.Close();
}
}
catch (Exception ex)
{
client.Close();
//如果显示提示,那么在后台静默等待服务器开启时
//就会出现显示即程序暂停的情况
}
}
/// <summary>
/// 接收数据调用方法
/// </summary>
/// <param name="ar"></param>
private void ReceiveCallBack(IAsyncResult ar)
{
try
{
if (client.Connected)
{
int REnd = client.EndReceive(ar);
byte[] tmpbyte = new byte[REnd];
Array.Copy(receiveBuffer, 0, tmpbyte, 0, REnd);
byteSource.AddRange(tmpbyte);//接收数据
#region 使用长度来标记是否已接收结束
if (ReceiveDataLength <= 0
&& byteSource.Count >= 4)
{//还未计算接收长度,同时,接收到的长度已经大于或等于长度位长度
byte[] byteLengthg = new byte[4];
Array.Copy(byteSource.ToArray(), 0, byteLengthg, 0, 4);
ReceiveDataLength = Cls_WaterSocketMessage.byteArrayToInt(byteLengthg);//使转换方式与JAVA一致
}
if (byteSource.Count >= ReceiveDataLength + 4)
{//数据已全部接收到
if (doServerBack != null)
{//返回方法不为空
string data = Encoding.UTF8.GetString(byteSource.ToArray(), 4, ReceiveDataLength);
//实际使用中发现会出现数据传输错误,故加入哈希验证,减少错误数据读取率
int indexf = data.LastIndexOf('\f');
string resultData = data.Substring(0, indexf);
string hashData = data.Substring(indexf + 1, data.Length - indexf - 1);
if (Cls_WaterSocketMessage.GetHashValue(resultData) == hashData)
{//哈希值字符串比较
doServerBack(resultData);
}
else
{
doServerBack("FALSE");
}
}
timeoutObject.Set();//这里设置结果超时控制的阻塞
client.Close();//结束连接
return;
}
#endregion
if (client.Connected)
{
client.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, 0, new AsyncCallback(ReceiveCallBack), null);
}
}
}
catch (Exception ex)
{
}
}
#endregion
/// <summary>
/// 断开连接
/// </summary>
public void DeConnect()
{
try
{
if (client != null)
{
client.Shutdown(SocketShutdown.Both);
client.Close();
}
}
catch (Exception ex)
{
string a = ex.Message;
}
}
/// <summary>
/// 修改为添加哈希验证
/// </summary>
/// <param name="sendmessage"></param>
private void SendMessage(string sendmessage)
{
byte[] sendData = Encoding.UTF8.GetBytes(sendmessage); //报文内容转 byte
byte[] hashcode = Encoding.UTF8.GetBytes("\f" + Cls_WaterSocketMessage.GetHashValue(sendmessage));//报文内容的哈希值
byte[] sendHead = Cls_WaterSocketMessage.intToByteArray(sendData.Length + hashcode.Length);//报文头,使转换方式与JAVA一致
if (client != null)
{
//发送报文长度,int 占4个byte
client.Send(sendHead); //发送报文头
client.Send(sendData); //发送报文信息
client.Send(hashcode); //发送信息哈希验证码
}
}
/// <summary>
/// 如果连接失败或是未返回就中断,则返回false
/// 成功连接返回true
/// 这里设置了最长等待判断时长为10000毫秒,即10秒,否则可能出现长时间等待
/// </summary>
/// <returns></returns>
public bool CheckConnect()
{
try
{
if (this.client == null)
{
return false;
}
else if (this.client.Connected == false)
{
DeConnect();
return false;
}
if (!timeoutObject.WaitOne(millisecondsTimeout,false))
{
DeConnect();
return false;
}
return true;
}
catch
{
return false;
}
}
}
}
其中的byte转int,网上一大堆,这里就不多说了