网络分为内网(局域网)和外网,内网可以不需要网络,相同网线相连即可。外网就是上网浏览网页等。对于同一网吧的电脑或连接同一无线网的电脑,他们的内网ip是不同的,但是对外网他们的ip是同一个,如网吧的电脑与外部同一台电脑A聊QQ,A看他们的IP是一样的,而网吧电脑收到信息时会从外网的ip转到内网的ip。IP是指互联网协议地址,,地址是可以自动分配的,MAC地址在每个网卡出场的时候就有一个全球唯一的MAC地址
IP:每个设备在网络中的唯一标识。每台网络终端在网络中都有一个独立的地址,我们在网络中传输数据就是使用这个地址。但一台电脑可以有多个ip地址,ip也是变化的,是随机分配的,
本地回路地址:127.0.0.1
广播地址:255.255.255.255
端口号:每个程序在设备上的唯一标识
每个网络程序都需要绑定一个端口号,传输数据的时候除了确定发到哪台机器上,还要明确发到哪个程序。
端口号范围从0-65535
编写网络应用就需要绑定一个端口号,尽量使用1024以上的,1024以下的基本上都被系统程序占用了。
默认端口号:
DNS 域名解析服务:53
HTTP 超文本传输服务:80
HTTPS 加密的超文本传输服务:433
MYSQL数据库端口:3306
Redis数据库端口:6379
TCP服务端默认端口:8080
Nginx服务器的端口:8888
协议:为计算机网络中进行数据交换而建立的规则、标准或约定的集合。
UDP:面向无连接,数据不安全,速度快。不区分客户端与服务端。
TCP: 面向连接(三次握手),数据安全,速度略低。分为客户端和服务端。
三次握手: 客户端先向服务端发起请求, 服务端响应请求, 传输数据
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
购买服务器的配置:
购买服务器(使用买的公共ip)–分组配置设置端口号–根据需求可以更改服务器端的电脑配置–在计算机命令里输入mstsc打开与远程的连接–输入服务器地址–也可修改本地资源详细信息里把本地的C盘等传过去–在服务端安装数据库软件–可网上下也可在传过去的本地盘里复制–在把数据备份复制过去–服务端代码里监听的ip是服务器私有ip,客户端监听的是公有ip–右键服务端发布–把发布的服务端复制到服务器–在服务器上打开服务端
TCP协议
//异步并解决粘包分包服务端代码(同步的可以使用线程解决)
class Program
{
static MsgManager msgMng;
static void Main(string[] args)
{
AsycConnect();
}
static void AsycConnect()
{
// 1,创建socket
Socket ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2,绑定ip跟端口号
ServerSocket.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 7788));//向操作系统申请一个可用的ip跟端口号 用来做通信
//3,开始监听 (等待客户端连接)
ServerSocket.Listen(100);//参数是最大连接数,只是设定参数的作用,为0时不限制个数
ServerSocket.BeginAccept(AsycAcceptFunc, ServerSocket);//只有连接上才会进入回调函数,异步等待,不会暂停卡住后面的代码
Console.ReadKey();
}
private static void AsycAcceptFunc(IAsyncResult ar)
{
Socket socket = ar.AsyncState as Socket; //这个是传过来的上面那个服务端,如果服务器是全局变量就不需要这行代码了
Socket clientSocket = socket.EndAccept(ar);//客户端是多个,不能用全局变量
//连接上向客户端发送信息
msgMng = new MsgManager();//信息管理类
string message = "hello 欢迎你";
byte[] data = Encoding.UTF8.GetBytes(message);
clientSocket.Send(data);
clientSocket.BeginReceive(msgMng.MsgByte, msgMng.startIndex, msgMng.RemainSize() , SocketFlags.None, ReceiveFunc, clientSocket);//解决粘包,分包
// clientSocket.BeginReceive(dataBuffer, 0, 1024, SocketFlags.None, ReceiveFunc, clientSocket);//不解决时正常的代码
socket.BeginAccept(AsycAcceptFunc, socket);//递归,又给了一次新的异步等待,从而实现无限连接
}
private static void ReceiveFunc(IAsyncResult ar)
{
Socket socket = ar.AsyncState as Socket;//这个是传过来的客户端
try//解决客户端直接关闭的异常
{
int i = socket.EndReceive(ar);
msgMng.ReadMessage(i);//读取信息
socket.BeginReceive(msgMng.MsgByte, msgMng.startIndex, msgMng.RemainSize(), SocketFlags.None, ReceiveFunc, socket);
//不解决时的代码
// string msg = Encoding.UTF8.GetString(dataBuffer, 0, i);
// Console.WriteLine("我从客户端接收的信息是:" + msg);
// socket.BeginReceive(dataBuffer, 0, 1024, SocketFlags.None, ReceiveFunc, socket);
}
catch(Exception e)
{
Console.WriteLine(e);
if (socket != null)
socket.Close();
}
}
//异步并解决粘包分包服务端拆分收到的信息
class MsgManager
{
private Byte[] msgByte = new Byte[1024];//太小的话会分包
public Byte[] MsgByte//接收信息的字节数组
{
get { return msgByte; }
}
public int startIndex=0;//当前数组拥有的字节数
public int RemainSize()//剩余的数组空间
{
return message.Length - startIndex;
}
/// <summary>
/// startIndex在外面获得了收到的字节数,在这个函数中分条获取
/// </summary>
public void ReadMessage(int i)
{
startIndex += i;
while (true)
{
string msg;
if (startIndex <= 4) return;
int count = BitConverter.ToInt32(message, 0);//只会解析前四个字节,因为int是4个字节
if (startIndex - 4 >= count)
{
msg = Encoding.UTF8.GetString(message, 4, count);
Console.WriteLine("解析出一条数据:" + msg);
Array.Copy(message, count + 4, message, 0, startIndex - count - 4);
startIndex -= count + 4;
}
else
break;
}
}
}
//异步并解决粘包分包客户端代码
class Program
{
static void Main(string[] args)
{
//1,创建socket
Socket tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2,发起建立连接的请求
IPAddress ipaddress = IPAddress.Parse("127.0.0.1");//可以把一个字符串的ip地址转化成一个ipaddress的对象
IPEndPoint point = new IPEndPoint(ipaddress, 7788);
tcpClient.Connect(point);
//接收服务器的消息(可不写)
byte[] data = new byte[1024];
int length = tcpClient.Receive(data);//这里传递一个byte数组,实际上这个data数组用来接收数据
string message = Encoding.UTF8.GetString(data, 0, length);//length返回值表示接收了多少字节的数据
Console.WriteLine(message);
while (true)
{
//向服务器端发送消息
string message2 = Console.ReadLine();//读取用户的输入 把输入发送到服务器端
tcpClient.Send(MessagePack.GetBytes(message2));//把字符串转化成字节数组,然后发送到服务器端
//第二种解决服务端异常的办法,在客户端主动关闭连接,但是服务端哪里要加个i>0的条件,不然一直接收空信息
if (message2 == "c")
{
tcpClient.Close();
return;
}
}
}
}
//异步并解决粘包分包客户端包装发送的信息
class MessagePack
{
public static byte[] GetBytes(string s)
{
byte[] byte_1 = Encoding.UTF8.GetBytes(s); //要发送的信息
int count = byte_1.Length; //这个信息有多少个字节
byte[] byte_2 = BitConverter.GetBytes(count); //把字节个数存在数组里(因为是int类型,固定四个字节)
byte[] byte_3 = byte_2.Concat(byte_1).ToArray();//把两个字节数组相连,前面是信息的字节个数,后面是信息内容(注意这两个数组存的内容不一样)
return byte_3;
}
}
1.粘包:服务端与客户端没有约定好要使用的数据结构。Socket Client实际是将数据包发送到一个缓存buffer中,通过buffer刷到数据链路层。因服务端接收数据包时,不能断定数据包1何时结束,就有可能导致服务器读取数据包1和其后面的数据包。这种现象称为粘包
产生原因:如果发送的网络数据包太小,太快,消息没有被及时从缓存区取走,下次在取数据的时候可能就会出现一次取出多个数据包的情况
分包:数据包数据被分开一部分发送出去,服务端一次读取数据时可能读取到完整数据包的一部分,剩余部分被分批读取。
产生原因:可能是IP分片传输导致的,也可能是传输过程中丢失部分包导致出现的半包,还有可能就是一个包可能被分成了两次传输,在取数据的时候,先取到了一部分(还可能与接收的缓冲区大小有关系),总之就是一个数据包被分成了多次接收。
2.服务端:可同步连接Accept可异步连接BeginAccept/EndAccept,可同步接收Receive可异步接收BeginReceive/EndReceive
客户端:只能连接一个Connect,但可同步接收Receive可异步接收Receive可异步接收BeginReceive/EedReceive
UDP协议
//服务端
class Program
{
private static Socket udpServer;
static void Main(string[] args) {
//1,创建socket
udpServer = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);
//2,绑定ip跟端口号
udpServer.Bind( new IPEndPoint( IPAddress.Parse("127.0.0.1"),7788 ) );
//3,接收数据
Thread thread =new Thread(ReceiveMessage);
thread.IsBackground = true;
thread.Start();
Console.ReadKey();
}
static void ReceiveMessage()
{
while (true)
{
EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
byte[] data = new byte[1024];
int length = udpServer.ReceiveFrom(data, ref remoteEndPoint);//这个方法会把数据的来源(ip:port)放到第二个参数上
string message = Encoding.UTF8.GetString(data, 0, length);
Console.WriteLine("从ip:" + (remoteEndPoint as IPEndPoint).Address.ToString() + ":" + (remoteEndPoint as IPEndPoint).Port + "收到了数据:" + message);
}
}
}
//客户端
class Program {
static void Main(string[] args) {
//创建socket
Socket udpClient = new Socket(AddressFamily.InterNetwork,SocketType.Dgram, ProtocolType.Udp);
while (true)
{
//发送数据
EndPoint serverPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 7788);
string message = Console.ReadLine();
byte[] data = Encoding.UTF8.GetBytes(message);
udpClient.SendTo(data, serverPoint);
}
udpClient.Close();
Console.ReadKey();
}
}
1.解决方案资源管理器–右键解决方案工程名–属性–生成–选择路径–调试–生成,重新生成解决方案。若生成dll的路径选择的是Unity工程的文件夹中,后面在对代码做修改,直接点重新生成解决方案即可,可同步到Unity的工程中(放到Unity中的dll要改下框架Unity3.5.Net.Subset.base,并且为类库(.NET Framework))