C#网络通信Socket详解
最近在做一个联机的双人对战网络游戏,看教程之后对于Socket仍然一知半解,查完资料之后,就明白了,分享给大家,有错误欢迎指出,留言讨论。
一、准备
协议分为TCP和UDP。
TCP:传输控制协议
- TCP是面向连接的、可靠的
- TCP是基于字节流的传输层协议
UDP:用户数据报协议
- UDP与TCP相反,是无连接的、不可靠的协议
- UDP是基于数据报的传输(因其不可靠传输效率比TCP高)
我们采用TCP来实现B-S通信。
如上图,一次完整的Socket连接的流程,其中Socket是指套接字,套接字是支持TCP/IP协议网络通信的基本单元。
- 开启一个连接之前,建立Socket连接
- Bind使Socket与一个本地终结结点相关联,就是绑定IP和端口号
- Listen开始监听,参数是监听几个客户端,0是不受限制。
主线程和线程池:非主线程不能设置Unity3d的组件
当客户端使用BeginReceive实现异步接收的时候,因为C#使用线程池处理异步调用,所以ReceiveCb并不在主线程中,所以采用中介者模式来调用,只有主线程能设置(Satrt() Update()属性主线程)UI组件,所以在ReceiveCb方法中只设置传递的字符,具体在主线程里云处理。
非主线程无法设置unity中的组件:
多线程转换成单线程,由于C#中的异步通信由线程池实现,BeginReceive属于多线程,所以需要通过中介在主线程之中处理消息。
服务器端
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Servece
{
class Program
{
static void Main(string[] args)
{
//搭建 Socket
Socket serverSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
IPAddress ip = IPAddress.Parse("192.168.1.14");
IPEndPoint ipPoint = new IPEndPoint(ip, 1234);
//Bind
serverSocket.Bind(ipPoint);//建立连接
//Listen
serverSocket.Listen(0);//监听
//Socket clientSocket = serverSocket.Accept(); 只能接收一个客户端
//Accept
serverSocket.BeginAccept(AcceptCall, serverSocket);
Console.ReadKey();//很重要,不然会报错。作用:等待键盘输入,退出程序,使调试时能看到输出结果。如果没有此句,命令窗口会一闪而过。
}
static void AcceptCall(IAsyncResult ar) {
Socket severSocket = ar.AsyncState as Socket;
//给客户端发送信息
Socket clientSocket =severSocket.EndAccept(ar);
string msg = "你已连上服务器";
byte[] data = Encoding.UTF8.GetBytes(msg);
clientSocket.Send(data);
//接收客户端发来的信息
dataBuffer = new byte[1024];
clientSocket.BeginReceive(dataBuffer, 0, 1024, SocketFlags.None, ReciveClient, clientSocket);
severSocket.BeginAccept(AcceptCall, severSocket);
}
static byte[] dataBuffer=new byte[1024];
static void ReciveClient(IAsyncResult ar)
{
Socket clientSocket = null;
//用try来处理客户端突然关闭的情况
try
{
clientSocket = ar.AsyncState as Socket;
int count = clientSocket.EndReceive(ar);
//当没有传过来信息的时候,关闭客户端
if (count == 0) {
clientSocket.Close();
return;
}
string msg = Encoding.UTF8.GetString(dataBuffer, 0, count);
Console.WriteLine("从客户端接收到消息:" + msg);
clientSocket.BeginReceive(dataBuffer, 0, 1024, SocketFlags.None, ReciveClient, clientSocket);
}
catch (Exception e)
{
clientSocket.Close();
Console.WriteLine(e);
}
}
}
}
Socket serverSocket = new
Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
AdddressFamily:
InterNetwork |
使用IPv4 |
InterNetworkV6 |
使用IPv6 |
SocketType
Stream |
字节流(支持可靠、双向、基于连接的字节流) |
Dgram |
支持数据报、无连接、不可靠的数据报类型。 |
BeginReceive参数说明
buffer |
Byte类型的数组,用于存储接收到的数据 |
offset |
buffer参数中存储数据的位置,该位置从零开始计数 |
size |
最大字节数 |
socketFlags |
SocketFlags值的按位组合,这里设置为0 |
callback |
回调函数,一个AsyncCallback委托 |
state |
一个用户定义对象,其中包含接收操作的相关信息,当操作完成时,该对象会传递给EndReceive委托 |
Accept:
当开始监听之后,服务器调用Accept(0接收客户端的连接,若没有客户端连接,将会调用阻塞方法,也就是当没有客户端连接的时候,程序卡在Accept()不会往下执行,直到接收到客户端的连接。返回值是一个client(新客户端的Socket) 。
Receive:
阻塞方法,和Accept一样,参数为byte[]类型存储接收的数据,返回值为接收到数据的长度。
Send:
接受一个byte[]类型的参数指明要发送的内容。返回值指明发送数据的长度,服务器将字符串转换成byte[]类型发送给客户端
异步Socket:实现多个客户端。
BeginAccept
参数 |
说明 |
asyncCallBack |
回调函数 |
state |
表示状态信息,必须保证state中包含socket句柄 |