C# + Socket实现公网聊天(C#大作业)
|字数总计:2.4k|阅读时长:10分钟|阅读量:
[toc]
简述
关于Socket的原理我就不在这里赘述了,有大佬已经作详细的说明了:
Socket原理讲解
因为网上大多介绍的是在一台PC端使用虚拟服务器和本机进行通信,本质还是内网通信。
这里要介绍的是怎么用Socket进行公网通信,也就是在不同的局域网之间通信。其实代码实现和内网通信大差不差,重点区别在于创建监听Socket时,绑定或连接的IP的正确性显得尤为重要,一旦设置错误,连接就会失败!
在这里先简单讲一下计网知识,对于服务器来说,在它和同一内网的设备看来,它的IP是内网IP;对于处在不同局域网的设备看来,它的IP是公网IP。
因此,
服务器端:因为是要放到服务器上的,创建监听Socket用的IP为服务器自身的内网IP。
客户端:用Socket连接时,要连接服务器端的公网IP。
接下来我们直接上结果展示和代码吧!
功能演示视频(b站)
C#+Socket 聊天室(公网通信 客户端-服务器端-客户端)
准备工作
- 一台云服务器(推荐阿里云、腾讯云等大厂的,轻量最低配即可),windows系统的,安全组设置开放如下端口:

说明:
服务器安全组放通3389端口,才能进行远程登录。
再放通我们要用来进行通信的50000端口。
把写好的服务器端程序形成的可执行文件复制到服务器启动即可。
- 一台或多台PC(可实现多PC同时在线聊天)
服务器端
服务器端界面

服务器端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
| using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Net; using System.Net.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) { Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPAddress ip = IPAddress.Parse(txtIP.Text); IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text)); 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"); } #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]; 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); } } } #endregion private void ServerForm_Load(object sender, EventArgs e) { Control.CheckForIllegalCrossThreadCalls = false; } } } class objClass { public Socket objSocket; public byte[] objData; }
|
客户端
客户端界面

客户端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
| using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Net; using System.Net.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() { Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); ClientSocekt = socket; try { IPAddress iPAddress = IPAddress.Parse("121.4.211.53"); socket.Connect(iPAddress, 50000); } catch (Exception ex) { MessageBox.Show("连接失败,请重新连接"); return; } ShowMsg("连接服务器成功!"); 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; } #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); 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; } } #endregion private 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]; 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一个自己的聊天室,这篇博客主要是让大家了解怎么实现客户端-服务器端-客户端的网络通信。
工程文件下载
链接:https://pan.baidu.com/s/1vl7I0Rk0zkgJUmLWRLuIdQ
提取码:qy8w