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

作者:一点一滴的Beer http://beer.cnblogs.com/

在程序设计中,涉及数据存储和数据交换的时候,不管是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.Threading;

namespace ConsoleClientSocketDemo
{
class Program
{
static void Main(string[] args)
{
int recvPort = 600;//客户端一直对600端口进行侦听---接收图片的端口
RecvPic recvPic = new RecvPic(recvPort);//监听接收来自图片服务器的图片以及客户端的命令
Thread tRecvPic = new Thread(new ThreadStart(recvPic.thread));
tRecvPic.IsBackground = true;
tRecvPic.Start();

string strPicServerIP = "127.0.0.1";//图片服务器的IP----127.0.0.1(localhost)--以本机为例
int sendRequestPort = 400;//发送图片请求的端口
SendStrMsg(strPicServerIP, sendRequestPort);

Console.ReadKey();//直接main里边最后加个Console.Read()不就好了。要按键才退出。
}

/// <summary>
/// 向目标主机发送字符串 请求图片
/// </summary>
/// <param name="strPicServerIP">目标图片服务器IP</param>
/// <param name="sendRequestPort">目标图片服务器接收请求的端口</param>
private static void SendStrMsg(string strPicServerIP, int sendRequestPort)
{

//可以在字符串编码上做文章,可以传送各种信息内容,目前主要有三种编码方式:
//1.自定义连接字符串编码--微量
//2.JSON编码--轻量
//3.XML编码--重量
string strPicRequest = "PicRequest;Hello world,need some pictures~!";//图片请求

IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse(strPicServerIP.ToString()), sendRequestPort);
Socket answerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
answerSocket.Connect(ipEndPoint);//建立Socket连接
byte[] sendContents = Encoding.UTF8.GetBytes(strPicRequest);
answerSocket.Send(sendContents, sendContents.Length, 0);//发送二进制数据
}
catch (Exception ex)
{
Console.Write(ex.Message);
}
finally
{
answerSocket.Close();
}

}

}
}

RecvPic.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();

之后的语句上断点。

附件:SocketDemo.rar

附近演示程序的说明:

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字符串会有安全隐患的。

------------------------------------------------------------------

Author:一点一滴的Beer

Email /Gtalk:dreamzsm@gmail.com

From:http://www.cnblogs.com/beer

Notes:欢迎转贴,但请在页面中加个链接注明出处

Time:2010-8-17

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

  1. .NET(C#)基于Socket编程实现平行主机之间网络通讯有图片传输的Demo演示

    本文讲下C#通过Socket编程实现平行主机之间网络通讯的详细讲解,非常不错的博文,一起来看下吧.      在程序设计中,涉及数据存储和数据交换的时候,不管是B/S还是C/S模式 ,都有这样一个概念 ...

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

    ping命令 可用于测试主机之间网络的连通性.执行ping命令会使用ICMP传输协议,发出要求回应的信息,若远端主机的网络功能没有问题,就会回应该信息,因而可得知该主机运作正常. ping命令的参数选 ...

  3. Linux 实用指令 -- 网络配置(查看网络IP和网关、 ping 测试主机之间网络连通、Linux网络环境配置(指定固定ip))

    文章目录 1. 网络配置 1.1 查看网络IP和网关 1.1.1 查看虚拟网络编辑器 1.1.2 这里可以修改ip地址(修改虚拟网络的ip) 1.1.3 这里可以修改网关(虚拟网络的网关) 1.1.4 ...

  4. socket的延续以及UDP网络通讯

    socket的延续以及UDP网络通讯 远程执行命令的示例: import socket import subprocess phone = socket.socket(socket.AF_INET,s ...

  5. qemu服务器虚拟机与主机通讯,虚拟机:QEMU虚拟机和主机无线网络通讯设置

    步骤如下: 1. 创建 tap 设备: sudo ip tuntap add dev tap0 mode tap 2. enable tap0接口: sudo ip link set tap0 up ...

  6. ping命令 ——测试主机间网络连通性

    ping命令 --测试主机间网络连通性 一.ping命令 1.概述 2.格式 3.常用参数 4.实例 ①.检查连通性 ②.仅仅连ping 5次后停止 ③.设置ping 5 次,每次间隔0.5秒 ④.p ...

  7. 基于国密算法实现主机之间的密钥分发和安全传输-2021西北工业大学网络空间安全学院暑期夏令营

    2021年西北工业大学网络空间安全学院暑期夏令营 一.设计内容与要求: 二.基本思路与实现: 三.设计原理与流程: 3.1 总体流程: 3.2 设计原理: 3.2.1 建立套接字连接: 3.2.2 根 ...

  8. 【网络通信与信息安全】之深入解析两台主机之间的通信过程和原理

    一.前言 本文通过在 Docker 容器中执行命令,来深入了解两台主机之间的通信过程.阅读完本文,您将熟悉以下内容: Docker 的基本操作: 创建 socket 并发送 HTTP 请求: 路由表. ...

  9. 实现两个主机之间的密钥分发和安全传输

    实现两个主机之间的密钥分发和安全传输 一.设计要求 编写一段程序,实现两个主机之间的密钥分发和安全传输. 要求: 用 RSA 算法实现两个主机之间的密钥分发,分发的密钥为 0x 01 23 45 67 ...

最新文章

  1. SQLite编译(How To Compile SQLite)
  2. oracle ssl发送邮件,使用javax.mail发送带有ssl的电子邮件
  3. 精美在线课程教育学习培训平台网站模板源码 HTML纯静态
  4. 电脑安装python-如何安装python
  5. Mybatis的简单增删查改(CRUD)
  6. 如何利用情感词典做中文文本的情感分析?
  7. python评分卡模型 简书_评分卡模型开发-定量特征筛选
  8. LOAP引擎:clickhouse06:简单介绍几个JSON函数
  9. ITIL入门 ITSM入门 事件入门 事件管理入门
  10. 为什么选php语言做网站,php做网站教程:PHP语言怎么做网站
  11. 番茄花园幕后公司揭秘
  12. 【转】谈谈CICS ENQ,DEQ的使用_REDEMPTION_百度空间
  13. 最优分配问题——拍卖算法
  14. Camtasia Studio2023中文版下载安装图文教程
  15. linux网卡linux_up和link_down事件.
  16. 新浪的邮箱怎么总有问题?
  17. 离线使用cifar-10-batches-py.tar.gz失败
  18. Answer to assessment of Fundamentals of Accelerated Computing with OpenACC
  19. 中国智慧城市“热战”的2018 1
  20. 抓包工具(Fiddler4)

热门文章

  1. PHP双引号的小隐患
  2. bzoj1588[HNOI2002]营业额统计
  3. Android系统中标准Intent的使用
  4. Ubuntu安装搜狗输入法
  5. 步步为营:Asp.Net 淘宝通用应用接口攻略
  6. 从Turbo C到Borland C++
  7. python中module_Python中的模块(Module)
  8. shell脚本——系统变量 与 变量(定义 使用 只读 删除)
  9. 27、Label 自适应文本 xib
  10. HLS-Demo IOS 视屏直播样例