最近阅读UNIX网络编程第四章时,书本末尾介绍了两个函数getsockname()和getpeername(),可以用于获取服务器端和客户端的IP地址与端口,原本很简单的两个函数,过一眼即明白函数的用法,但在实际编程测试中,却出现了一个让人意外的结果,如下图所示:

这两个函数在第一个客户连接时解析出的IP地址和端口全部为0,出乎我的期望。而在后面的客户连接时,打印出的IP地址和端口却是正确的。

下面先给出客户端和服务端的代码:

客户端:

#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <pton32>
#include <iostream> int main(int argc, char const *argv[])
{
int sockfd;
sockaddr_in srvaddr; bzero(&srvaddr, sizeof(srvaddr));
srvaddr.sin_family = AF_INET;
if (argc < )
{
std::cout << "usage:" << argv[] << " <IP address>" << std::endl;
return -;
}
srvaddr.sin_addr = pton32(argv[]);
srvaddr.sin_port = htons(); sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (connect(sockfd, (sockaddr *)&srvaddr, sizeof(srvaddr)) != )
{
std::cout << "connect error,retry please.";
throw;
} char str[] = {'\0'};
while ((read(sockfd, str, )) > )
{
std::cout << str << ' ';
} std::cout << std::endl; close(sockfd); return ;
}

服务端:

 #include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <pton32>
#include <iostream> int main(int argc, char const *argv[])
{
int listenfd, connfd;
sockaddr_in cliaddr, srvaddr; bzero(&srvaddr, sizeof(srvaddr));
srvaddr.sin_family = AF_INET;
srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
srvaddr.sin_port = htons(); listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (bind(listenfd, (sockaddr *)&srvaddr, sizeof(srvaddr)))
{
std::cout << "bind failed." << std::endl;
return -;
} if (listen(listenfd, ))
{
std::cout << "listen failed." << std::endl;
return -;
} char str[] = {'\0'}; bzero(&cliaddr, sizeof(cliaddr)); while (true)
{
socklen_t length = sizeof(cliaddr);
connfd = accept(listenfd, (sockaddr *)&cliaddr, &length); sockaddr_in local, peer;
socklen_t local_len, peer_len;
bzero(&local, sizeof(local));
bzero(&peer, sizeof(srvaddr)); getsockname(connfd, (sockaddr *)&local, &local_len);
getpeername(connfd, (sockaddr *)&peer, &peer_len); char buff_local[] = {'\0'}, buff_peer[] = {'\0'}; if (inet_ntop(AF_INET, (void *)&local.sin_addr, buff_local, ))
{
std::cout << "local ip: " << buff_local
<< "\t\tlocal port: " << ntohs(local.sin_port);
} if (inet_ntop(AF_INET, (void *)&peer.sin_addr, buff_peer, ))
{
std::cout << "\npeer ip: " << buff_peer
<< "\t\tpeer port: " << ntohs(peer.sin_port);
} std::cout << std::endl; write(connfd, str, );
close(connfd);
}
return ;
}

其中的pton32头文件是我个人自定义的头文件,定义如下:

#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <iostream> in_addr pton32(const char *str)
{
in_addr s_addr;
try
{
if ( == inet_pton(AF_INET, str, &s_addr))
return s_addr;
else
throw false;
}
catch (bool b)
{
std::cout << "convert failed." << std::endl;
} bzero(&s_addr, sizeof(s_addr));
return s_addr;
}

定义这个函数的目的在于一步到位快速转换IP地址。

在给出的服务端代码中,程序的流程逻辑依然是:socket →bind → listen → accept → read/write → close。

我个人的代码规范是:尽量在需要用到变量时才定义该变量,保持变量的定义和使用联系紧密。40~41行中的变量在即将被使用前进行定义。

这里需要关注的是第45~46行,getsockname()和getpeername()函数使用一个已连接的socket描述符分别获取服务端和客户端的IP地址和端口。并在50~58行中进行打印输出。运行服务端和客户端得到的结果在文章开头的贴图里给出。这是个相当奇怪的结果,getsockname()和getpeername()函数是对一个已连接socket描述符进行读取,却没有正确获得结果,利用搜索引擎搜索良久未得到结果,最终通过查看getsockname()函数的man手册说明,看到一句说明才找到问题所在,说明如下:

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

getsockname() returns the current address to which the socket sockfd is bound,in the buffer pointed to by addr. The addrlen argument should be initialized to indicate the amount of space (in bytes) pointed to by addr. On return it contains the actual size of the socket address.

The returned address is truncated if the buffer provided is too small;  in  this case, addrlen will return a value greater than was supplied to the call.

通过手册说明,可以看到addrlen参数所指的对象必须初始化,另外,如果初始提供的值太小,getsockname()函数在返回时,新写入addrlen指向的对象的值将会大于所调用时提供的值。

根据此特性,我的服务端程序出现的问题可以如此进行推断了:

由于我是在linux环境下使用的gcc编译器,在编译时,编译器会将main函数中未初始化的局部变量全部初始化未0,因此在第一次调用getsockname()和getpeername()函数时,第三个参数所指的对象的值为0,getsockname()和getpeername()没有对套接字地址结构进行写入操作,另外,初始提供的第三个参数所指的对象的值太小,getsockname()和getpeername()函数在返回时,用一个更大的值覆盖了原来的值。

接下来,在while循环中不断建立销毁40~41行代码中定义的变量,但是程序所使用的栈内存位置不变,由于没有初始化操作,因此内存残留的值(第一次函数调用返回的更大值)在下一次循环中被读取使用,造成了后续的结果正确输出。

接下来验证推断,稍稍改动一下服务端程序,在41行后增加两行可以输出定义的变量的值和地址的语句

std::cout << "local_len: "  << local_len  << "\t\t\t" << "peer_len: "  << peer_len  << std::endl;
std::cout << "local_len: " << &local_len << '\t' << "peer_len: " << &peer_len << std::endl;

重新编译后运行,测试结果如下:

在结果中证明了上面的推测,每次循环都重复使用之前的内存,也就读取了之前内存的值。

以上,再一次说明了,C++定义变量时一定要初始化。

关于getsockname()/getpeername()函数第一次被调用得到0.0.0.0结果的说明的更多相关文章

  1. getsockname()/getpeername()函数第一次被调用得到0.0.0.0结果

    int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); getsockname() returns the cu ...

  2. UNIX网络编程——getsockname和getpeername函数

    UNIX网络编程--getsockname和getpeername函数   来源:网络转载   http://www.educity.cn/linux/1241293.html     这两个函数或者 ...

  3. 你好,C++(27)在一个函数内部调用它自己本身 5.1.5 函数的递归调用

    5.1.5 函数的递归调用 在函数调用中,通常我们都是在一个函数中调用另外一个函数,以此来完成其中的某部分功能.例如,我们在main()主函数中调用PowerSum()函数来计算两个数的平方和,而在P ...

  4. python中函数的定义,调用,全局变量,局部变量,函数的嵌套使用-初级篇

    函数的基本概述 在学习函数之前,一直遵循:面向过程编程,即:根据业务逻辑从上到下实现功能,可以思考一下如果有某个功能的代码是在多个地方使用的是否可以只写一次?此时的代码该如何定义.先观察以下的案例: ...

  5. python—day9 函数的定义、操作使用方法、函数的分类、函数的嵌套调用

    一.函数的定义 函数的四个组成部分: 函数名. 函数体. 函数返回值. 函数参数 1.概念:重复利用的工具,可以完成特定功能的代码块,函数是存放代码块的容器 2.定义: def:声明函数的关键词 函数 ...

  6. VC++函数只被调用一次

    如何保证某个函数只被调用一次   一个函数caller会在其内部调用另外一个函数callee,现在的情况是,caller可能会在多个地方被多次调用,而你希望callee只在第一次被调用时被调用一次.一 ...

  7. javascript、jQuery函数定义和调用方法

    一.javascript 1.var aaa=function(){...} var 方式定义的函数,不能先调用函数,后声明,只能先声明函数,然后调用. 2.function aaa(){...} f ...

  8. php闭包实现函数的自调用,也是递归

    php的闭包可能不常用,但是在某些场合之下还是可以考虑用php的闭包来实现某些功能的,比如递归,这里讲一下用php的闭包实现递归 //php闭包实现函数的自调用,也就是实现递归 function cl ...

  9. [Effective JavaScript 笔记]第17条:间接调用eval函数优于直接调用

    eval函数不仅仅是一个函数.大多数函数只访问定义它们所在的作用域,而不能访问除此之外的作用域(词法作用域).eval函数具有访问调用它时的整个作用域的能力.编译器编写者首次设法优化js时,eval函 ...

随机推荐

  1. Mac 安装nodejs

    原文链接:http://blog.csdn.net/u010053344/article/details/50545304 Mac 安装nodejs 这几日因为需求需要又临时用到nodejs,之前安装 ...

  2. HDU4726——Kia's Calculation——2013 ACM/ICPC Asia Regional Online —— Warmup2

    题目的意思是给你两个数字(多达10^6位) 做加法,但是有一点,没有进位(进位不算,相当于这一位相加后对10取模) 你可以任意排列两个数字中的每一位,但是不能是0开头. 现在题目要求以这种不进位的算法 ...

  3. vue项目使用eslint

    转载自 https://www.cnblogs.com/hahazexia/p/6393212.html eslint配置方式有两种: 注释配置:使用js注释来直接嵌入ESLint配置信息到一个文件里 ...

  4. 左连接,右连接和等值连接(left join,right join和inner join)

    left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录 right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录inner join(等值连接) 只 ...

  5. 页面: Fork me on GitHub

    一.实现效果如下: 二.代码地址:https://github.com/blog/273-github-ribbons 这是一个国外网友开发的代码, 里面有很多种样式,可以自已随心选择. 三.我们只拿 ...

  6. MT【153】缩小包围圈

    (清华2017.4.29标准学术能力测试3) 集合$S=\{1,2,\cdots,25\}$,$A\subseteq S$,且$A$ 的所有子集中元素之和不同.则下列选项正确的有(      ) A. ...

  7. 【BZOJ1853】幸运数字(搜索,容斥)

    [BZOJ1853]幸运数字(搜索,容斥) 题面 BZOJ 洛谷 题解 成功轰下洛谷rk1,甚至超越了一个打表选手 这题思路很明显吧,先搞出来所有范围内的合法数字,然后直接容斥, 容斥的话显然没有别的 ...

  8. 【BZOJ2006】【NOI2010】超级钢琴(主席树,优先队列)

    [BZOJ2006]超级钢琴(主席树,优先队列) 题面 BZOJ 题解 既然是一段区间 首先就要变成单点 所以求一个前缀和 这个时候贪心很明显了: 枚举每一个点和可以和它组成一段的可行的点 全部丢进一 ...

  9. JS的强制类型转换

    将值从一种类型转换为另一种类型通常称为类型转换,这是显式的情况,隐式的情况称为强制类型转换. JavaScript中的强制类型转换总是返回标量基本类型值,如字符串.数字和布尔值,不会返回对象和函数. ...

  10. NOIWC2017&&THUWC2017 滚粗记

    因为NOI WC的时候一直在生病,浑浑噩噩就过去了7天,基本没什么记忆了,所以就压到一篇里好了. day -1 第一次发现高铁的椅子原来还可以转过来,于是我们四个小伙伴面对面愉快的打了一路宣红A. 在 ...