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

目录

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和端口为keysocketList.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(); // 清空字典// 关闭客户端socketsocket.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和端口为keysocketList.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(); // 清空字典// 关闭客户端socketsocket.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.rhkb.cn/news/387146.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

渗透测试:筑牢网络安全的坚固防线

在当今这个互联网高度发达的时代&#xff0c;网络安全已成为维护社会稳定和经济发展的重要基石。随着互联网的普及&#xff0c;网络攻击手段日益复杂多变&#xff0c;各类安全威胁层出不穷。为了有效应对这些挑战&#xff0c;渗透测试作为一种重要的安全测试与评估方法&#xf…

arduino程序-数字输出-学用led(led电路及相关函数)(基础知识)

arduino程序-数字输出-学用led&#xff08;led电路及相关函数&#xff09;&#xff08;基础知识&#xff09; 1-10 数字输出1-学用ledLED发光二极管LED电压特性电阻 1-11 数字输出arduino控制LEDLed与arduino连接电路图高电平及低电平含义 1-10 数字输出1-学用led 元器件初步介…

关于 AGGLIGATOR(猛禽)网络宽频聚合器

AGGLIGATOR 是一个用于多个链路UDP/IP带宽聚合的工具软件&#xff0c;类似MTCP的作用&#xff0c;不过它是针对UDP/IP宽频聚合的。 举个例子&#xff1a; 中国大陆有三台公网服务器&#xff0c;中国香港有一台大带宽服务器。 那么&#xff1a; AGGLIGATOR 允许中国大陆的客户…

Day7-指针专题二

1. 字符指针与字符串 C语言通过使用字符数组来处理字符串 通常&#xff0c;我们把char数据类型的指针变量称为字符指针变量。字符指针变量与字符数组有着密切关系&#xff0c;它也被用来处理字符串 初始化字符指针是把内存中字符串的首地址赋予指针&#xff0c;并不是把该字符串…

独占电脑资源来执行一个应用

1. 背景 在人工智能时代&#xff0c;随着神经网络的发展&#xff0c;训练人工智能模型需要越来越多的硬件资源&#xff0c;例如&#xff0c;利用10万条棋局数据、使用一台PC电脑、完整地训练一次确定性神经网络五子棋模型&#xff0c;需要花费一年半的时间。随着训练数据的增长…

<PLC><HMI><汇川>在汇川HMI画面中,如何为UI设置全局样式?

前言 汇川的HMI软件是使用了Qt来编写的,因此在汇川的HMI程序编写过程,是支持使用qt的样式来自定义部件样式的,即qss格式。 概述 汇川的软件本身提供三个系统的style样式,我们可以直接使用,但是,如果系统提供的样式不符合你的需求,那么你可以对其进行修改,或者自己新建…

进程间通信与线程间通信的方法汇总

目录 一、进程间通信机制 管道(pipe)&#xff1a; 命名管道(FIFO)&#xff1a; 消息队列(MQ)&#xff1a; 信号量(semaphore)&#xff1a; 共享内存(shared memory)&#xff1a; 信号(signal)&#xff1a; 内存映射(mapped memory)&#xff1a; 内存映射和共享内存的区…

NFTScan 正式上线 ERC404 浏览器和 NFT API 数据服务

近日&#xff0c;NFTScan 团队正式对外发布了 ERC404 浏览器&#xff0c;将为 ERC404 生态的 NFT 开发者和用户提供简洁高效的 NFT 数据搜索查询服务。NFTScan 作为全球领先的 NFT 数据基础设施服务商&#xff0c;帮助用户更方便地访问和分析 ERC404 相关的 NFT 数据&#xff0…

git使用总结

概述 简介 Git是一种代码托管技术&#xff0c;很多代码托管平台也是基于Git来实现的。 Git可以帮我们做到很多的事情&#xff0c;比如代码的版本控制&#xff0c;分支管理等。 网址 git官网&#xff1a;https://git-scm.com/ 版本控制系统【VCS】 可以完整保存项目的快照&#…

力扣Hot100-543二叉树的直径

给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由它们之间边数表示。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,4,5] 输出&a…

【Vue】权限控制

权限管理 分类&#xff1a; 页面权限功能(按钮)权限接口权限 vue3-element-admin 的实现方案 一般我们在业务中将 路由可以分为两种&#xff0c;constantRoutes 和 asyncRoutes。 constantRoutes&#xff1a; 代表那些不需要动态判断权限的路由&#xff0c;如登录页、404(或…

Skywalking 入门与实战

一 什么是 Skywalking? Skywalking 时一个开源的分布式追踪系统&#xff0c;用于检测、诊断和优化分布式系统的功能。它可以帮助开发者和运维人员深入了解分布式系统中各个组件之间的调用关系、性能瓶颈以及异常情况&#xff0c;从而提供系统级的性能优化和故障排查。 1.1 为…

嵌入式初学-C语言-八

#接嵌入式初学-C语言-七# 分支结构 分支结构&#xff1a;又被称之为选择结构 选择结构的形式 多分支 语法&#xff1a; if(条件1) { 语句1; } else if(条件2) { 语句2; } ... else { 语句n1; }案例&#xff1a; #include <stdio.h> int main() { // 需求&#xff…

Apache、nginx

一、Web 1、概述 Web&#xff1a;为⽤户提供的⼀种在互联⽹上浏览信息的服务&#xff0c;Web 服务是动态的、可交互的、跨平台的和图形化的。 Web 服务为⽤户提供各种互联⽹服务&#xff0c;这些服务包括信息浏览服务&#xff0c;以及各种交互式服务&#xff0c;包括聊天、购物…

线程的同步互斥

互斥 互斥保证了在一个时间内只有一个线程访问一个资源。 先看一段代码&#xff1a;三个线程同时对全局变量val进行--&#xff0c;同时val每自减一次其线程局部存储的全局变量 #include <iostream> #include <thread> #include <vector> #include <uni…

Stable Diffusion WebUI本地环境搭建

一、项目代码下载 git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui 二、环境配置 conda create --n stafu python3.10.6 实际上跟自己创建的环境没有关系&#xff0c;项目启动会自动复制这个环境&#xff0c;之后项目根据这个基础环境构建 也可以在自己…

C++高性能通信:图形简述高性能中间件Iceoryx

文章目录 1. 概述2. 支持一个发布者多个订阅者2.2 Iceoryx为何不支持多个发布者发布到同一个主题 3. Iceoryx的架构和数据传输示意图3.1 发布者与订阅者的通信机制3.2 零拷贝共享内存通信机制 4. 使用事件驱动机制4.1 WaitSet机制4.2 Listener机制 5. 已知限制6. 参考 1. 概述 …

ImportError: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.20‘ 报错解决办法

1.查找 libstdc.so.6* find / -name libstdc.so.6*2.copy一个libstdc.so.6.0.19到/usr/lib64/下 cp /usr/lib64/libstdc.so.6 /usr/lib64/3.创建软连接 ln -sf /usr/lib64/libstdc.so.6.0.31 /usr/lib64/libstdc.so.6完毕&#xff01;

中电金信:云原生时代IT基础设施管理利器——基础设施即代码(IaC)

在数字化转型、零售业务快速发展、信创建设驱动下&#xff0c;应用架构、技术架构、基础架构都已向云原生快速演进&#xff0c;银行业IT基础设施管理产生了非常大的变化&#xff0c;当前银行业&#xff0c;正在开展新一轮的核心应用系统重构、基础平台统一建设等重点任务&#…