ws2_32.dll是Windows Sockets应用程序接口, 用于支持Internet和网络应用程序
Windows和需要 执行TCP/IP网络通信的应用程序会调用动态链接库ws2_32.dll
如果ws2_32.dll不可用,你的计算机连接网络的操作会不稳定(即使不连接到外部网络)
编程体现:
#pragma comment (lib, "ws2_32.lib") //加载 ws2_32.dll
Socket接口 是 网络编程(通常是TCP/IP协议)的 API
最早的Socket接口是Berkeley接口 在Unix操作系统中实现
WinSock 是 基于Socket模型的API
在Microsoft Windows操作系统类中使用
它在Berkeley接口函数的基础之上 还增加了基于消息驱动机制的Windows扩展函数
Winscok1.1只支持TCP/IP网络 WinSock2.0增加了对更多协议的支持
编程体现:
#include
它定义的函数有很多:
用于网络I/O的函数如:
accept、closesocket、connect、recv、send
不涉及网络I/O、在本地端完成的函数,如:
bind、
上面介绍了Winsock API 怎么调用它的函数呢?
第一件事情就是 通过WSAStartup函数完成对Winsock服务的初始化
该函数的第一个参数指明程序请求使用的Socket版本 其中高位字节指明副版本、低位字节指明主版本
操作系统利用第二个参数返回请求的Socket的版本信息
当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库
然后绑定对应的Socket库到该应用程序中以后应用程序就可以调用所请求的Socket库中的其它Socket函数了
写面这块代码的目的是加载Winsock库
编程体现:
WORD sockVersion = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(sockVersion, &wsaData)!=0)
{
return 0; //代表失败
}
完成这一步就可以调用socket函数了
释放Winsock库
WSACleanup();
MAKEWORD(2,2): 调用2.2版的Winsock
MAKEWORD(1,1)就是调用1.1版
宏的原始定义:
#define MAKEWORD(a, b) ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8))
返回值:一个无符号16位整形数
通过上面的介绍知道
WSAStartup()函数的第一个参数就是MAKEWORD(a, b)的返回值
还知道第一个参数的 高位字节指明副版本 低位字节指明主版本
makeword是将两个byte型合并成一个word型 一个在高8位(b) 一个在低8位(a)
这两个结构体用来处理网络通信的地址
sockaddr #include
struct sockaddr {
sa_family_t sin_family;//地址族
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
sockaddr_in #include
struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};
sin_family指代协议族,在socket编程中只能是AF_INET
sin_port存储端口号(使用网络字节顺序)
sin_addr存储IP地址 使用in_addr这个数据结构
sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节
编程体现:
//绑定套接字
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
sockAddr.sin_family = PF_INET; //使用IPv4地址
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //回送IP地址
sockAddr.sin_port = htons(1234); //端口
bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
函数原型:
#include
#include
int socket(int domain, int type, int protocol);
// 当套接字创建成功时 返回套接字 失败返回“-1”
// “protocol”一般设置为“0”
// 当套接字使用的协议簇和类型确定时 这个参数的值就为0
// 但是有时候创建原始套接字时 并不知道要使用的协议簇和类型
// 即在domain参数未知情况下 这时protocol这个参数就起作用了---确定协议的种类
AF_UNIX(本机通信)
AF_INET(TCP/IP – IPv4)
AF_INET6(TCP/IP – IPv6)
其中 “type”参数指的是套接字类型,常用的类型有:
SOCK_STREAM(TCP流)
SOCK_DGRAM(UDP数据报)
SOCK_RAW(原始套接字)
创建套接字:
编程体现:
//创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, 0);
if(slisten == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序 通常有小端 大端两种字节顺序
小端字节序 低字节数据存放在内存低地址处 高字节数据存放在内存高地址处
大端字节序 高字节数据存放在低地址处 低字节数据存放在高地址处
网络字节顺序是TCP/IP中规定好的一种数据表示格式 它与具体的CPU类型 操作系统等无关 从而可以保证数据在不同主机之间传输时能够被正确解释
写网络程序的时候 往往会遇到字节的网络顺序 和 主机顺序的问题
htons 是将整型变量从主机字节顺序转变成网络字节顺序
网络字节顺序采用big-endian(大端)排序方式
而我们常用的 x86 CPU (intel, AMD) 电脑是 little-endian
我们在Intel机器下,执行以下程序
voidmain()
{
inta=16,b;
b=htons(a);
cout<<"a="<<a<<endl; // 打印16
cout<<"b="<<b<<endl; // 打印4096
}
数字16的16进制表示为0x00 10
数字4096的16进制表示为0x10 00
由于Intel机器是小尾端
存储数字16时实际顺序为10 00存储4096时实际顺序为00 10
(一个字节可以表示8位二进制, 一位16进制对应4位二进制信息
所以两个十六位位为一字节 所以 低字节放高字节 是两位一块放的
比如0x12 34 变为大端存储: 0x34 12, 而不是 0x4321
)
因此在发送网络包时为了报文中数据为0010 需要经过htons进行字节转换
编程体现:
serAddr.sin_port = htons(8888);
inet_addr()作用是 将 一个 IP字符串 转化为一个 网络字节序的整数值 用于sockaddr_in.sin_addr.s_addr
不知道sockAddr.sin_addr.s_addr可以看一下, 上面介绍的sockaddr_in
变量 sockAddr 是一个结构体 是上面介绍的struct sockaddr_in类型
然后这个结构体有个成员是这么定义的
struct in_addr sin_addr;
所以 sockAddr . sin_addr . s_addr
in_addr这个结构体也有个成员 s_addr
编程体现:
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //回送IP地址
bind函数把一个本地协议地址赋予一个套接字
对于网际协议:
协议地址是 32位的IPv4地址 或 是128位的IPv6地址与16位的TCP或UDP端口号的组合
在connect()或listen()调用前使用
#include
int bind(int sockfd, const struct sockaddr, socklen_t addrlen);
bind函数返回的一个常见错误是EADDRINUSE(“Address already in use”地址已使用)
第二个参数是一个指向特定协议的地址结构的指针 第三个参数是该地址结构的长度 对于TCP 调用bind函数可以指定一个端口号 或指定一个IP地址 也可以两者都指定 还可以都不指定
编程体现:
bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));