网络通信模型
OSI七层模型
- 应用层 ApplicationLayer
功能:文件传输、电子邮件、文件服务、虚拟终端
TCP/IP协议:TFTP、HTTP、SNMP、FTP、SMTP、DNS、Telnet... - 表示层 PresentationLayer
功能:数据格式化、代码转换、数据加密
TCP/IP协议:无 - 会话层 SessionLayer
功能:解除或建立于其他节点的联系
TCP/IP协议:无 - 传输层 TransportLayer
功能:提供端对端的接口
TCP/IP协议:UDP、TCP - 网络层 NetworkLayer
功能:为数据包选择路由
TCP/IP协议:IP、ICMP、RIP、OSPF、BGP、IGMP - 数据链路层 DataLinkLayer
功能:传输有地址的帧,错误检测功能。
TCP/IP协议:SLIP、CSLIP、PPP、ARP、RARP、MTU - 物理层 PhysicalLayer
功能:以二进制数据形式在屋里媒体上传输数据
TCP/IP协议:ISO2110、IEEE802、IEEE802.2
TCP/IP五层协议
- 应用层
网路设备:无 - 传输层
网络设备:四层交换机、工作在四层的路由器 - 数据链路层
网络设备:网桥、以太网交换机(二层交换机)、网卡(一半工作在物理层,一半工作在数据链路层) - 物理层
物理设备:中继器、集线器、双绞线
协议
协议是数据通信的标准,规定了数据的格式,传递和接收的双方都按照标准的格式对数据进行处理。
例如,在用户模块中规定传输的数据包括:
- 用户类型
- 用户信息
- 操作类型:1表示登录 2表示注册
- 操作数据:账户、密码
通信中可自定义协议但仅限于应用层,其他层的传输协议都是行业标准,需要遵守。
IPEndPoint类
- 抽象类EndPoint的实现类
- Socket对象的RemoteEndPoint、LocalEndPoint都是这个类型
- Address属性表示使用IPv4的地址
- Port属性表示使用int表示的端口
Socket套接字类
- Socket类即可以用于做服务器端的开发也可以用作客户端的开发
- 构造方法的参数
- AddressFamily 指定使用IPv4的地址InterNetwork
- SocketType 指定使用流式传输Stream
- ProtocolType 指定协议类型,默认为TCP。
- Bind()
用于绑定IP和端口即成为服务器,可以监听指定IP的特定端口。 - Listene()
开始监听,监听状态,参数是最大的挂起数(客户端等待状态的数量)。 - Accept()
用于接收客户端连接并返回Socket对象,此方法会阻塞当前线程,建议开启新线程执行此方法,结合尾递归可接收多个客户端。 - Receive()
接收客户端发送过来的消息,以字节为单位进行操作,Receive()方法会阻塞当前线程,建议开启新线程执行此方法,结合尾递归可持续接收多条信息。 - Send()
发送消息以字节为单位
聊天室
操作流程:
- 创建服务器并能接收客户端
- 创建客户端并能与服务器建立连接
- 客户端向服务器发送消息
- 服务器接收消息
- 服务器回复客户端的消息
- 服务器向客户端广播消息
![4933701-661262a97cf3dc7c.png](https://img-blog.csdnimg.cn/img_convert/1f6fa80ace85023c228ab54b74a7a0e6.png)
P2P通信
服务器
- 创建解决方案Server添加控制台应用程序
- 添加服务器操作类
ServerControl.cs
$ vim ServerControl.cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Server
{
/**
* 服务器
*/
public class ServerControl
{
private Socket serverSocket;
public ServerControl()
{
//创建套接字
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Console.WriteLine("Server: Init");
}
public void Start(int port)
{
serverSocket.Bind(new IPEndPoint(IPAddress.Any, port));
serverSocket.Listen(100);
Console.WriteLine("Server: Start");
//新建线程避免主线程被挂起
Thread threadAccept = new Thread(Accept);
//设置为后台线程
threadAccept.IsBackground = true;
//启动线程
threadAccept.Start();
//接收客户端,此方法会挂起当前线程。
//Socket client = serverSocket.Accept();
//IPEndPoint point = client.RemoteEndPoint as IPEndPoint;
//Console.WriteLine("Clinet {0}:{1} connect success", point.Address, point.Port);
}
/// <summary>
/// 接收客户端连接
/// Socket.Accept()会挂起当前线程
/// </summary>
private void Accept()
{
//接收客户端连接
Socket client = serverSocket.Accept();
IPEndPoint point = client.RemoteEndPoint as IPEndPoint;
Console.WriteLine("[{0}:{1}]: connect", point.Address, point.Port);
//接收客户端发送过来的消息
//byte[] buffer = new byte[1024];
//int buflen = client.Receive(buffer);//Receive()会挂起,放入新线程中
//string message = Encoding.UTF8.GetString(buffer, 0, buflen);
//Console.WriteLine("Receive Message: {0}", message);
//开启新线程
Thread threadReceive = new Thread(Receive);
threadReceive.IsBackground = true;
threadReceive.Start(client);
//尾递归
Accept();
}
private void Receive(object obj)
{
//线程传递只能传递对象,进行强类型转换。
Socket client = obj as Socket;
IPEndPoint point = client.RemoteEndPoint as IPEndPoint;
IPAddress ip = point.Address;
int port = point.Port;
try
{
//接收客户端发送过来的消息
byte[] buffer = new byte[1024];
int buflen = client.Receive(buffer);//Receive()会挂起,放入新线程中
string rqmsg = Encoding.UTF8.GetString(buffer, 0, buflen);
Console.WriteLine("[{0}:{1}]: {2}", ip, port, rqmsg);
//向客户端发送消息
byte[] rpmsg = Encoding.UTF8.GetBytes(rqmsg + " " + DateTime.Now);
client.Send(rpmsg);
//尾递归
Receive(client);
}
catch
{
Console.WriteLine("[{0}:{1}]: close", ip, port);
}
}
}
}
- 入口点调用
$ vim Program.cs
using System;
namespace Server
{
public class Program
{
public static void Main(string[] args)
{
ServerControl server = new ServerControl();
server.Start(12321);
Console.ReadKey();
}
}
}
客户端
- 创建解决方案Client选择控制台应用程序
- 添加客户端处理类
ClientControl.cs
$ vim ClientControl.cs
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Client
{
public class ClientControl
{
private Socket clientSocket;
/// <summary>
/// 构造方法
/// 初始化客户端套接字
/// </summary>
public ClientControl()
{
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Console.WriteLine("Client: Init");
}
/// <summary>
/// 客户端与服务器建立连接
/// </summary>
/// <param name="ip">服务端IP地址</param>
/// <param name="port">服务端端口</param>
public void Connect(string ip, int port)
{
clientSocket.Connect(ip, port);
Console.WriteLine("Client: Connect");
//接收消息
Thread threadReceive = new Thread(Receive);
threadReceive.IsBackground = true;
threadReceive.Start();
}
/// <summary>
/// 客户端向服务器发送消息
/// </summary>
/// <param name="message">消息内容</param>
public void Send(string message)
{
byte[] buffer = Encoding.UTF8.GetBytes(message);
clientSocket.Send(buffer);
Console.WriteLine("Client: {0}", message);
}
/// <summary>
/// 客户端接收服务器发送过来的消息
/// </summary>
private void Receive()
{
try
{
byte[] buffer = new byte[1024];
int buflen = clientSocket.Receive(buffer);
string message = Encoding.UTF8.GetString(buffer, 0, buflen);
Console.WriteLine("Server: {0}", message);
//尾递归
Receive();
}
catch
{
Console.WriteLine("Server: close");
}
}
}
}
- 入口点调用
$ vim Program.cs
using System;
using System.Net;
using System.Net.Sockets;
namespace Client
{
class Program
{
public static void Main(string[] args)
{
//实例化
ClientControl client = new ClientControl();
//建立连接
string ip = GetLocalIP();
int port = 12321;
client.Connect(ip, port);
//发送消息
Console.WriteLine("Please input message, input [exit] to quit.");
string message = Console.ReadLine();
while (message != "exit")
{
client.Send(message);
message = Console.ReadLine();
}
Console.ReadKey();
}
/// <summary>
/// 获取本机IP地址
/// </summary>
/// <returns></returns>
public static string GetLocalIP()
{
try
{
string hostname = Dns.GetHostName();
IPHostEntry ipEntry = Dns.GetHostEntry(hostname);
for(int i=0; i<ipEntry.AddressList.Length; i++)
{
if(ipEntry.AddressList[i].AddressFamily == AddressFamily.InterNetwork)
{
return ipEntry.AddressList[i].ToString();
}
}
return string.Empty;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return string.Empty;
}
}
}
}
使用注意:先启动服务器后启动客户端
服务器向客户端广播消息
服务端
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Server
{
/**
* 服务器
*/
public class ServerControl
{
private Socket serverSocket;
private List<Socket> clientList;
public ServerControl()
{
//创建套接字
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//客户端集合初始化
clientList = new List<Socket>();
Console.WriteLine("Server: Init");
}
public void Start(int port)
{
serverSocket.Bind(new IPEndPoint(IPAddress.Any, port));
serverSocket.Listen(100);
Console.WriteLine("Server: Start");
//新建线程避免主线程被挂起
Thread threadAccept = new Thread(Accept);
//设置为后台线程
threadAccept.IsBackground = true;
//启动线程
threadAccept.Start();
//接收客户端,此方法会挂起当前线程。
//Socket client = serverSocket.Accept();
//IPEndPoint point = client.RemoteEndPoint as IPEndPoint;
//Console.WriteLine("Clinet {0}:{1} connect success", point.Address, point.Port);
}
/// <summary>
/// 接收客户端连接
/// Socket.Accept()会挂起当前线程
/// </summary>
private void Accept()
{
//接收客户端连接
Socket client = serverSocket.Accept();
IPEndPoint point = client.RemoteEndPoint as IPEndPoint;
Console.WriteLine("[{0}:{1}]: connect", point.Address, point.Port);
//将客户端添加到集合中
clientList.Add(client);
//接收客户端发送过来的消息
//byte[] buffer = new byte[1024];
//int buflen = client.Receive(buffer);//Receive()会挂起,放入新线程中
//string message = Encoding.UTF8.GetString(buffer, 0, buflen);
//Console.WriteLine("Receive Message: {0}", message);
//开启新线程
Thread threadReceive = new Thread(Receive);
threadReceive.IsBackground = true;
threadReceive.Start(client);
//尾递归
Accept();
}
private void Receive(object obj)
{
//线程传递只能传递对象,进行强类型转换。
Socket client = obj as Socket;
IPEndPoint point = client.RemoteEndPoint as IPEndPoint;
IPAddress ip = point.Address;
int port = point.Port;
try
{
//接收客户端发送过来的消息
byte[] buffer = new byte[1024];
int buflen = client.Receive(buffer);//Receive()会挂起,放入新线程中
string rqmsg = Encoding.UTF8.GetString(buffer, 0, buflen);
Console.WriteLine("[{0}:{1}]: {2}", ip, port, rqmsg);
//向客户端发送消息
//byte[] rpmsg = Encoding.UTF8.GetBytes(rqmsg + " " + DateTime.Now);
//client.Send(rpmsg);
//向客户端广播消息
string message = DateTime.Now+" " + rqmsg;
Broadcast(message, client);
//尾递归
Receive(client);
}
catch
{
//客户端断开连接则移除
clientList.Remove(client);
Console.WriteLine("[{0}:{1}]: close", ip, port);
}
}
/// <summary>
/// 向所有客户端广播消息
/// </summary>
/// <param name="message">准备广播的消息</param>
/// <param name="clientExclude">需排除的客户端</param>
private void Broadcast(string message, Socket clientExclude)
{
foreach(var client in clientList)
{
if(client != clientExclude)
{
client.Send(Encoding.UTF8.GetBytes(message));
}
}
}
}
}
客户端
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Client
{
public class ClientControl
{
private Socket clientSocket;
/// <summary>
/// 构造方法
/// 初始化客户端套接字
/// </summary>
public ClientControl()
{
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Console.WriteLine("Client: Init");
}
/// <summary>
/// 客户端与服务器建立连接
/// </summary>
/// <param name="ip">服务端IP地址</param>
/// <param name="port">服务端端口</param>
public void Connect(string ip, int port)
{
clientSocket.Connect(ip, port);
Console.WriteLine("Client: Connect");
//接收消息
Thread threadReceive = new Thread(Receive);
threadReceive.IsBackground = true;
threadReceive.Start();
}
/// <summary>
/// 客户端向服务器发送消息
/// </summary>
/// <param name="message">消息内容</param>
public void Send(string message)
{
byte[] buffer = Encoding.UTF8.GetBytes(message);
clientSocket.Send(buffer);
Console.WriteLine("Client: {0}", message);
}
/// <summary>
/// 客户端接收服务器发送过来的消息
/// </summary>
private void Receive()
{
try
{
byte[] buffer = new byte[1024];
int buflen = clientSocket.Receive(buffer);
string message = Encoding.UTF8.GetString(buffer, 0, buflen);
Console.WriteLine("Server: {0}", message);
//尾递归
Receive();
}
catch
{
Console.WriteLine("Server: close");
}
}
/// <summary>
/// 客户端回复消息
/// </summary>
public void Reply()
{
Thread threadReply = new Thread(InputToSend);
//threadReply.IsBackground = true;
threadReply.Start();
}
private void InputToSend()
{
Console.WriteLine("Please input [EXIT] to Quit");
string message = Console.ReadLine();
while (message.ToLower() != "exit")
{
clientSocket.Send(Encoding.UTF8.GetBytes(message));
message = Console.ReadLine(); //堵塞
}
}
}
}
服务器架构
![4933701-14e4da4adb7bfc19.png](https://img-blog.csdnimg.cn/img_convert/42ecf69271191bcc3bb9cfeec5bbc4d5.png)
基础服务器架构
![4933701-7333e06ed8eaeea8.png](https://img-blog.csdnimg.cn/img_convert/53b03518a20799b47f8ddf7808515f6d.png)
大厅游戏服务器架构
自定义协议
规则
- 模块
- 操作
- 数据
- 附加
数据传输方式
- 二进制数据:保证数据量最小
- JSON
基础服务器架构
服务端