C语言网络编程:bind函数详解

函数功能

bind API能够将套接字文件描述符、端口号和ip绑定到一起
注意:
绑定的一定是自己的 ip和和端口,不是对方的;比如对于TCP服务器来说绑定的就是服务器自己的ip和端口

函数头文件
 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>
函数使用

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数参数
  • sockfd 表示socket函数创建的通信文件描述符
  • addrlen 表示所指定的结构体变量的大小
  • addr 表示struct sockaddr的地址,用于设定要绑定的ip和端口
    struct sockaddr {
       sa_family_t sa_family;
       char        sa_data[14];
    }
    
    sa_family 用于指定AF_***表示使用什么协议族的ip
    sa_data 存放ip和端口
    这里有一个问题,直接向sa_data中写入ip和端口号有点麻烦,内核提供struct sockaddr_in结构体进行写入,通过/usr/include/linux/in.h可以看到结构体原型
    使用该结构体时需要包含<netinet/in.h>头文件,且sockaddr_in结构体是专门为tcp/ip协议族使用,其他协议族需要使用其对应的转换结构体,比如“域通信协议族” 使用的是sockaddr_un结构体
    struct sockaddr_in {
    	  __kernel_sa_family_t  sin_family;     /* Address family               */
    	  __be16                sin_port;       /* Port number                  */
    	  struct in_addr        sin_addr;       /* Internet address             */
    	
    	  /* Pad to size of `struct sockaddr'. 设置IP端口号这个成员暂时用不到 */
    	  unsigned char         __pad[__SOCK_SIZE__ - sizeof(short int) -
    	                        sizeof(unsigned short int) - sizeof(struct in_addr)];
    };
    
    /* Internet address.填补相比于struct sockaddr所缺的字节数,保障强制转换不要出错 */
    struct in_addr {
            __be32  s_addr; // __be32是32位的unsigned int ,因为ipv4是无符号32位整型 
    };
    
    可以看到以上sockaddr_in结构体中存放的端口和ip是分开的,所以设置起来非常方便,使用struct sockaddr_in设置后,让后将其强制转换为struct sockaddr类型,然后传递给bind函数即可
函数举例
struct sockaddr_in addr;
addr.sin_family = AF_INET; //设置tcp协议族
addr.sin_port = htons(6789); //设置端口号
addr.sin_addr.s_addr = inet_addr("192.168.1.105"); //设置ip地址

ret = bind(skfd, (struct sockaddr*)&addr, sizeof(addr));

如果是跨局域网或者城域网通信,这里设置的ip地址一定为通信设备所在路由器的外网ip地址。

如下c代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h> //struct sockadd_in 结构体的头文件
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>

void print_err(char *str, int line, int err_no) {
	printf("%d, %s :%s\n",line,str,strerror(err_no));
	_exit(-1);
}

int main()
{
	int skfd = -1;
	skfd = socket(AF_INET, SOCK_STREAM, 0);
	if ( -1 == skfd) {
		print_err("socket failed",__LINE__,errno);
	}

	struct sockaddr_in addr;
	addr.sin_family = AF_INET; //设置tcp协议族
	addr.sin_port = htons(6789); //设置端口号
	addr.sin_addr.s_addr = inet_addr("192.168.102.169"); //设置ip地址

	int ret = bind(skfd, (struct sockaddr*)&addr, sizeof(addr));
	if ( -1 == ret) {
		print_err("bind failed",__LINE__,errno);
	}
	return 0;
}
为什么需要bind函数

bind函数就是让套接字文件在通信时使用固定的IP和端口号(针对服务器来说)
可以看到如上实现代码,调用socket函数创建的套接字仅仅执行了通信等协议,但是并没有指定通信时所需的ip地址和端口号

  • ip 是对方设备的唯一标识
  • 端口号 区分同一台计算机上的不同的网络通信进程

如果不调用bind函数指定ip和端口,则会自己指定一个ip和端口,此时违背了TCP通信的可靠性和面向连接的特点。

服务器如何知道客户端的ip和端口号

可以通过上文TCP通信模型中看到,客户端通信时不需要指定ip和端口号,直接创建一个socket套接字文件描述符即可参与通信。
此时当客户端和服务器建立连接的时候,服务器会从客户的数据包中提取出客户端ip和端口,并保存起来,如果是跨网通信,那么记录的就是客户端所在路由器的公网ip

htons函数
  • #include <arpa/inet.h>
  • uint16_t htons(uint16_t hostshort); 函数全拼为host to net short
  • 函数功能
    a. 将端口从"主机端序" 转为 “网络端序”
    b. 如果给定的端口不是short,则转为short
  • 返回值: 函数的调用永远都是成功的,返回转换后的端口号
htons兄弟函数htonl,ntohs,ntohl
  • htonlhtons唯一的区别时,转换完的端口号为long
  • ntohshtons恰好相反,是从网络字节序转换为主机字节序
  • ntohl 表示从网络字节转换为主机序,同时转换完的端口号为long
为什么要进行端口的大小端序的转换

大端序:
大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;
小端序:
小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。

大小端序是由具体的操作系统来决定的
可以使用如下代码测试系统是大端还是小端:

#include <stdio.h>
int main()
{
    int a = 1;
    char pc = *(char*)(&a);
    if (pc == 1)
        printf("第一个字节为1,小端存储\n");
    else
        printf("第一个字节为0,大端存储\n");

    return 0;
}

同样,网络通信的时候发送端计算机和接收端计算机可能端序不一致,比如发送者是大端序,接受者是小端序,如果通信时数据的端序处理不好很可能出现乱码,甚至无法接收到数据。
如果发送者和接受者端序一致则也能够正常传输数据,不用htons函数进行转换,不过保证数据序列正确的得进行传输,建议使用htons函数进行端口号的转换

inet_addr函数
  • <sys/socket.h> <netinet/in.h> <arpa/inet.h>
  • in_addr_t inet_addr(const char *cp);
  • 函数功能:
    a. 将字符串形式的IP "192.168.102.169"转换为IPV4的32位无符号整型数的IP
    b. 将无符号整型数的ip,从主机端序转为网络端序
  • 参数:字符串形式的ip
  • 返回值:永远成功,返回网络端序的、32位无符号整型数的ip
  • 31
    点赞
  • 129
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
std::bind函数可以将可调用对象和其参数进行绑定,并返回一个绑定后的函数对象。绑定的结果可以使用std::function保存。它可以用于绑定普通函数、成员函数以及函数对象,并且可以通过占位符来指定参数的位置。 当绑定普通函数时,std::bind的第一个参数是函数名,普通函数做实参时会隐式转换成函数指针。例如,std::bind(my_divide,_1,2)等价于std::bind(&my_divide,_1,2),其中_1表示占位符,位于std::placeholders中。调用绑定后的函数对象时,传入的参数将替换占位符的位置,实现参数的绑定。 而当绑定成员函数时,std::bind的第一个参数是成员函数的地址,后续参数是成员函数调用时需要传入的参数。通过占位符来指定参数的位置。调用绑定后的函数对象时,传入的参数将替换占位符的位置,实现参数的绑定。 总结而言,std::bind函数可以将可调用对象和其参数进行绑定,以创建一个新的函数对象。这个新的函数对象可以方便地进行参数的绑定,减少传入参数的个数,使得代码更加灵活和易读。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [C++11 std::bind](https://blog.csdn.net/mayue_web/article/details/87915389)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [C++11中的std::bind 简单易懂](https://blog.csdn.net/Jxianxu/article/details/107382049)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值