医院网站建设 利法拉网络,led 网站建设,网站如何兼容大多浏览器,wordpress网站导航菜单插件引言#xff1a;
北京时间#xff1a;2023/8/25/15:52#xff0c;昨天刚把耗时3天左右的文章更新#xff0c;充分说明我们这几天并不是在摆烂中度过#xff0c;而是在为了更文不懈奋斗#xff0c;历时这么多天主要是因为该部分知识比较陌生#xff0c;所以需要我们花费…引言
北京时间2023/8/25/15:52昨天刚把耗时3天左右的文章更新充分说明我们这几天并不是在摆烂中度过而是在为了更文不懈奋斗历时这么多天主要是因为该部分知识比较陌生所以需要我们花费大量的时间去细细研究为后面无论是TCP套接字还是网络的学习都能更加融会贯通。并且这几天在闲暇时间把《一念永恒》听了一下目前还在过渡期不过根据一些伏笔我认为小高潮即将来临根据前期的一些内容我意识到为什么该小说能被动漫公司拍成动漫主要应该是因为耳根对于主角前期的角色塑造相比于其它小说来说更加独具匠心以人性最朴实的长生、怕死为目的再配上各种小心思和漫不经心为行文规律很好的就将大众在心目中对人性的理解给刻画出来再加上耳根独到的幽默理解可以说一个完美的小说主角就被呈现出来了。这也可能就是耳根能屹立网文巅峰的原因吧相比那些爽文其实也不错但是相比于这种角色刻画更深的小说差距还是非常大的白金就是白金虽然套路单一但是文笔确实无可挑剔ok不谈了该篇博客我们承接上篇博客有关UDP套接字的知识来看一看有关TCP套接字相关的知识并根据TCP套接字实现一份TCP版本的网络通信客户端和服务端。 深入套接字编程
在上篇博客中我们重点对socket编程中的sockaddr结构体进行了深入理解并且对有关socket编程的接口进行了详细认识最后结合sockaddr_in结构体和有关接口实现了三种不同场景的UDP服务端/客户端并成功让其完成数据的接收和传输真正意义上实现了一份基于UDP协议的socket网络通信代码。但由于时间以及内容问题上篇博客我们只讲解了基于UDP版本的socket网络通信所以该篇博客就让我们一起来看看如何使用socket编程实现一份TCP版本的服务端/客户端从而实现基于TCP协议的socket网络通信代码。
理解TCP版本的套接字接口
在上篇博客中我们对套接字常见API进行了详解介绍但准确的来说我们介绍的大部分都是基于UDP版本下的套接字接口所以此时对于TCP版本来说我们需要补充几个在TCP版本下套接字编程中会被使用到的接口其中为什么在TCP版本下需要新增不同的套接字接口本质原因非常简单也就是基于TCP特点带来区别因为TCP需要满足可靠性传输事先建立连接面向字节流等原则。所以接下来我们就来看看TCP版本套接字编程的新增接口吧 int listen(int sockfd, int backlog); 功能将套接字设置为监听状态服务器处于阻塞用于监听指定端口号客户端的连接请求并且维护一个监听队列/等待队列用于存储向该服务端发送的连接请求最终将该监听队列提供给accept接口使用从而实现服务端和客服端之间的连接。换一个角度理解也就是说服务器代码在执行时会被阻塞在该处监听从而一直处于监听状态只有当某客户端发送连接请求被监听到存储在监听队列之后代码才会继续向后执行从而让accept接口完成连接。第一个参数sockfd重新理解sockfd 在创建套接字时返回的一个套接字描述符虽然之前我们一直称其为文件描述符但更准确应该称为套接字描述符同理存储在对应的文件描述符表上是一种套接字的引用套接字的标识符也就是可通过该套接字描述符访问到套接字从而可以对套接字进行管理和操作最终让套接字实现不同的功能完成对应的工作接口。第二个参数backlog同理上述所说该参数与监听队列有关用于表示监听队列存储客户端连接请求的上限也就是个数。返回值同理监听成功返回0监听失败返回-1且错误码被设置。 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 功能同理上述listen接口中所说该接口用于与指定端口号客户端建立连接直接从监听队列中获取客户端请求注意在客户端使用connect接口发送连接请求时并不会直接将客户端的端口号和IP地址发送给服务端而将端口号和IP地址发送给服务端的这个过程一般是系统依据TCP协议完成所以最终accept接口再根据TCP协议对监听队列中对客户端发送的请求连接进行解包从而获取客户端IP地址和端口号的同时也获取到服务端的IP地址和端口号客户端发送从而让客户端和服务端建立连接。第一个参数sockfd同理套接字描述符用于对指定套接字进行accept操作第二个参数addr同理作为输出型参数接收客户端的IP地址和端口号让服务端可以显式的打印和识别客户端的IP地址和端口号第三个参数addrlen同理表示sockaddr_in结构体的长度也就是大小。返回值注意此时这个返回值是一个新的套接字描述符也就是说在accept接口中一定也存在socket接口的使用具体为什么需要返回新的文件描述符而不是像UDP版本一样使用一个套接字文件描述符就能实现网络通信下述讲解。 int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen); 功能同理上述accept接口中所说在客户端中使用向服务端发送连接请求当然基于此时我们对listen和accept接口的理解明白此时connect接口发送的请求肯定是先被listen监听到监听成功之后才被accept处理尝试建立连接并且对于connect接口来说并不是每次一申请就能成功一般需要多次申请因为有很多原因会导致失败如监听队列满了的情况然后同理就是该客户端向指定服务端发送请求肯定是需要知道该服务端的IP地址和端口号所以connect接口在发送请求时就会把服务端的IP地址和端口号发送过去这也就是为什么accept接口默认不仅知道客户端IP地址和端口号还知道服务端IP地址和端口号的原因。第一个参数sockfd同理套接字描述符用于对指定套接字进行connect操作第二个参数addr同理表示客户端需要知道向那个服务端发送连接请求表示服务端的IP地址和端口号第三个参数addrlen同理表示服务端sockaddr_in的长度。返回值同理成功返回0失败返回-1且错误码被设置。 ssize_t read(int fd, void *buf, size_t count); 功能首先明白在当初学习系统编程之文件系统相关知识时对该接口有过一定的了解该接口就是用来从特定的文件描述符中读取数据同理此时对于套接字描述符而言read接口同样可以从其读取数据第一个参数fd同理套接字描述符向指定套接字中读取数据第二个参数buffer作为输出型参数存储被读取数据第三个参数count读取数据的字节数返回值同理返回实际读取数据的字节数。 ssize_t write(int fd, const void *buf, size_t count); 功能同理该接口用来向特定的文件描述符中写入数据在socket编程中也就是向套接字描述符中写入数据第一个参数fd同理套接字描述符第二个参数buffer存储需要写入数据第三个参数count写入数据的大小。返回值同理写入数据的实际字节数。
行文来到此处对于有关TCP版本新增的套接字接口我们就大致有了一定了解但是根据上述所说此时我们还需要解决一个问题也就是为什么accept接口将服务端和客户端建立连接之后需要返回一个新的套接字描述符而不是像UDP版本一样使用一个套接字描述符就能实现本质也就是我想强调出TCP与UDP实现网络通信之间的区别所以在特别区分UDP和TCP不同版本的套接字通信之前此时我们回顾一下UDP版本下的套接字通信第一步同理创建套接字获取套接字描述符第二步初始化sockaddr_in结构体并将其绑定到内核套接字中第三步使用sendto和recvfrom这类多参数接口用于指定目标地址以及接收目标地址最终成功实现客户端和服务端之间的数据传输。可发现在UDP版本下实现套接字通信相比于TCP需要使用listen、accept和connect等接口来说较为容易而根据上述对接口的分析以及功能TCP版本下的套接字通信不仅需要对服务端套接字监听且还需要对监听队列中的客户端连接请求建立联系然后在建立连接之后才允许进行数据间传输所以对于TCP套接字来说此时就不仅仅只是像UDP一样完成数据间传输就行重点在于它还需要对服务端和客户端建立连接所以同理对于listen接口和accept接口来说它们本质都需要直接作用于套接字描述符从而让套接字具备监听和建立连接的能力且又因为套接字还需要具备数据传输的能力所以如果一个套接字监听到客户端请求完成连接进行数据传输时此时其它客户端就不能再使用该套接字与该服务端建立连接因为此时对于服务端来说它唯一的套接字此时正在完成数据传输工作所以这样就会导致服务端的效率非常低下不能并发处理多个客户端所以最终为了解决这一问题此时在accpet接口完成服务端与客户端的连接之后就需要返回一个新的套接字描述符本质也就是向操作系统再申请一个网络通信请求并分配一定的资源空间资源等只有这样才能让每个客户端在与该服务端建立连接之后都拥有一个属于自己的套接字本质理解也就是让服务端不再像UDP版本一样整份代码只有唯一的套接字而是多个套接字每与一个客户端建立连接就有一个新的套接字供其使用让服务端在建立连接和传输数据之间无冲突。
注意 虽然TCP协议需要满足可靠性传输、建立连接、面向字节流等特性但并不意味着上述接口就能实现这些特性如TCP协议需要满足可靠性传输而想要实现可靠性传输则需要通过一系列机制如确认应答、重传、流量控制和拥塞控制等… 这些机制的实现都是体现在TCP协议当中也就是说我们使用套接字编程实现TCP版本的网络通信本质只需要让系统识别这是一份TCP协议版本的代码并且编码符合TCP的这三大特性就行本质也就是因为这些特性都是TCP协议规定好的我们只需要遵从这也就是为什么我们在编码时使用read和write接口作为来进行数据传输因为其具有面向字节流的特性。同理再次强调套接字的本质就是一种网络通信机制socket创建套接字的本质也就是向操作系统申请进行网络通信请求向操作系统分配资源的一个过程并且使用各种接口对套接字进行操作本质就是一种对网络通信功能的实现与控制而已。
正式进入TCP套接字代码编写
在正式进入代码编写前我们需要先明白几个点首先在上述对TCP套接字接口有了一定理解此时我们明白TCP套接字相对于UDP套接字而言关键就在于在数据传输前让服务端与客户端建立连接所以我们对accept接口进行着重讲解明白为什么其返回值是一个新的套接字描述符但此时我们要明白无论是UDP版本的服务端还是TCP版本的服务端如果我们不对其进行多执行流的控制那么它们无论如何都不支持客户端的并发访问所以就算是TCP套接字中accept会返回一个新的套接字描述符它也无法同时进行数据传输和与客户端建立连接本质就是因为单执行流必须按照顺序执行代码明白这点之后此时对于TCP套接字网络通信我们依然分为三个场景单执行流服务端、父子进程服务端、多线程及线程池服务端。
首先是客户端的实现
同理客户端代码不存在特殊改动所以此时客户端代码我们只举例一份如下所示
第一个场景单执行流实现 重点强调 根据上述代码及其注解我们需要重点明白两点一点是在某客户端向服务端发送连接请求之前该服务端是在listen接口处处于阻塞状态而不是accept接口处二点是accept接口中自带数据发送和接收功能也就是recvfrom和sendto接口的实现并且本质建立连接这个概念就是在内部对recvfrom和sendto接口进行封装因为accept接口天生具有获取客户端地址信息和服务端地址信息的能力客户端会将服务端的地址信息也传过来从外部理解也就是在服务端与客户端之间建立一个双向通道通信而对于返回一个新的套接字描述符的本质也就可以理解为返回accept接口中使用socket接口专门为recvfrom和sendto接口创建的那个套接字描述符。
第二个场景父子进程实现服务端 对于使用父子进程来实现多执行流解决服务端并发访问问题从上述代码来看非常合理唯一的缺点就是创建进程带来的效率消耗问题本质也就是该服务端除了会为每一个客户端创建一个新套接字描述符之外此时还需要为每一个客户端创建一个子进程所以这也就是为什么我们需要使用多线程方法来改善的原因。当然这部分知识下述讲到这里我重点想要回顾一下父进程回收子进程的问题上述代码是一种设计套路并不是经典的我们学过的回收子进程的方法我们学过的父进程的回收子进程的方法一共有三种第一种是直接使用waitpid接口但因为此时我们的目的是子进程处理客户端数据的同时父进程同时也可以去调用accept接口让另一个客户端和服务端建立连接所以我们不能让父进程处于阻塞式等待所以在使用waitpid接口时应该采用非阻塞式等待 waitpid(id, nullptr, WNOHANG); 但最后因为需要防止父进程在执行accept接口时监听队列中没有客户端的请求连接而导致父进程被阻塞在accept接口处无法执行waitpid接口回收子进程所以这个方法不推荐在服务端上使用第二种是使用子进程退出信号回收子进程因为子进程退出时会向父进程发送一个退出信号SIGCHLD所以我们只需要在父进程收到该信号时让父进程将该信号的信号递达方式设置为忽略此时系统自动会帮父进程回收该子进程 signal(SIGCHLD, SIG_IGN); 第三种同理是使用子进程退出信号SIGCHLD处理在父进程收到该信号时让父进程将该信号的默认处理方式设置为waitpid接口实现子进程的回收signal(SIGCHLD, handler); 明白了上述有关子进程回收相关知识对于使用父子进程实现服务端的知识我们就理解到这本质没有难度重点就在于子进程的回收而已。
第三个场景多线程实现服务端 以上代码就是将服务端以多线程的形式实现多执行流来解决并发访问问题本质对比父子进程实现多执行流好处就在于不需要频繁创建进程提高服务端效率编码关键就在于新建线程参数控制其它过程同理本质就是为了实现多执行流一个负责处理客户端数据一个负责建立连接。当然因为我们学习了线程池相关的知识所以对于这种临时创建线程的方法我们依然可以使用线程池的方法来改进具体下篇博客详解主要是因为还需要重点讲解有关日志方面的知识所以需要留一份代码来看看日志具体如何使用。
总结有关TCP版本的socket网络通信我们就讲到这啦剩余有关线程池服务端这个终极版本我们留到下篇博客结合日志一起学习See you!