文章目录

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

简述

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

Socket原理讲解

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

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

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

因此,

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

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

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

功能演示视频(b站)

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

准备工作

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

说明:

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

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

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

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

服务器端

服务器端界面

服务器端代码

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){//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 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(){//客户端链接服务器端//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一个自己的聊天室,这篇博客主要是让大家了解怎么实现客户端-服务器端-客户端的网络通信。

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

  1. 【计算机网络】Socket聊天室程序

    计算机网络第一次实验报告 实验名称:Socket聊天室程序 实验内容 使用流式Socket设计聊天协议,聊天信息带有时间标签和类型标签,本报告中将说明交互消息的类型.语法.语义.时序等具体的消息处理方 ...

  2. 基于C#的socket聊天室(附源码)

    基于C#-socket聊天室 前言 源代码:https://gitee.com/TL0902/term/blob/master/C%23%E8%81%8A%E5%A4%A9%E5%AE%A4/Tcha ...

  3. Linux socket聊天室

    目录 一.运行效果 1.分别编译客户端和服务端代码 2.运行 3.使用效果 二.代码 chat.h 服务端代码 客户端代码 一.运行效果 1.分别编译客户端和服务端代码 gcc client.c -o ...

  4. java仿聊天室项目总结_Java团队课程设计-socket聊天室(个人总结)

    Java团队课程设计-socket聊天室(个人总结) 一.团队课程设计博客链接 二.本人负责模块或任务说明 任务1 服务端对socket线程的接受以及对客户端的数据转发操作 任务2 数据库的查找,添加 ...

  5. java仿聊天室项目总结_Java团队课程设计-socket聊天室(Day4总结篇)

    Java团队课程设计-socket聊天室(Day4总结篇) 团队名称: ChatRoom 项目git地址: git提交记录(仅截取部分): 面向对象设计包图.类图 包图 UML类图 总结: 首先总结一 ...

  6. 从0实现基于Linux socket聊天室-实现聊天室的公聊、私聊功能-4

    前面文章链接如下: <从0实现基于Linux socket聊天室-多线程服务器模型-1> <从0实现基于Linux socket聊天室-多线程服务器一个很隐晦的错误-2> &l ...

  7. java socket 聊天室_java利用Socket实现聊天室功能实例

    导读热词 最近研究了下Java socket通信基础,利用代码实现了一个简单的多人聊天室功能,现把代码共享下,希望能帮到有兴趣了解的人. 目录结构: ChatClient: package com.p ...

  8. linux下多进程聊天室,从0实现基于Linux socket聊天室-多线程服务器模型-1

    前言 Socket在实际系统程序开发张中,应用非常广泛,也非常重要.实际应用中服务器经常需要支持多个客户端连接,实现高并发服务器模型显得尤为重要.高并发服务器从简单的循环服务器模型处理少量网络并发请求 ...

  9. 项目:畅所欲言聊天室---网页IM通信聊天室

    文章目录 项目名称 项目描述 开发环境 设计思路 详细设计 数据库模块设计 安装 mariadb 服务 更改配置 用户信息表设计 使用自己的数据管理模块对数据库的访问 Mysql所需接口 业务处理模块 ...

最新文章

  1. 斯坦福大学报告称中国AI论文引用率首超美国!但李国杰院士也发文灵魂拷问!...
  2. Python:C语言扩展
  3. java 查找链表中间元素_如何在Java中一次性查找Java中链表的中间元素
  4. android c 11 编译,Android NDK r9b和编译C 11
  5. 数据自治开放的软件开发和运行环境
  6. 2012微软校园招聘笔试题
  7. php的函数是谁写的,一个用PHP写的中文分词函数
  8. mysql b tree索引原理_MySQL中B+Tree索引原理
  9. “乌木”到底是何物?它是怎样形成的?为何总能卖出天价?
  10. make clean、make mrproper、make distclean的区别【转】
  11. C# 表格跨行和跨列应用实例
  12. 来自微信官方:微信支付跨平台软件架构首次曝光
  13. centos7 redis配置
  14. 二类查询(中兴笔试)
  15. 学渣上手 LaTeX 完成毕业论文
  16. 如何利用ORIGIN拟合曲线
  17. 【论文阅读】深度学习与多种机器学习方法在不同的药物发现数据集进行对比
  18. OCP大会 | 腾讯开放光网络平台(附PDF)
  19. i7服务器cpu型号推荐,CPU型号那么多 详细数据教你选_键鼠新闻-中关村在线
  20. 【Web3 系列开发教程——创建你的第一个 NFT(9)】如何在手机钱包里查看你的 NFT

热门文章

  1. DSL element 'useProguard' is obsolete and will be removed soon. Use 'android.enableR8' in gradle.pro
  2. socket网络编程封装错误处理函数
  3. Docker入门到专精08-Docker Compose实战
  4. my ReadHumor work / youmo
  5. 超好用的八款办公软件,你的电脑都装了吗?
  6. 洛谷 P2862 [USACO06JAN]把牛Corral the Cows
  7. 零信任牢不可破,首届CSA西塞杯攻防挑战赛圆满成功
  8. 全网最全面的npm包管理学习
  9. 文梳理世界11国家和地区ICO政策:监管趋严,展现定义证券倾向
  10. 苹果怎么找回删除的照片?2021最新教程