C# 网络通信

网络通信模型

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()
    发送消息以字节为单位

聊天室

操作流程:

  1. 创建服务器并能接收客户端
  2. 创建客户端并能与服务器建立连接
  3. 客户端向服务器发送消息
  4. 服务器接收消息
  5. 服务器回复客户端的消息
  6. 服务器向客户端广播消息
4933701-661262a97cf3dc7c.png
P2P通信

服务器

  1. 创建解决方案Server添加控制台应用程序
  2. 添加服务器操作类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);
            }
        }
    }
}
  1. 入口点调用
$ 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();
        }
    }
}

客户端

  1. 创建解决方案Client选择控制台应用程序
  2. 添加客户端处理类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");
            }
        }
    }
}
  1. 入口点调用
$ 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
基础服务器架构
4933701-7333e06ed8eaeea8.png
大厅游戏服务器架构

自定义协议

规则

  • 模块
  • 操作
  • 数据
  • 附加

数据传输方式

  • 二进制数据:保证数据量最小
  • JSON

基础服务器架构

服务端

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值