七、socket基本概念
1、简介
a、协议簇和地址簇
TCP/IP协议簇符号标识是PF_*,地址簇符号标识是AF_*。由于TCP/IP的协议簇只有一种地址簇,已有的实现都把他们定义为相同的值。
b、端口
能通过端口找到相应的进程
2、WinSocket和Berkeley socket的区别
3、WinSocket I/O模式
在WinSocket中主要有三种I/O操作模式:阻塞、非阻塞和重叠I/O。在默认情况下,Berkeley和WinSocket都是建立阻塞的socket。为了建立非阻塞的socket,应用程序使用ioctlsocket的FIONBIO命令把socket设置为非阻塞的。在创建socket中制定了重叠I/O属性时,才能使用重叠I/O功能
a、阻塞I/O
线程使用了一个recv调用,但socket上没有数据,线程将被阻塞,直到socket接收到数据,并把数据复制到线程接收函数提供的缓冲区中。阻塞I/O虽然简单,但他不是最有效的编程方式
b、非阻塞I/O
和上面的一个简单的区别就是应用程序使用select检测是否有数据刻度,如有则使用recv接收数据,确保recv不会阻塞
c、重叠I/O
这是在WinSocket2.0版本中新加的特征。发送数据时,程序可以连续多次调用发送函数提供多个数据缓冲区去排队输出数据。发送数据时,程序使用WSASend或WSASendTo提供用户数据的缓冲区,如果Winsock不能立即发送数据,这些数据将按调用的顺序排队,当可以发送时再把数据发送到网络上。用户在接受到数据已经成功发送出去的通知之前,不能破坏缓冲区中的内容
d、同步和异步
同步传输数据块与数据块之间的时间间隔是固定的,必须严格规定它们的时间关系。同步有两种传输方式:字节同步和位同步,一般采用位同步方式
异步传输是指字符之间(前一个字符结束到下一个字符开始)的时间间隔是可变的,并不需要严格地限制它们的时间关系
八、转换函数
1、字节序
一般有两种选择:从多字节数据的小段(右端,最低有效位)开始,称为小尾数;另一种是从大端(左端,最高有效位)开始顺序传输,称为大尾数
主机上面的字节序称为主机字节序
在只有一台计算机的情况下,不需要考虑字节相关的问题,计算机会自动确定用哪种方式存储数值数据。但如果把计算机连接到网络上,就必须考虑字节序问题。当两台计算机的字节序不同时,如果在传输和接收数据时都按自己的方式处理数据,就会导致错误
2、一个判断字节序的程序
在这个程序中有一个联合体,第一个成员是sval,是程序中要判断的数组,第二个成员cval与sval占用相同的内存大小,这样就可以知道sval中的高位和地位都在内存的什么位置了。这两个成员有同样的长度,当联合体中的成员长度不同时,最大的成员长度就是联合的长度
HostEndian.cpp:
#include "stdafx.h" #include "iostream" union endian { unsigned short sval; unsigned char cval[4]; }; int _tmain(int argc, _TCHAR* argv[]) { char *info = "unknow endian"; union endian t = { 0x1234 }; if (t.cval[0] == 0x12 && t.cval[1] == 0x34) { info = "big-endian"; } else if (t.cval[0] == 0x34 && t.cval[1] == 0x12) { info = "little-endian"; } std::cout << "host is : " << info << std::endl; return 0; }
3、网络字节序
为了防止字节不统一的情况发生,即通信的双方对同一数组数据做出不同的解释,而导致错误,Internet定义了存储数值数据的标准顺序----高序字节在低序字节之前,保存在内存的低地址,即大尾数字节序,也称为网络字节序,而计算机上的字节序称为主机字节序。这两个字节序有可能不一样,所以需要先进行判断
NetConvt.cpp:
#include "stdafx.h" #include "iostream" #include "winsock2.h" #define INADDR_INVALD 0xffffffff //无效地址 static unsigned long range[] = { 0xffffffff, 0xffffff, 0xffff, 0xff }; unsigned short NetHtons(unsigned short shost) { unsigned char *p = (unsigned char *)&shost; return (unsigned short)(p[0] << 8 | p[1]); } unsigned short NetNtohs(unsigned short snet) { unsigned char *p = (unsigned char *)&snet; return (unsigned short)(p[0] << 8 | p[1]); } unsigned long NetHtonl(unsigned long lhost) { unsigned char *p = (unsigned char *)&lhost; return (unsigned long)(p[3] | (p[2] << 8) | (p[1] << 16) | (p[0] << 24)); } unsigned long NetNtohl(unsigned long lnet) { unsigned char *p = (unsigned char *)&lnet; return (unsigned long)(p[3] | (p[2] << 8) | (p[1] << 16) | (p[0] << 24)); } /************************************************************************** * * 函数功能: 把网络字节序地址转换为点分十进制的IP地址. * * 参数说明: [IN] addr, 网络字节序地址. * * 返 回 值: 返回点分十进制的IP地址. * **************************************************************************/ char *NetNtoa(struct in_addr addr) { static char buf[16]; //转换后的最大长度为15 unsigned char *p = (unsigned char *)&addr; sprintf(buf, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); return buf; } /************************************************************************** * * 函数功能: 把点分十进制的IP地址转换为网络字节序地址. * * 参数说明: [IN] cp, 点分十进制的IP地址. * * 返 回 值: 成功返回网络字节序地址, 失败返回 INADDR_INVALD. * **************************************************************************/ unsigned long NetAddr(const char *cp) { unsigned char addr[4], *p = addr, c = *cp; unsigned long value; int i, n, base, digit; while (true) { base = 10, value = 0; digit = 0; if (!isdigit(c)) { return !INADDR_INVALD; } //1.处理前缀:0:8进制;0x:16进制;否则10进制 if (c == '0') { base = 8, c = *++cp; if (c == 'x' || c == 'X') { base = 16, c = *++cp; } } //2.解析地址的一部分,处理数字,知道‘.’或非数字字符 while (c = *cp++) { if (isdigit(c)) { if (base == 8 && c >= '8') { return INADDR_INVALD; } value = value * base + (c - '0'); } else if (base == 16 && isxdigit(c)) { value = (value << 4) | (c + 10 - (islower(c) ? 'a' : 'A' )); } else { break; } digit = 1; //到这说明至少有一个数字 } //3.遇到‘.’的处理 if (c == '.') { if (!digit || p >= addr + 3 || value > 0xff) { return INADDR_INVALD; } *p++ = (unsigned char)value; c = *cp; } else { break; } } n = p - addr; //已经保存到addr中的数字个数 //解析完成,看是否有数字,检查尾部字节及值的范围 if (!digit || (value > range[n]) || c != '\0' && !isspace(c)) { return INADDR_INVALD; } //把地址的最后一部分保存到addr中 for (i = 3; i >= n; i--, value >>= 8) { addr[i] = (unsigned char)(value & 0xff); } value = *(unsigned long *)addr; return value; } int _tmain(int argc, _TCHAR* argv[]) { unsigned short host_s = 0x1234, net_s; unsigned long host_l = 0x12345678, net_l; char *addr_dec = "192.168.10.26", *p; struct in_addr addr; net_s = NetHtons(host_s); net_l = NetHtonl(host_l); std::cout << "net byte order is : net_s = 0x" << std::hex << net_s << ", net_l = 0x" << std::hex << net_l << std::endl; addr.s_addr = NetAddr(addr_dec); p = NetNtoa(addr); std::cout << "net addr is : net_s = " << addr.s_addr << ", string addr is " << p << std::endl; return 0; }
程序不太难,比如NetNtohs、NetHtonl主要思想就是得到主机序列,在移位过来
先处理前缀部分,主要是让前面的数字乘以基数base并加上本次的数值,最后保存到addr数组中的一个过程NetNtoa就是一个用sprintf来分割的操作;NetAddr只是一位一位的处理输入参数(点分十进制ip地址的一个过程)----首