Socket编程-tcp

1. 前言

tcp套接字编程这里,我们将完成两份代码,一份是基于tcp实现普通的对话,另一份加上业务,client输入要执行的命令,server将执行结果返回给client

2. tcp_echo_server

udp类似,前两步:创建socket文件与bindIP和port到内核,不同的是socket函数第二个参数在tcp这里我们使用SOCK_STREAM,表示提供面向字节流、可靠的传输

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

udp中,接下来我们就可以让client与server通信了,但由于tcp是面向连接的,因此需要等待client连接,在连接之前,需要将sockfd设置为监听状态

#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog); # backlog 通常为4/8/16

服务器启动后,需要不断获取新连接

#include <sys/types.h>
#include <sys/socket.h>

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

accept返回一个文件描述符,与socket返回的文件描述符相比,两者的关系就像餐馆里的服务员与餐馆外的迎宾员,迎宾员通过各种说辞,将客人(也就是连接)引致餐馆交给服务员后转头又去迎接下一位客人,真正为客人提供服务的是餐馆里的服务员

这里的服务员就是accept返回的文件描述符,它才是真正的sockfd;迎宾员就是socket的返回值,也称为监听套接字(listensockfd)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对两者加以区分后,我们也将原来的命名加以修改,且由于后续需要用到listensocfd,将它设置为成员变量

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

服务器启动,不断accept新连接,当获取一个连接失败时,进行获取下一个连接,好比迎宾员的邀请收到了你的冷落,它转头又去邀请下一位客人;当有连接到来时,交给sockfd,给连接提供服务

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在提供服务时,server首先读到来自client发送的信息,需要注意的时,tcp的读写使用recv/sendudp的读写使用recvfrom/sendto

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
# flags默认设为0

在这里,由于tcp是面向字节流的,读到的数据不一定是一个完整的报文,可能是半个,也可能是多个,需要我们进行处理,具体的细节在后面的文章中会详讲,这边就当作一个完整的数据

收到消息后,再给client回复;如果recv的返回值为0,表示client退出了,类似于管道:当读端读到0,表示写端关闭了

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在client方面,创建好socket文件之后,应该要bind自身IP和port,但上一篇文章说过,不需要显示bind;server在等待连接,client则需要去连接,当连接成功时,OS会自动bind好client的IP和port

#include <sys/types.h>
#include <sys/socket.h>

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

连接成功后,就能和server正常通信了

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当然,目前的代码缺点也很明显,它只能为一个client提供服务,由于client的服务是长服务,而我们只有一个进程,当第二个client来了,server在服务第一个进程,获取不上连接

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.1 多进程版本

当获取到新连接时,创建子进程,让子进程去提供服务,父进程等待;我们知道,父子进程之间独立,子进程会继承父进程的pcb,包括文件描述符表;子进程继承了父进程的listensockfdsockfd

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

父进程获取新连接拿到的sockfd,子进程会以写时拷贝的方式独有一份,此时父进程继续去获取连接,不会使用该sockfd,二我们知道文件描述符的个数是有上限的,父进程占着该文件描述符不用,可能会导致文件描述符泄漏的问题,因此,父进程要关闭sockfd

子进程只需要从父进程继承下来的sockfd即可,因此子进程最好关闭listensockfd

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

父进程在waitpid这里是阻塞等待,我们希望父进程继续去获取下一个连接,可以直接对SIGCHID进行忽略,这样子进程退出自动回收

这里使用的方法是,子进程再创建孙子进程,让后退出,父进程直接waitpid成功,继续获取下一个连接,孙子进程由于子进程先退,变成孤儿进程,被系统领养,释放的工作由系统完成

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.2 多线程版本

当主线程获取到新连接时,创建新线程,让新线程去执行服务

这里的问题是:

  1. 如何调用TcpServer中的Service函数
  2. 如何将sockfdaddr传给Service函数

我们建立一个内部类,存放TcpServer的指针和需要传递的变量,使用该类创建实例传给线程函数,在线程函数中调用

同时,为了不让主线程对新线程join而阻塞,将新线程分离

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意:这里不能使用上面一种方式创建实例,否则会因为释放两次ThreadData对象而导致client退出时server崩溃

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.3 线程池版本

利用之前的线程池,将Service函数作为任务交给线程池

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3. command_server

我们想实现当客户端发送一条指令给服务器时,服务器执行该指令,并将执行结果返回给客户端;服务器只负责读取数据,如何处理数据交给业务板块,也就是做到IO与业务解耦

我们知道,linux下的命令是创建子进程,由子进程执行,再将执行结果返回给bash,这里也类似,当服务器收到一条指令,需要创建子进程,由子进程处理

我们有现成的接口可以直接调用

#include <stdio.h>

FILE *popen(const char *command, const char *type);
# 父进程向管道读,type设为"r"

popen内部会创建管道和子进程,由子进程执行命令,再将执行结果写入管道,我们服务器就只需要向管道读取即可,返回值是一个封装的FILE*类型

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们还可以限制客户端能够执行的命令

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为什么要让服务器帮我们执行命令,我们直接在linux的命令行解释器下直接执行不就行了?实际上,在云服务器中,我们的指令不是在我们本主机上执行的,都是交给一个叫sshd的服务远程执行,再将执行结果返回的

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

因此,上面的代码能帮我们更好的了解云服务器的指令执行原理

4. 支持断线重连的客户端

有时因为网络问题导致与服务器的连接断开了,每次都要我们重新启动客户端太麻烦了,我们将客户端改成能够断线后重连,当重连一定次数还是无法连接服务器,此时才退出

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
[NetWork/Lesson2/2. command_server · baiyahua/Linux - 码云 - 开源中国](https://gitee.com/baiyahua/linux/tree/master/NetWork/Lesson2/2. command_server)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值