五、网络编程实例:聊天室

本文介绍了使用C#构建一个简单的聊天室应用,包括服务器和客户端的实现。通过对话框和按钮操作,实现了服务器的启动、关闭以及消息的接收和发送。在本地测试成功后,将服务器部署到腾讯云服务器,通过外网IP地址实现了远程连接和通信。此程序展示了基础的TCP Socket通信原理和异步编程技巧。
摘要由CSDN通过智能技术生成

首先,我们先新建一个C#项目,并生成一个对话框一个按钮。可以通过这个对话框把接收到的消息显示出来。然后可以通过这个按钮来部署服务器与建立连接:

在这里插入图片描述
在这里插入图片描述
就先使用这样一个朴实无华的界面,然后按钮下面的逻辑代码其实和前面介绍的就很像了,以下是包含注释的代码实现:

服务器:
namespace SocketServer
{
    public partial class Form1 : Form
    {
        Socket server;
        Socket accept;
        public Form1()
        {
            InitializeComponent();
        }

        private void btnBind_Click(object sender, EventArgs e)
        {
            Button btn = sender as Button;
            if (btn.Text == "Bind")
            {
                //绑定,启动异步接受连接
                try
                {
                    int port = int.Parse(tbPort.Text);      //输入端口号
                    IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, port);      //注意服务器不用输入IP,直接用IPAddress.Any即可
                    server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);   //建立套接字
                    server.Bind(endpoint);      //服务器socket进行绑定
                    server.Listen(10);          //开启监听队列
                    server.BeginAccept(AcceptCallback, server);     //用异步方法来开始接受连接请求,并用回调函数       
                    btn.Text = "Close";
                }
                catch
                { }
            }
            else if (btn.Text == "Close")
            {
                server.Close();
                btn.Text = "Bind";
            }
        }
        private void AcceptCallback(IAsyncResult ar)
        {
            Socket sock = ar.AsyncState as Socket;
            try
            {
                accept = sock.EndAccept(ar);
                //得到连接套接字accept,这是发起请求的套接字,用于往回发消息
                StateObject so = new StateObject(accept);
                this.BeginInvoke(new ThreadStart(() =>      //通过代理来访问对话框,由于对话框是在主线程建立的,因此需要通过代理访问。
                {                                           //有兴趣的读着可以自行了解有关线程的知识   
                    this.tbMsg.AppendText("\t收到连接:" + so.endpoint + "\n");
                }));
                accept.BeginReceive(so.buffer, 0, so.buffer.Length, SocketFlags.None, ReceiveCallback, so);     //开始接受消息
                server.BeginAccept(AcceptCallback, server);           //重新调用Accept,在回调函数中调用本身,形成死循环,一直在等待请求
            }
        }
        private void ReceiveCallback(IAsyncResult ar)
        {
            StateObject so = ar.AsyncState as StateObject;      //存储连接信息的结构体,定义在下面
            try
            {
                int length = so.sock.EndReceive(ar);        //接受信息长度
                string recvstr = System.Text.Encoding.UTF8.GetString(so.buffer, 0, length);     //反编码,从流数据编码回字符串形式
                this.BeginInvoke(new ThreadStart(() =>          //同上
                {
                    this.tbMsg.AppendText(so.endpoint + ":" + recvstr +  "\n");     //输出信息
                }));
                so.buffer = new byte[1024];     //接受数据的存储信息
                so.sock.BeginReceive(so.buffer, 0, so.buffer.Length, SocketFlags.None, ReceiveCallback, so);       //在回调函数再次调用,形成死循环
            }
        }

        private void btnSend_Click(object sender, EventArgs e)        //服务器主动发送聊天信息
        {
            if (server != null)
            {
                byte[] sendbytes = System.Text.Encoding.UTF8.GetBytes(this.tbMsgSend.Text);
                accept.Send(sendbytes);
                this.tbMsg.AppendText("我:" + this.tbMsgSend.Text + "\n");
                this.tbMsgSend.Clear();
            }
		} 
    }
    public class StateObject
    {
        public byte[] buffer = new byte[1024];
        public Socket sock = null;
        public string endpoint;
        public StateObject(Socket sock)
        {
            this.sock = sock;
            endpoint = sock.RemoteEndPoint.ToString();
        }
    }
}
客户端
namespace SocketClient
{
    public partial class Form1 : Form
    {
        Socket client;
        public Form1()
        {
            InitializeComponent();
        }
        private void btnConnect_Click(object sender, EventArgs e)
        {
            int port = int.Parse(tbPort.Text);      //输入端口号
            IPAddress ipaddress = IPAddress.Parse(tbIP.Text);       //输入IP地址
            IPEndPoint endpoint = new IPEndPoint(ipaddress, port);  //IP地址与端口号唯一标识主机
            if (client == null)
            {
                //开始异步连接
                client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                client.BeginConnect(endpoint, ConnectCallback, client);
            }
        }
        private void ConnectCallback(IAsyncResult ar)   //一部连接回调函数
        {
            Socket sock = ar.AsyncState as Socket;
            try
            {
                sock.EndConnect(ar);
                this.BeginInvoke(new ThreadStart(() =>  //通过代理来访问对话框,由于对话框是在主线程建立的,因此需要通过代理访问。
                {                                       //有兴趣的读着可以自行了解有关线程的知识
                    this.tbMsg.AppendText("\t连接成功:)\n");
                }));
                StateObject so = new StateObject(sock); //记录服务器信息,类的定义如下
                sock.BeginReceive(so.buffer, 0, so.buffer.Length, SocketFlags.None, ReceiveCallback, so);   //开始接受信息的异步方法

            }
        }
        private void ReceiveCallback(IAsyncResult ar)       //接收消息的回调函数
        {
            StateObject so = ar.AsyncState as StateObject;
            try
            {
                int length = so.sock.EndReceive(ar);    //取得接受信息长度
                string recvmsg = System.Text.Encoding.UTF8.GetString(so.buffer, 0, length);    //对发送消息进行反编码,从流数据编码回字符串
                this.BeginInvoke(new ThreadStart(() =>
                {   
                    this.tbMsg.AppendText(so.remotepoint + ":" + recvmsg + "\n");       //接收消息提示信息
                }));
                so.buffer = new byte[1024];     //接收消息的存储空间
                so.sock.BeginReceive(so.buffer, 0, so.buffer.Length, SocketFlags.None, ReceiveCallback, so);    //再次开始接收消息,在回到函数调用产生死循环

            }
        }
        private void btnSend_Click(object sender, EventArgs e)  //用户发送信息给服务器
        {
            if (client != null)
            {
                byte[] sendbytes = System.Text.Encoding.UTF8.GetBytes(this.tbMsgSend.Text);     //编码,从字符串编码为流数据
                client.Send(sendbytes);     //发送流数据
                this.tbMsg.AppendText("我:" + this.tbMsgSend.Text + "\n");       //写入文本框
                this.tbMsgSend.Clear();     
            }
        }
    public class StateObject
    {
        public byte[] buffer = new byte[1024];
        public Socket sock;
        public string remotepoint;
        public StateObject(Socket sock)
        {
            this.sock = sock;
            this.remotepoint = sock.RemoteEndPoint.ToString();
        }
    }
}

这两个简单的程序即可生成相应的exe文件,我们可以打开刚才准备好的腾讯云服务器。而window系统的好处就是可以很自由的使用,例如我可以下载一个QQ然后用QQ把我们的程序发过去,事实上我一直都是这样做的。

程序在本机测试时就可以使用127.0.0.1这个虚拟网络IP地址,这个地址是单机模拟接入互联网的,可以得到如下的运行结果:

在这里插入图片描述
可见上面简单的程序的确可以实现两个程序进行交流。现在我们尝试把服务器程序放到我们真正的服务器上,看看效果如何。

在这里插入图片描述
如此通过QQ把程序发给自己的小号,然后在服务器上进行部署,同时用客户端程序在本地计算机进行连接:

在这里插入图片描述
上图中,左边是远程服务器虚拟机,右边是本地,可以看到通过输入对应服务器的IP地址以及端口号:129.204.252.166:8000可以很好的连接服务器,从而可以进行聊天室功能的简单通话。而在服务器端可以看到在笔者家中的外网IP地址是120.230.131.229,不再是之前的127.0.0.1了,说明我们成功通过外网实现了程序的连接。一个简单的聊天室功能就可以实现了。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值