C#.NET通过Socket实现平行主机之间网络通讯(含图片传输的Demo演示)
在程序设计中,涉及数据存储和数据交换的时候,不管是B/S还是C/S模式,都有这样一个概念:数据库服务器。这要求一台性能和配置都比较好的主机作为服务器,以满足数目众多的客户端进行频繁访问。但是对于一些数据交换的要求不主同,而且涉及到的通讯个体数目不多,如果还采用“一主机多客户机”的模式,便要求一台硬件配置良好而且软件上安装了相关数据服务软件,这样会造成硬件和软件上的很多不必要的成本,这时Socket在点对点的平行对象之间的网络通讯的优势就就发挥出来了。
其实对于Socket通讯来说,服务器和客户端的界定不像数据库服务器与客户端那样明显,甚至可以说Socket通讯里面的服务器和客户端只是相对的,因为网络通讯的对象基本上是处于平等层面的,只是为了方便对两台联网通讯的主机的描述才这样定义称谓的。
由于在.NET中Socket通讯的建立很容易,所以本文主要介绍一个Socket的比较典型的应用的流程:客户端向服务器发送图片请求,图片服务器接收到请求,并将服务器硬盘上的图片编码,发送到客户端,客户端得到图片数据后,再将这些数据写成图片文件,保存在客户端上。
本文主要是对Socket的一个应用进行介绍,所以至于其原理在此没有深究,至于如何建立Socket还有如何实现网络的七层协议在此都没有进行相关研究和介绍,本文主要介绍如何实现一个用户想要的功能,即在两台主机之间进行通讯,通过网络来收发用户想要收发的数据。
二、通讯相关的代码
本文以Windows控制台程序为例来实现引功能。
不管是通讯服务器或者通讯客户端,本文均以一个不断运行的线程来实现对端口的侦听,将通讯相关的变量的函数做成一个类,在Program.cs中只负责初始化一些参数,然后建立通讯的线程。具体代码如下:
2.1服务器端
Program.cs:
- using System;
- using System.Net;
- using System.Net.Sockets;
- using System.Threading;
- namespace ConsoleSocketsDemo
- {
- class Program
- {
- static void Main(string[] args)
- {
- int sendPicPort = ;//发送图片的端口
- int recvCmdPort = ;//接收请求的端口开启后就一直进行侦听
- SocketServer socketServerProcess = new SocketServer(recvCmdPort, sendPicPort);
- Thread tSocketServer = new Thread(new ThreadStart(socketServerProcess.thread));//线程开始的时候要调用的方法为threadProc.thread
- tSocketServer.IsBackground = true;//设置IsBackground=true,后台线程会自动根据主线程的销毁而销毁
- tSocketServer.Start();
- Console.ReadKey();//直接main里边最后加个Console.Read()不就好了。要按键才退出。
- }
- }
- }
SocketServer.cs:
- using System;
- using System.Text;
- using System.Net;
- using System.Net.Sockets;
- using System.IO;
- namespace ConsoleSocketsDemo
- {
- class SocketServer
- {
- Socket sRecvCmd;
- int recvCmdPort;//接收图片请求命令
- int sendPicPort;//发送图片命令
- public SocketServer(int recvPort,int sendPort)
- {
- recvCmdPort = recvPort;
- sendPicPort = sendPort;
- //建立本地socket,一直对4000端口进行侦听
- IPEndPoint recvCmdLocalEndPoint = new IPEndPoint(IPAddress.Any, recvCmdPort);
- sRecvCmd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- sRecvCmd.Bind(recvCmdLocalEndPoint);
- sRecvCmd.Listen();
- }
- public void thread()
- {
- while (true)
- {
- System.Threading.Thread.Sleep();//每个线程内部的死循环里面都要加个“短时间”睡眠,使得线程占用资源得到及时释放
- try
- {
- Socket sRecvCmdTemp = sRecvCmd.Accept();//Accept 以同步方式从侦听套接字的连接请求队列中提取第一个挂起的连接请求,然后创建并返回新的 Socket
- sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, );//设置接收数据超时
- sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, );//设置发送数据超时
- sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, ); //设置发送缓冲区大小 1K
- sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, );//设置接收缓冲区大小1K
- byte[] recvBytes = new byte[];//开启一个缓冲区,存储接收到的信息
- sRecvCmdTemp.Receive(recvBytes); //将读得的内容放在recvBytes中
- string strRecvCmd = Encoding.Default.GetString(recvBytes);//
- //程序运行到这个地方,已经能接收到远程发过来的命令了
- //*************
- //解码命令,并执行相应的操作----如下面的发送本机图片
- //*************
- string[] strArray = strRecvCmd.Split(';');
- if (strArray[] == "PicRequest")
- {
- string[] strRemoteEndPoint =sRecvCmdTemp.RemoteEndPoint.ToString().Split(':');//远处终端的请求端IP和端口,如:127.0.0.1:4000
- string strRemoteIP = strRemoteEndPoint[];
- SentPictures(strRemoteIP, sendPicPort); //发送本机图片文件
- recvBytes = null;
- }
- }
- catch(Exception ex)
- {
- Console.Write(ex.Message);
- }
- }
- }
- /// <summary>
- /// 向远程客户端发送图片
- /// </summary>
- /// <param name="strRemoteIP">远程客户端IP</param>
- /// <param name="sendPort">发送图片的端口</param>
- private static void SentPictures(string strRemoteIP, int sendPort)
- {
- string path = "D:\\images\\";
- string strImageTag = "image";//图片名称中包含有image的所有图片文件
- try
- {
- string[] picFiles = Directory.GetFiles(path, strImageTag + "*", SearchOption.TopDirectoryOnly);//满足要求的文件个数
- if (picFiles.Length == )
- {
- return;//没有图片,不做处理
- }
- long sendBytesTotalCounts = ;//发送数据流总长度
- //消息头部:命令标识+文件数目+……文件i长度+
- string strMsgHead = "PicResponse;" + picFiles.Length + ";";
- //消息体:图片文件流
- byte[][] msgPicBytes = new byte[picFiles.Length][];
- for (int j = ; j < picFiles.Length; j++)
- {
- FileStream fs = new FileStream(picFiles[j].ToString(), FileMode.Open, FileAccess.Read);
- BinaryReader reader = new BinaryReader(fs);
- msgPicBytes[j] = new byte[fs.Length];
- strMsgHead += fs.Length.ToString() + ";";
- sendBytesTotalCounts += fs.Length;
- reader.Read(msgPicBytes[j], , msgPicBytes[j].Length);
- }
- byte[] msgHeadBytes = Encoding.Default.GetBytes(strMsgHead);//将消息头字符串转成byte数组
- sendBytesTotalCounts += msgHeadBytes.Length;
- //要发送的数据流:数据头+数据体
- byte[] sendMsgBytes = new byte[sendBytesTotalCounts];//要发送的总数组
- for (int i = ; i < msgHeadBytes.Length; i++)
- {
- sendMsgBytes[i] = msgHeadBytes[i]; //数据头
- }
- int index = msgHeadBytes.Length;
- for (int i = ; i < picFiles.Length; i++)
- {
- for (int j = ; j < msgPicBytes[i].Length; j++)
- {
- sendMsgBytes[index + j] = msgPicBytes[i][j];
- }
- index += msgPicBytes[i].Length;
- }
- //程序执行到此处,带有图片信息的报文已经准备好了
- //PicResponse;2;94223;69228;
- //+图片1比特流+……图片2比特流
- try
- {
- #region 发送图片
- Socket sSendPic = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- IPAddress ipAddress = IPAddress.Parse(strRemoteIP);//remoteip = "127.0.0.1"
- try
- {
- sSendPic.Connect(ipAddress, sendPort);//连接无端客户端主机
- sSendPic.Send(sendMsgBytes, sendMsgBytes.Length, );//发送本地图片
- }
- catch (System.Exception e)
- {
- System.Console.Write("SentPictures函数在建立远程连接时出现异常:" +e.Message);
- }finally
- {
- sSendPic.Close();
- }
- #endregion
- }
- catch
- {
- }
- }
- catch(Exception ex)
- {
- Console.Write(ex.Message);
- }
- }
- }
- }
2.2客户端端
Program.cs:
- using System;
- using System.Text;
- using System.Net;
- using System.Net.Sockets;
- using System.IO;
- namespace ConsoleClientSocketDemo
- {
- class RecvPic
- {
- Socket sRecvPic;//接收图片的socket
- int recvPicPort;//接收图片端口
- public RecvPic(int recvPort)
- {
- recvPicPort = recvPort;
- IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, recvPicPort);
- sRecvPic = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- sRecvPic.Bind(localEndPoint);
- sRecvPic.Listen();
- }
- public void thread()
- {
- while (true)
- {
- System.Threading.Thread.Sleep();//每个线程内部的死循环里面都要加个“短时间”睡眠,使得线程占用资源得到及时释放
- try
- {
- Socket sRecvPicTemp = sRecvPic.Accept();//一直在等待socket请求,并建立一个和请求相同的socket,覆盖掉原来的socket
- sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, ); //设置接收数据超时
- sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, );//设置发送数据超时
- sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, );//设置发送缓冲区大小--1K大小
- sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, ); //设置接收缓冲区大小
- #region 先取出数据头部信息---并解析头部
- byte[] recvHeadBytes = new byte[];//先取1K的数据,提取出数据的头部
- sRecvPicTemp.Receive(recvHeadBytes, recvHeadBytes.Length, );
- string recvStr = Encoding.UTF8.GetString(recvHeadBytes);
- string[] strHeadArray = recvStr.Split(';');//PicResponse;2;94223;69228;
- string strHeadCmd = strHeadArray[];//头部命令
- int picCounts = Convert.ToInt32(strHeadArray[]) ;//数据流中包含的图片个数
- int[] picLength=new int[picCounts];//每个图片的长度
- for (int i = ; i < picCounts;i++ )
- {
- picLength[i] = Convert.ToInt32(strHeadArray[i+]);
- }
- #endregion
- int offset=;//数据头的长度
- for (int k = ; k < strHeadArray.Length - ;k++ )
- {
- offset += strHeadArray[k].Length + ;//因为后面的分号
- }
- int picOffset = recvHeadBytes.Length - offset;//第一张图片在提取数据头的时候已经被提取了一部分了
- if (strHeadCmd == "PicResponse")
- {
- #region 储存图片--为了节约内存,可以每接收一次就保存一次图片
- for (int i = ; i < picCounts; i++)
- {
- byte[] recvPicBytes = new byte[(picLength[i])];//每次只接收一张图片
- if (i == )//第一幅图片有一部分在提取数据头的时候已经提取过了。
- {
- byte[] recvFirstPicBuffer = new byte[picLength[i] -picOffset];
- sRecvPicTemp.Receive(recvFirstPicBuffer, recvFirstPicBuffer.Length, );
- for (int j = ; j < picOffset; j++)
- {
- recvPicBytes[j] = recvHeadBytes[offset + j];//第一幅图片的前一部分
- }
- for (int j = ; j < recvFirstPicBuffer.Length; j++)//第一张图片的后半部分
- {
- recvPicBytes[picOffset + j] = recvFirstPicBuffer[j];
- }
- //将图片写入文件
- SavePicture(recvPicBytes, "-0");
- }
- else
- {
- sRecvPicTemp.Receive(recvPicBytes, recvPicBytes.Length, );//每次取一张图片的长度
- SavePicture(recvPicBytes, "-"+i.ToString());
- //将图片数据写入文件
- }
- }
- #endregion
- }
- }
- catch(Exception ex)
- {
- Console.Write(ex.Message);
- }
- finally
- {
- }
- }
- }
- /// <summary>
- /// 保存图片到指定路径
- /// </summary>
- /// <param name="picBytes">图片比特流</param>
- /// <param name="picNum">图片编号</param>
- public void SavePicture(byte[] picBytes, string picNum)
- {
- string filename = "receivePic";
- if (!Directory.Exists("E:\\images\\"))
- Directory.CreateDirectory("E:\\images\\");
- if (File.Exists("E:\\images\\" + filename + picNum + ".jpg"))
- return;
- FileStream fs = new FileStream("E:\\images\\" + filename + picNum + ".jpg", FileMode.OpenOrCreate, FileAccess.Write);
- fs.Write(picBytes, , picBytes.Length);
- fs.Dispose();
- fs.Close();
- }
- }
- }
三、测试socket的连接方法,telnet远程登录
用户可以同时对客户端和服务器端的Socket程序进行编写,然后进行联调,也可以一次只编写一个,然后通过下面的方法来测试Socket连接。
一般通过远程登录来测试连接是否成功,比如测试本机的400端口是否能连接成功:
“运行->cmd->telnet 127.0.0.1 400”
在没有运行对本机的400端口进行不断侦听的程序时,会出现连接失败的提示:
如果连接成功,则会弹出另外一个窗口:
如果在侦听线程里面设置断点,通常连接成功后,就会在
Socket sRecvCmdTemp = sRecvCmd.Accept();
之后的语句上断点。
--------------------------------------------------------------------------------------------------
http://shihuan830619.iteye.com/blog/1113837 (我JavaEye的博客, 有附件)
附近演示程序的说明:
1.使用VS2005创建。
2.主要实现的功能是:主机A向主机B发图片请求,主机B将D盘image目录下的image0.jpg,image1.jpg文件编码发送到主机B,主机B再解码并写成图片文件到E盘的image目录下。
3.为了方便调试,演示程序将服务器和客户端同时放在本机上,即localhost或者127.0.0.1,即本程序最终实现的效果就是将本机的D盘image目录下的两个指定名称的图片传送到E盘image目录下。所以在运行本程序前,先在D:/image目录下放置两张命名为image0.jpg,image1.jpg的图片文件
4.先运行服务器程序,再运行客户端程序
特别声明:目前,对于传输和图片数据的报文格式存在一定的小问题,因为目前是用的分号“;”作为分隔符,所以在当图片数据流中存在和分号的ASCII码值相同的数时,在客户端解码是便会出现问题,比较稳妥的方法是严格限定死数据头报文的长度(宁可多花几位为空都可以,但要有严格的编码格式),然后在解码的时候,按照位来解码,而不是按照分号的分隔符来解码。所以应用Byte数组来进行编码,而不应该是string字符串,用string字符串的话会出现很多问题的:比如,遇到空字符串就认为是结尾了,遇到“;”就表示是编码分隔符号,而这些字符都是有可能在图片数据中存在的,所以用sting字符串会有安全隐患的。
出处:http://blog.sina.com.cn/s/blog_4f925fc3010186mf.html
C#.NET通过Socket实现平行主机之间网络通讯(含图片传输的Demo演示)的更多相关文章
- linux ping-测试主机之间网络的连通性
博主推荐:更多网络测试相关命令关注 网络测试 收藏linux命令大全 ping命令用来测试主机之间网络的连通性.执行ping指令会使用ICMP传输协议,发出要求回应的信息,若远端主机的网络功能没有问 ...
- ping---测试主机之间网络的连通性
ping命令用来测试主机之间网络的连通性.执行ping指令会使用ICMP传输协议,发出要求回应的信息,若远端主机的网络功能没有问题,就会回应该信息,因而得知该主机运作正常. 选项 -d:使用Socke ...
- 10.9 ping:测试主机之间网络的连通性
ping命令 可用于测试主机之间网络的连通性.执行ping命令会使用ICMP传输协议,发出要求回应的信息,若远端主机的网络功能没有问题,就会回应该信息,因而可得知该主机运作正常. ping命令 ...
- C++ 利用socket实现TCP,UDP网络通讯
学习孙鑫老师的vc++深入浅出,有一段时间了,第一次接触socket说实话有点儿看不懂,第一次基本上是看他说一句我写一句完成的,第二次在看SOCKET多少有点儿感觉了,接下来我把利用SOCKET完成T ...
- python实现两台不同主机之间进行通信(客户端和服务端)——Socket
大家好,我是辰哥~ 今天教大家通过Python进行Socket网络编程 (做一个聊天程序) 可以实现在不同的主机(电脑)之间进行通话. 具体效果如何,接着往下看 可以看到客户端(上方)向服务器端(下方 ...
- Linux主机之间传输文件的几种方法对比
1.scp传输 scp -r /data/file root@ip:/data/ scp -C /data/sda.img root@ip:/data/img/#-r: 支持目录#-C: 启用压缩传送 ...
- 两台Linux主机之间文件的复制
使用scp命令可以实现两台Linux主机之间的文件复制,基本格式是: scp [可选参数] file_source file_target 1. 复制文件 命令格式: scp local_file r ...
- Socket网络通讯开发总结之:Java 与 C进行Socket通讯 + [备忘] Java和C之间的通讯
Socket网络通讯开发总结之:Java 与 C进行Socket通讯 http://blog.sina.com.cn/s/blog_55934df80100i55l.html (2010-04-08 ...
- 两台主机之间单向Ping不通的问题
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px ".PingFang SC"; color: #454545 } p.p2 ...
随机推荐
- open-falcon api相关
本文描述通过被监控endpoint的名称获取该endpoint的eid和监控项,从而获取到该endpoint的监控历史数据,使用python代码的 api操作方法 注:同步open-falcon和ag ...
- Ubuntu软件包管理器
Ubuntu软件包管理 Ubuntu下对软件管理工具有:apt,dpkg,tasksel,aptitude等,我们常用的就是前三个工具.下面就介绍这三个工具的用法. dpkg 在Linux发展之初,安 ...
- arm-linux-ld命令
我们对每个c或者汇编文件进行单独编译,但是不去连接,生成很多.o 的文件,这些.o文件首先是分散的,我们首先要考虑的如何组合起来:其次,这些.o文件存在相互调用的关系:再者,我们最后生成的bin文件是 ...
- Kali2018.1
目录 制作U盘启动盘 安装 Kali Linux 之后的事 更新源 配置 Zsh 配置 Vim 修改 Firefox 语言为中文 安装 Gnome 扩展 美化 安装 Google 拼音输入法 安装常用 ...
- openwrt下使用wget出现Failed to allocate uclient context
一.场景重现 root@OpenWrt:/# wget www.baidu.com Downloading 'www.baidu.com' Failed to allocate uclient con ...
- 在Github上搭建博客
貌似还是这个链接最靠谱呀 http://my.oschina.net/nark/blog/116299 如何利用github建立个人博客:之一 在线编辑器http://markable.in/ed ...
- [kata]数值内3和5的倍数的总和求解
这个题是这样的,方法参数接受一个数值,以3,5为基数,返回小于这个参数的3,5的倍数,加上3,5本身总和. 朋友段帅说头疼,估计是天气原因吧,好起来吧,还得战斗呢.
- nodejs真的是单线程吗?
[原文] 一.多线程与单线程 像java.python这个可以具有多线程的语言.多线程同步模式是这样的,将cpu分成几个线程,每个线程同步运行. 而node.js采用单线程异步非阻塞模式,也就是说每一 ...
- C#SendMessage用法
C#SendMessage用法 分类: C#操作内存相关 2011-11-26 23:52 1255人阅读 评论(0) 收藏 举报 函数功能:该函数将指定的消息发送到一个或多个窗口.此函数为指定的窗口 ...
- Docker常用命令汇总,和常用操作举例
Docker命令 docker 常用命令如下 管理命令: container 管理容器 image 管理镜像 network 管理网络 node 管理Swarm节点 plugin 管理插件 secre ...