win_socket编程学习---只用看这一篇 看完你就可以套代码了

本文详细介绍了Windows Socket编程,包括ws2_32.dll的作用,Winsock API,WSAStartup函数,MAKEWORD宏,sockaddr_in结构体,socket函数,字节顺序及htons(),inet_addr()和bind()函数的使用,旨在帮助读者快速掌握网络编程基础。
摘要由CSDN通过智能技术生成

所有的概念请结合下面的编程体现来看,有助于理解



ws2_32.dll

ws2_32.dll是Windows Sockets应用程序接口, 用于支持Internet和网络应用程序
Windows和需要 执行TCP/IP网络通信应用程序会调用动态链接库ws2_32.dll
如果ws2_32.dll不可用,你的计算机连接网络的操作会不稳定(即使不连接到外部网络)

编程体现:

#pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll 



Winsock API

Socket接口 网络编程(通常是TCP/IP协议)的 API

最早的Socket接口是Berkeley接口 在Unix操作系统中实现

WinSock 基于Socket模型的API
在Microsoft Windows操作系统类中使用

它在Berkeley接口函数的基础之上 还增加了基于消息驱动机制的Windows扩展函数

Winscok1.1只支持TCP/IP网络 WinSock2.0增加了对更多协议的支持

编程体现:

 #include <winsock2.h>

它定义的函数有很多:
用于网络I/O的函数如:
accept、closesocket、connect、recv、send
不涉及网络I/O、在本地端完成的函数,如:
bind、




WSAStartup函数

上面介绍了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)函数

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_in

这两个结构体用来处理网络通信的地址

sockaddr #include <sys/socket.h>

struct sockaddr {  
     sa_family_t sin_family;//地址族
    char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息               
   };

sockaddr_in #include<netinet/in.h>或#include <arpa/inet.h>

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));



socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) 函数

函数原型:

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
// 当套接字创建成功时  返回套接字  失败返回“-1”  
// “protocol”一般设置为“0”   
// 当套接字使用的协议簇和类型确定时  这个参数的值就为0 
// 但是有时候创建原始套接字时  并不知道要使用的协议簇和类型 
// 即在domain参数未知情况下  这时protocol这个参数就起作用了---确定协议的种类
  1. IPPROTO_TCP 和 IPPROTO_IP代表两种不同的协议,分别代表IP协议族里面的TCP协议和IP协议
  2. int domain”参数表示套接字要使用的协议簇 常用的协议簇:
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()

写网络程序的时候 往往会遇到字节的网络顺序 和 主机顺序的问题

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()

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()

bind函数把一个本地协议地址赋予一个套接字
对于网际协议:
协议地址是 32位的IPv4地址 是128位的IPv6地址与16位的TCP或UDP端口号的组合

在connect()或listen()调用前使用

#include<sys/socket.h>
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));
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值