理解计算机系统_网络编程(4)_套接字api

前言
       

        以<深入理解计算机系统>(以下称“本书”)内容为基础,对程序的整个过程进行梳理。本书内容对整个计算机系统做了系统性导引,每部分内容都是单独的一门课.学习深度根据自己需要来定

引入

        接续上一篇理解计算机系统_网络编程(3)-优快云博客

        在上一篇帖子中,感觉学习方法有所失误.api的学习有两个好方法

        1.做一个简单的例子---这个例子要比较典型,最好能包含知识点所有内容(最简),然后把他背下来---这个观点来自笔者学习的<C++ Prime Plus> 6th Edition这本书中的原话,过去某篇帖子中有提及.

        2.读别人写的代码---这个观点本书P662也提到了.本质上和第1条相同,可能效果上会弱一点,综合起来可以手抄一遍或多遍(没错,手抄).这样学起来比较快,理解深刻.

        还有一种办法:读api的源码.

        反过来看背诵api的学习方法,一是不够深刻,二是容易出错,事倍功半.本书没有getaddrinfo的源码,查了权威书籍<Unix 网络编程 卷1:套接字联网API>(简介中号称"不朽的经典")也没找到源码,所以不在getaddrinfo上继续了(包括getnameinfo),如果以后有时间再细说.

学习方法回顾

        磨刀不误砍柴工,对学习本身也要有个清晰的认识.学习=理解(记忆)+应用(练习)

        1>学习概念. 编程是一种逻辑的表达.有趣的是,学习本身也是在建立一种"未知"和"已知"的逻辑,也就是理解.此外有的知识没有逻辑可言,是一种"公理",这就需要记忆了.所以,对概念的学习是"理解优先,记忆在后"

        2>练习.贯彻"学以致用"的思路,做例子巩固学习效果.例子做多少视情况而定,难度上一般循序渐进.如果想简单出效果,就用"做一个简单的例子并背诵"这个办法.

        注意:学习概念和练习最好一起进行,不要分开.在学会的基础上,可以增加"熟练"的要求,只要一提到某个知识点马上就浮现出相关的文字,图,思路并解决问题,这就是从新手进阶到高手了.

        虽然此处有点班门弄斧,但笔者也不怕被人嘲笑(*^_^*)

套接字辅助函数 

        前面的套接字函数看起来很费劲,那么让封装后的套接字辅助函数来帮忙.回忆一下,编程不也是在把机器指令层层封装?

          辅助函数介绍了两个open_clientfd和open_listenfd,字面意思打开客户端描述符,和打开监听描述符.

open_clientfd

函数解读

        含义:客户端调用open_clientfd建立与服务器的连接.

        函数原型如下:

#include"csapp.h"

int open_clientfd(char *hostname,char *port);
//返回值:若成功返回描述符clientfd,若出错返回-1

           参数说明:

           返回值clientfd,看前面向导图,客户端连接准备阶段的完成标志,生成一个clientfd的文件描述符,然后可以用Unix I/O函数做输入输出.---也就是一步到位,中间过程被封装.

            hostname:服务器主机名,也就是域名.

             ---主机名的解读:互联网设备有IP地址作为身份证明,域名通常和IP地址一一对应,也有一对多,多对一.笔者理解服务器的主机名是其域名,客户端有没有域名也无所谓---只用IP地址做标识,某度上举了个例子,某一台电脑是客户端,右键"我的电脑"上可以修改的那个电脑名,可以看作是客户端的域名.

             port:端口号,作监听连接请求.

              ---解读:这里有一点迷惑,端口号照理说是个short或者int整型,这里是char*类型,例如前面提到了著名的端口号80,难道是char a=80;然后参数传入&a?  

源码解读

                 这一步很重要,看getaddrinfo函数如何使用,各个部分的功能(注释).实在读不懂还可以照着抄.源码在本书P661.

                 第5-10行,注释:获得一个潜在服务器地址的链表.

                前面不想读的话,第10行调用了Getaddrinfo---包装函数,得到一个struct addrinfo**类型的数据&listp.这个双重指针表示一个链表结点的指针,即链表结点的集合.而每个链表结点中封装了可能的地址参数.

======================内容分割线↓==========================================

这里有个语法方面的新内容,以前没有遇见过

        给双重指针传入指向链表的指针,是想表达什么意思? 

复习指针的用法:

        指针基本内容:指向单个元素,指向数组,指向链表

        当函数形参用指针作参数,可以访问和改变传入实参指针指向的数据值.

        要点:某类型的指针→某类型的值.如int*修改int值,而int**修改int*的值,即指针指向不同的地址.

        举例:下面给int*形参传入int数组a,可以访问和修改数组a里的数值

//单层指针,已测试
#include<stdio.h>
int a[] = { 0,1,2 };

void fun1(int* p,int size) {       //指针指向数组
    for (int i = 0; i < size; i++) {
        *p += 1;
        printf("数组传入的值加1等于%d\n", *p);
    }
 }

int main(void) {
    int size = sizeof(a) / sizeof(int);
    fun1(a,size);                   //a数组内元素加1;
    
}

双重指针作形参的用法,通常有两种:

                1需要修改某个指针变量指向的地址,传入这个指针变量的地址;

//双重指针,已测试,部分用不了
#include<stdio.h>
int a[] = { 0,1,2 };
int b[] = { 3,4,5 };


//如果传入大于0数字,双重指针指向第一个数组
void fun2(int** pd, int* p1, int* p2, int judge) {       //双重指针表示返回的数组
    if (judge >= 0)
        *pd = p1;
    else
        *pd = p2;
}
//如果传入大于0数字,双重指针指向第1个数字
void fun3(int* pd, int a, int b, int judge) {           //指针表示返回值
    if (judge >= 0)
        *pd = a;
    else
        *pd = b;
}
//用不了:打印数组
void show(int* pd, int size) {
    for (int i = 0; i < size; i++) {
        printf("数组里的第%d值是:%d\n", i, pd[i]);
    }
        
}
int main(void) {
    int* p=NULL;
    int** pd = &p;
    fun2(pd, a, b, 0);                                      //传入0,*pd指向a;
//    int size = sizeof(*pd) / sizeof(int);                //不支持*pd表示数组
//   show(*pd, size);
    printf("选择后数组中第1个数字是:%d\n", **pd);          //求得第1个值,即a[0]
    printf("选择后数组中第2个数字是:%d\n", *(*pd+1));      //求得第2个值,即a[1]     
    printf("选择后数组中第3个数字是:%d\n", *(*pd+2));      //求得第3个值,即a[2]
    fun3(p, 3, 4, 0);
    printf("选择后的数字是:%d\n", *p);
}

 这段代码有点勉强,有个问题没解决,就是不能用*pd表示数组,以后再说.

看fun3这个函数来推导,本来可以把需要的数据放在返回值,但放在了形参里用指针返回.

                2双重指针指向二维数组,访问和修改二维数组中的数值.---示例略

注意:链表的数据被包裹在指向链表的指针中.当给指针传入指向链表首元素的指针时,他可以遍历整个链表并且修改链表中的数据.但是链表的机制和数组有所不同,一般不会说传入双重指针来修改其指向(笔者想到一种可能,就是函数里有多个链表,这种情况下是可能的,不过那也太复杂了,可不考虑)

//指向链表的指针,已测试
#include<stdio.h>

typedef struct node {               //链表定义
    int n;
    struct node* next;
}Node;
//很简单的链表两个元素,n1在前,n2在后
Node n2 = { 2,NULL };   
Node n1 = { 1,&n2 };

void fun2(Node* nd) {               //指针指向链表
    while (nd) {
        nd->n += 2;
        printf("链表传入的值加2等于%d\n", nd->n);
            nd = nd->next;
    }
}

int main(void) {
    fun2(&n1);                      //传入链表
}

        所以:结合上面fun3的推导,双重指针传入链表指针的意思是返回函数中做好的链表

//伪代码
void fun4(Node** listp){
    //构建链表
    NOde* p=....
    //返回这个链表
    *listp=p;
}

Node* p=NULL;    //初始化一个指空的指针,一般会报警告
fun4(&p);        //传入双重指针
//执行后p指向了fun4里做好的链表p

        小结:当使用指针作形参时,可以访问和修改指针指向的数据,或者以指针形式返回函数中的数据.

======================内容分割线↑==========================================
 

                  第12-16行,注释:遍历链表,找到能成功连接的结点.

                  第13行遍历链表结点,第15行调用socket函数(原型在上一帖找),传入结点数据,如果返回值小于0,需要继续遍历.当大于或等于0表示连接准备工作完成.

                  第18行,注释:连接服务器

                  第19行调用connect函数(原型在上一帖找),传入客户端描述符clientfd,判断返回值,如果≠-1,则连接成功并跳出循环.至此客户端连接完毕,可以用clientfd描述符+Unix I/O读写传送数据.如果不成功则关闭clientfd.

                    第24行,注释:释放链表  代码在第25行调用包装函数Freeaddrinfo

                    后面是判断,如果前面连接不成功,p遍历到了NULL跳出循环,所以写的是if(!p)此时返回-1,表示此次连接不成功,后面可以继续这个main函数来重复.

                    与此对应的是连接成功,把描述符clientfd返回,用于后面的操作.

函数小结

        读完源码,因为是选择性解读,所以显得难度不大.此外还有之前函数的一些用法.有时候函数的应用是这样的:即使有一些模糊的部分,你读不太懂,那就依样画葫芦--抄.但抄的时候要特别留意注释,以免出错.

         这个函数的核心和前一贴联系起来,他的目的是在客户端返回一个可进行读写的描述符clientfd,其中封装了socket函数和connect函数,大大简化了使用.

open_listenfd

函数解读

        含义:服务器端调用open_listenfd创建一个监听描述符,准备好连接请求.

        函数原型如下:

#include"csapp.h"

int open_listenfd(char *port);
//若成功则为描述符,若出错则为-1.

        open_listenfd函数打开(?)和返回一个监听描述符,这个描述符准备好在端口port上接收连接请求.---黑体字是本书原话,估计是翻译笔误,是打开某个端口port,返回一个监听描述符listenfd.这里同样和前一贴对应上,得到一个监听描述符.接下来仍需要调用accept函数建立连接.在后面的例子中将看到这一点.

        参数就一个port,同样有前面的迷惑,返回值是一个监听描述符,为得到描述符connfd作准备.

源码解读

        大体上和open_clientfd的思路差不多:封装了bind和listen函数,得到了一个监听描述符listenfd.

        注意:P661倒数第2行,使用setsockopt函数来配置服务器,默认30秒拒绝客户端的连接请求,严重阻碍了测试(本书原话).---又引出了新的内容,这种情况就适合"抄",这也是用框架或者别人封装函数的缺点:有的东西被固定了.如果想修改的话,看SOL_SOCKET,SO_REUSEADDR这两个枚举值是否可以改变.

小结

        本贴分析了两个函数的优缺点,大大简化了建立连接的过程.同时回顾了指针的用法,并分析了getaddrinfo函数中出现的双重指针的由来,算是基础知识的巩固

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

重庆彭枫

你的鼓励是我创作的动力,谢谢

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值