【总结】学习Socket编写的聊天室小程序

1.前言

在学习Socket之前,先来学习点网络相关的知识吧,自己学习过程中的一些总结,Socket是一门很高深的学问,本文只是Socket一些最基础的东西,大神请自觉绕路。

传输协议

TCP:Transmission Control Protocol 传输控制协议TCP是一种面向连接(连接导向)的、可靠的、基于字节流的运输层(Transport layer)通信协议。

特点:

面向连接的协议,数据传输必须要建立连接,所以在TCP中需要连接时间。

传输数据大小限制,一旦连接建立,双方可以按统一的格式传输大的数据。

一个可靠的协议,确保接收方完全正确地获取发送方所发送的全部数据。

说到TCP就不得不说经典的三次握手。

在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接

第一次握手:客户端尝试连接服务器,向服务器发送syn包(同步序列编号Synchronize Sequence Numbers),syn=j,客户端进入SYN_SEND状态等待服务器确认

第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态

第三次握手:第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手

UDP: User Datagram Protocol的简称, 中文名是用户数据包协议,是 OSI 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。

特点:

每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。

UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。

UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方。

TCP协议:就好比两个电话机 通过电话线进行数据交互的格式约定

HTTP协议:就好比两个人 通过电话机 说话的语法。

(1)公认端口(WellKnownPorts):从0到1023,它们紧密绑定(binding)于一些服务。通常这些端口的通讯明确表明了某种服务的协议。例如:80端口实际上总是HTTP通讯。

(2)注册端口(RegisteredPorts):从1024到49151。它们松散地绑定于一些服务。也就是说有许多服务绑定于这些端口,这些端口同样用于许多其它目的。例如:许多系统处理动态端口从1024左右开始。

(3)动态和/或私有端口(Dynamicand/orPrivatePorts):从49152到65535。理论上,不应为服务分配这些端口。实际上,机器通常从1024起分配动态端口。

OSI网络7层模型

TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。

UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。

应用层 (Application):应用层是个很广泛的概念,有一些基本相同的系统级 TCP/IP 应用以及应用协议,也有许多的企业商业应用和互联网应用。

传输层 (Transport):传输层包括 UDP 和 TCP,UDP 几乎不对报文进行检查,而 TCP 提供传输保证。

网络层 (Network):网络层协议由一系列协议组成,包括 ICMP、IGMP、RIP、OSPF、IP(v4,v6) 等。

链路层 (Link):又称为物理数据网络接口层,负责报文传输。

IP地址

每台联网的电脑都有一个唯一的IP地址。

长度32位,分为四段,每段8位,用十进制数字表示,每段范围 0 ~ 255

特殊IP:127.0.0.1 用户本地网卡测试

版本:V4(32位) 和 V6(128位,分为8段,每段16位)

端口

在网络上有很多电脑,这些电脑一般运行了多个网络程序。每种网络程序都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的网络程序。

常用端口:21 FTP  ,25 SMTP  ,110 POP3  ,80 HTTP , 443 HTTPS

有两种常用Socket类型:

流式Socket(STREAM):
是一种面向连接的Socket,针对于面向连接的TCP服务应用,安全,但是效率低

数据报式Socket(DATAGRAM):
是一种无连接的Socket,对应于无连接的UDP服务应用.不安全(丢失,顺序混乱,在接收端要分析重排及要求重发),但效率高.

说了那么多,让我们来看看socket在网络7层协议中的位置。如下图所示

2.聊天室原理

Socket 流式(服务器端和客户端

服务器端的Socket(至少需要两个)

一个负责接收客户端连接请求(但不负责与客户端通信)

每成功接收到一个客户端的连接便在服务端产生一个对应的负责通信的Socket

在接收到客户端连接时创建.

为每个连接成功的客户端请求在服务端都创建一个对应的Socket(负责和客户端通信).

客户端的Socket

客户端Socket

必须指定要连接的服务端IP地址和端口。

通过创建一个Socket对象来初始化一个到服务器端的TCP连接

Socket的通讯过程

服务器端:

申请一个socket

绑定到一个IP地址和一个端口上

开启侦听,等待接授连接

客户端:

申请一个socket

连接服务器(指明IP地址和端口号)

l服务器端接到连接请求后,产生一个新的socket(端口大于1024)与客户端建立连接并进行通讯,原监听socket继续监听。

Socket常用的一些类和方法

IPAddress类:包含了一个IP地址

IPEndPoint类:包含了一对IP地址和端口号

Socket (): 创建一个Socket

Bind(): 绑定一个本地的IP和端口号(IPEndPoint)

Listen(): 让Socket侦听传入的连接尝试,并指定侦听队列容量

Connect(): 初始化与另一个Socket的连接

Accept(): 接收连接并返回一个新的socket

Send(): 输出数据到Socket

Receive(): 从Socket中读取数据

Close(): 关闭Socket (销毁连接)

3.聊天室代码

服务器端代码:

using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Server
{
    using System.Net.Sockets;
    using System.Net;
    using System.Threading;
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            TextBox.CheckForIllegalCrossThreadCalls = false;
        } 


        //服务端 监听套接字
        Socket socketWatch = null;
        //服务端 监听线程
        Thread threadWatch = null;
        //字典集合:保存 通信套接字
        Dictionary<string, Socket> dictCon = new Dictionary<string, Socket>(); 
        private void btnStartListen_Click(object sender, EventArgs e)
        {

            try
            {
                //1.创建监听套接字 使用 ip4协议,流式传输,TCP连接
                socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //2.绑定端口
                //2.1获取网络节点对象
                IPAddress address = IPAddress.Parse(txtIP.Text);
                IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text));
                //2.2绑定端口(其实内部 就向系统的 端口表中 注册 了一个端口,并指定了当前程序句柄)
                socketWatch.Bind(endPoint);
                //2.3设置监听队列
                socketWatch.Listen(10);
                //2.4开始监听,调用监听线程 执行 监听套接字的 监听方法
                threadWatch = new Thread(WatchConnecting);
                threadWatch.IsBackground = true;
                threadWatch.Start();
                ShowMsg("枫伶忆,服务器启动啦!");
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                throw;
            }

        } 
        void WatchConnecting()
        {
            //2.4开始监听:此方法会阻断当前线程,直到有 其它程序 连接过来,才执行完毕
            Socket sokMsg = socketWatch.Accept();
            //将当前连接成功的 【与客户端通信的套接字】 的 标识 保存起来,并显示到 列表中
            //将 远程客户端的 ip和端口 字符串 存入 列表
            this.lbOnline.Items.Add(sokMsg.RemoteEndPoint.ToString());
            //将 服务端的通信套接字 存入 字典集合
            dictCon.Add(sokMsg.RemoteEndPoint.ToString(), sokMsg);

            ShowMsg("有客户端连接了!");
            //2.5创建 通信线程
            Thread thrMsg = new Thread(ReceiveMsg);
            thrMsg.IsBackground = true;
            thrMsg.Start(sokMsg);
        }
        void ReceiveMsg(object obj)
        {
            try
            {
                Socket sokMsg = obj as Socket;
                //3.通信套接字 监听 客户端的 消息
                //3.1创建 消息缓存区
                byte[] arrMsg = new byte[1024 * 1024 * 1];
                while (isReceive)
                {
                    //3.2接收客户端的消息 并存入 缓存区,注意:Receive方法也会阻断当前的线程
                    sokMsg.Receive(arrMsg);
                    //3.3将接收到的消息 转成 字符串
                    string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg);
                    //3.4将消息 显示到 文本框
                    ShowMsg(strMsg);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                throw;
            }
        }
        void ShowMsg(string strmsg)
        {
            this.txtShow.AppendText(strmsg + "\r\n");
        } 
        private void btnSend_Click_1(object sender, EventArgs e)
        {

            string strClient = this.lbOnline.Text;
            if (string.IsNullOrEmpty(strClient))
            {
                MessageBox.Show("请选择你要发送消息的客户端");
                return;
            }
            if (dictCon.ContainsKey(strClient))
            {
                string strMsg = this.txtInput.Text.Trim();
                ShowMsg("\r\n向客户端【" + strClient + "】说:" + strMsg);

                //使用 指定的 通信套接字 将 字符串 发送到 指定的客户端
                byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
                dictCon[strClient].Send(arrMsg);
            }
            this.txtInput.Text = "";
        }
     }

}

客户端代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Client
{
    using System.Net.Sockets;
    using System.Net;
    using System.Threading;
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            TextBox.CheckForIllegalCrossThreadCalls = false;
        } 


       //客户端 通信套接字
        Socket socketMsg = null;
        //客户端 通信线程
        Thread threadMsg = null;

        bool isRec = true;//标记任务
        private void btnConnect_Click(object sender, EventArgs e)
        {
            try
            {
                //1.创建监听套接字 使用 ip4协议,流式传输,TCP连接
                socketMsg = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //2.获取要连接的服务端 节点
                //2.1获取网络节点对象
                IPAddress address = IPAddress.Parse(txtIP.Text);
                IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text));
                //3.向服务端 发送链接请求
                socketMsg.Connect(endPoint);
                ShowMsg("连接服务器成功~~!");
                //4.开启通信线程
                threadMsg = new Thread(RecevieMsg);
                threadMsg.IsBackground = true;
                threadMsg.Start();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                throw;
            }

        } 
        void RecevieMsg()
        {
            try
            {
                //3.1创建 消息缓存区
                byte[] arrMsg = new byte[1024 * 1024 * 1];
                while (isRec)
                {
                    socketMsg.Receive(arrMsg);
                    string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg);
                    ShowMsg("\r\n服务器说:" + strMsg);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                throw;
            }
        } 
        private void btnSend_Click_1(object sender, EventArgs e)
        {
            string strMsg = this.txtInput.Text.Trim();
            byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
            socketMsg.Send(arrMsg);
            this.txtInput.Text = "";
        }
        void ShowMsg(string strmsg)
        {
            this.txtShow.AppendText(strmsg + "\r\n");
        } 
    }

}

最终的效果图如下:

4.注意

至少要定义一个要连接的远程主机的IP和端口号。

端口号必须在 1 和 65535之间,最好在1024以后。

要连接的远程主机必须正在监听指定端口,也就是说你无法随意连接远程主机。

如:

IPAddress addr = IPAddress.Parse("127.0.0.1");

IPEndPoint endp = new IPEndPoint(addr, 8989);

服务端先绑定:serverWelcomeSocket.Bind(endp)

客户端再连接:clientSocket.Connect(endp)

一个Socket一次只能连接一台主机。

Socket关闭后无法再次使用。

每个Socket对象只能一台远程主机连接. 如果你想连接到多台远程主机, 你必须创建多个Socket对象

5.扩展

l实现传送文件

如果接收数据是文件还是文字?

设计"协议":

把要传递的字节数组前面都加上一个字节做为标识。0:表示文字  1:表示文件

即:文字:  0+文字(字节数组表示)

文件:1+文件的二进制信息

比如Socket的分包,黏包问题,异步编程在后续的文章继续讨论

时间: 2024-10-01 07:08:11

【总结】学习Socket编写的聊天室小程序的相关文章

关于Socket编写简单聊天工具的总结(原创)

这段时间再看socket编程,虽然现在是刚刚接触,但是还是忍不住想写一篇总结,来激励自己努力学习,写的不好的地方,还请大家指教啊! 下面针对一个简单的发送消息和文件的程序说说吧.   首先是服务器需要准备二个Socket和二个Thread如下: //和客户机进行通信 private Socket sckCommit; //监听客户机 private Socket sckListen; private Thread thdListen; private Thread thdCommit; 对客户机

java socket控制台版本聊天室程序源码下载

原文:java socket控制台版本聊天室程序源码下载 代码下载地址:http://www.zuidaima.com/share/1550463257578496.htm java socket控制台版本聊天室程序源码下载,学习的时候写的,适合学习java基础 java网络编程基础用 标签: java socket 控制台 聊天室 源码话题: 网络编程 java socket控制台版本聊天室程序源码下载,布布扣,bubuko.com

Python Socket 编程——聊天室示例程序

原文:Python Socket 编程--聊天室示例程序 上一篇 我们学习了简单的 Python TCP Socket 编程,通过分别写服务端和客户端的代码了解基本的 Python Socket 编程模型.本文再通过一个例子来加强一下对 Socket 编程的理解. 聊天室程序需求 我们要实现的是简单的聊天室的例子,就是允许多个人同时一起聊天,每个人发送的消息所有人都能接收到,类似于 QQ 群的功能,而不是点对点的 QQ 好友之间的聊天.如下图: 图来自:http://www.ibm.com/de

基于Socket的Android聊天室

1        基于Socket的Android聊天室 Socket通信是网络通信中最常用的技术之一,通过Socket建立的可靠连接,可以让多个终端与服务器保持通信,最典型的应用是建立一个多人聊天程序.本实例使用ServerSocket建立聊天服务器.将服务器端所有的通讯线程保存到一个集合当中,当有用户发来数据,则转发给所有用户,实现聊天室效果.Android端通过使用Socket建立客户端链接,并且在AsyncTask中执行网络读写的任务,将用户输入的内容发送到服务器,并接收服务器发来的数据

学习Android之第八个小程序文件保存(Notification、AndroidTestCase)

效果图:       .java文件有MainActivity.java.FileService.java.FileServiceTest.java, .xml文件有activity_main.xml. 本次注重AndroidTestCase类的使用,在开发中非常实用.用于测试某一功能. 使用AndroidTestCase类,有如下的要求: 1.在AndroidManifest.xml文件中,<manifest></manifest>中添加如下: <instrumentati

Linux网络编程--聊天室客户端程序

聊天室客户端程序 #define _GNU_SOURCE 1 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <string.h> #in

node.js + socket.io实现聊天室一

前段时间,公司打算在社区做一个聊天室.决定让我来做.本小白第一次做聊天类功能,当时还想着通过ajax请求来实现.经过经理提示,说试试当前流行的node.js 和socket.io来做.于是就上网学习研究起来. 首先,先看一下node.js.本小白当时还以为是一个什么js的框架或者类库.结果才知道,原来完全不是那么回事. node.js 诞生于2009年,是一种轻量级的web服务.如果查看node.js的官网,真的非常简答,简短的几行代码,就搭建了一个web服务. var http = requi

使用 Socket.IO 开发聊天室

前言 Socket.IO 是一个用来实现实时双向通信的框架,其本质是基于 WebSocket 技术. 我们首先来聊聊 WebSocket 技术,先设想这么一个场景: · 用户小A,打开了某个网站的充值界面,该界面上有一个付款的二维码. · 当小A 用某宝的 APP 扫码付款之后,网页要自动跳转到付款成功的界面.最简单的方法就是网页每隔一段时间就请求一次服务器--"怎么样?那货付款没有?","怎么样?还没付吗?","怎么样?这次总该付了吧". ·

node.js学习笔记之简洁聊天室

近来刚开始学node.js,基础,深入,express,koa,react,keystone,mongress,等等各种各样的关于Node.js的东西,每个都学的迷迷糊糊的,原因在于没有练习,感觉学的每一个知识点就像一块拼图,最终却没有拼到一起,于是就想多写几个小项目练练手,于是,就有了本文... ----------------------------------我是分割线----------------------------------------------- 恩,就按我写代码的顺序来记