Win + R 打开控制台输入CMD 打开小黑窗, 输入ipconfig 查询本机地址
“外网IP是全世界唯一的IP地址,仅分配给一个网络设备。而内网IP是由路由器分配给每一部内部使用的IP地址,而内网的所有用户都是通过同一个外网IP地址进行上网的,而内网的IP地址每个人的都不一样,Internet上的用户也无法直接访问到内网用户。简单来说呢,外网IP就是标示了您在整个互联网上的地址,就相当于小区的地址,而内网IP呢,就是标识着您在局域网里面的地址,也就是小区内的几栋几楼几号房子。
内网IP地址一般是192.168开头 ,连接到wifi或网线的每个设备都有自己的一个内网ip
外网可以理解为公司的总网,也就是牵的网线里的网络
而内网是由路由器为每个设备分配的ip
端口号就是用来决定将消息传给同一内网IP下的某个程序
三次握手,四次挥手
TCP 协议比较稳定,需要与接收方建立连接,可以确保数据不会丢失,可以保证数据发送的是否正确,需得到接收方的返回,如果在一定时间内没有收到回应则会重新发送。
UDP 速度快,但是不稳定安全,不用建立连接 (只管发,不管接,可能会被拦截,或断掉)
三次握手: 建立连接
四次挥手:断开连接
IPv6的出现是因为IPv4不够用了
TCP 用Stream 流通信比较稳定, 创建服务端代码如下
using System.Net.Sockets;
using System.Net;
namespace TCP学习
{
class Program
{
static void Main(string[] args)
{
StartServerAsync();
Console.ReadKey();
}
static void StartServerAsync()
{
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//本机IP: 192.168.1.195 /127.0.0.1(127永远代表本机IP) //196不是固定的,重启后或一定时间后路由器会重新分配IP
//IPAddress xxx.xxx.xxx.xxx IPEndPoint xxx.xxx.xxx.xxx:port
//IPAddress iPAddress = new IPAddress(new byte[] { 192, 168, 1, 195 });
IPAddress iPAddress = IPAddress.Parse("192.168.1.195");//推荐
IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, 88);
serverSocket.Bind(iPEndPoint); //绑定IP和端口号
serverSocket.Listen(0); //开始监听端口号,参数为监听队列长度,
//Listen(10)指超过10个后的消息不接收,设置为0不限制
//同步接收客户端的连接(旧方法)
//Socket clientSocket = serverSocket.Accept(); // 接收一个客户端链接 程序在这里暂停,直到有客户端连接
//向链接的客户端发送消息
//string msg = "Hello你好";
//byte[] msgB = System.Text.Encoding.UTF8.GetBytes(msg); //把字符串转为字节数组
//clientSocket.Send(msgB);
//dataBuffer = new byte[10240];
//异步接收客户端的消息,不影响下面的代码执行//当接收到客户端消息时才调用回调方法,最后一个参数是回调方法的参数
//clientSocket.BeginReceive(dataBuffer, 0, 1024, SocketFlags.None, ReceiveCallBack, clientSocket);
//同步接收客户端的消息(旧方法)
//byte[] msgC = new byte[1024];
//int count = clientSocket.Receive(msgC); //用msgC储存接收的数据.//程序在这里暂停,等待客户端发送消息//返回值是接收到的数据的长度
//string msgD = System.Text.Encoding.UTF8.GetString(msgC, 0, count); //把前count个的字节数组数据转为字符串 //从0开始读取count个数据
//Console.WriteLine(msgD);
//Console.ReadKey();
//clientSocket.Close(); //关闭的和客户端的一个连接
//serverSocket.Close(); //关闭自身的连接,停服
serverSocket.BeginAccept(AcceptCallBack, serverSocket);
//到目前为止 服务器端已经可以处理多个客户端的连接,也可以处理多个来自同一个客户端的消息 最终处理来自多个客户端的多个消息
}
static byte[] dataBuffer = new byte[1024];
static void AcceptCallBack(IAsyncResult ar)
{
Socket serverSocket = ar.AsyncState as Socket;
Socket clientSocket = serverSocket.EndAccept(ar);
string msg = "Hello你好";
byte[] msgB = System.Text.Encoding.UTF8.GetBytes(msg);
clientSocket.Send(msgB);
clientSocket.BeginReceive(dataBuffer, 0, 1024, SocketFlags.None, ReceiveCallBack, clientSocket);
serverSocket.BeginAccept(AcceptCallBack, serverSocket);
}
static void ReceiveCallBack(IAsyncResult ar)
{
Socket clientSocket = null;
try //try 异常捕捉是为了当客户端异常关闭(非正常关闭)时,让所持有的客户端被释放且无法用该客户端继续接收消息
{
clientSocket = (Socket)ar.AsyncState;
int count = clientSocket.EndReceive(ar); //客户端如果发送"" 是接收不到的,当客户端Close时,会收到客户端的空消息,个数为0
//所以当客户端的消息个数为0时,客户端主动断开(正常关闭)了
if (count == 0)
{
clientSocket.Close();
return;
}
string msg = System.Text.Encoding.UTF8.GetString(dataBuffer, 0, count);
Console.WriteLine("从客户端接收到数据:" + msg);
clientSocket.BeginReceive(dataBuffer, 0, 1024, SocketFlags.None, ReceiveCallBack, clientSocket);
}
catch (Exception e)
{
Console.WriteLine(e);
if (clientSocket != null)
{
clientSocket.Close();
}
}
finally
{
}
}
}
}
创建客户端代码如下
using System.Net.Sockets;
using System.Net;
namespace TCP客户端
{
internal class Program
{
static void Main(string[] args)
{
//客户端只需要和服务端建立连接 ,不需要绑定Bind
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSocket.Connect(new IPEndPoint(IPAddress.Parse("192.168.1.195"),88));
//接收这部分和服务器端一样
byte[] msgC = new byte[1024];
int count = clientSocket.Receive(msgC); //接收服务端的消息,用msgC储存接收的数据.
//返回值是接收到的数据的长度
// 执行到Receive程序会暂停,直到接收到服务端的消息
string msgD = System.Text.Encoding.UTF8.GetString(msgC, 0, count); //把前count个的字节数组数据转为字符串
//从0开始读取count个数据
Console.WriteLine(msgD);
while (true)
{
string str = Console.ReadLine();
if (str == "Close") //客户端发送Close 主动要求断开连接
{
clientSocket.Close();
return;
}
clientSocket.Send(System.Text.Encoding.UTF8.GetBytes(str));
}
Console.ReadKey();
clientSocket.Close();
}
}
}
同步方式:Accept Connect / Receive
异步方式:BeginAccept EndAccept / BeginReceive EndReceive
效果如下,多客户端连接一个服务器
粘包和分包
粘包 :当发送数据比较频繁,且数据量小,TCP 在你点发送时不会立马把数据发出去,而是等很多条数据达到一定量整合到一起发出去,服务器端或客户端执行一次Receive就可以收到多条消息的整合,如果点一次发一次会造成数据传输性能开销,
粘包虽然会粘在一起,但是到达的先后顺序是不会变的
分包: 当数据量很大,上千上万的数据,TCP会分几次发送,大包占用网速,运送慢, 占用时间长,发送失败还要重新发送,服务器端或客户端执行一次Receive可能收到的不是一个完整的消息,而是被分割的消息段
我们定义的数组是1024字节,所以一次消息只能发1024字节数据,数据超过了就要分包发送,
可以加大每次发送的字节容量大小来避免分包(前提是异步,如果是同步,即使容量大也会分包),不过一般不考虑,很少有一次会发送那么大的数据,应该考虑的是粘包问题,因为游戏交互每次发送数据的量很小。次数也多
处理粘包问题:
一个字符占一个字节 ,一个空格占用一个字节 ,一个汉字占3个字节
int count = 15686;
byte[] data = BitConverter.GetBytes(count);//支持值类型参数
foreach (byte b in data)
{
Console.Write(b + ":"); //可以确保数据头只占用int32 4个字节
}