《TCP IP网络编程》尹圣雨——读书小记

之前学了一些网络编程的东西,但还没有系统看过相关书籍,故先选了这本书来读,并记下一些阅读过程中觉得值得记录的东西,作为回顾之用。这里的记录只有Linux下的相关知识,没有Windows的相关操作。

第一章 理解网络编程和套接字

接电话套接字:套接字编程就像电话机。首先要安装电话机(socket函数),接着要给电话机分配号码(bind函数),还要给电话机接上电话线(listen),如果电话响了就可以接听电话了(accept函数)

第二章 套接字类型与协议设置

int socket(int domain, int type, int protocol)

1. domain:协议簇,常用的就是PF_INET(IPV4互联网协议簇)

2. type(数据传输方式):

    1. SOCK_STREAM :面向连接的套接字

    2. SOCK_DGRAM:面向消息的套接字

3. protocol(协议的最终选择):如果前面两个参数已经确定好了协议,则最后一个参数传递0即可。

TCP套接字和UDP套接字不会共用端口号,所以允许重复。

第三章 地址簇与数据序列

《TCP IP网络编程》尹圣雨——读书小记_第1张图片

uint16_t, in_addr等都是POSIX(可移植操作系统接口)定义的数据类型,好处是可扩展,不管到了哪里uint16_t都是两个字节的无符号数。

sin_port和sin_addr都是以网络字节序保存
sin_zero的目的是为了让sockaddr_in和结构体sockaddr保持一致而插入的成员,必须填充为0。

《TCP IP网络编程》尹圣雨——读书小记_第2张图片

对于网络中数据的传输,在传输前会自动将数据转化为网络字节序,接收的数据也会自动转化为主机字节序,不需要程序员手动转化。

in_addr_t inet_addr(const char * string):将字符串信息转化为网络字节序的整数型,如果返回值等于INADDR_NONE,则出错(1.2.3.256则会出错)

int inet_iton(const char * string, struct in_addr * addr),成功返回1,失败返回0,string转化之后的值存放在addr中。

char * inet_ntoa(struct in_addr adr):将网络字节序转化为字符串形式。

《TCP IP网络编程》尹圣雨——读书小记_第3张图片

这里的memset是为了让sin_zero初始化为0。

在监听服务器上所有IP地址时,可以使用INADDR_ANY。
这里写图片描述

#include 
#include 
#include 
#include 
int main(int argc, char * argv[]) {
    char * ip = "127.12.11.11";
    char * port = "4000";
    struct sockaddr_in addr;
    //这是必须的,因为最后的8位为0
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    //字符串转换为地址的第一种方法
    inet_aton(ip, &addr.sin_addr);
    //第二种方法:addr.sin_addr.s_addr = inet_addr(ip);
    //字符串转化为整数再转化为网络字节序
    addr.sin_port = htons(atoi(port));
    //地址转化为字符串
    char * str_ptr = inet_ntoa(addr.sin_addr);
    //这里要格外注意,inet_ntoa返回一个char *,而这个char *的空间是在inet_ntoa里面静态分配的,所以inet_ntoa后面的调用会覆盖上一次的调用。所以最好先复制出来
    char str_arr[20];
    strcpy(str_arr, str_ptr);
    printf("IP:%s\n", str_arr);

    return 0;
}

int bind(int sockfd, struct sockaddr * myaddr, socklen_t addrlen):成功返回0,失败返回-1。

第四章 基于TCP的服务器端/客户端(1)

int listen(int sockfd, int backlog):成功返回0,失败返回-1,调用listen之后进入等待连接请求状态(指客户端请求连接时,受理连接前一直使请求处于等待状态) backlog表示已完成队列的最大长度。

int accept(int sock, struct sockaddr * addr, socklen_t * addrlen):成功时返回创建的套接字描述符,失败返回-1。地址是客户端地址。

int connect(int sock, struct sockaddr * addr, socklen_t addrlen):成功返回0,失败返回-1。

《TCP IP网络编程》尹圣雨——读书小记_第4张图片

服务端只有bind()没有Listen(),客户端会Connect()成功吗?
1. Connect()失败
2. bind()操作只是服务端绑定IP:Port(其他进程便无法bind()此IP:Port),并没有监听,lsof -i找不到端口对应的Fd

服务端只有Listen()没有accept(),客户端会Connect()成功吗?
1. Connect()成功,且tcpdump有完整的3次握手报文
2. Listen()操作后,内核会维护一个监听队列,用于与客户端建立连接(完成3次握手),故客户端能Connect()成功。
服务端accept()会产生网络通讯吗?
1. 没有,tcpdump显示没有报文产生
2. accept()操作只是从Listen()的监听队列中取出一个连接,并建立一个新Socket用于与客户端通讯,故没有网络通讯产生。
服务端只有Listen()没有accept(),客户端Connect()成功后可以调用write()写数据吗?
1. 可以写数据,write()调用返回成功,且tcpdump有完整的请求[PSH]-应答[ACK]报文产生
2. 虽然没有accept(),但客户端依然能write()数据,这时数据存储在服务端的TCP缓冲区中,等到进程accept()之后还可以read()到
服务端只有Listen()没有accept(),客户端连接能正常断开吗?
1. 不能,客户端调用close()之后,tcpdump显示没有完整的4次握手断开报文,只有前2个报文[FIN]和[ACK]
2. 由于服务端没有accept()取出连接fd调用close(),对于服务端没有调用close()的连接,由上图可知客户端的TCP连接会停留在FIN_WAIT_2状态,一直占用客户端资源到FIN_WAIT_2状态超时

《TCP IP网络编程》尹圣雨——读书小记_第5张图片

迭代服务器:反复调用accept函数

echo(回声)服务器端/客户端:将客户端传来的原封不动传回。课本上还有一个计算器的示例代码。

第五章 基于TCP的服务器端/客户端(2)

  1. 一个计算器程序
  2. 《TCP IP网络编程》尹圣雨——读书小记_第6张图片
  3. write函数在数据移到输出缓冲时返回,TCP会保证对输出缓冲数据的传输。

第六章 基于UDP的服务器端/客户端

UDP的工作原理相当于寄信,填好寄信人和收信人的地址,放进邮筒即可。
流控制是区分UDP和TCP的最重要的标志。

这里写图片描述

UDP服务器端和客户端均只需要一个套接字,而不需要每两台电脑之间建立一个唯一的连接。

《TCP IP网络编程》尹圣雨——读书小记_第7张图片

《TCP IP网络编程》尹圣雨——读书小记_第8张图片

这里写图片描述

UDP存在着数据边界,先调用三次sendto, 之后服务端也得调用三次recvfrom才可以。
《TCP IP网络编程》尹圣雨——读书小记_第9张图片

《TCP IP网络编程》尹圣雨——读书小记_第10张图片

第七章 优雅地断开套接字连接

调用close()函数将断开输入流和输出流,不仅己方无法再发送数据,另一端发送给己方的数据也无法接收。
《TCP IP网络编程》尹圣雨——读书小记_第11张图片

情况1:C(client)与S(server)建立链接之后, 当C向S发送数据之后调用shutdown来关闭写操作(断开链接的四次挥手中的前两次)告诉S, C端已发送数据完成, 此时S依然可向C发送数据.
情况2:C(client)与S(server)建议链接之后, 当C向S发送数据之后调用close来关闭socket(同样发送断开链接的四次挥手的前两次, 后两次挥手将由S端调用close来完成), 此时S端被其他条件阻赛并不调用close函数. 然后此时S端向C端发送数据将会引起C端回应rst数据包.

我对shutdown和close跟四次挥手关系的理解

为何需要半关闭
客户端要接收到服务端所有信息后返回一个数据报,为了判断服务端是否已经传输完成,需要用到半关闭(服务端关闭输出流)让客户端读不到数据了,这样客户端才知道读完了然后就返回数据报给服务端。

第八章 域名及网络地址

将域名写入程序比将IP地址写入程序好,因为IP地址会经常变更,但是域名一般不会。

《TCP IP网络编程》尹圣雨——读书小记_第12张图片

h_addr_list中存的指针是指向in_addr结构体变量地址值而非字符串。

《TCP IP网络编程》尹圣雨——读书小记_第13张图片

#include 
#include 
#include 
#include 
#include 
int main(int argc, char * argv[]) {
    char * ip = "www.zhougb3.cn";
    struct hostent * host = gethostbyname(ip);
    printf("%s\n", inet_ntoa(*(struct in_addr *)(host->h_addr_list[0])));
    return 0;
}

第九章 套接字的多种可选项

套接字有多种可选项,并且是按协议层分的,有SOL_SOCKET,IPPROTO_IP,IPPROTO_TCP

《TCP IP网络编程》尹圣雨——读书小记_第14张图片

《TCP IP网络编程》尹圣雨——读书小记_第15张图片

套接字类型只能在创建时决定,以后不能再更改。

我们可以通过这两种方法读取和更改IO缓冲大小,但是更改IO缓冲大小时,并不一定能够按照我们的要求更改,我们只是表达了我们要更改的需求。

binding error : 当服务器主动断开连接之后进入TIME_WAIT状态,这个时候该端口在短时间内不可用,使用binding函数的话就会报错。

如果断开连接的主动发起方在TIME_WAIT状态时收到重复的FIN报文,则会重启定时器,因此可能导致TIME_WAIT状态一直持续。

内核在处理一个设置了SO_REUSEADDR的socket绑定时,如果其绑定的ip和port和一个处于TIME_WAIT状态的socket冲突时,内核将忽略这种冲突,即改变了系统对处于TIME_WAIT状态的socket的看待方式。

nagle算法

第十章 多进程服务器端

每个进程都有一个进程ID,1要分配给操作系统启动后的第一个进程,用户进程无法得到ID值1。

调用fork函数后,子进程返回0,父进程返回子进程ID。

僵尸进程:会占用系统资源。
子进程终止方式: 调用exit函数,在main函数中return。
只有当父进程主动发起请求时,操作系统才会将子进程返回值给父进程,否则子进程会一直保留为僵尸进程(或者一直保留到父进程也终止了才跟着一起终止)。

调用wait函数时,如果没有已经终止的子进程,那么程序将阻塞直到有子进程终止。

《TCP IP网络编程》尹圣雨——读书小记_第16张图片

《TCP IP网络编程》尹圣雨——读书小记_第17张图片

《TCP IP网络编程》尹圣雨——读书小记_第18张图片

父子进程都要进行繁忙的工作,那父进程就不可能一直调用wait函数来中止子进程,而我们同时也要去除僵尸进程,那应该怎么做呢?(信号处理,由操作系统告知)

《TCP IP网络编程》尹圣雨——读书小记_第19张图片

该程序执行时间很短,因为每次产生信号时都会唤醒进程,进程一旦被唤醒就不会再进入休眠状态。

现在都使用下面的这个函数:
《TCP IP网络编程》尹圣雨——读书小记_第20张图片

消灭僵尸进程:
《TCP IP网络编程》尹圣雨——读书小记_第21张图片

第十一章 进程间通信

为什么要完成双向通信需要两个管道?
如果两个进程同时使用一个管道的读端和写端,那么可能一个进程写的东西会被自己读走。

第十二章 IO复用

《TCP IP网络编程》尹圣雨——读书小记_第22张图片

这个很重要,但已经掌握的比较熟练了,就不多叙述。

第十三章 多种IO函数

Linux下的send和recv函数
《TCP IP网络编程》尹圣雨——读书小记_第23张图片

《TCP IP网络编程》尹圣雨——读书小记_第24张图片

常见的可选项(flag)
MSG_OOB:TCP不存在真正意义上的带外数据,只利用TCP的紧急模式(督促数据接收对象尽快处理数据,仍然保持传输顺序)进行传输。

带外数据的应用情况
如果发送客户端程序由于一些原因需要取消已经写入服务器的请求,那么他就需要向服务器紧急发送一个标识取消的请求。使用带外数据的实际程序例子就是telnet,rlogin,ftp命令。前两个程序(telnet和rlogin)会将中止字符作为紧急数据发送到远程端。这会允许远程端冲洗所有未处理的输入,并且丢弃所有未发送的终端输出。这会快速中断一个向我们屏幕发送大量数据 的运行进程。ftp命令使用带外数据来中断一个文件的传输。
服务器端程序里面捕捉SIGURG信号来及时接受带外数据,带外数据只能有一个byte。

MSG_PEEK和MSG_DONTWAIT验证输入缓冲中是否存在接收的数据。MSG_PEEK读取了缓冲中数据也不会删除,MSG_DONTWAIT以非阻塞方式读。

《TCP IP网络编程》尹圣雨——读书小记_第25张图片

使用readv和writev可以将位于不同缓冲区中的数据(数组)一起发送,减少调用IO次数,提高效率。

第十四章 多播和广播

第十五章套接字和标准IO

使用C语言的标准IO:移植性,利用缓冲提高性能(使用IO函数缓冲,可以减少向套接字缓冲移动 次数,自然性能有所提升)。

《TCP IP网络编程》尹圣雨——读书小记_第26张图片

流和FILE对象
标准IO库:是围绕流进行的,当打开一个流时,标准IO函数fopen返回一个指向FILE对象的指针,而FILE是一个结构体,包含着管理该流需要的所有信息,其中就包含文件描述符,指向缓冲区的指针,缓冲区的长度,出错标志等。
系统调用:是围绕文件描述符,当打开一个文件,即返回一个文件描述符,然后该文件描述符就用于后续的操作。

《TCP IP网络编程》尹圣雨——读书小记_第27张图片

《TCP IP网络编程》尹圣雨——读书小记_第28张图片

《TCP IP网络编程》尹圣雨——读书小记_第29张图片

第十六章 关于IO流分离的其他内容

在将文件描述符变为文件指针时,我们根据读和写分别返回了两个文件指针,关闭任何一个都会完全终止套接字,因此我们要学会对FILE指针进行半关闭操作。

《TCP IP网络编程》尹圣雨——读书小记_第30张图片

创建file指针前先复制文件描述符即可,只有文件描述符的数量为0,套接字才会销毁。

《TCP IP网络编程》尹圣雨——读书小记_第31张图片

《TCP IP网络编程》尹圣雨——读书小记_第32张图片

第十七章 优于select的epoll

《TCP IP网络编程》尹圣雨——读书小记_第33张图片

第十八章 多线程服务器端的实现

《TCP IP网络编程》尹圣雨——读书小记_第34张图片
这里写图片描述

当主线程(进程)执行完毕后,子线程如果还没执行完毕也会被中止。下图即是等待线程中止。

《TCP IP网络编程》尹圣雨——读书小记_第35张图片

《TCP IP网络编程》尹圣雨——读书小记_第36张图片

工作线程模型

互斥量和信号量:互斥量是在同一个线程里面上锁解锁,而信号量(二进制)一般在两个线程间,一边上锁一边解锁。

《TCP IP网络编程》尹圣雨——读书小记_第37张图片
《TCP IP网络编程》尹圣雨——读书小记_第38张图片

《TCP IP网络编程》尹圣雨——读书小记_第39张图片

线程不像进程,一个进程中的线程之间是没有父子之分的,都是平级关系。即线程都是一样的, 退出了一个不会影响另外一个。但是所谓的”主线程”main,其入口代码是类似这样的方式调用main的:exit(main(…))。main执行完之后, 会调用exit()。exit() 会让整个进程over终止,那所有线程自然都会退出。
如果进程中的任一线程调用了exit,_Exit或者_exit,那么整个进程就会终止。

参考:https://blog.csdn.net/inuyashaw/article/details/53465294

综合以上要想让子线程总能完整执行(不会中途退出),一种方法是在主线程中调用pthread_join对其等待,即pthread_create/pthread_join/pthread_exit或return;一种方法是在主线程退出时使用pthread_exit,这样子线程能继续执行,即pthread_create/pthread_detach/pthread_exit;还有一种是pthread_create/pthread_detach/return,这时就要保证主线程不能退出,至少是子线程完成前不能退出。现在的项目中用的就是第三种方法,主线程是一个死循环,子线程有的是死循环有的不是。

课本中还有一个简单的聊天软件

最后面还有一个http服务器

你可能感兴趣的:(网络编程)