我用C#实现了一个多客户端与服务端通讯的小工具,效果图如下:
涉及到的源码将开放下载!
它由server端和Client端组成,典型的C/S结构例子.这个例子很简单,但是一应俱全,首先我们来看服务端的代码部分:
因为我们会用到Socket编程和线程技术,所以要引入下面三个命名空间
using System.Net.Sockets;
using System.Net;
using System.Threading;
完整的代码为:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Collections;
namespace mytcpserver
{
public partial class Form1 : Form
{
Socket s;
IPEndPoint ServerIPEP;
private Thread th;
Socket uc;
private ArrayList alSock;//
public Form1()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false;
}
public void  Server()
{
int port = 5656;
ServerIPEP = new IPEndPoint(IPAddress.Any, port);
s = new Socket(ServerIPEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
s.Bind((EndPoint)ServerIPEP);
s.Listen(10);
alSock = new ArrayList();
while (true)
{
try
{
uc = s.Accept();
alSock.Add(uc);
byte[] data = new byte[2048];
int rect = uc.Receive(data);
byte[] chat = new byte[rect];
Buffer.BlockCopy(data, 0, chat, 0, rect);
}
catch (Exception ex)
{
//  MessageBox.Show(ex.Message);
}
}
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
//开始监听部分
this.button1.Enabled = false;
this.button2.Enabled = true;
th = new Thread(new ThreadStart(Server));//新建一个用于监听的线程
th.Start();//打开新线程
}
private void button2_Click(object sender, EventArgs e)
{
//停止监听部分
try
{
this.button2.Enabled = false;
this.button1.Enabled = true;
s.Close();
th.Abort();//终止线程
}
catch (Exception ex)
{
// MessageBox.Show(ex.Message);
}
}
private void button3_Click(object sender, EventArgs e)
{
/
/回送消息部分
if (uc == null)
{
MessageBox.Show("我说你要发给谁啊.");
}
else
{
int index = int.Box2.Text.Trim());
Socket sc = (Socket)alSock[index];
//发送数据
sc.Send(System.Text.Encoding.Default.GetBytes(this.answermessage.Text.Trim()));
}
}
}
}
我不想对上面的代码部分讲更多的解释,如果你有不明白的问题跟贴留言,上面的这代码还是很容易理解的,注:这儿使用的是单线程技术
下面我们再来看客户端的代码:
相对而言客户端的代码更加简单
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;
using System.Threading;
namespace mytcpchat
{
public partial class Form1 : Form
{
private Thread th;
Socket c;
public Form1()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false;
}
private void Form1_Load(object sender, EventArgs e)
{
}
public void Client()
{
try
{
IPEndPoint ServerIPEP = new IPEndPoint(IPAddress.Parse(thisboBox1.Text.Trim()), 5656);
c = new Socket(ServerIPEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
c.Connect((EndPoint)ServerIPEP);
c.Send(System.Text.Encoding.Default.GetBytes(this.sendmessage.Text.Trim()));
byte[] data = new byte[2048];
while (true)
{
int rect = c.Receive(data);
byte[] chat = new byte[rect];
Buffer.BlockCopy(data, 0, chat, 0, rect);
}
}
catch (Exception ex)
{
//  MessageBox.Show(ex.Message);
}
}
private void button1_Click(object sender, EventArgs e)
{
try
{
th = new Thread(new ThreadStart(Client));//新建一个用于监听的线程
th.Start();//打开新线程
}
catch (Exception ex)
{
MessageBox.Show(ex.Messa
ge);
}
}
}
}
OK,你现在可能会说,老温给我源代码吧
注意:你阅读上面部分之后,你能解决的网络环境还处于A网段到B网段是直通的情况,没有双方处于内网中,也就是发起的一方必须知道S端的公网IP地址,那么我两个网络可能存在两个局域网中会怎么样呢?不是发不通了,下面我们来继续解决这个问题,先来讲一下打洞的概念,比较枯燥,但是为了成功的喜悦值得!
网络上的大多数UDP原理研究分析后,自己总结了下面的结果.
1、client A 登录 通过http服务器 webservices 验证成功获取自身信息与好友列表信息等。
2、Http服务器登记client A的NAT后的IP地址与端口
3、Client B登录 通过http服务器 webservices 验证成功获取自身信息与好友列表信息等。
4、Http服务器登记client B的NAT后的IP地址与端口
5、Client A想发送消息给Client B,向HTtp服务器获取Client B的在线IP地址
6、Client A获得Client B的IP地址后并发送UDP信息到Client B
7、Client A与Client B请求失败,信息丢失,此时Client A报告Http服务器要求服务器帮忙对Client B进行通知
8、Http服务器接到此命令后,将Client A的IP地址发给Client B,要求他连接 字符串数组怎么转成byte
9、Client B收到HTTP服务器的信息后发送请求到 Client A
10、由于此时Client A NAT已经存在Clinet B的session,所以此时 Client A与Client B建立链接成功。
11、Client A发送消息到 Client B成功,不经HTTP服务器中转
总结:换句话说就是HTTP服务器启动了“和事老”的功能。为不信任的两个人发出命令从而使他们达到了一种互相承认而此建立链接关系 其中最主要的是Server 的一个方法就是发送消息到被打洞的客户端
客户端发送消息需要判断该消息是否发送成功,不成功则要求请求打洞.若成功则不需要要求打洞!客户端在接收到服务器的打洞指令后,则作出UDP发送响应
接下来我们一起来利用一个例子进行学习:p2pDemo工程
这是一个基于控制台应用程序。该代码乃本人一位朋友编写,故代码将不贴出来,如果学习的可以下载进行学习,然后在此提出留言进行讨论!
本人强烈推荐您学习,你不管搞C#/C++/JAVA,学习的是它的打洞原理
在本人参与的项目应用中,既然我们在编写通讯程序,加密与解密我们一定要考虑进去。
首先我们来看加密与解密部分:
公钥加密私钥解密,  没问题,也可以说是"公共密钥加密系统" 
私钥加密公钥解密,一般不这么说,应叫"私钥签名,公钥验证",也可以说是“公共密钥签名系统” 
再来说一下"公共密钥签名系统"目的:(如果晕就多看几遍,这个没搞清,后面的代码就更
晕) 
A欲传(信息)给B,但又怕B不确信该信息是A发的。 
1.A选计算(信息)的HASH值,如用MD5方式计算,得到:[MD5(信息)] 
2.然后用自已的私钥加密HASH值,得到:[私钥(MD5(信息))] 
3.最后将信息与密文一起传给B:传给B:[(信息)  +  私钥(MD5(信息))] 
B接到  :[(信息)  +  私钥(MD5(信息))] 
1.先用相同的HASH算法算出(信息)的HASH值,这里也使用MD5方式   
得到:  [MD5(信息)!] 
2.  再用A的公钥解密  [  私钥(MD5(信息))] 
[公钥(私钥(MD5(信息)))]  =  [(MD5(信息)] 
如能解开,证明该  [  私钥(MD5(信息))]是A发送的 
3.再比效[MD5(信息)!]与[(MD5(信息)] 
如果相同,表示(信息)在传递过程中没有被他人修改过。
下面我们再来讲一下数字签名技术:
我们对加解密算法已经有了一定理解,可以进一步讨论"数字签名"(注意不要与数字认证混淆)的问题了,即如何给一个计算机文件进行签字。数字签字可以用对称算法实现,也可以用公钥算法实现。但前者除了文件签字者和文件接受者双方,还需要第三方认证,较麻烦;通过公钥加密算法的实现方法,由于用秘密密钥加密的文件,需要靠公开密钥来解密,因此这可以作为数字签名,签名者用秘密密钥加密一个签名(可以包括姓名、证件号码、短信息等信息),接收人可以用公开的、自己的公开密钥来解密,如果成功,就能确保信息来自该公开密钥的所有人。
  公钥密码体制实现数字签名的基本原理很简单,假设A要发送一个电子文件给B,A、B双方只需经过下面三个步骤即可:
  1. A用其私钥加密文件,这便是签字过程
  2. A将加密的文件送到B
  3. B用A的公钥解开A送来的文件
  这样的签名方法是符合可靠性原则的。即:
  签字是可以被确认的,
  签字是无法被伪造的,
  签字是无法重复使用的,
  文件被签字以后是无法被篡改的,
  签字具有无可否认性,
  数字签名就是通过一个单向函数对要传送的报文进行处理得到的用以认证报文来源并核实报文是否发生变化的一个字母数字串。用这几个字符串来代替书写签名或印章,起到与书写签名或印章同样的法律效用。国际社会已开始制定相应的法律、法规,把数字签名作为执法的依据。
我想读者最关注的是如果利用C#进行这方面的编程,由于种种原因本人不能将项目中的代码全部公开,将原理重要点公布,如下:
序列化类:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace common
{
public class SerializeData
{
public static byte[] Serialize(object obj)
{
BinaryFormatter binaryF = new BinaryFormatter();
MemoryStream ms = new MemoryStream(1024 * 10);
binaryF.Serialize(ms, obj);
ms.Seek(0, SeekOrigin.Begin);
byte[] buffer = new byte[(int)ms.Length];
ms.Read(buffer, 0, buffer.Length);
ms.Close();
return buffer;
}
public static object Deserialize(byte[] buffer)
{
BinaryFormatter binaryF = new BinaryFormatter();
MemoryStream ms = new MemoryStream(buffer, 0, buffer.Length, false);
object obj = binaryF.Deserialize(ms);
ms.Close();
return obj;
}
}
}
TDES加密算法,注意加密的长度,一定要多测,注意双字节!害了本人不少天
using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
namespace common
{
public class TripleDES
{
public static int GetStrLength(string content)
{
int length = 0;
for (int i = 0; i < content.Length; i++)
{
if (Regex.IsMatch(content[i].ToString(), @"[\u4e00-\u9fa5]+") || Regex.IsMatch(content[i].ToString(), @"[^\x00-\x7F]+"))
{
length += 2;
}
else
{
length++;
}
}
return length;
}
//MD5散列方法
public  string MD5(string content)
{
MD5 md5 = new MD5CryptoServiceProvider();
byte[] bt = Encoding.Default.GetBytes(content);//将待加密字符转为 字节型数组
byte[] resualt = md5.ComputeHash(bt);//将字节数组转为加密的字节数组
return BitConverter.ToString(resualt).Replace("-", ""); //将数字转为string 型去掉内部的无关字符 
}
//使用对称加密加密字符串
public byte[] EncryptText(string str, byte[] Key, byte[] IV)
{
//创建一个内存流
MemoryStream memoryStream = new MemoryStream();
/
/使用传递的密钥和IV创建加密流
CryptoStream cryptoStream = new CryptoStream(memoryStream, new TripleDESCryptoServiceProvider().CreateEncryptor(Key, IV), CryptoStreamMode.Write);
//将传递的字符串转换为字数组
byte[] toEncrypte = Encoding.Default.GetBytes(str);
try
{
//将字节数组写入加密流,并清除缓冲区
cryptoStream.Write(toEncrypte, 0, toEncrypte.Length);

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。