C#实现简单异步Echo服务端和客户端(实现聊天室)

上篇文章中我们使用Connect,Receive和Send来进行接收数据,会阻塞,它是在单一线程完成的,不具备灵活性

因此可以使用BeginConnect和EndConnect等API完成相同功能完成异步连接和异步的发送接收

他们的函数原型如下:

BeginConnect:

public IAsyncResult BeginConnect(  string host,  int port,  AsyncCallback requestCallback,  object state )

对应IP,端口,回调函数,state参数用于传递用户自定义的对象或数据给回调函数

EndConnect:

public void EndConnect(  IAsyncResult asyncResult )

BeginReceive:

public IAsyncResult BeginReceive (  byte[] buffer,  int offset,  int size,  SocketFlags socketFlags,  AsyncCallback callback, object state )

EndReceive:

public int EndReceive(  IAsyncResult asyncResult )

BeginSend:

public IAsyncResult BeginSend(  byte[] buffer,  int offset,  int size,  SocketFlags socketFlags,  AsyncCallback callback,  object state )

EndSend:

public int EndSend (  IAsyncResult asyncResult )

BeginAccept:

public IAsyncResult BeginAccept(  AsyncCallback callback,  object state )

EndAccept:

public Socket EndAccept(  IAsyncResult asyncResult )

一、客户端

1:异步Socket创建和连接(BeginConnect/EndConnect)

byte[] readBuff = new byte[1024];
string recvStr = ""; 

socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.BeginConnect("127.0.0.1", 8888, ConnectionCallback, socket);    //异步连接,socket作为参数传递给回调函数
public void ConnectCallback(IAsyncResult ar)    //回调函数
{  
 try
   {  
    Socket socket = (Socket)ar.AsyncState;    //ar.AsyncState 属性中获取了传递给 BeginConnect 方法的状态对象,即原始的Socket
    socket.EndConnect(ar);    //用于等待连接操作完成,并返回连接的结果。如果连接成功,则该方法会返回并且不会抛出异常;如果连接失败,则会抛出相应的异常。
    Debug.Log("Socket Connect Succ");
   }
 catch (SocketException ex)
 {  
    Debug.Log("Socket Connect fail" + ex.ToString());
  
  }
}

注:每次调用BeginConnect方法启动异步连接操作时,都应该在适当的地方调用EndConnect方法来结束该操作

2:异步接收消息(基于异步连接)(BeginReceive/EndReceive)

byte[] readBuff = new byte[1024];
string recvStr = ""; 

socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.BeginConnect("127.0.0.1", 8888, ConnectionCallback, socket);
public void ConnectCallback(IAsyncResult ar) 
{  
 try
   {  
    Socket socket = (Socket)ar.AsyncState; 
    socket.EndConnect(ar); 
    Debug.Log("Socket Connect Succ");
    socket.BeginReceive( readBuff, 0, 1024, 0,  ReceiveCallback, socket);     //继续开启接收数据的回调
   }
 catch (SocketException ex)
 {  
    Debug.Log("Socket Connect fail" + ex.ToString());
  
  }
}
private void ReveiveCallBack(IAsyncResult ar)    //异步回调
    {
        try
        {
            Socket socket = (Socket)ar.AsyncState;    //从BeginReceive中获取到的socket,就是原始Socket
            int count = socket.EndReceive(ar);    //同理,EndReceive和BeginReceive同时出现,结束异步操作,返回实际字节数
            recvStr = System.Text.Encoding.Default.GetString(readBuff, 0, count);
            socket.BeginReceive(readBuff, 0, 1024, 0, ReveiveCallBack, socket);    //再次调用回调,继续接收下一批数据
        }
        catch(SocketException e)
        {
            Debug.Log("Socket Receive fail" + e.ToString());
        }
    }
    
    private void Update()
    {
        text.text = recvStr;
    }

注:两个地方调用了BeginReceive,一个在Connect的回调函数,一个在结束一次接收后,解析后再次调用

3:异步发送消息(BeginSend/EndSend)

public void Send() {
  //Send
  string sendStr = InputFeld.text;
  byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
  socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallback, socket);
  }
public void SendCallback(IAsyncResult ar)
{  
    try
        {
            Socket socket = (Socket) ar.AsyncState;
            int count = socket.EndSend(ar);
            Debug.Log("Socket Send succ" + count);
        }  
     catch (SocketException ex)
     {
         Debug.Log("Socket Send fail" + ex.ToString());
      } 
}

注:一般情况下,EndSend的返回值count与要发送数据的长度相同, 代表数据全部发出

二、服务端

1:多客户端准备

服务端需要处理多个连接,所以需要创建一个存储多个连接的列表,先定义一个ClientState类,保存一个客户端的全部信息,包括Socket,读缓冲区readBuff

然后创建一个能存储多个用户信息的字典

class ClientState
{
    public Socket socket;
    public byte[] readBuff = new byte[1024];
}
class MainClass
{
    static Socket listenfd;
    public static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
}

2:异步接受连接(BeginAccept/EndAccept)

class ClientState
{
    public Socket socket;
    public byte[] readBuff = new byte[1024];
}
class MainClass
{
    static Socket listenfd;
    public static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
    static void Main(string[] args)
    {
        listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        IPAddress ipAdr = IPAddress.Parse("127.0.0.1");
        IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);
        listenfd.Bind(ipEp);
        listenfd.Listen(0);
        Console.WriteLine("[服务器]启动成功");
        listenfd.BeginAccept (AcceptCallback, listenfd); 
    }
    public static void AcceptCallback(IAsyncResult ar)
    {
    try{
        Console.WriteLine("[服务器]Accept");
        Socket listenfd = (Socket) ar.AsyncState;    //获取原始的Socket连接
        Socket clientfd = listenfd.EndAccept(ar);    //从原始Socket中读取到客户端Socket
        
        //把读取到的客户端添加到字典
        ClientState state = new ClientState();
        state.socket = clientfd;
        clients.Add(clientfd, state);
        
        //接收数据BeginReceive
        clientfd.BeginReceive(state.readBuff, 0, 1024, 0,  ReceiveCallback, state);
        //继续Accept
        listenfd.BeginAccept (AcceptCallback, listenfd);
        }
    }
    catch(SocketException e)
    {
        Console.WriteLine("Socket Accept fail" + e.ToStrin());
    }  
}

三、聊天室

1:客户端

using System;
using System.Net.Sockets;
using UnityEngine;
using UnityEngine.UI;

public class ChatClient : MonoBehaviour
{
    public InputField inputField;
    public Text text;
    byte[] readBuff = new byte[1024];
    string recvStr = "";

    Socket socket;
    private void Start()
    {
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        socket.BeginConnect("127.0.0.1", 8888, ConnectCallback, socket);
    }

    private void ConnectCallback(IAsyncResult ar)
    {
        try
        {
            Socket socket = (Socket)ar.AsyncState;
            socket.EndConnect(ar);
            Debug.Log("Socket Connect Succ");
            socket.BeginReceive(readBuff, 0, 1024, 0,ReceiveCallback, socket);
        }
        catch(Exception e)
        {
            Debug.Log("Socket connect fail" + e.ToString());
        }
    }

    private void ReceiveCallback(IAsyncResult ar)
    {
        try
        {
            Socket socket = (Socket)ar.AsyncState;
            int count = socket.EndReceive(ar);
            string s = System.Text.Encoding.Default.GetString(readBuff, 0, count);
            recvStr = s + "\n" + recvStr;//差异在于会显示历史数据
            socket.BeginReceive(readBuff, 0, 1024, 0, ReceiveCallback, socket);
        }
        catch(Exception e)
        {
            Debug.Log("Socket Receive fail" + e.ToString());
        }
    }
    public void Send()
    {
        string sendStr = inputField.text;
        byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
        socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallback, socket);
    }

    private void SendCallback(IAsyncResult ar)
    {
        try
        {
            Socket socket = (Socket)ar.AsyncState;
            int count = socket.EndSend(ar);
            Debug.Log("Socket Send succ" + count);
        }
        catch (SocketException ex)
        {
            Debug.Log("Socket Send fail" + ex.ToString());
        }
    }

    private void Update()
    {
        text.text = recvStr;
    }
}

2:服务端

聊天室与Echo程序的不同之处在于服务端对消息的处理

同样需要把BeginAccept放到死循环里,让它一直接收消息,避免退出

using System.Net;
using System.Net.Sockets;

class ClientState{
    public Socket? socket;
    public byte[] readBuff = new byte[1024];
}
class MainClass
{
    static Socket listenfd;
    public static Dictionary<Socket,ClientState> clients = new Dictionary<Socket, ClientState> ();
    static void Main(string[] args)
    {
        listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        IPAddress iP = IPAddress.Parse ("127.0.0.1");
        IPEndPoint iPEp = new IPEndPoint(iP, 8888);
        listenfd.Bind (iPEp);
        listenfd.Listen(0);
        Console.WriteLine("[服务器]启动成功");
        while (true)
        {
            listenfd.BeginAccept(AcceptCallback, listenfd);
        }
    }

    private static void AcceptCallback(IAsyncResult ar)
    {
        try
        {
            Console.WriteLine("[服务器]Accept");
            Socket listenfd = (Socket) ar.AsyncState;
            Socket clientfd = listenfd.EndAccept(ar);

            ClientState state = new ClientState();
            state.socket = clientfd;
            clients.Add(clientfd, state);

            clientfd.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);
            listenfd.BeginAccept(AcceptCallback, listenfd);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Socket Accept fail" + ex.ToString());
        }
    }

    private static void ReceiveCallback(IAsyncResult ar)
    {
        try
        {
            ClientState state = (ClientState)ar.AsyncState;
            Socket clientfd = state.socket;
            int count = clientfd.EndReceive(ar);
            if(count == 0)
            {
                clientfd.Close();
                clients.Remove(clientfd);
                Console.WriteLine("Socket Close");
                return;
            }
            string recvStr = System.Text.Encoding.Default.GetString(state.readBuff, 0, count);
            byte[] sendBytes = System.Text.Encoding.Default.GetBytes("echo" + recvStr);
            foreach(ClientState s in clients.Values)
            {
                s.socket.Send(sendBytes);
            }
            clientfd.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Socket Receive fail" + ex.ToString());
        }
    }
}

  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个使用C#编写的简单服务端客户端程序,用于客户端服务端发送消息,服务端接收消息并回复消息。 服务端代码: ```csharp using System; using System.Net; using System.Net.Sockets; using System.Text; class Program { static void Main(string[] args) { TcpListener server = null; try { // 设置IP地址和端口号 IPAddress ipAddress = IPAddress.Parse("127.0.0.1"); int port = 8888; // 创建TcpListener对象 server = new TcpListener(ipAddress, port); // 开始监听 server.Start(); Console.WriteLine("服务端已启动,等待客户端连接..."); // 接收客户端连接 TcpClient client = server.AcceptTcpClient(); Console.WriteLine("客户端已连接"); // 获取网络流 NetworkStream stream = client.GetStream(); // 接收消息并回复消息 byte[] buffer = new byte[1024]; int byteCount; while ((byteCount = stream.Read(buffer, 0, buffer.Length)) != 0) { string message = Encoding.UTF8.GetString(buffer, 0, byteCount); Console.WriteLine("收到消息:" + message); // 回复消息 byte[] reply = Encoding.UTF8.GetBytes("已收到消息:" + message); stream.Write(reply, 0, reply.Length); } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { // 关闭TcpListener对象 if (server != null) { server.Stop(); } } Console.ReadKey(); } } ``` 客户端代码: ```csharp using System; using System.Net; using System.Net.Sockets; using System.Text; class Program { static void Main(string[] args) { TcpClient client = null; try { // 设置IP地址和端口号 IPAddress ipAddress = IPAddress.Parse("127.0.0.1"); int port = 8888; // 创建TcpClient对象 client = new TcpClient(); // 连接服务端 client.Connect(ipAddress, port); Console.WriteLine("连接到服务端"); // 获取网络流 NetworkStream stream = client.GetStream(); // 发送消息 string message = "Hello, server!"; byte[] buffer = Encoding.UTF8.GetBytes(message); stream.Write(buffer, 0, buffer.Length); Console.WriteLine("发送消息:" + message); // 接收回复消息 buffer = new byte[1024]; int byteCount = stream.Read(buffer, 0, buffer.Length); string reply = Encoding.UTF8.GetString(buffer, 0, byteCount); Console.WriteLine("收到回复消息:" + reply); } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { // 关闭TcpClient对象 if (client != null) { client.Close(); } } Console.ReadKey(); } } ``` 注意:在使用这个示例程序时,需要先启动服务端,再启动客户端服务端等待客户端连接,客户端连接到服务端后发送消息,服务端接收消息并回复消息,客户端接收回复消息并输出。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Laker404

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值