1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > C#+Socket 聊天室(实现公网通信 客户端-服务器端-客户端)

C#+Socket 聊天室(实现公网通信 客户端-服务器端-客户端)

时间:2020-01-16 01:27:42

相关推荐

C#+Socket 聊天室(实现公网通信 客户端-服务器端-客户端)

文章目录

简述功能演示视频(b站)准备工作服务器端服务器端界面服务器端代码客户端客户端界面客户端代码总结

简述

关于Socket的原理我就不在这里赘述了,有大佬已经作详细的说明了:

Socket原理讲解

因为网上大多介绍的是在一台PC端使用虚拟服务器和本机进行通信,本质还是内网通信。

这里要介绍的是怎么用Socket进行公网通信,也就是在不同的局域网之间通信。其实代码实现和内网通信大差不差,重点区别在于创建监听Socket时,绑定或连接的IP的正确性显得尤为重要,一旦设置错误,连接就会失败!

在这里先简单讲一下计网知识,对于服务器来说,在它和同一内网的设备看来,它的IP是内网IP;对于处在不同局域网的设备看来,它的IP是公网IP。

因此,

服务器端:因为是要放到服务器上的,创建监听Socket用的IP为服务器自身的内网IP。

客户端:用Socket连接时,要连接服务器端的公网IP。

接下来我们直接上结果展示和代码吧!

功能演示视频(b站)

C#+Socket 聊天室(公网通信 客户端-服务器端-客户端)

准备工作

一台云服务器(推荐阿里云、腾讯云等大厂的,轻量最低配即可),windows系统的,安全组设置开放如下端口:

说明:

服务器安全组放通3389端口,才能进行远程登录。

再放通我们要用来进行通信的50000端口。

把写好的服务器端程序形成的可执行文件复制到服务器启动即可。

一台或多台PC(可实现多PC同时在线聊天)

服务器端

服务器端界面

服务器端代码

using System;using System.Collections.Generic;using ponentModel;using System.Data;using System.Drawing;using System.Linq;using ;using .Sockets;using System.Text;using System.Threading;using System.Threading.Tasks;using System.Windows.Forms;namespace Socket_Server{public partial class ServerForm : Form{List<Socket> ClientSocketList = new List<Socket>();public ServerForm(){InitializeComponent();}private void btnStart_Click(object sender, EventArgs e){//1. 创建一个负责监听的SocektSocket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//绑定端口IP(服务器内网)//创建IP地址和端口号对象IPAddress ip = IPAddress.Parse(txtIP.Text);IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));//让负责监听的Socekt绑定IP地址和端口号try{socketWatch.Bind(point);}catch (Exception ex){MessageBox.Show("无法启动服务器:" + ex.Message);return;}btnStart.Enabled = false;//设置监听队列socketWatch.Listen(20);//创建一个新线程执行监听程序Thread th = new Thread(Listen);th.IsBackground = true;th.Start(socketWatch);}private void ShowMsg(string str){txtLog.AppendText(str);txtLog.AppendText("\r\n");}//这个Listen是自定义的方法#region 监听线程private void Listen(object o){Socket socketWatch = o as Socket;ShowMsg("服务器端开始接收客户端的连接!");while (true){Socket proxSocket = socketWatch.Accept(); //阻塞进程直到有客户端连接ShowMsg(string.Format("客户端:{0}上线了!", proxSocket.RemoteEndPoint.ToString()));//开启一个不断接收客户端的新线程Thread th = new Thread(ReceiveData);th.IsBackground = true;th.Start(proxSocket);}}#endregion//服务器端不停地接收客户端发送过来的消息private void ReceiveData(object o){Socket proxsocket = o as Socket;SendMsgForAll(string.Format("客户端:{0}上线了!", proxsocket.RemoteEndPoint.ToString()));ClientSocketList.Add(proxsocket);while (true){byte[] data = new byte[1024 * 1024];//当客户端连接成功后,服务器应该接收客户端发来的消息//获取收到的数据的字节数int len = 0;try{len = proxsocket.Receive(data, 0, data.Length, SocketFlags.None);}catch (Exception ex){if (ClientSocketList.Contains(proxsocket)){//异常退出ClientSocketList.Remove(proxsocket);ShowMsg(string.Format("客户端:{0}非正常退出!", proxsocket.RemoteEndPoint.ToString()));SendMsgForAll(string.Format("客户端:{0}非正常退出!", proxsocket.RemoteEndPoint.ToString()));return;}}//客户端正常退出if (len <= 0){if (ClientSocketList.Contains(proxsocket)){ClientSocketList.Remove(proxsocket);ShowMsg(string.Format("客户端[{0}]正常退出!", proxsocket.RemoteEndPoint.ToString()));SendMsgForAll(string.Format("客户端[{0}]正常退出!", proxsocket.RemoteEndPoint.ToString()));}return;}#region 接收到的是字符串if (data[0] == 1){//开启字符串转发线程objClass stringobj = new objClass();stringobj.objSocket = proxsocket;stringobj.objData = data;Thread stringth = new Thread(new ParameterizedThreadStart(TransReceiveStringAll));stringth.Start(stringobj);}#endregion#region 接收到的是“戳一戳”else if (data[0] == 2){foreach (var proxSocket in ClientSocketList){if (proxsocket.Connected&&proxSocket!=proxsocket){proxSocket.Send(new byte[] {2 }, SocketFlags.None);}}}#endregion}}#region 转发接收到的字符串private void TransReceiveStringAll(object o){objClass result = o as objClass;Socket proxSocket = result.objSocket;byte[] data = result.objData;string strTmp = ProcessReceiveString(data);strTmp = string.Format("客户端[" + proxSocket.RemoteEndPoint.ToString() +"]"+":"+ strTmp);ShowMsg(strTmp);if (ClientSocketList.Contains(proxSocket)){foreach(Socket socketTmp in ClientSocketList){if(socketTmp != proxSocket){SendMsg(socketTmp, strTmp);}}}}#endregion#region 处理接收到的字符串private string ProcessReceiveString(byte[] data){//把实际的字符串拿到string str = Encoding.Default.GetString(data,1,data.Length-1);return str;}#endregion#region 发送字符串消息private void SendMsg(Socket socketTmp, string Msg){//原始字符串转成字节数组byte[] data = Encoding.Default.GetBytes(Msg);//对原始的数据数组加上协议的头部字节byte[] result = new byte[data.Length + 1];//设置当前的协议头部字节是1:代表字符串result[0] = 1;//把原始的数据放到最终的字节数组里去Buffer.BlockCopy(data, 0, result, 1, data.Length);socketTmp.Send(result, 0, result.Length, SocketFlags.None);}#endregion#region 给所有当前连接上的客户端发送字符串消息private void SendMsgForAll(string Msg){foreach (var socketTmp in ClientSocketList){if (socketTmp.Connected){SendMsg(socketTmp, Msg);}}}#endregion#region 服务器端发送消息private void btnSendMsg_Click(object sender, EventArgs e){ShowMsg("服务器端:"+txtMsg.Text);SendMsgForAll("服务器端:" + txtMsg.Text);txtMsg.Clear();txtMsg.Focus();}#endregion#region 戳一戳private void btnShock_Click(object sender, EventArgs e){foreach (var proxSocket in ClientSocketList){if (proxSocket.Connected){proxSocket.Send(new byte[] {2 }, SocketFlags.None);}}}#endregionprivate void ServerForm_Load(object sender, EventArgs e){Control.CheckForIllegalCrossThreadCalls = false;}}}class objClass{public Socket objSocket;public byte[] objData;}

客户端

客户端界面

客户端代码

using System;using System.Collections.Generic;using ponentModel;using System.Data;using System.Drawing;using System.Linq;using ;using .Sockets;using System.Text;using System.Threading;using System.Threading.Tasks;using System.Windows.Forms;namespace ClientForm{public partial class ClientForm : Form{public Socket ClientSocekt {get; set; }public ClientForm(){InitializeComponent();ConnectInit();}public void ConnectInit(){//客户端链接服务器端//1. 创建Socekt对象Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);ClientSocekt = socket;//2. 链接服务器端try{//填写要连接服务器的公网ip和端口号IPAddress iPAddress = IPAddress.Parse("121.4.211.53");socket.Connect(iPAddress, 50000);}catch (Exception ex){MessageBox.Show("连接失败,请重新连接");return;}ShowMsg("连接服务器成功!");//3. 发送消息 接收消息Thread th = new Thread(ReceiveData);th.IsBackground = true;th.Start(ClientSocekt);}public void ShowMsg(string str){txtLog.AppendText(str);txtLog.AppendText("\r\n");}public void ReceiveData(object o){Socket proxSocket = o as Socket;while (true){byte[] data = new byte[1024 * 1024];//客户端连接成功后,服务器应该接收客户端发来的消息//获取收到数据的字节数int len = 0;try{len = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);}catch (Exception ex){//异常退出try{ShowMsg(string.Format("服务器端非正常退出!"));}catch (Exception ex1){}StopConnect();return;}//服务端正常退出if (len <= 0){try{ShowMsg(string.Format("服务器端正常退出!"));}catch (Exception ex2){}//关闭链接StopConnect();return;}//接收到的数据中的第一个字节 1:字符串,2:闪屏,3:文件#region 接收到的是字符串if (data[0] == 1){string strMsg = ProcessRecieveString(data);ShowMsg(strMsg);}#endregion#region 接收到的是闪屏else if (data[0] == 2){shock();}#endregion}}private void StopConnect(){try{if (ClientSocekt.Connected){ClientSocekt.Shutdown(SocketShutdown.Both);//超过100s未关闭成功则强行关闭ClientSocekt.Close(100);}}catch (Exception ex){}}#region 处理接收到的字符串ProcessRecieveString(byte[] data)public string ProcessRecieveString(byte[] data){//把实际的字符串拿到string str = Encoding.Default.GetString(data, 1, data.Length - 1);return str;}#endregion#region 闪屏方法shock()private void shock(){//把窗体最原始的坐标记住Point oldLocation = this.Location;Random r = new Random();for (int i = 0; i < 50; i++){this.Location = new Point(r.Next(oldLocation.X - 5, oldLocation.X), r.Next(oldLocation.Y, oldLocation.Y));Thread.Sleep(50);this.Location = oldLocation;}}#endregionprivate void btnSendMsg_Click(object sender, EventArgs e){if (ClientSocekt.Connected){ShowMsg("我:"+txtMsg.Text);//原始字符串转成字节数组byte[] data = Encoding.Default.GetBytes(txtMsg.Text);//对原始的数据数组加上协议的头部字节byte[] result = new byte[data.Length + 1];//设置当前的协议头部字节是1:代表字符串result[0] = 1;//把原始的数据放到最终的字节数组里去Buffer.BlockCopy(data, 0, result, 1, data.Length);ClientSocekt.Send(result, 0, result.Length, SocketFlags.None);txtMsg.Clear();txtMsg.Focus();}else{ShowMsg("发送失败,未连接服务器!");}}private void btnShock_Click(object sender, EventArgs e){ClientSocekt.Send(new byte[] {2 }, SocketFlags.None);}private void ClientForm_Load(object sender, EventArgs e){Control.CheckForIllegalCrossThreadCalls = false;}private void ClientForm_FormClosed(object sender, FormClosedEventArgs e){StopConnect();System.Environment.Exit(0);}}}

总结

个人认为公网通信相较于内网通信更具有普遍应用意义,虽然博主是通信专业的学生,但是计网还没深入学习,于是找了各种资料加上自身对计网的理解,终于在内网通信的基础上实现了公网的聊天应用,大家可根据自己的喜好DIY一个自己的聊天室,这篇博客主要是让大家了解怎么实现客户端-服务器端-客户端的网络通信。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。