C# Socket网络编程(附赠练手小项目)
在公司花了两天的时间来研究Socket编程,感觉也还是学到了一点点皮毛,不过也还是有一点点的小成就。
配合Winform窗体界面,实现了简单的窗体间互相通信的小项目(可以互相发消息,服务器可以给客户端发送文件和震动弹出)。
现在把这两天学习到的知识点记录下来分享。。。
首先要声明:本篇博客并不会详细的讲解Socket底层的实现逻辑,只是讲解他是怎么运用的,是怎么实现窗体间通信的。
Socket的简单原理
Socket的两个概念:端口和协议。
端口
我们是通过服务端去访问应用程序的,但是在同一个服务端中的应用程序,他们的IP地址都是一样的,所以单单通过IP地址去访问对应的应用程序是不可能的,所以,得再结合端口去实现具体的访问。
每个应用程序就会有一个端口,我们就可以根据IP地址和端口号去实现访问了。(每个应用程序的端口号都是唯一的)
通过端口和IP地址客户端就可以准确无误的去访问服务器里面的应用程序了。
在访问的过程中,又会涉及到协议的问题!
协议
Socket有两个协议:TCP 和 UDP;
TCP
其中我们要知道,TCP协议是网络上比较安全稳定的协议,一般不会发送数据丢失。使用TCP协议建立网络连接,需要经过“三次握手”才建立连接。如下图:
只有客户端和服务器完成了这三次握手的过程,服务器才会和客户端进行数据的传输。(只要是少了一次都不会进行互相通信)
TCP建立网络连接的优缺点:
- 优点:安全,稳定,防止数据丢失。
- 缺点:效率低,经过三次握手的过程,需要耗费一定的时间。
UDP
UDP协议是与TCP协议相反的。
UDP协议不管服务器有没有空,就是一直给服务器发送消息,直到发完为止。他不管服务器有没有接收到,他只是要完成任务就行。
如果服务器很忙的话,并没有时间去处理客户端发过来的消息,那么就会造成数据丢失。
UDP建立网络连接的优缺点:
- 优点:快速,效率高。
- 缺点:不稳定,容易造成数据丢失。
两个协议各有的优缺点,也不好说哪个比较好。
像UDP协议一般用于视频的传输等。
好了TCP/UDP协议了解到这里就好了!
Socket
需要包含命名空间:
using System.Net;
using System.Net.Sockets;
一个服务器至少有两个Socket用于通信,一个用来监听,一个用来连接客户端进行通信。
我们先看一张图:
这张图描述的就是客户端和服务其进行互相通信的过程。
下面的讲解也是根据这张图来说明。
- Socket
他是一个类,用来定义对象进行通信。
首先创建一个用于监听的Socket(监听有没有客户端连接服务器)
如代码:
// 一个负责监听的Socket ************************************************************************// ip地址的类型(ipV4/ipv6) 选择以流的方式 流对应的是Tcp协议
Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
参数一:填写IP地址的类型(ipv4:InterNetwork; ipv6:InterNetworkV6)
参数二:填写TCP/UDP连接对应的方式
补充知识点:TCP是以流的方式(Stream);UDP是以数据报的方式(Dgram)。
参数三:填写TCP/UDP协议
这里就用了TCP的传输方式。
然后创建ip地址和端口号对象:
// 创建ip地址和端口号对象
IPAddress iPAddress = IPAddress.Any; //IPAddress.Parse(this.txtSever.Text);
IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, Convert.ToInt32(this.txtPort.Text));
Any是自动获取连接进行的客户端的IP地址。
端口号是获取我们textBox控件里面的。
Bind
绑定监听端口// 让负责监听的Socket绑定IP地址和端口号 socketWatch.Bind(iPEndPoint);
到了这里,就完成了监听的工作。
Listen
设置监听队列
在同一个时间段允许最大的连接个数// 设置监听队列(比如在同一个时间段允许最大的连接个数) socketWatch.Listen(10);
比如在一秒内最多允许10个客户端连接服务器,从第11个客户端往后都得排队等待连接。
Accept
该函数用于等待客户端与服务器进行连接,并返回一个新的Socket,用于与客户端进行通信。// 负责监听的Socket来接受客户端的连接(创建跟客户端通信的Socket)Socket socket = socketWatch.Accept(); // Accept() : 等待客户端连接
利用死循环可以一直与多个客户端进行连接
为了不卡死主线程,可以使用多线程对其进行操作!// 该线程的作用是:服务器不停的监听,等待客户端连接,并且创建与之通信用的Socket Thread th = new Thread(Listen); th.IsBackground = true; th.Start(socketWatch);
private void Listen(Object o) {Socket socketWatch = o as Socket;// 等待客户端连接,并且创建与之通信用的Socketwhile (true) {// 负责监听的Socket来接受客户端的连接(创建跟客户端通信的Socket)Socket socket= socketWatch.Accept(); // Accept() : 等待客户端连接 }}
Receive
获取客户端发过来的消息(单位:byte)// 客户端连接成功后,服务器接收客户端发来的消息 byte[] buffer = new byte[1024 * 1024 * 2]; // b:实际接收到的有效字节数 int b = socket.Receive(buffer); // 调用Receive函数返回客户端发来的消息(单位:字节)
可以根据b来判断客户端发过来的消息的字节个数。
Send
服务器给客户端发送消息仅支持字节发送数组发送
byte[] newByte = Encoding.UTF8.GetBytes("我是服务器"); socket.Send(newByte);
好了,服务器的流程基本上就这样了。
客户端与服务端雷同,就不演示了。
练手小项目
可能讲的不是很好,但是没关系,我把我自己写好的代码上传到此给需要的朋友玩一下。(可以实现两个窗口间互相通信)
界面如下:
服务器:
客户端:
代码如下:
服务器
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
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 {public partial class Form1 : Form {public Form1() {InitializeComponent();}private void Form1_Load(object sender, EventArgs e) {Control.CheckForIllegalCrossThreadCalls = false;}private void btnStart_Click(object sender, EventArgs e) {try {// 一个负责监听的Socket ************************************************************************// ip地址的类型(ipV4/ipv6) 选择以流的方式 流对应的是Tcp协议Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);// 创建ip地址和端口号对象 IPAddress iPAddress = IPAddress.Any; //IPAddress.Parse(this.txtSever.Text);IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, Convert.ToInt32(this.txtPort.Text));// 让负责监听的Socket绑定IP地址和端口号socketWatch.Bind(iPEndPoint);ShowMsg("监听成功!");// 设置监听队列(比如在同一个时间段允许最大的连接个数)socketWatch.Listen(10);// 该线程的作用是:服务器不停的监听,等待客户端连接,并且创建与之通信用的SocketThread th = new Thread(Listen);th.IsBackground = true;th.Start(socketWatch);}catch (Exception) { }}// 存储连接服务器的客户端的IP地址/端口号和负责通信的socketDictionary<string, Socket> dictionary = new Dictionary<string, Socket>();Socket socket; // 负责通信private void Listen(Object o) {Socket socketWatch = o as Socket;// 等待客户端连接,并且创建与之通信用的Socketwhile (true) {try {// 负责监听的Socket来接受客户端的连接(创建跟客户端通信的Socket)socket = socketWatch.Accept(); // Accept() : 等待客户端连接// 将连接成功后的客户端IP地址和端口号显示出来ShowMsg(socket.RemoteEndPoint.ToString() + "连接成功\r\n");// 将远程连接的客户端的IP地址和socket存入集合中dictionary.Add(socket.RemoteEndPoint.ToString(), socket);// 将远程连接的客户端的IP地址和端口号存储到下拉框中this.cobUsers.Items.Add(socket.RemoteEndPoint.ToString());// 该线程的作用是:使服务器不停的接收客户端发过来的消息Thread th = new Thread(Recive);th.IsBackground = true;th.Start(socket);}catch (Exception) { }}}// 服务器不停的接收客户端发过来的消息private void Recive(Object o) {Socket socket = o as Socket;while (true) {try {// 客户端连接成功后,服务器接收客户端发来的消息byte[] buffer = new byte[1024 * 1024 * 2];// b:实际接收到的有效字节数 int b = socket.Receive(buffer); // 调用Receive函数返回客户端发来的消息(单位:字节)// 用户点击了叉×if (b == 0) {break;}// 将其转为人能看得懂的字符串类型string str = Encoding.UTF8.GetString(buffer, 0, b);ShowMsg(socket.RemoteEndPoint.ToString() + "(客户端):" + str); // 显示出来}catch (Exception) { }}}private void ShowMsg(string str) {this.txtLog.AppendText(str + "\r\n");}private void btnSend_Click(object sender, EventArgs e) {try {// 获取文本的值并转换为byte类型数组byte[] by = Encoding.UTF8.GetBytes(this.txtMsg.Text.Trim());this.txtMsg.Text = "";// 定义一个泛型集合,将文本表示符0 和 byte数组存储入集合中List<byte> list = new List<byte>();list.Add(0);list.AddRange(by);// 将泛型集合转换为数组byte[] newByte = list.ToArray();// 服务器给客户端发送消息//socket.Send(by);string ip = this.cobUsers.SelectedItem.ToString(); // 获得下拉框中选中项的IP地址和端口号dictionary[ip].Send(newByte); // 将其传入Dictionary中获得跟客户端通信的socket}catch (Exception) { }}// 选择需要发送的文件private void btnSelect_Click(object sender, EventArgs e) {try {OpenFileDialog ofd = new OpenFileDialog();// 设置初始路径ofd.InitialDirectory = @"C:\Users\yangg\Desktop";ofd.Title = "请选择你需要发送的文件";// 设置文件筛选ofd.Filter = "所有文件|*.*";if (ofd.ShowDialog() == DialogResult.OK) {this.txtPath.Text = ofd.FileName;}}catch (Exception) { }}private void btnSentFile_Click(object sender, EventArgs e) {try {// 获取文件路径string path = this.txtPath.Text;// 使用文件流以读的方式打开文件using (FileStream fileRead = new FileStream(path, FileMode.Open, FileAccess.Read)) {byte[] buffer = new byte[1024 * 1024 * 10];// 将文件中的数据读取进字节数组中,返回读取到的个数int r = fileRead.Read(buffer, 0, buffer.Length);List<byte> list = new List<byte>();list.Add(1);list.AddRange(buffer);byte[] newBuffer = list.ToArray();// 发送dictionary[this.cobUsers.SelectedItem.ToString()].Send(newBuffer, 0, r + 1, SocketFlags.None);}}catch (Exception) { }}private void btnZD_Click(object sender, EventArgs e) {byte[] buffer = new byte[1];buffer[0] = 2;dictionary[this.cobUsers.SelectedItem.ToString()].Send(buffer);}}
}
客户端
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
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 _Client {public partial class Form1 : Form {public Form1() {InitializeComponent();}Socket socket;private void btnStart_Click(object sender, EventArgs e) {try {// 创建一个发送消息的Socketsocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);// 建立IP地址和端口号的对象IPAddress iPAddress = IPAddress.Parse(this.txtSever.Text);IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, Convert.ToInt32(this.txtPort.Text));// 客户端服务器建立连接socket.Connect(iPEndPoint);Shows("连接成功!");// 建立线程,无限的接收服务器发过来的消息Thread th = new Thread(Recive);th.IsBackground = true;th.Start();}catch (Exception) { }}private void Shows(string str) {this.txtLog.AppendText(str + "\r\n");}// 客户端给服务器发送消息private void benSend_Click(object sender, EventArgs e) {try {// 获取待发送的文本,并转换为byte字节数组byte[] by = Encoding.UTF8.GetBytes(this.txtMsg.Text.Trim());this.txtMsg.Text = "";// 客户端给服务器发送消息socket.Send(by);}catch (Exception) { }}// 不停的接收服务器发过来的消息private void Recive() {while (true) {try {byte[] by = new byte[1024 * 1024 * 10];// 获取服务器发过来的消息,返回接收到的个数int r = socket.Receive(by);if (r == 0) {break;}// 表示发送文字消息if (by[0] == 0) {string str = Encoding.UTF8.GetString(by, 1, r - 1);Shows(socket.RemoteEndPoint.ToString() + "(服务器):" + str);} else if (by[0] == 1) { // 表示发送文件SaveFileDialog sfd = new SaveFileDialog();sfd.InitialDirectory = @"C:\Users\yangg\Desktop";sfd.Title = "请选择报错位置";sfd.Filter = "所有文件|*.*";if (sfd.ShowDialog(this) == DialogResult.OK) {string path = sfd.FileName;using (FileStream fileWrite = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write)) {fileWrite.Write(by, 1, r - 1); }MessageBox.Show("保存成功");}} else if (by[0] == 2) { // 表示发送震动ZD();}}catch (Exception) { }}}// 震动private void ZD() {int x = this.Location.X;int y = this.Location.Y;for (int i = 0; i < 300; i++) {this.Location = new Point(x - 50, y);this.Location = new Point(x, y + 50);this.Location = new Point(x + 50, y);this.Location = new Point(x, y - 50);}this.Location = new Point(x, y);}private void Form1_Load(object sender, EventArgs e) {Control.CheckForIllegalCrossThreadCalls = false;}}
}
CSDN:
https://download.csdn.net/download/cpp_learner/12761444
百度连接:
链接:https://pan.baidu.com/s/16TE3Q51NhxMXNv2sp9gfcQ
提取码:fc4n
总结:
也就基本上是这样了,其他的也就不会了,希望对大家有帮助吧!
C# Socket网络编程(附赠练手小项目)相关推荐
- c++小项目_编程初学者的练手小项目(Pythonamp;C/C++)
老是有一些刚接触编程的小伙伴问,怎么样才能快速提高编程能力?"一切不写代码的学编程,都是瞎搞!",想要提高编程能力,当然是写代码.写代码.写代码.在不断做项目的过程中,加强基础语法 ...
- Golang练手小项目系列
Golang练手小项目系列 本系列整理了10个工作量和难度适中的Golang小项目,适合已经掌握Go语法的工程师进一步熟练语法和常用库的用法. golang练手小项目系列(1)-位向量 golang练 ...
- 爬虫练手小项目:豆瓣高分图书TOP100
爬虫练手小项目:豆瓣高分图书TOP100 import requests import re from requests.exceptions import RequestException impo ...
- go语言面向对象 练手小项目
学习一门语言,最重要的就是动手,一个项目的代码写三遍,看起来笨的方法,实际上也是最快的方法. go语言的第一个练手小项目: 家庭收支记录软件的编写: 1.项目需求说明 模拟现实基于文本界面的<家 ...
- ssm练手小项目_20 个 JavaScript+Html+CSS 练手的小项目
前言: 最近在 GitHub 上发现了一个 vanillawebprojects[1] 开源仓库,里面收集了 20 个 JavaScript+Html+CSS的练手项目,没有使用任何框架,可以让你从基 ...
- 台式小风扇(HTML+CSS+JS练手小项目)
台式小风扇(HTML+CSS+JS练手小项目) 功能介绍 外观展示 HTML代码 CSS代码 JS代码 总结 功能介绍 前段时间看到这样的风扇特效,感觉还挺好玩,就自己也写一个练练手. 风扇有四个档位 ...
- 数据结构练手小项目(AVL树、哈希表、循环链表、MySQL数据库)
文章目录 前言 正文(无删减) 我的想法(删减修改版) 数据导入与数据存储 功能实现 数据结构 用户结构 SIM卡结构 AVL树数据结构 哈希表结构 数据表 用户表 SIM卡表 时间安排 前言 本月主 ...
- html+css+js之20个练手小项目(一)
html+css+js之20个练手小项目(一)--Hangman 前言 一.HTML 二.CSS 三.JS 前言 前端新手练习,记录不迷失. 主要练习html和CSS布局以及JS. 来源github, ...
- 练手小项目,爬取3DM图片
博客原文:https://weweweha.com 1. 概述 爬取3DM指定网页的游戏壁纸,并且通过多线程来加速爬取图片的速度. 2.使用库 request库用来1解析指定网页,re库用来搜 ...
最新文章
- SSL/TLSV1.0 programe in linux
- pandorabox php7,【恐惧交响4之潘多拉】ProjectSAM Symphobia 4 Pandora v1.0.7
- 2019 好笔友-见字如面
- 【学习笔记】硬件设备选型
- [Python人工智能] 二十四.易学智能GPU搭建Keras环境实现LSTM恶意URL请求分类
- Java并发包常见异常_Java并发包1--线程的状态及常用方法
- LINUX添加一块网卡地址配置及问题
- ACM/CF赛制getstart模板
- 获取计算机显示屏高度,CSS 使用calc()获取当前可视屏幕高度
- STM32WB55开发板(一)单板设计-硬件介绍
- 躲避方块的球——C语言游戏
- cJSON基本使用方法
- 开播虚拟人物特效插件 yy歪歪直播使用主播专用
- github免用户名密码管理代码
- 使用Dash快速构建你的数据可视化前端
- 【收藏】如何优雅的在 Python设置报警通知(邮件、短信、电脑外放声音)
- Spring 工作原理
- 微信小程序实现大转盘抽奖----踩坑之路
- 格局大小决定你的人生高度---the height of your life depends on the size of your life pattern.
- 我的微信小程序入门学习-地图定位