TCP/IP协议——使用Socket套接字实现

news2024/12/28 19:17:43

目录

Socket

使用Socket实现TCP客户端和服务器的过程

使用Socket搭建TCP服务器

线程优化

向客户端发送消息

连接的断开

客户端主动断开

服务端主动断开

服务器完整的程序

使用Socket编写客户端程序连接TCP服务器


Socket

Socket是一种网络通信协议,它允许不同计算机上的应用程序通过网络进行数据传输和通信。 Socket协议基于TCP/IP协议族,可以使用TCP或UDP协议进行数据传输。在Socket协议中,通信涉及两个主要部分:服务端和客户端。服务端提供服务并等待客户端的连接请求,而客户端发起连接请求并与服务端建立连接后进行数据传输。Socket协议使用‌IP地址和‌端口号来唯一标识网络中的进程或应用程序,通过这种方式,应用程序可以建立与服务端的连接并进行数据的收发

在C#中,一般使用Socket类来完成Tcp、Udp协议的连接和操作,我们使用一个简单的例子学习如何创建一个TCP服务器,以及如何连接TCP服务器进行通讯

使用Socket实现TCP客户端和服务器的过程

使用Socket搭建TCP服务器

[端口号的范围是从0到65535]

我们循序渐进的搭建一个TCP服务器,这是对以下代码块一些参数的简单描述

* AddressFamily:指当前 Socket 的寻址方案
           * InterNetwork->IPV4
          * InterNetworkV6->IPV6
* SocketType:指定当前套接字实例的类型
          * Stream(TCP):支持可靠、双向、基于连接的字节流,而无需复制数据,不保留边界。一个Socket这种类型的通信与单个对等方并在可以开始通信之前需要远程主机的连接
          * Dgram(UDP):支持数据报,即为固定(通常很小)的最大长度的无连接的、不可靠的消息。消息可能会丢失或重复,并且可能不按顺序抵达。一个Socket类型的SocketType.Dgram不需要任何连接之前发送和接收数据,并且可以与多个对等方通信。
* ProtocolType:使用哪种协议类型(TCP/UDP)

// 1.创建 socket 链接
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 2、绑定socket的ip地址和端口号
// IPAddress.Parse():IP地址将字符串转换为IPAddress实例。
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("192.168.107.81"),3030);
socket.Bind(endPoint);
// 3.将 socket 置为监听状态
// backlog:挂起连接队列的最大长度。
socket.Listen(100);
// 4.接收来自客户端的连接请求(创建一个新Socket为新创建的连接)
// 该代码将会卡住进程,直到有客户端连接到当前Socket服务时向下执行
Socket socketClient = socket.Accept();
richTextBox1.Invoke(new Action(() =>
{
    richTextBox1.AppendText($"{socketClient.RemoteEndPoint}已经连接\r\n");
}));

// 5. Receive:从 socket 中读取字符(接收该连接发送的数据)
// 该代码将会卡住进程,直到接收到客户端数据时向下执行
byte[] buffer = new byte[1024];  // 大小按情况而定
int length = socketClient.Receive(buffer);  // 返回本次接收数据的字节数
string value = Encoding.Default.GetString(buffer, 0, length);
richTextBox1.Invoke(new Action(() =>
{
    richTextBox1.AppendText($"接收到{socketClient.RemoteEndPoint}发送的消息:{value}\r\n");
}));

线程优化

在实际运用中可能面临着一下问题

1. 卡线程,导致页面处于假死状态
2. 只能接收一次数据,第二次接收时服务器读不到
3. 只能一个客户端连接

我们可以使用分线程、循环解决,代码优化后如下

private void button1_Click(object sender, EventArgs e)
{
    // 1.创建 socket 链接
    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    // 2、绑定socket的ip地址和端口号
    IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 3030);
    socket.Bind(endPoint);
    // 3.将 socket 置为监听状态
    socket.Listen(100);
    // 4、开启连接
    startSocket(socket);
}
/// <summary>
/// 在分线程中循环监听来自客户端的连接请求
/// </summary>
/// <param name="socket">Socket连接对象</param>
private void startSocket(Socket socket)
{
    // 在分线程中循环监听来自客户端的连接
    Task.Run(() =>
    {
        while (true)
        {
            // 4.接收来自客户端的连接请求(创建一个新Socket为新创建的连接)
            Socket socketClient = socket.Accept();
            richTextBox1.Invoke(new Action(() =>
            {
                richTextBox1.AppendText($"{socketClient.RemoteEndPoint}已经连接\r\n");
            }));
            startRecive(socketClient);
        }
    });
}
/// <summary>
/// 在分线程中循环监听来自客户端的数据
/// </summary>
/// <param name="socket">客户端连接Socket对象</param>
private void startRecive(Socket socket)
{
    Task.Run(() =>
    {
        while (true)
        {
            // 5.Receive:从 socket 中读取字符(接收该连接发送的数据)
            // 该代码将会卡住进程,直到接收到客户端数据时向下执行
            byte[] buffer = new byte[1024];  // 大小按情况而定
            int length = socket.Receive(buffer);  // 返回本次接收数据的字节数
            string value = Encoding.Default.GetString(buffer, 0, length);
            richTextBox1.Invoke(new Action(() =>
            {
                richTextBox1.AppendText($"接收到{socket.RemoteEndPoint}发送的消息:{value}\r\n");
            }));
        }
    });
}

向客户端发送消息

通过调用客户端Socket对象的Send方法可以发送数据到客户端,首先,我们应该将所有的客户端连接都进行保存。修改`startSocket`​代码如下

使用异步监听连接的客户端并且保存:

    Dictionary<string, Socket> socketList = new Dictionary<string, Socket>();
    private void startSocket(Socket socket)
    {
        // 在分线程中循环监听来自客户端的连接
        Task.Run(() =>
        {
            while (true)
            {
                // 接收来自客户端的连接请求(创建一个新Socket为新创建的连接)
                Socket socketClient = socket.Accept();
                // 将客户端连接Socket存储到字典中,以IP和端口为key
                socketList.Add(socketClient.RemoteEndPoint.ToString(), socketClient);
                richTextBox1.Invoke(new Action(() =>
                {
                    richTextBox1.AppendText($"{socketClient.RemoteEndPoint}已经连接\r\n");
                }));
                startRecive(socketClient);
            }
        });
    }

使用循环将需要发送的消息逐个发送给客户端

// 发送信息给客户端
private void sendBtn_Click(object sender, EventArgs e)
{
    byte[] bytes = Encoding.Default.GetBytes(msgTextbox.Text);
    // 循环所有的客户端发送数据(也可以根据IP选择给谁发送)
    foreach (var item in socketList)
    {
        item.Value.Send(bytes);
    }
}

连接的断开

连接的断开分为客户端主动断开、服务器主动断开两种

客户端主动断开

一般情况下,当服务端接收到一个空的数据包时,表示客户端要断开连接。

如果客户端被强制终止,会来不及发送一个空的数据包,我们的代码将会抛出错误,也应断开连接。

我们修改`startRecive`​代码如下

    private void startRecive(Socket socket)
    {
      Task.Run(() =>
      {
        while (true)
        {
          try
          {
            byte[] buffer = new byte[1024];
            int length = socket.Receive(buffer);
            if (length == 0)
            {
                // 从字典中移除当前连接
                socketList.Remove(socket.RemoteEndPoint.ToString());
                richTextBox1.Invoke(new Action(() =>
                {
                    richTextBox1.AppendText($"{socket.RemoteEndPoint}连接已断开\r\n");
                }));
                // 退出循环,停止侦听数据
                break;
            }
            else
            {
                string value = Encoding.Default.GetString(buffer, 0, length);
                richTextBox1.Invoke(new Action(() =>
                {
                    richTextBox1.AppendText($"接收到{socket.RemoteEndPoint}发送的消息:{value}\r\n");
                }));
            }
          }
          catch
          {
              socketList.Remove(socket.RemoteEndPoint.ToString());
              richTextBox1.Invoke(new Action(() =>
              {
                  richTextBox1.AppendText($"{socket.RemoteEndPoint}连接已断开\r\n");
              }));
              // 退出循环,停止侦听数据
              break;
           }
        }
      });
    }
服务端主动断开

服务端断开连接要将所有客户端的连接都断开,关闭代码如下,需修改`startSocket`​方法中的代码

    // 关闭按钮点击
    private void closeBtn_Click(object sender, EventArgs e)
    {
        // 控制按钮状态
        closeBtn.Enabled = false;
        openBtn.Enabled = true;
        // 停止所有客户端连接
        foreach (var item in socketList)
        {
            Socket socket = item.Value;
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
        }
        socketList.Clear(); // 清空字典
        // 关闭客户端socket
        socket.Close();
        socket = null;
    }
    private void startSocket(Socket socket)
    {
        // 在分线程中循环监听来自客户端的连接
        Task.Run(() =>
        {
            while (true)
            {
                try
                {
                    Socket socketClient = socket.Accept();
                    socketList.Add(socketClient.RemoteEndPoint.ToString(), socketClient);
                    richTextBox1.Invoke(new Action(() =>
                    {
                        richTextBox1.AppendText($"{socketClient.RemoteEndPoint}已经连接\r\n");
                    }));
                    startRecive(socketClient);
                }
                catch (Exception ex)
                {  // 当socket被关闭后,Accept会抛出错误,抛出错误时结束循环,关闭线程
                    socket.Close();
                    break;
                }
            }
        });
    }

服务器完整的程序

Socket socket;
Dictionary<string, Socket> socketList = new Dictionary<string, Socket>();

private void button1_Click(object sender, EventArgs e)
{
    // 按钮状态
    openBtn.Enabled = false;
    closeBtn.Enabled = true;
    // 1.创建 socket 链接
    socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    // 2、绑定socket的ip地址和端口号
    IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, int.Parse(textBox2.Text));
    socket.Bind(endPoint);
    // 3.将 socket 置为监听状态
    socket.Listen(100);
    // 4、开启连接
    startSocket(socket);
}
/// <summary>
/// 在分线程中循环监听来自客户端的连接请求
/// </summary>
/// <param name="socket">Socket连接对象</param>
private void startSocket(Socket socket)
{
    // 在分线程中循环监听来自客户端的连接
    Task.Run(() =>
    {
        while (true)
        {
            try
            {
                // 接收来自客户端的连接请求(创建一个新Socket为新创建的连接)
                Socket socketClient = socket.Accept();
                // 将客户端连接Socket存储到字典中,以IP和端口为key
                socketList.Add(socketClient.RemoteEndPoint.ToString(), socketClient);
                addRichTextBox($"{socketClient.RemoteEndPoint}已经连接\r\n");
                startRecive(socketClient);
            }
            catch (Exception ex)
            {
                socket.Close();
                break;
            }
        }
    });
}
/// <summary>
/// 在分线程中循环监听来自客户端的数据
/// </summary>
/// <param name="socket">客户端连接Socket对象</param>
private void startRecive(Socket socket)
{
    string ip = socket.RemoteEndPoint.ToString();
    Task.Run(() =>
    {
        while (true)
        {
            try
            {
                // 5.Receive:从 socket 中读取字符(接收该连接发送的数据)
                // 该代码将会卡住进程,直到接收到客户端数据时向下执行
                byte[] buffer = new byte[1024];  // 大小按情况而定
                int length = socket.Receive(buffer);  // 返回本次接收数据的字节数
                if (length == 0)
                {
                    throw new Exception($"客户端{ip}断开连接"); // 抛出异常进行终止
                }
                else
                {
                    string value = Encoding.Default.GetString(buffer, 0, length);
                    addRichTextBox($"接收到{ip}发送的消息:{value}\r\n");
                }
            }
            catch
            {
                // 从字典中移除当前连接
                socketList.Remove(ip);
                addRichTextBox($"{ip}连接已断开\r\n");
                break;
            }
        }
    });
}

// 发送信息给客户端
private void sendBtn_Click(object sender, EventArgs e)
{
    byte[] bytes = Encoding.Default.GetBytes(msgTextbox.Text);
    // 循环所有的客户端发送数据(也可以根据IP选择给谁发送)
    foreach (var item in socketList)
    {
        item.Value.Send(bytes);
    }
}
// 关闭按钮点击
private void closeBtn_Click(object sender, EventArgs e)
{
    // 控制按钮状态
    closeBtn.Enabled = false;
    openBtn.Enabled = true;
    // 停止所有客户端连接
    foreach (var item in socketList)
    {
        Socket socket = item.Value;
        socket.Shutdown(SocketShutdown.Both);
        socket.Close();
    }
    socketList.Clear(); // 清空字典
    // 关闭客户端socket
    socket.Close();
    socket = null;
}
private void addRichTextBox(string text)
{
    richTextBox1.Invoke(new Action(() =>
    {
        richTextBox1.AppendText(text);
        // 让rich滚动到最下面
        richTextBox1.SelectionStart = richTextBox1.Text.Length;
        richTextBox1.ScrollToCaret();
    }));
}

使用Socket编写客户端程序连接TCP服务器

Socket socket;
private void openBtn_Click(object sender, EventArgs e)
{
    openBtn.Enabled = false;
    closeBtn.Enabled = true;
    string ip = ipTextBox.Text;
    string port = portTextBox.Text;
    // 1.创建 socket 链接
    socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    // 2.链接指定TCP服务器的端口
    socket.Connect(new IPEndPoint(IPAddress.Parse(ip), int.Parse(port)));
    startReceive();
}
private void startReceive()
{
    Task.Run(() =>
    {
        byte[] receiveData = new byte[1024];
        while (true)
        {
            // 3. 客户端接收服务器返回的数据
            try
            {
                int length = socket.Receive(receiveData);
                if(length == 0)
                {
                    addRichTextBox("服务器断开连接");
                    throw new Exception("服务器断开连接");
                }
                string msg = Encoding.Default.GetString(receiveData, 0, length);
                addRichTextBox($"服务器发送了信息:{msg}\r\n");
            }
            catch (Exception ex)
            {
                socket.Close();
                BeginInvoke(new Action(() =>
                {
                    openBtn.Enabled = true;
                    closeBtn.Enabled = false;
                }));
                break;
            }
        }
    });
}
// 4 向服务器发送消息
private void sendBtn_Click(object sender, EventArgs e)
{
    // 把字符串转换为 byte[](批注:一个汉字会变成3个字节。一个数字或字母会变成1个字节;都是一堆数字)
    byte[] bytes = Encoding.Default.GetBytes(msgTextbox.Text);
    socket.Send(bytes);
}
// 关闭客户端连接
private void closeBtn_Click(object sender, EventArgs e)
{
    openBtn.Enabled = true;
    closeBtn.Enabled = false;
    socket.Shutdown(SocketShutdown.Both);
    socket.Close();
}
private void addRichTextBox(string text)
{
    richTextBox1.Invoke(new Action(() =>
    {
        richTextBox1.AppendText(text);
        // 让rich滚动到最下面
        richTextBox1.SelectionStart = richTextBox1.Text.Length;
        richTextBox1.ScrollToCaret();
    }));
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1960391.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

不再担心数据丢失:用rsync打造你的自动化备份解决方案

在现代IT环境中&#xff0c;数据备份是一项至关重要的任务。无论是个人文件还是企业数据&#xff0c;都需要有可靠的备份机制来防止数据丢失。今天&#xff0c;我们将介绍一种高效的备份方案&#xff1a;使用rsync实现自动化备份目录。 什么是rsync&#xff1f; rsync 是一个开…

vscode+cmake+msvc+vcpkg的入门使用

一.环境安装 1.下载vscode并安装: Download Visual Studio Code - Mac, Linux, Windows 2.安装完成后&#xff0c;安装C和cmake 相关工具&#xff0c;如图。 3.vcpkg的下载和安装 克隆vcpkg的仓库到本地&#xff1a;https://github.com/microsoft/vcpkg.git&#xff0c;运行bo…

《学会 SpringMVC 系列 · 基础篇》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

deeplapv3 动机

语义分割是计算机视觉领域中重要的任务之一&#xff0c;语义分割的目的是为图像中的每个像素分配标签。相比于传统方法&#xff0c;以深度学习为基础的全卷积网络极大地提高了语义分割算法的性能。 在语义分割网络中&#xff0c;常用到如下2种结构&#xff1a; 空间金字塔池化…

C++STL简介(三)

目录 1.vector的模拟实现 1.1begin&#xff08;&#xff09; 1.2end&#xff08;&#xff09; 1.3打印信息 1.4 reserve&#xff08;&#xff09; 1.5 size&#xff08;&#xff09; 1.6 capacity&#xff08;&#xff09; 1.7 push_back() 1.8[ ] 1.9 pop_back() 1.10 insert&…

【涵子来信】——AI革新:1.新时代是便捷的,要会用

各位读者朋友们&#xff1a; 我们现在AI时代的十字路口&#xff0c;AI是为生活带来便利的&#xff0c;我们要会使用AI。今天这篇文章来讲述一下AI的正确使用。 一、 AI的使用 1.1.便捷之中要会辨别 AI是带来强大的&#xff0c;利用好可以给生活带来便捷。 像之前WWDC24宣传…

SAP 字符串关键字找程序

关键字查询程序 &#xff1a; RPR_ABAP_SOURCE_SCAN或RS_ABAP_SOURCE_SCAN

Python网络爬虫:基础与实战!附淘宝抢购源码

Python网络爬虫是一个强大的工具&#xff0c;用于从互联网上自动抓取和提取数据。下面我将为你概述Python网络爬虫的基础知识和一些实战技巧。 Python网络爬虫基础 1. HTTP请求与响应 网络爬虫的核心是发送HTTP请求到目标网站并接收响应。Python中的requests库是处理HTTP请求…

C语言——运算符及表达式

C语言——运算符及表达式 运算符运算符的分类&#xff08;自增运算符&#xff09;、--&#xff08;自减运算符&#xff09;赋值运算符逗号运算符&#xff08;顺序求值运算符&#xff09; 表达式 运算符 运算符的分类 C语言的运算符范围很宽&#xff0c;除了控制语句和输入输出…

从数据血缘谈一谈如何实现数据管理的“自治理”

数据治理是企业数据管理的核心&#xff0c;它通过系统性的管理行为&#xff0c;确保数据的完整性、准确性、安全性、合规性和价值最大化。数据治理有助于提高数据质量和可用性&#xff0c;减少数据管理的风险&#xff0c;增强企业对数据的信任&#xff0c;从而提升业务效率和竞…

mongodb中ret resulted in status UnknownError: 24: Too many open files

mongodb使用中遇到的问题 Invariant failure: ret resulted in status UnknownError:24:Too many open files at *** 错误原因为打开文件过多的错误&#xff0c;即“句柄数超出系统显示”。 1.需要更改一下系统的句柄数&#xff1a; 查看一下系统目前设置的句柄数 open files 对…

算法刷题day2|贪心:122. 买卖股票的最佳时机 II、55. 跳跃游戏、45. 跳跃游戏 II、1005. K 次取反后最大化的数组和

122. 买卖股票的最佳时机 II 贪心一 画出股票的折线图&#xff0c;将图中折线上升区间相加即是最大利润。 class Solution { public:int maxProfit(vector<int>& prices) {int result 0;for (int i 1; i < prices.size(); i){//下降区间直接跳过if (prices[i]…

创建完整的APP页面

完整的页面创建过程包括三个步骤 在layout目录下创建XML文件 创建与XML文件对应的Java代码 在AndroidMainfest.xml&#xff08;清单文件&#xff09;中注册页面配 一步到位的activity创建 跳转&#xff1a;意图 创建一个意图实例&#xff0c;使用setClass&#xff08;&#…

python机器学习12--Regression回归分析

1.数据准备 第一步&#xff1a;数据内容一定要有以下两种值域的因果数据。  特征&#xff08;Feature&#xff09;&#xff1a;因&#xff0c;在统计学称为自变量&#xff08;Independent Variable&#xff09;。  标签答案&#xff08;Label&#xff09;&#xff1a;果&a…

4条社交规则,让你受益

一个人只要在社会生活中&#xff0c;那他就免不了社会交往。 有时候&#xff0c;我们在社交中之所以不受欢迎&#xff0c;主要还是因为方法和策略的问题&#xff0c;也就是说缺乏对社交的深入思考&#xff0c;没有去做正确的事情。要知道&#xff0c;只要思路和方向对了&#…

【计算机毕设论文】基于SpringBoot的语音识别系统的设计与实现

&#x1f497;博主介绍&#xff1a;✌全平台粉丝5W,高级大厂开发程序员&#x1f603;&#xff0c;博客之星、掘金/知乎/华为云/阿里云等平台优质作者。 【源码获取】关注并且私信我 感兴趣的可以先收藏起来&#xff0c;同学门有不懂的毕设选题&#xff0c;项目以及论文编写等相…

传统制造业物流管理有什么缺陷?智能供应链技术推动制造业转型!

在科技迅猛发展的今天&#xff0c;智能制造已成为推动工业转型和升级的关键力量。作为智能制造的重要组成部分&#xff0c;物流管理的智能化不仅关系到企业运营的效率&#xff0c;更直接影响到整个制造业的竞争力。随着中国社会物流总额的持续攀升&#xff0c;物流行业展现出了…

C语言内存函数超详解

文章目录 前言1. memcpy1. 1 memcpy 的使用1. 2 memcpy 的模拟实现 2. memmove2. 1 memmove 使用 3. memset3. 1 memset 函数的使用3. 2 memset 的模拟实现 4. memcmp4. 1 memcmp 函数的使用4. 2 memcmp 的模拟实现 前言 C语言为我们提供了字符串的一些函数&#xff0c;比如复…

arduino程序-面包板(电路搭建及上传程序控制led))(基础知识)

arduino程序-面包板&#xff08;电路搭建及上传程序控制led&#xff09;&#xff08;基础知识&#xff09; 1-12 面包板&#xff08;电路搭建及上传程序控制led&#xff09;如何使用面包板使用实际元器件搭建电路上传程序到开发板作业 1-12 面包板&#xff08;电路搭建及上传程…