Socket通信就是在局域网中通过TCP或者UDP协议进行通信。这句话是我自己概括的这其中的TCP与UDP的区别我将作如下简单介绍
TCP通信:即有连接的通信,这里的建立连接是通过三次握手的方式,简单来说就是客户端给服务器说我要建立连接了,服务器回应说可以连接,然后客户端与服务器建立连接。当然断开连接需要经过四次挥手,不过我们在项目中用到的Socket TCP通信在断开的连接的时候通常就是直接关闭程序了,可能没有四次挥手的过程。既然是有连接的通信,互相之间数据传输就是相对来说是可靠的,简单来说就是数据传输无丢失,无失序。不过这也取决于传输的数据协议,所谓的数据协议也就是数据传输双方要求的数据传输格式。可以没有协议,单纯的用字符串,也可以像C语言中定义结构体,在C++,C#中定义类。然后再通过某种转换方式转换成我们需要的数据格式。还有一点需要注意的是因为TCP在传输过程中数据长度默认是不固定的,所以会导致粘包丢包的问题,解决方法也很简单,就是定义协议,或者传输指定长度的内容,在接收的时候也按照指定长度接收,还可以在每段内容之间加上特殊的字段分隔符,在读取的时候遇到分隔符代表一个内容读取完成。最后一点就是TCP的应用场景,因为是有连接的,所以对网络和数据实时性较高,通常在游戏数据传输,视频流获取播放等等场景使用较多。
UDP通信: 即无连接的通信,相对应的就是不可靠的传输,但是有一点就是UDP比TCP的传输效率要高,因为不需要建立连接,对于网络的要求不是很高,很多时候我们发现在微某信的视频聊天时,网络不好,但是视频没有挂断而是一帧一帧的或者一顿一顿的进行视频通话,这就类似于UDP通信的形式,即使网路不佳也会断断续续进行数据传输。UDP传输是固定长度的,所以没有粘包的情况发生。
介绍完两种传输层通信协议,那么正式开始讲解C# Socket TCP通信
第一步:通信环境准备
通信双方如果要实现通信则需要在一个局域网中。这个局域网可以有网络与外界互通,也可以没有网路,单纯的是机器内部的网络,简称内网。这个互通除了要在一个局域网中,重要的一点是关闭机器的防火墙,如果两台机器在确定在局域网中又互相ping不通,可以尝试检查一下防火墙。还有一点是进行Socket通信的两台机器无需配置防火墙的入站出站规则,因为你已经把防火墙关闭了。如果是在同一台机上实现Socket通信那怎么样都无所谓。
第二步:服务端程序编写
2.1 ----建立连接
IPAddress IP = IPAddress.Any;//监视本机的所有局域网IP
int Port = 8765;//根据自己的需求设置端口号
Socket ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //用于Server建立连接的套接字
Socket socket;//用于建立连接后通信的套接字
bool isConnect=false;
IPEndPoint IEP = new IPEndPoint(IP, Port);//创建绑定了IP和端口号的IPEndPoint对象
try
{
ServerSocket.Bind(IEP); //绑定本地IP和端口号
ServerSocket.Listen(10);//服务器监听传入的连接 参数为监听的个数
while (true)
{
socket= ServerSocket.Accept();
/*堵塞等待客户端连接,我这里用了一个循环等待客户端的连接,
是因为我当时只有一个客户端,而且不定时的重启,所以就用一个socket变量就可以了,
如果有多个客户端的话可以用一个Vector<Socket> sockets,有新的连接就加入进去,这种情况通过socket通信需要注意客户端是否断开连接,可以通过发送心跳包的方式,
或者try catch捕捉异常去除向量中的某一项。
心跳包的方式就是服务器定期给向量里的每个socket发送数据包,客户端通过创建线程等方式处理并返回信息告诉服务器仍在正常运行。第二种方式服务器通过socket发送数据给客户端时出现套接字错误的异常,即该客户端不存在,则删除相应的套接字*/
string connectMsg=socket.RemoteEndPoint.ToString();//把远端的连接信息转成字符串
MessageBox.show("有新的链接信息: " + connectMsg,"连接提示");
isConnect=true; //连接中
byte[] sendMsg =new byte[1024];
sendMsg = Encoding.UTF8.GetBytes("hello");//根据需要发送的内容
socket.Send(sendMsg);//向客户端发送
}
}
catch
{
MessageBox.show("重启服务器后主动向客户端发送信息出现错误","连接错误");
isConnect=false;//出现错误连接状态置为false
}
2.2 ----发送数据
if (isConnect)
{
try
{
byte[] sendMsg = new byte[1024];
sendMsg = Encoding.UTF8.GetBytes("hello");//将服务器发送
socket.Send(sendMsg);//向客户端发送Token
}
catch (SocketException)
{
MessageBox.show("访问套接字出错,客户端已退出","客户端错误");
}
catch (ObjectDisposedException)
{
MessageBox.show("套接字已关闭","套接字错误");
}
catch (Exception ex)
{
MessageBox.show("未知错误"+ex.Message)
}
}
else
{
MessageBox.show("客户端未连接");
}
2.3 ----接收数据
while (true)
{
try
{
//获取从客户端发来的数据
int length = clientSocket.Receive(buffer);
Console.WriteLine("接收客户端{0},消息{1}", clientSocket.RemoteEndPoint.ToString(), Encoding.UTF8.GetString(buffer, 0, length));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
clientSocket.Shutdown(SocketShutdown.Both);//SocketShutdown是一个枚举类型它包括Send,Receive,Both
clientSocket.Close();
break;
}
}
第三步:客户端程序编写
Socket ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //用于Client连接与通信的套接字
IPEndPoint IPE = new IPEndPoint(connectIP, Port);//IP是服务器的IP,Port是服务器监听的端口
try
{
ClientSocket.Connect(IPE);
byte[] receiveByte=new byte[1024];
int len =ClientSocket.Receive(receiveByte);//接收服务器发来的内容
string content= Encoding.UTF8.GetString(receiveByte,0,len);
//更新数据库,我的程序里有这个内容,各位按照自己的要求对数据进行处理即可
string createTime = DateTime.Now.ToString("d");
DateTime dt = Convert.ToDateTime(createTime).AddDays(10); //获取新token之后 截止日期是10天后
string newDeadLine = dt.ToString();
string updateSql = string.Format("update t_token set token='{0}',createtime='{1}',status='{2}',deadline='{3}'","hello", createTime, "0", newDeadLine);
sqliteHelper.ExecuteNonQuery(updateSql);//这个类的方法参考我之前讲的sqlite数据库操作
Thread_recvToken.Start(ClientSocket);//连接成功后即触发持续接收函数,告诉线程用哪个套接字进行通信
}
catch( ArgumentNullException)
{
MessageBox.Show("连接服务器失败,请检查服务器是否开启,服务器对应IP是否存在,端口服务是否开启,接受数组是否为空");
}
catch(SocketException)
{
MessageBox.Show("客户端连接服务器时出错----访问套接字失败");
}
catch(SecurityException)
{
MessageBox.Show("客户端连接服务器时出错----调用堆栈中的较高调用方无权执行所请求的操作");
}
catch (ObjectDisposedException)
{
MessageBox.Show("客户端连接服务器时出错----套接字已关闭");
}
catch
{
MessageBox.Show("客户端连接服务器时出错----未知错误");
}
private void recvToken() //这是我自定义的类的方法
{
try
{ byte[] receiveByte = new byte[1024];
int len =ClientSocket.Receive(receiveByte);//接收服务器发来的内容
string content= Encoding.UTF8.GetString(receiveByte,0,len);
//更新数据库,大家根据自己的要求对数据进行处理,以下是我自己的处理
string createTime = DateTime.Now.ToString("d");
DateTime dt = Convert.ToDateTime(createTime).AddDays(10); //获取新token之后 截止日期是10天后
string newDeadLine = dt.ToString();
string updateSql = string.Format("update t_token set token='{0}',createtime='{1}',status='{2}',deadline='{3}'", "hello", createTime, "0", newDeadLine);
sqliteHelper.ExecuteNonQuery(updateSql);
}
catch (ArgumentNullException)
{
MessageBox.Show("连接服务器失败----接受数组是否为空");
}
catch (SocketException)
{
MessageBox.Show("客户端连接服务器时出错----访问套接字失败");
}
catch (SecurityException)
{
MessageBox.Show("客户端连接服务器时出错----调用堆栈中的较高调用方无权执行所请求的操作");
}
catch (ObjectDisposedException)
{
MessageBox.Show("客户端连接服务器时出错----套接字已关闭");
}
catch
{
MessageBox.Show("客户端连接服务器时出错----未知错误");
}
}
特别注意
//string content= Encoding.UTF8.GetString(receiveByte).Trim(); 无法消除小于缓存区的数据中的空白,好像那个不能叫空格。要用如下的方式获取数据流长度然后获取该长度的数据
int len =ClientSocket.Receive(receiveByte);//接收服务器发来的内容
string content= Encoding.UTF8.GetString(receiveByte,0,len);
客户端的发送与服务器的发送类似,服务区的接收也需要做相应的长度处理