文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

网络字节序——TCP接口及其实现简单TCP服务器

2023-09-05 10:27

关注

森格 (2)

文章目录

简单TCP服务器的实现

void initserver(){//1.创建套接字_listensock=socket(AF_INET,SOCK_STREAM,0);if(_listensock<0){    logMessage(FATAL,"create listensocket error");    exit(SOCK_ERR);} logMessage(NORMAL, "create socket success: %d", _listensock);//2.bind ip和portstruct sockaddr_in local;local.sin_family=AF_INET;local.sin_port=htons(_port);local.sin_addr.s_addr=INADDR_ANY;if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)//绑定失败{    logMessage(FATAL,"bind error");    exit(BIND_ERR);} logMessage(NORMAL,"bind success");//3.将套接字设置为监听模式if(listen(_listensock,0)<0){    logMessage(FATAL,"listen error");    exit(LISTEN_ERR);}logMessage(NORMAL,"listen success");}

socket函数原型

#include #include int socket(int domain, int type, int protocol);

bind函数原型

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

listen函数原型

int listen(int sockfd, int backlog);

总的来说initserver函数作用是先创建套接字,然后填充指定的端口号和ip,并将套接字设置为监听状态

void start(){    while(true)    {        struct sockaddr_in cli;        socklen_t len=sizeof(cli);        bzero(&cli,len);        int sock=accept(_listensock,(struct sockaddr*)&cli,&len);        if(sock<0)        {            logMessage(FATAL,"accept client error");            continue;        }        logMessage(NORMAL,"accept client success");        cout<<"accept sock: "<<sock<<endl;        }

accept函数原型

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

start函数作用是阻塞接受客户端发送来的连接请求,使得服务器与客户端建立通信

tcpclient.cc

#include#include#include#include"tcpclient.hpp"using namespace std;using namespace client;static void Usage(string proc){    cout<<"\nUsage :\n\t"<<proc<<" serverip serverport\n"<<endl;}int main(int argc, char* argv[]){    if(argc!=3)    {        Usage(argv[0]);        exit(1);    }string serverip=argv[1];uint16_t serverport=atoi(argv[2]);unique_ptr<tcpclient> tc(new tcpclient(serverip,serverport));tc->initclient();tc->start();    return 0;}

tcpclient.hpp

#pragma once#include #include #include #include #include #include #include #include using namespace std;#define NUM 1024namespace client{    class tcpclient{public:tcpclient(const string& ip,const uint16_t& port):_sock(-1),_port(port),_ip(ip){}void initclient(){//1.创建sockfd_sock=socket(AF_INET,SOCK_STREAM,0);if(_sock<0){   cerr<<"socket create error"<<endl;   exit(2);}//2.绑定 ip port,不显示绑定,OS自动绑定}void start(){struct sockaddr_in ser;bzero(&ser,sizeof(ser));socklen_t len=sizeof(ser);ser.sin_family=AF_INET;ser.sin_port=htons(_port);ser.sin_addr.s_addr=inet_addr(_ip.c_str());if(connect(_sock,(struct sockaddr *)&ser,len)!=0){    cerr<<"connect error"<<endl;}else{    string msg;    while(true)    {        cout<<"Enter# ";        getline(cin,msg);        write(_sock,msg.c_str(),msg.size());                char inbuffer[NUM];        int n=read(_sock,inbuffer,sizeof(inbuffer)-1);        if(n>0)        {            cout<<"server return :"<<inbuffer<<endl;        }else        {            break;        }    }}}~tcpclient(){    if(_sock>=0) close(_sock);}private:int _sock;uint16_t _port;string _ip;};}

tcpserver.cc

#include"tcpserver.hpp"#include"log.hpp"#include#include#includeusing namespace Server;using namespace std;static void Usage(string proc){    cout<<"\nUsage:\n\t"<<proc<<" local_port\n\n"<<endl;}int main(int argc,char* argv[]){if(argc!=2){    Usage(argv[0]);    exit(USAGE_ERR);}uint16_t port=atoi(argv[1]);//将字符串转化为整数unique_ptr<tcpserver> ts(new tcpserver(port));ts->initserver();ts->start();return 0;}

tcpserver.hpp

#pragma once#include #include #include #include #include #include #include #include #include #include #include #include #include"log.hpp"#define NUM 1024using namespace std;namespace Server{    enum    {        USAGE_ERR=1,SOCK_ERR,BIND_ERR,LISTEN_ERR    };class tcpserver;    class ThreadData    {public:ThreadData( tcpserver* self,int psock):_this(self),_psock(psock){}tcpserver* _this;int _psock;    };class tcpserver{ public:tcpserver(const  uint16_t& port):_port(port),_listensock(-1){}void initserver(){//1.创建套接字_listensock=socket(AF_INET,SOCK_STREAM,0);if(_listensock<0){    logMessage(FATAL,"create listensocket error");    exit(SOCK_ERR);} logMessage(NORMAL, "create socket success: %d", _listensock);//2.bind ip和portstruct sockaddr_in local;local.sin_family=AF_INET;local.sin_port=htons(_port);local.sin_addr.s_addr=INADDR_ANY;if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)//绑定失败{    logMessage(FATAL,"bind error");    exit(BIND_ERR);} logMessage(NORMAL,"bind success");//3.将套接字设置为监听模式if(listen(_listensock,0)<0){    logMessage(FATAL,"listen error");    exit(LISTEN_ERR);}logMessage(NORMAL,"listen success");}void start(){     // signal(SIGCHLD, SIG_IGN);    threadPool<Task>::getthpptr()->run();    while(true)    {        struct sockaddr_in cli;        socklen_t len=sizeof(cli);        bzero(&cli,len);        int sock=accept(_listensock,(struct sockaddr*)&cli,&len);        if(sock<0)        {            logMessage(FATAL,"accept client error");            continue;        }        logMessage(NORMAL,"accept client success");        cout<<"accept sock: "<<sock<<endl;        // serviceIO(sock);//客户端串行版        // close(sock);        //多进程版---        //一个客户端占用一个文件描述符,原因在于孙子进程执行IO任务需要占用独立的文件描述符,而文件描述符是继承父进程的,而每次客户端进来都要占用新的文件描述符        //因此若接收多个客户端不退出的话文件描述符会越来越少。//         pid_t id=fork();//创建子进程//         if(id==0)//子进程进入//         {//             close(_listensock);//子进程不需要用于监听因此关闭该文件描述符//             if(fork()>0)  exit(0);// //子进程创建孙子进程,子进程直接退出,让孙子进程担任IO任务,且孙子进程成为孤儿进程被OS领养,// //除非客户端退出IO任务结束否则该孤儿进程一直运行下去不会相互干扰,即并行完成服务器和客户端的通信// //孙子进程// serviceIO(sock);// close(sock);// exit(0);//         }//         //父进程//         pid_t ret=waitpid(id,nullptr,0);//         if(ret<0)//         {//             cout << "waitsuccess: " << ret << endl;//         }//多线程版// pthread_t pid;// ThreadData* th=new ThreadData(this,sock);// pthread_create(&pid,nullptr,start_routine,th);threadPool<Task>::getthpptr()->push(Task(sock,serviceIO));    }}// static void* start_routine(void* args)// {//     pthread_detach(pthread_self());//     ThreadData* ret=static_cast(args);//     ret->_this->serviceIO(ret->_psock);//     close(ret->_psock);//     delete ret;//     return nullptr;// } // void serviceIO(int sock)// {//     char inbuffer[NUM];//     while(true)//     {//         ssize_t n=read(sock,inbuffer,sizeof(inbuffer)-1);//         if(n>0)//         {//             inbuffer[n]=0;//             cout<<"recv message: "<//             string outb=inbuffer;//             string outbuffer=outb+"[server echo]";//             write(sock,outbuffer.c_str(),outbuffer.size());//         }// else// {//     logMessage(NORMAL,"client quit,i quit yep");//     break;// }//     }// }~tcpserver(){}private:int _listensock;//用于监听服务器的sock文件描述符uint16_t _port;//端口号};}

1. 单进程版:客户端串行版

#pragma once#include #include #include #include #include #include #include #include #include #include #include #include #include"log.hpp"#define NUM 1024using namespace std;namespace Server{    enum    {        USAGE_ERR=1,SOCK_ERR,BIND_ERR,LISTEN_ERR    };class tcpserver{ public:tcpserver(const  uint16_t& port):_port(port),_listensock(-1){}void initserver(){//1.创建套接字_listensock=socket(AF_INET,SOCK_STREAM,0);if(_listensock<0){    logMessage(FATAL,"create listensocket error");    exit(SOCK_ERR);} logMessage(NORMAL, "create socket success: %d", _listensock);//2.bind ip和portstruct sockaddr_in local;local.sin_family=AF_INET;local.sin_port=htons(_port);local.sin_addr.s_addr=INADDR_ANY;if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)//绑定失败{    logMessage(FATAL,"bind error");    exit(BIND_ERR);} logMessage(NORMAL,"bind success");//3.将套接字设置为监听模式if(listen(_listensock,0)<0){    logMessage(FATAL,"listen error");    exit(LISTEN_ERR);}logMessage(NORMAL,"listen success");}void start(){    while(true)    {        struct sockaddr_in cli;        socklen_t len=sizeof(cli);        bzero(&cli,len);        int sock=accept(_listensock,(struct sockaddr*)&cli,&len);        if(sock<0)        {            logMessage(FATAL,"accept client error");            continue;        }        logMessage(NORMAL,"accept client success");        cout<<"accept sock: "<<sock<<endl;         serviceIO(sock);//客户端串行版         close(sock);    }}void serviceIO(int sock){    char inbuffer[NUM];    while(true)    {        ssize_t n=read(sock,inbuffer,sizeof(inbuffer)-1);        if(n>0)        {            inbuffer[n]=0;            cout<<"recv message: "<<inbuffer<<endl;            string outb=inbuffer;            string outbuffer=outb+"[server echo]";            write(sock,outbuffer.c_str(),outbuffer.size());        }else{    logMessage(NORMAL,"client quit,i quit yep");    break;}    }}~tcpserver(){}private:int _listensock;//用于监听服务器的sock文件描述符uint16_t _port;//端口号};}

注意:客户端串行給服务器发数据是在哪里堵塞?由于阻塞在accept函数处,即accept等待客户端接入是阻塞式等待。accept函数接收了一个连接请求后,后来的客户端连接请求需要在accept函数处等待,当上一个客户端退出后,服务器才能accept当前客户端发送来的连接请求成功,才能接收当前客户端的数据。即服务器串行接收处理客户端发送来的数据

2. 多进程版:客户端并行版

tcpserver.hpp

#pragma once#include #include #include #include #include #include #include #include #include #include #include #include #include"log.hpp"#define NUM 1024using namespace std;namespace Server{    enum    {        USAGE_ERR=1,SOCK_ERR,BIND_ERR,LISTEN_ERR    };class tcpserver{ public:tcpserver(const  uint16_t& port):_port(port),_listensock(-1){}void initserver(){//1.创建套接字_listensock=socket(AF_INET,SOCK_STREAM,0);if(_listensock<0){    logMessage(FATAL,"create listensocket error");    exit(SOCK_ERR);} logMessage(NORMAL, "create socket success: %d", _listensock);//2.bind ip和portstruct sockaddr_in local;local.sin_family=AF_INET;local.sin_port=htons(_port);local.sin_addr.s_addr=INADDR_ANY;if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)//绑定失败{    logMessage(FATAL,"bind error");    exit(BIND_ERR);} logMessage(NORMAL,"bind success");//3.将套接字设置为监听模式if(listen(_listensock,0)<0){    logMessage(FATAL,"listen error");    exit(LISTEN_ERR);}logMessage(NORMAL,"listen success");}void start(){     // signal(SIGCHLD, SIG_IGN);    while(true)    {        struct sockaddr_in cli;        socklen_t len=sizeof(cli);        bzero(&cli,len);        int sock=accept(_listensock,(struct sockaddr*)&cli,&len);        if(sock<0)        {            logMessage(FATAL,"accept client error");            continue;        }        logMessage(NORMAL,"accept client success");        cout<<"accept sock: "<<sock<<endl;                //多进程版---        //一个客户端占用一个文件描述符,原因在于孙子进程执行IO任务需要占用独立的文件描述符,而文件描述符是继承父进程的,而每次客户端进来都要占用新的文件描述符        //因此若接收多个客户端不退出的话文件描述符会越来越少。        pid_t id=fork();//创建子进程        if(id==0)//子进程进入        {            close(_listensock);//子进程不需要用于监听因此关闭该文件描述符            if(fork()>0)  exit(0);// //子进程创建孙子进程,子进程直接退出,让孙子进程担任IO任务,且孙子进程成为孤儿进程被OS领养,// //除非客户端退出IO任务结束否则该孤儿进程一直运行下去不会相互干扰,即并行完成服务器和客户端的通信// //孙子进程serviceIO(sock);close(sock);exit(0);        }        //父进程         // close(sock);//父进程不使用文件描述符就关闭        pid_t ret=waitpid(id,nullptr,0);        if(ret<0)        {            cout << "waitsuccess: " << ret << endl;        }    }}void serviceIO(int sock){    char inbuffer[NUM];    while(true)    {        ssize_t n=read(sock,inbuffer,sizeof(inbuffer)-1);        if(n>0)        {            inbuffer[n]=0;            cout<<"recv message: "<<inbuffer<<endl;            string outb=inbuffer;            string outbuffer=outb+"[server echo]";            write(sock,outbuffer.c_str(),outbuffer.size());        }else{    logMessage(NORMAL,"client quit,i quit yep");    break;}    }}~tcpserver(){}private:int _listensock;//用于监听服务器的sock文件描述符uint16_t _port;//端口号};}

image-20230825163937980

因此在父进程那里需要关闭不使用的文件描述符

image-20230825192206974

signal(SIGCHLD, SIG_IGN);

netstat查看网络信息

netstat 是一个用于查看网络连接和网络统计信息的命令行工具。它可以用来显示当前系统上的网络连接、路由表、接口统计信息等等。在 Linux 系统中,netstat 命令的用法如下:

netstat [options]

一些常用的选项包括:

image-20230825190401543

注意一下:这里出现了两个连接,原因在于服务器和客户端在同一台主机上,即服务器和客户端完成了本地环回,因此能看到两个连接。

3.多线程版:并行执行

tcpserver.hpp

#pragma once#include #include #include #include #include #include #include #include #include #include #include #include #include"log.hpp"#define NUM 1024using namespace std;namespace Server{    enum    {        USAGE_ERR=1,SOCK_ERR,BIND_ERR,LISTEN_ERR    };class tcpserver;    class ThreadData    {public:ThreadData( tcpserver* self,int psock):_this(self),_psock(psock){}tcpserver* _this;int _psock;    };class tcpserver{ public:tcpserver(const  uint16_t& port):_port(port),_listensock(-1){}void initserver(){//1.创建套接字_listensock=socket(AF_INET,SOCK_STREAM,0);if(_listensock<0){    logMessage(FATAL,"create listensocket error");    exit(SOCK_ERR);} logMessage(NORMAL, "create socket success: %d", _listensock);//2.bind ip和portstruct sockaddr_in local;local.sin_family=AF_INET;local.sin_port=htons(_port);local.sin_addr.s_addr=INADDR_ANY;if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)//绑定失败{    logMessage(FATAL,"bind error");    exit(BIND_ERR);} logMessage(NORMAL,"bind success");//3.将套接字设置为监听模式if(listen(_listensock,0)<0){    logMessage(FATAL,"listen error");    exit(LISTEN_ERR);}logMessage(NORMAL,"listen success");}void start(){    while(true)    {        struct sockaddr_in cli;        socklen_t len=sizeof(cli);        bzero(&cli,len);        int sock=accept(_listensock,(struct sockaddr*)&cli,&len);        if(sock<0)        {            logMessage(FATAL,"accept client error");            continue;        }        logMessage(NORMAL,"accept client success");        cout<<"accept sock: "<<sock<<endl;        //多线程版pthread_t pid;ThreadData* th=new ThreadData(this,sock);pthread_create(&pid,nullptr,start_routine,th);    }} static void* start_routine(void* args){    pthread_detach(pthread_self());//线程分离后让OS自动回收新线程    ThreadData* ret=static_cast<ThreadData*>(args);    ret->_this->serviceIO(ret->_psock);    close(ret->_psock);    delete ret;    return nullptr;}        void serviceIO(int sock){    char inbuffer[NUM];    while(true)    {        ssize_t n=read(sock,inbuffer,sizeof(inbuffer)-1);        if(n>0)        {            inbuffer[n]=0;            cout<<"recv message: "<<inbuffer<<endl;            string outb=inbuffer;            string outbuffer=outb+"[server echo]";            write(sock,outbuffer.c_str(),outbuffer.size());        }else{    logMessage(NORMAL,"client quit,i quit yep");    break;}    }}~tcpserver(){}private:int _listensock;//用于监听服务器的sock文件描述符uint16_t _port;//端口号};}

log.hpp

#pragma once#include #include #include#include  #include  #include #include using namespace std;#define DEBUG   0#define NORMAL  1#define WARNING 2#define ERROR   3#define FATAL   4#define NUM 1024#define LOG_STR "./logstr.txt"#define LOG_ERR "./log.err"const char* to_str(int level){    switch(level)    {        case DEBUG: return "DEBUG";        case NORMAL: return "NORMAL";        case WARNING: return "WARNING";        case ERROR: return "ERROR";        case FATAL: return "FATAL";        default: return nullptr;    }}void logMessage(int level, const char* format,...){    // [日志等级] [时间戳/时间] [pid] [messge]    // [WARNING] [2023-05-11 18:09:08] [123] [创建socket失败]    // 暂定  //  std::cout << message << std::endl;char logprestr[NUM];snprintf(logprestr,sizeof(logprestr),"[%s][%ld][%d]",to_str(level),(long int)time(nullptr),getpid());//把后面的内容打印进logprestr缓存区中char logeldstr[NUM];va_list arg;va_start(arg,format); vsnprintf(logeldstr,sizeof(logeldstr),format,arg);//arg是logmessage函数列表中的...  cout<<logprestr<<logeldstr<<endl;//  FILE* str=fopen(LOG_STR,"a");//  FILE* err=fopen(LOG_ERR,"a");//以追加方式打开文件,若文件不存在则创建 //  if(str!=nullptr||err!=nullptr)//两个文件指针都不为空则创建文件成功//  {//   FILE* ptr=nullptr;//   if(level==DEBUG||level==NORMAL||level==WARNING)//   {//     ptr=str;//   }//    if(level==ERROR||level==FATAL)//   {//     ptr=err;//   }//   if(ptr!=nullptr)//   {//     fprintf(ptr,"%s-%s\n",logprestr,logeldstr);//   }//   fclose(str);//   fclose(err); //}}

可变参数列表

  • va_list是(char*)重命名的类型,定义可以访问可变参数的变量。

  • va_start(ap, v) ap是定义的可变参数变量,v是形参中可变参数前第一个参数名,其作用是使ap指向可变参数部分。

  • va_arg(ap, t) ap是定义的可变参数变量,t是可变参数的类型,根据类型,访问可变参数列表中的数据。

  • va_end(ap) ap是定义的可变参数变量,使ap变量置空,作为结束使用。

vsnprintf函数原型

#include int vsnprintf(char *str, size_t size, const char *format, va_list ap);

image-20230825224540368

守护进程

守护进程(Daemon)是在计算机系统中以后台方式运行的一类特殊进程。它通常在操作系统启动时被初始化,并在整个系统运行期间保持活动状态,不需要与用户交互。守护进程通常用于执行系统任务、服务管理以及提供后台服务,如网络服务、定时任务等。

守护进程特点如下:

  1. 后台运行,守护进程在后台运行,不与用户交互,没有控制终端。
  2. 独立性:它通常独立于用户会话,即使用户注销或关闭终端,守护进程也会继续运行。
  3. 没有标准输入输出:守护进程通常没有标准输入和输出,因为它们不与用户交互。它们通常将输出写入日志文件。
  4. 分离自身:守护进程会通过一系列操作来与终端、会话和控制组脱离连接,以确保它不会意外地被控制终端关闭。

一个服务器中可以具有多个会话,例如一个服务器上有一个root用户和多个普通用户,当普通用户登录上服务器时即成为一个会话。

一个会话具有多个后台任务,但只能具有一个前台任务(bash)。

image-20230825231612464

fg、bg
  1. fg 作业号:将作业放到前台

  2. bg 作业号:将作业放到后台,或者继续执行后台作业

  3. ctrl+Z将前台任务暂停并把作业放到后台

image-20230825233513024

setsid

在Unix和类Unix系统中,setsid 是一个用于创建新会话的系统调用函数。会话(Session)是一组相关的进程组合,通常由一个控制终端和一些子进程组成。setsid 函数的主要作用是将调用它的进程从当前会话中分离出来,并创建一个新的会话。

 #include pid_t setsid(void);

daemon.hpp

#pragma once#include #include #include #include #include #include #include #define DEV "/dev/null"void daemonSelf(const char *currPath = nullptr){    // 1. 让调用进程忽略掉异常的信号signal(SIGPIPE,SIG_IGN);//选择忽略SIGPIPE信号    // 2. 如何让自己不是组长,setsidif(fork()>0)exit(0);//父进程退出    // 子进程 -- 守护进程,精灵进程,本质就是孤儿进程的一种!pid_t ret=setsid();assert(ret!=-1);    // 3. 守护进程是脱离终端的,关闭或者重定向以前进程默认打开的文件int fd=open(DEV,O_RDWR);if(fd>=0){    //dup2(oldfd,newfd):将oldfd的内容填充到newfd中,这样输入到newfd的内容被重定向到oldfd    dup2(fd,0);    dup2(fd,1);    dup2(fd,2);}else{    close(0);    close(1);    close(2);}    // 4. 可选:进程执行路径发生更改if(currPath) chdir(currPath);//更改currPath的路径}

tcpserver.cc

#include"tcpserver.hpp"#include"log.hpp"#include"daemon.hpp"#include#include#includeusing namespace Server;using namespace std;static void Usage(string proc){    cout<<"\nUsage:\n\t"<<proc<<" local_port\n\n"<<endl;}int main(int argc,char* argv[]){if(argc!=2){    Usage(argv[0]);    exit(USAGE_ERR);}uint16_t port=atoi(argv[1]);//将字符串转化为整数unique_ptr<tcpserver> ts(new tcpserver(port));ts->initserver();daemonSelf();ts->start();return 0;}

image-20230826112937206

TCP协议通信流程

image-20230902193547174

首先需要服务器初始化

image-20230902162505124

服务器初始化:

  • 调用socket, 创建文件描述符,该文件描述符用于监听;
  • 调用bind, 将当前的文件描述符和ip/port绑定在一起; 如果这个端口已经被其他进程占用了, 就会bind失败;
  • 调用listen, 声明当前这个文件描述符作为一个服务器的文件描述符, 该文件描述符处于监听状态,等待客户端发起连接;
  • 调用accecpt, 并阻塞, 等待客户端连接过来;

建立连接的过程(通常称为三次握手)

建立连接的过程(内含三次握手)

  • 客户端调用socket,创建文件描述符;
  • 客户端调用connect,向指定地址端口的服务器发起请求;(请求的过程中会进行三次握手)
  • connect会发出SYN段給服务器并阻塞等待服务器应答;(第一次握手)
  • 服务器收到客户端的SYN段后,会应答一个SYN-ACK段表示"同意建立连接";(第二次握手)
  • 客户端收到SYN-ACK后,会从connet()返回,同时发送一个应答ACK段給服务器;(第三次握手)
  • 服务器收到客户端发来的ACK段后,会从accpet()返回,返回(分配)一个新的文件描述符connfd用于与客户端通信

image-20230902163100168

**对于建链接的3次握手,**主要是要初始化Sequence Number 的初始值。通信的双方要互相通知对方自己的初始化的Sequence Number(缩写为ISN:Inital Sequence Number)——所以叫SYN,全称Synchronize Sequence Numbers。也就上图中的 x 和 y。这个号要作为以后的数据通信的序号,以保证应用层接收到的数据不会因为网络上的传输的问题而乱序(TCP会用这个序号来拼接数据)。来自陈浩大佬对于三次握手的部分诠释

数据传输的过程

image-20230902174502858

image-20230902174857760

  • 建立连接后,TCP协议提供全双工的通信服务; 所谓全双工的意思是, 在同一条连接中, 同一时刻, 通信双方 可以同时写数据; 其原因在于服务器和客户端的应用层和传输层都有两个缓冲区,一个是发送缓冲区另一个是接收缓冲区,那么服务器和客户端进行发送和读取并不会互相影响。相对的概念叫做半双工, 同一条连接在同一时刻, 只能由一方来写数据;

  • 服务器从accept()返回后立刻调 用read(), 读socket就像读管道一样, 如果没有数据到达就阻塞等待;

  • 这时客户端调用write()发送请求给服务器, 服务器收到后从read()返回,对客户端的请求进行处理, 在此期 间客户端调用read()阻塞等待服务器的应答;

  • 服务器调用write()将处理结果发回给客户端, 再次调用read()阻塞等待下一条请求;

  • 客户端收到后从read()返回, 发送下一条请求,如此循环下去

断开连接的过程

image-20230902192244687

  • 如果客户端没有更多的请求了, 就调用close()关闭连接, 客户端会向服务器发送FIN段;(第一次握手)
  • 此时服务器收到FIN后, 会回应一个ACK, 同时read会返回0 ;(第二次握手)
  • read返回之后, 服务器就知道客户端关闭了连接, 也调用close关闭连接, 这个时候服务器会向客户端发送 一个FIN; (第三次握手)
  • 客户端收到FIN, 再返回一个ACK给服务器; (第四次握手)

当客户端不与服务器通信时需要断开连接的原因

  • 其实,网络上的传输是没有连接的,包括TCP也是一样的。而TCP所谓的“连接”,其实只不过是在通讯的双方维护一个“连接状态”,让它看上去好像有连接一样。所以,TCP的状态变换是非常重要的。若通信结束不及时断开连接,即占用着操作系统的资源不使用,会导致系统的资源越来越少。
  • 服务器能够与多个客户端建立连接,意味着服务器会收到大量的连接,因此操作系统要对这些连接进行管理,即"先组织再管理",在服务端就需要维护连接相关的数据结构,把这些数据结构组织起来,那么对连接的管理转变为对数据结构的管理。
  • 操作系统需要维护这些连接相关的数据结构,势必需要消耗资源,而不通信的连接不断开,会导致操作系统的资源浪费。而TCP与UDP的区别之一在于TCP需要对连接相关的资源进行管理。

来源地址:https://blog.csdn.net/m0_71841506/article/details/132509902

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-服务器
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯