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 = 600;//发送图片的端口
            int recvCmdPort = 400;//接收请求的端口开启后就一直进行侦听

            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(100);
        }      

        public void thread()
        {
            while (true)
            {
                System.Threading.Thread.Sleep(1);//每个线程内部的死循环里面都要加个“短时间”睡眠,使得线程占用资源得到及时释放

                try
                {
                    Socket sRecvCmdTemp = sRecvCmd.Accept();//Accept 以同步方式从侦听套接字的连接请求队列中提取第一个挂起的连接请求,然后创建并返回新的 Socket

                    sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 5000);//设置接收数据超时
                    sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 5000);//设置发送数据超时
                    sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, 1024); //设置发送缓冲区大小 1K
                    sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, 1024);//设置接收缓冲区大小1K

                    byte[] recvBytes = new byte[1024];//开启一个缓冲区,存储接收到的信息
                    sRecvCmdTemp.Receive(recvBytes); //将读得的内容放在recvBytes中
                    string strRecvCmd = Encoding.Default.GetString(recvBytes);//
                    //程序运行到这个地方,已经能接收到远程发过来的命令了

                    //*************
                    //解码命令,并执行相应的操作----如下面的发送本机图片
                    //*************
                    string[] strArray = strRecvCmd.Split(‘;‘);
                    if (strArray[0] == "PicRequest")
                    {
                        string[] strRemoteEndPoint =sRecvCmdTemp.RemoteEndPoint.ToString().Split(‘:‘);//远处终端的请求端IP和端口,如:127.0.0.1:4000
                        string strRemoteIP = strRemoteEndPoint[0];
                        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 == 0)
                {
                    return;//没有图片,不做处理
                }

                long sendBytesTotalCounts = 0;//发送数据流总长度

                //消息头部:命令标识+文件数目+……文件i长度+
                string strMsgHead = "PicResponse;" + picFiles.Length + ";";

                //消息体:图片文件流
                byte[][] msgPicBytes = new byte[picFiles.Length][];
                for (int j = 0; 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], 0, msgPicBytes[j].Length);
                }

                byte[] msgHeadBytes = Encoding.Default.GetBytes(strMsgHead);//将消息头字符串转成byte数组
                sendBytesTotalCounts += msgHeadBytes.Length;
                //要发送的数据流:数据头+数据体
                byte[] sendMsgBytes = new byte[sendBytesTotalCounts];//要发送的总数组

                for (int i = 0; i < msgHeadBytes.Length; i++)
                {
                    sendMsgBytes[i] = msgHeadBytes[i]; //数据头
                }

                int index = msgHeadBytes.Length;
                for (int i = 0; i < picFiles.Length; i++)
                {

                    for (int j = 0; 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, 0);//发送本地图片
                    }
                    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(100);
        }

        public void thread()
        {
            while (true)
            {
                System.Threading.Thread.Sleep(1);//每个线程内部的死循环里面都要加个“短时间”睡眠,使得线程占用资源得到及时释放
                try
                {
                    Socket sRecvPicTemp = sRecvPic.Accept();//一直在等待socket请求,并建立一个和请求相同的socket,覆盖掉原来的socket
                    sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 5000); //设置接收数据超时
                    sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 5000);//设置发送数据超时
                    sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, 1024);//设置发送缓冲区大小--1K大小
                    sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, 1024); //设置接收缓冲区大小

                    #region 先取出数据头部信息---并解析头部

                    byte[] recvHeadBytes = new byte[1024];//先取1K的数据,提取出数据的头部
                    sRecvPicTemp.Receive(recvHeadBytes, recvHeadBytes.Length, 0);
                    string recvStr = Encoding.UTF8.GetString(recvHeadBytes);
                    string[] strHeadArray = recvStr.Split(‘;‘);//PicResponse;2;94223;69228;
                    string strHeadCmd = strHeadArray[0];//头部命令
                    int picCounts = Convert.ToInt32(strHeadArray[1]) ;//数据流中包含的图片个数
                    int[] picLength=new int[picCounts];//每个图片的长度
                    for (int i = 0; i < picCounts;i++ )
                    {
                        picLength[i] = Convert.ToInt32(strHeadArray[i+2]);
                    }

                    #endregion

                    int offset=0;//数据头的长度
                    for (int k = 0; k < strHeadArray.Length - 1;k++ )
                    {
                        offset += strHeadArray[k].Length + 1;//因为后面的分号
                    }

                    int picOffset = recvHeadBytes.Length - offset;//第一张图片在提取数据头的时候已经被提取了一部分了

                    if (strHeadCmd == "PicResponse")
                    {
                        #region 储存图片--为了节约内存,可以每接收一次就保存一次图片
                        for (int i = 0; i < picCounts; i++)
                            {
                                byte[] recvPicBytes = new byte[(picLength[i])];//每次只接收一张图片

                                if (i == 0)//第一幅图片有一部分在提取数据头的时候已经提取过了。
                                {
                                    byte[] recvFirstPicBuffer = new byte[picLength[i] -picOffset];
                                    sRecvPicTemp.Receive(recvFirstPicBuffer, recvFirstPicBuffer.Length, 0);
                                    for (int j = 0; j < picOffset; j++)
                                    {
                                        recvPicBytes[j] = recvHeadBytes[offset + j];//第一幅图片的前一部分
                                    }

                                    for (int j = 0; j < recvFirstPicBuffer.Length; j++)//第一张图片的后半部分
                                    {
                                        recvPicBytes[picOffset + j] = recvFirstPicBuffer[j];
                                    }

                                    //将图片写入文件
                                    SavePicture(recvPicBytes, "-0");
                                }
                                else
                                {
                                    sRecvPicTemp.Receive(recvPicBytes, recvPicBytes.Length, 0);//每次取一张图片的长度
                                    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, 0, 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演示),布布扣,bubuko.com

时间: 2024-10-12 20:42:30

C#.NET通过Socket实现平行主机之间网络通讯(含图片传输的Demo演示)的相关文章

ping---测试主机之间网络的连通性

ping命令用来测试主机之间网络的连通性.执行ping指令会使用ICMP传输协议,发出要求回应的信息,若远端主机的网络功能没有问题,就会回应该信息,因而得知该主机运作正常. 选项 -d:使用Socket的SO_DEBUG功能: -c<完成次数>:设置完成要求回应的次数: -f:极限检测: -i<间隔秒数>:指定收发信息的间隔时间: -I<网络界面>:使用指定的网络界面送出数据包: -l<前置载入>:设置在送出要求信息之前,先行发出的数据包: -n:只输出数值

10.9 ping:测试主机之间网络的连通性

ping命令 可用于测试主机之间网络的连通性.执行ping命令会使用ICMP传输协议,发出要求回应的信息,若远端主机的网络功能没有问题,就会回应该信息,因而可得知该主机运作正常. ping命令的参数选项及说明 -c <次数>         指定发送ICMP报文的次数.否则,ping命令将一直发送报文 -i <时间间隔>          相邻两次发送报文的时间间隔,默认时间间隔为1s -n               不查询主机名,直接显示其IP地址 -q           

unity3D中Socket链接与服务之间的通讯测试程序

Client.js: 1 var net=require('net'); 2 var client=net.connect({port:8124,host:'127.0.0.1'},function(){ 3     console.log('Success'); 4     client.write('world'); 5 6 }); 7 //客户端接收数据 8 client.on('data',function(data){ 9     console.log(data.toString()

C++ 利用socket实现TCP,UDP网络通讯

学习孙鑫老师的vc++深入浅出,有一段时间了,第一次接触socket说实话有点儿看不懂,第一次基本上是看他说一句我写一句完成的,第二次在看SOCKET多少有点儿感觉了,接下来我把利用SOCKET完成TCP和UDP两种通讯模式的流程和代码分享一下,希望对大家多少能有点儿帮助,有什么说的不对的欢迎各位大神指正. TCP TCP是点对点的通讯模式,数据传输质量高,对于传输数据完整性要求高的情况一般用TCP,具体到vc++中,一般选用的是服务器/客户端模式,socket实现TCP通讯在服务器端一般分为以

Linux主机之间传输文件的集中方法对比

1.scp传输 scp -r /data/file [email protected]:/data/ scp -C /data/sda.img [email protected]:/data/img/#-r: 支持目录#-C: 启用压缩传送 scp传输速度较慢,但使用ssh通道保证了传输的安全性 2.rsync差异化传输(支持断点续传,数据同步) rsync -av /backup/ -e ssh [email protected]192.168.1.110:/bak #-a: archive归

Python 网络编程socket大全 用途---用于客户端和服务器端之间相互通讯

本章目录 一.什么是socket 二.为什么需要socket 三.socket的发展 四.python中的socket 五.基于TCP的socket 六.基于UDP的socket 六. 粘包问题详解 七.粘包的解决方案 八.socketserver实现并发通讯 **引入:为什么一定要先学习网络协议?** 之所以学习网络编程就是为了让我们的程序能够利用网络来传输数据,开发出C/S构架的应用程序 而网络的核心,就是协议,没有协议就没有互联网,我们要开发出C/S结构程序则必须遵循这些协议的标准! `就

&lt;两台主机之间日志的同步&gt;

首先要知道两台主机之间同步文件udp虽然安全性不高,但是它的同步速率远远高于tcp.这点是不可否认的. 我们以简单的为例: 服务端: 允许别的主机可以把日志同步到自己的主机上来. # vim /etc/rsyslog.conf $ModLoad imudp  //开启udp协议,添加udp模块. $UDPServerRun 514 /etc/init.d/rsyslog restart 重启使配置生效 # tail -f /var/log/messages  //动态监控日志 客户端 把日志往哪

Oracle Study之-AIX6.1构建主机之间的信任关系(ssh)

Oracle Study之-AIX6.1构建主机之间的信任关系(ssh)    在AIX环境下构建主机信任关系首选rsh,但在构建Oracle 11g RAC时需要ssh支持,以下文档介绍如何在AIX6.1下构建ssh的信任关系.    默认aix没有安装ssh软件包,首先要安装ssh软件包: 1.下载.解压软件包 [[email protected] ssh]#lsOpenSSH_5.8.0.6102.tar.Z  openssl-0.9.8.1802.tar.Z  ssh.txt 解压后通过

Traceroute侦测主机到目的主机之间所经路由情况的重要工具

ICMP的应用--Traceroute Traceroute是用来侦测主机到目的主机之间所经路由情况的重要工具,也是最便利的工具.前面说到,尽管ping工具也可以进行侦测,但是,因为ip头的限制,ping不能完全的记录下所经过的路由器.所以Traceroute正好就填补了这个缺憾. Traceroute的原理是非常非常的有意思,它受到目的主机的IP后,首先给目的主机发送一个TTL=1(还记得TTL是什么吗?)的UDP(后面就 知道UDP是什么了)数据包,而经过的第一个路由器收到这个数据包以后,就