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.mzph.cn/news/877213.shtml

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

相关文章

x-cmd pkg | yazi - 超快终端文件管理器

目录 简介快速入门功能特点竞品和相关项目进一步阅读 简介 yazi 是由 github.com/sxyazi 用 Rust 开发的终端文件管理器&#xff0c;支持预览文本文件、pdf 文件、图像、视频&#xff0c;内置代码高亮功能。在内部&#xff0c;它使用 Tokio 作为其异步运行时&#xff0c;以非阻…

【Vue3】watch 监视多种类型数据

【Vue3】watch 监视多种类型数据 背景简介开发环境开发步骤及源码 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗…

大数据-53 Kafka 基本架构核心概念 Producer Consumer Broker Topic Partition Offset 基础概念了解

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

实战:Zookeeper 简介和单点部署ZooKeeper

Zookeeper 简介 ZooKeeper是一个开源的分布式协调服务&#xff0c;它是Apache软件基金会下的一个项目&#xff0c;旨在解决分布式系统中的协调和管理问题。以下是ZooKeeper的详细简介&#xff1a; 一、基本定义 ZooKeeper是一个分布式的、开放源码的分布式应用程序协调服务&a…

系统架构师考点--系统架构设计(下)

大家好。今天总结一下系统架构设计的最后一部分知识点。 一、软件系统的质量属性 软件系统的质量属性 软件系统的质量属性可分为开发期质量属性和运行期质量属性2个部分。 1、开发期质量属性主要指在软件开发阶段所关注的质量属性&#xff0c;主要包含6个方面&#xff1a; …

C++ 关键字与库函数 学习总结

sizeof与strlen 含义 sizeof&#xff1a;是一个操作符&#xff0c;用于计算数据类型或变量的大小&#xff08;以字节为单位&#xff09;。在编译时求值strlen&#xff1a; 是一个函数&#xff0c;用于计算字符串的长度&#xff08;不包括终止符 \0&#xff09;。在运行时求值不…

【Code】Street-Gaussian代码复现笔记

文章目录 1. EnvironmentBug 1 2. TrainingBug 2Bug 3 1. Environment Follow the original instructions, conda create --name street-gaussians-ns -y python3.8 conda activate street-gaussians-ns pip install --upgrade pippip install torch2.1.2cu118 torchvision0.…

bugku-web-ctf-变量1

<?php error_reporting(0); include "flag1.php"; highlight_file(__file__); if(isset($_GET[args])){$args $_GET[args];if(!preg_match("/^\w$/",$args)){die("args error!");}eval("var_dump($$args);"); } ?> error_r…

扩展------正向代理和反向代理怎么理解?

今天看博客园&#xff0c;突然看到正向代理和反向代理&#xff0c;发现自己还不会就稍微研究了一下。 正向代理----(看做是服务员) 当我们进饭店吃饭&#xff0c;我们不可能会直接去后厨叫厨师给我们做菜&#xff0c;而是通过服务员去通知&#xff0c;这个就叫做正向代理。 再…

当年很流行,现在已经淘汰的Java技术,请不要学了!【建议收藏】

在Java技术的发展历程中&#xff0c;确实有一些曾经流行但现在已经被淘汰或不再推荐使用的技术。了解这些技术可以帮助你避免学习过时的知识&#xff0c;从而更高效地提升自己的技能。 以下是一些曾经流行但现在已经不太推荐学习的Java技术&#xff1a; 1. Servlet 2.x&#x…

AI作图接口要怎么调用呢?

一、什么是AI作图&#xff1f; 基于AI大模型的深度学习算法和大规模的图像数据训练&#xff0c;输入图片和关键词&#xff0c;可生成独特及富有创意的山水风格图片。 二、AI作图使用场景有哪些呢&#xff1f; 1.广告与营销&#xff1a; 为产品制作吸引人的宣传海报、广告图片…

git拉完代码总是自动创建一个新的节点

git拉完代码&#xff0c;总是自动生成弹出这个信息 然后还会在git上面留下一个节点&#xff0c;这个节点没啥用&#xff0c;显示着感觉有点碍事。 而且后续的git push 之后&#xff0c;会覆盖掉自己的git commit 的提示&#xff0c;其他人cr代码的时候看到的是 解决方法&#…

vite + postcss 安装配置

安装 npm install postcss-pxtorem --save-dev配置postcss.config.js 新建 postcss.config.js export default {plugins: {postcss-pxtorem: {rootValue: 16, // 根元素大小&#xff0c;用于转换rem的基准值unitPrecision: 5, // rem的小数位数propList: [*], // 需要转换的…

A股两市继续低开,缩量震荡前行!

今天的A股&#xff0c;让人心情极度炸裂&#xff0c;你们知道是为什么吗&#xff1f;盘面上出现2个重要信号&#xff0c;一起来看看&#xff1a; 1、今天两市低开&#xff0c;让人心情极度炸裂。 2、盘面上出现2个重要信号&#xff1a;一是为了迎接今日上证权益指数问世&#x…

LeetCode刷题笔记第682题:棒球比赛

LeetCode刷题笔记第682题&#xff1a;棒球比赛 题目&#xff1a; 想法&#xff1a; 遍历输入的列表&#xff0c;按照规则将分数和操作依次进行&#xff0c;存储在新建的列表中&#xff0c;最终输出列表中的元素和&#xff0c;代码如下&#xff1a; class Solution:def calPo…

妈吖,看过这个大厂的oracle主键自增,我的信心暴增!信创,国产数据库也能行。

创作不易 只因热爱!! 热衷分享&#xff0c;一起成长! “你的鼓励就是我努力付出的动力” 1.数据库oracle自增主键字段思维导图 在Oracle数据库中&#xff0c;可以通过创建序列&#xff08;SEQUENCE&#xff09;来实现自增功能。但也可以不在数据库中实现&#xff0c;而是通过程…

【Stable Diffusion】(基础篇六)—— embedding

embedding 本系列博客笔记主要参考B站nenly同学的视频教程&#xff0c;传送门&#xff1a;B站第一套系统的AI绘画课&#xff01;零基础学会Stable Diffusion&#xff0c;这绝对是你看过的最容易上手的AI绘画教程 | SD WebUI 保姆级攻略_哔哩哔哩_bilibili 除了大模型和VAE之外…

RewardBench:Evaluating Reward Models for Language Modeling

Leaderboard&#xff1a; https://hf.co/spaces/allenai/reward-bench Code&#xff1a; https://github.com/allenai/reward-bench Dataset&#xff1a; https://hf.co/datasets/allenai/reward-bench 在人类偏好的强化学习&#xff08;RLHF&#xff09;过程中&#xff0c;奖励…

Centos7重装系统保留数据安装方式记录

一、选择手动配置分区 二、选择/分区以后&#xff0c;输入挂载点/&#xff0c;选择标准分区&#xff0c;勾选重新格式化&#xff0c;单击更新设置。这样重装的时候&#xff0c;就格式化/分区内的数据。 三、选择swap分区&#xff0c;勾选重新格式化&#xff0c;单击更新设置…

【数据分享】2024年省市县行政区划数据(最新版本/带审图号/官方发布/免费获取/Shp格式)

省份\地级市\区县这三个级别的行政边界矢量&#xff08;shp格式&#xff09;数据是我们在各项研究中最常用的数据。在我们发表学术论文的时候&#xff0c;一旦涉及到行政边界&#xff0c;在期刊的投稿指南中都明确要求必须使用自然资源地图技术审查中心发布的标准地图底图&…