和Windows系统一样Linux也有静态/动态链接库,下面介绍创建和使用方法:
假设有下面几个文件:
头文件String.h,声明相关函数原形,内容如下:
Strlen.c:函数Strlen的实现,获取给定字符串的长度,内容如下:
Strlnen.c:函数StrNlen的实现,获取给定字符串的长度,如果输入字符串的长度大于指定的最大长度,则返回最大长度,否者返回字符串的实际长度,内容如下:
生成静态库:
利用GCC生成对应目标文件:
gcc –c Strlen.c Strnlen.c
如果对应的文件没有错误,gcc会对文件进行编译生成Strlen.o和Strnlen.o两个目标文件(相当于windows下的obj文件)。然后用ar创建一个名字为libstr.a的库文件,并把Strlen.o 和Strnlen.o的内容插入到对应的库文件中。,相关命令如下:
ar –rc libstr.a Strlen.o Strnlen.o
命令执行成功以后,对应的静态库libstr.a已经成功生成。
/***********************************
Filename : String.h
Description :
Author : HCJ
Date : 2006-5-7
************************************/
int Strlen(char *pStr);
int StrNlen(char *pStr, unsigned long ulMaxLen);
/**************************************
Filename : get string length
Description :
Author : HCJ
Date : 2006/5/7
**************************************/
#include<stdio.h>
#include<assert.h>
int Strlen(char *pStr)
{
unsigned long ulLength;
assert(NULL != pStr);
ulLength = 0;
while(*pStr++)
{
ulLength++;
}
return ulLength;
}
**********************************************
Fileneme: mystrnlen.c
Description: get input string length,if string large
max length input return max length,
else real length
Author: HCJ
Date : 2006-5-7
**********************************************/
#include<stdio.h>
#include<assert.h>
int StrNlen(char *pStr, unsigned long ulMaxLen)
{
unsigned long ulLength;
assert(NULL != pStr);
if(ulMaxLen <= 0)
{
printf('Wrong Max Length!\n');
return -1;
}
ulLength = 0;
while(*pStr++ && ulLength < ulMaxLen)
{
ulLength++;
}
return ulLength;
}
生成动态链接库:
gcc -fpic -shared -o libstr.so Strlen.c Strnlen.c
-fpic 使输出的对象模块是按照可重定位地址方式生成的。
-shared指定把对应的源文件生成对应的动态链接库文件libstr.so文件。
对应的链接库已经生成,下面看一下如何使用对应的链接库。
静态库的使用:
假设有下面的文件要使用对应的的静态库:
编译生成对应的目标文件:
gcc -c -I/home/hcj/xxxxxxxx main.c
生成可执行文件:
gcc -o main1 -L/home/hcj/xxxxxxxx main.o libstr.a
其中-I/home/hcj/xxxxxxxx和-L/home/hcj/xxxxxxxx是通过-I和-L指定对应的头文件和库文件的路径。libstr.a是对应的静态库的名称。这样对应的静态库已经编译到对应的可执行程序中。执行对应的可执行文件便可以对应得函数调用的结果。
/*****************************************
FileName: main.c
Description: test static/dynamic library
Author: HCJ
Date : 2005-5-7
******************************************/
#include<stdio.h>
#include <String.h> //静态库对应函数的头文件
int main(int argc, char* argv[])
{
char str[] = {'hello world'};
unsigned long ulLength = 0;
printf('The string is : %s\n', str);
ulLength = Strlen(str);
printf('The string length is : %d(use Strlen)\n', ulLength);
ulLength = StrNlen(str, 10);
printf('The string length is : %d(use StrNlen)\n', ulLength);
return 0;
}
动态库的分为隐式调用和显式调用两种调用方法:
隐式调用的使用使用方法和静态库的调用差不多,具体方法如下:
gcc -c -I/home/hcj/xxxxxxxx main.c
gcc -o main1 -L/home/hcj/xxxxxxxx main.o libstr.so //这里是*.so
在这种调用方式中,需要维护动态链接库的配置文件/etc/ld.so.conf来让动态链接库为系统所使用,通常将动态链接库所在目录名追加到动态链接库配置文件中。否则在执行相关的可执行文件的时候就会出现载入动态链接库失败的现象。在编译所引用的动态库时,可以在gcc采用 –l或-L选项或直接引用所需的动态链接库方式进行编译。在Linux里面,可以采用ldd命令来检查程序依赖共享库。
显式调用:
/*****************************************
FileName: main2.c
Description: test static/dynamic library
Author: HCJ
Date : 2005-5-7
******************************************/
#include<stdio.h>
#include<dlfcn.h>
int main(int argc, char* argv[])
{
//define function pointor
int (*pStrlenFun)(char* pStr); //声明对应的函数的函数指针
int (*pStrnlenFun)(char* pStr, int ulMaxLen);
char str[] = {'hello world'};
unsigned long ulLength = 0;
void *pdlHandle;
char *pszErr;
pdlHandle = dlopen('./libstr.so', RTLD_LAZY); //加载链接库/libstr.so
if(!pdlHandle)
{
printf('Failed load library\n');
}
pszErr = dlerror();
if(pszErr != NULL)
{
printf('%s\n', pszErr);
return 0;
}
//get function from lib
pStrlenFun = dlsym(pdlHandle, 'Strlen'); //获取函数的地址
pszErr = dlerror();
if(pszErr != NULL)
{
printf('%s\n', pszErr);
return 0;
}
pStrnlenFun = dlsym(pdlHandle, 'StrNlen');
pszErr = dlerror();
if(pszErr != NULL)
{
printf('%s\n', pszErr);
return 0;
}
printf('The string is : %s\n', str);
ulLength = pStrlenFun(str); //调用相关的函数
printf('The string length is : %d(use Strlen)\n', ulLength);
ulLength = pStrnlenFun(str, 10);
printf('The string length is : %d(use StrNlen)\n', ulLength);
dlclose(pdlHandle);
return 0;
}
gcc -o mian2 -ldl main2.c
用gcc编译对应的源文件生成可执行文件,-ldl选项,表示生成的对象模块需要使用共享库。执行对应得文件同样可以得到正确的结果。
相关函数的说明如下:
(1)dlopen()
第一个参数:指定共享库的名称,将会在下面位置查找指定的共享库。
-环境变量LD_LIBRARY_PATH列出的用分号间隔的所有目录。
-文件/etc/ld.so.cache中找到的库的列表,用ldconfig维护。
-目录usr/lib。
-目录/lib。
-当前目录。
第二个参数:指定如何打开共享库。
-RTLD_NOW:将共享库中的所有函数加载到内存
-RTLD_LAZY:会推后共享库中的函数的加载操作,直到调用dlsym()时方加载某函数
(2)dlsym()
调用dlsym时,利用dlopen()返回的共享库的phandle以及函数名称作为参数,返回要加载函数的入口地址
(3)dlerror()
该函数用于检查调用共享库的相关函数出现的错误,这样我们就用简单的例子说明了在Linux下静态/动态库的创建和使用
对穿越NAT做些总结:
先做个约定:
内网A中有:A1(192.168.0.8)、A2(192.168.0.9)两用户,
网关X1(一个NAT设备)有公网IP 1.2.3.4
内网B中有:B1(192.168.1.8)、B2(192.168.1.9)两用户,
网关Y1(一个NAT设备)有公网IP 1.2.3.5
公网服务器:C (6.7.8.9) D (6.7.8.10)
NAT两大类:
l NAT(Network Address Translators):称为基本的NAT

在客户机时
192.168.0.8:4000——6.7.8.9:8000
在网关时
1.2.3.4:4000——6.7.8.9:8000
服务器C
6.7.8.9:8000
其核心是替换IP地址而不是端口,这会导致192.168.0.8使用4000端口后,192.168.0.9如何处理?
具体参考RFC 1631
基本上这种类型的NAT设备已经很少了。或许根本我们就没机会见到。
l NAPT(Network Address/Port Translators)
其实这种才是我们常说的 NAT
NAPT的特点是在网关时,会使用网关的 IP,但端口会选择一个和临时会话对应的临时端口。
如下图:

在客户机时
192.168.0.8:4000——6.7.8.9:8000
在网关时
1.2.3.4:62000——6.7.8.9:8000
服务器C
6.7.8.9:8000
网关上建立保持了一个1.2.3.4:62000的会话,用于192.168.0.8:4000与6.7.8.9:8000之间的通讯。
对于NAPT,又分了两个大的类型:
差别在于,当两个内网用户同时与6.7.8.9:8000的处理方式不同:
1、Symmetric NAT型 (对称型)

在客户机时
192.168.0.8:4000——6.7.8.9:8000 192.168.0.8:4000——6.7.8.10:8000
在网关时,两个不同session但端口号不同
1.2.3.4:62000——6.7.8.9:8000 1.2.3.4:62001——6.7.8.10:8000
服务器C
6.7.8.9:8000
服务器 D
6.7.8.10:8000
这种形式会让很多p2p软件失灵。
2、Cone NAT型(圆锥型)

在客户机时
192.168.0.8:4000——6.7.8.9:8000 192.168.0.8:4000——6.7.8.10:8000
在网关时,两个不同session但端口号相同
1.2.3.4:62000——6.7.8.9:8000 1.2.3.4:62000——6.7.8.10:8000
服务器C
6.7.8.9:8000
服务器D
6.7.8.10:8000
目前绝大多数属于这种。Cone NAT又分了3种类型:
a) Full Cone NAT(完全圆锥型):从同一私网地址端口192.168.0.8:4000发至公网的所有请求都映射成同一个公网地址端口1.2.3.4:62000 ,192.168.0.8可以收到任意外部主机发到1.2.3.4:62000的数据报。
b) Address Restricted Cone NAT (地址限制圆锥型):从同一私网地址端口192.168.0.8:4000发至公网的所有请求都映射成同一个公网地址端口1.2.3.4:62000,只有当内部主机192.168.0.8先给服务器C 6.7.8.9发送一个数据报后,192.168.0.8才能收到6.7.8.9发送到1.2.3.4:62000的数据报。
c) Port Restricted Cone NAT(端口限制圆锥型):从同一私网地址端口192.168.0.8:4000发至公网的所有请求都映射成同一个公网地址端口1.2.3.4:62000,只有当内部主机192.168.0.8先向外部主机地址端口6.7.8.9:8000发送一个数据报后,192.168.0.8才能收到6.7.8.9:8000发送到1.2.3.4:62000的数据报。
请注意上述描叙中的区别!
穿越NAT的实现:

A1在客户机时
192.168.0.8:4000——6.7.8.9:8000
X1在网关时
1.2.3.4:62000——6.7.8.9:8000
服务器C
6.7.8.9:8000
B1在客户机时
192.168.1.8:4000——6.7.8.9:8000
Y1在网关时
1.2.3.5:31000——6.7.8.9:8000
两内网用户要实现通过各自网关的直接呼叫,需要以下过程:
1、 客户机A1、B1顺利通过格子网关访问服务器C ,均没有问题(类似于登录)
2、 服务器C保存了 A1、B1各自在其网关的信息(1.2.3.4:62000、1.2.3.5:31000)没有问题。并可将该信息告知A1、B2。
3、 此时A1发送给B1网关的1.2.3.5:31000是否会被B1收到?答案是基本上不行(除非Y1设置为完全圆锥型,但这种设置非常少),因为Y1上检测到其存活的会话中没有一个的目的IP或端口于1.2.3.4:62000有关而将数据包全部丢弃!
4、 此时要实现A1、B1通过X1、Y1来互访,需要服务器C告诉它们各自在自己的网关上建立 “UDP隧道”,即命令A1发送一个192.168.0.8:4000——1.2.3.5:31000的数据报,B1发送一个 192.168.1.8:4000——1.2.3.4:62000的数据报,UDP形式,这样X1、Y1上均存在了IP端口相同的两个不同会话(很显然,这要求网关为Cone NAT型,否则,对称型Symmetric NAT设置网关将导致对不同会话开启了不同端口,而该端口无法为服务器和对方所知,也就没有意义)。
5、 此时A1发给Y1,或者B1发给X1的数据报将不会被丢弃且正确的被对方收到
综合P2P可实现的条件需要:
1、 中间服务器保存信息、并能发出建立UDP隧道的命令
2、 网关均要求为Cone NAT类型。Symmetric NAT不适合。
3、 完全圆锥型网关可以无需建立udp隧道,但这种情况非常少,要求双方均为这种类型网关的更少。
4、 假如X1网关为Symmetric NAT, Y1为Address Restricted Cone NAT 或Full Cone NAT型网关,各自建立隧道后,A1可通过X1发送数据报给Y1到B1(因为Y1最多只进行IP级别的甄别),但B2发送给X1的将会被丢弃(因为发送来的数据报中端口与X1上存在会话的端口不一致,虽然IP地址一致),所以同样没有什么意义。
5、 假如双方均为Symmetric NAT的情形,新开了端口,对方可以在不知道的情况下尝试猜解,也可以达到目的,但这种情形成功率很低,且带来额外的系统开支,不是个好的解决办法。
6、 不同网关型设置的差异在于,对内会采用替换IP的方式、使用不同端口不同会话的方式,使用相同端口不同会话的方式;对外会采用什么都不限制、限制IP地址、限制IP地址及端口。
7、 这里还没有考虑同一内网不同用户同时访问同一服务器的情形,如果此时网关采用Address Restricted Cone NAT 或Full Cone NAT型,有可能导致不同用户客户端可收到别人的数据包,这显然是不合适的。
一些现在常用的技术:
ALG(应用层网关):它可以是一个设备或插件,用于支持SIP协议,主要类似与在网关上专门开辟一个通道,用于建立内网与外网的连接,也就是说,这是一种定制的网关。更多只适用于使用他们的应用群体内部之间。
UpnP :它是让网关设备在进行工作时寻找一个全球共享的可路由IP来作为通道,这样避免端口造成的影响。要求设备支持且开启upnp功能,但大部分时候,这些功能处于安全考虑,是被关闭的。即时开启,实际应用效果还没经过测试。
STUN(Simple Traversalof UDP Through Network):这种方式即是类似于我们上面举例中服务器C的处理方式。也是目前普遍采用的方式。但具体实现要比我们描述的复杂许多,光是做网关Nat类型判断就由许多工作,RFC3489中详细描述了。
TURN(Traveral Using Relay NAT):该方式是将所有的数据交换都经由服务器来完成,这样NAT将没有障碍,但服务器的负载、丢包、延迟性就是很大的问题。目前很多游戏均采用该方式避开NAT的问题。这种方式不叫p2p。
ICE(Interactive Connectivity Establishment):是对上述各种技术的综合,但明显带来了复杂性。
总之,NAT的存在代表着一种时尚,那就是——不求简单,但求复杂,坚决把你搞晕,反正没我责任。
using System;
using System.Collections.Generic;
using System.Text;
public class RC4Crypto
{
public static Byte[] Encrypt(Byte[] data, byte[] key)
{
if (data == null || key == null) return null;
Byte[] output = new Byte[data.Length];
Int64 i = 0;
Int64 j = 0;
Byte[] mBox = GetKey(key, 256);
// 加密
for (Int64 offset = 0; offset < data.Length; offset++)
{
i = (i + 1) % mBox.Length;
j = (j + mBox[i]) % mBox.Length;
Byte temp = mBox[i];
mBox[i] = mBox[j];
mBox[j] = temp;
Byte a = data[offset];
//Byte b = mBox[(mBox[i] + mBox[j] % mBox.Length) % mBox.Length];
// mBox[j] 一定比 mBox.Length 小,不需要在取模
Byte b = mBox[(mBox[i] + mBox[j]) % mBox.Length];
output[offset] = (Byte)((Int32)a ^ (Int32)b);
}
return output;
}
public static Byte[] Decrypt(Byte[] data, byte[] key)
{
return Encrypt(data, key);
}
/// <summary>
/// 打乱密码
/// </summary>
/// <param name="pass">密码</param>
/// <param name="kLen">密码箱长度</param>
/// <returns>打乱后的密码</returns>
static private Byte[] GetKey(Byte[] pass, Int32 kLen)
{
Byte[] mBox = new Byte[kLen];
for (Int64 i = 0; i < kLen; i++)
{
mBox[i] = (Byte)i;
}
Int64 j = 0;
for (Int64 i = 0; i < kLen; i++)
{
j = (j + mBox[i] + pass[i % pass.Length]) % kLen;
Byte temp = mBox[i];
mBox[i] = mBox[j];
mBox[j] = temp;
}
return mBox;
}
}
