ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

tcp服务器的实现

2021-05-20 16:57:35  阅读:145  来源: 互联网

标签:string 实现 ip sock tcp int 服务器 include port


1.服务端需要绑定,客户端不需要

1.1服务器为什么需要绑定

1.一般服务器端是固定的,是总所周知的,ip和port不需要也不能轻易的更改。比如:http对应的端口号是80 https:443 ssh:22

2.服务器面对的客户很多,服务器一旦改了,客户端立马找不到,就无法访问服务器了。比如在日常生活中110对应的就是报警,报警的第一反应就是110

3.服务器是一对多的,一旦发生变化,很多客户端都要做出相对的变动

1.2客户端为什么不需要绑定

1.客户有很多客户端,如果绑定,就需要让不同的公司商量一下,什么软件用什么端口,端口是标识进程的,一个端口只能对应一个进程,如果多个进程使用同一个端口,就会导致绑定是失败。并且这种让不同的公司进行沟通进行约定,是很不现实的。

如果进行了bind很容易发生端口冲突,导致客户端无法启动

2.客户端需要唯一性,不需要明确是那个端口,但是需要IP和port。

客户端使用udp服务器进行数据的交互之时,系统会自动进行Ip和端口号的绑定(OS最清楚端口号情况)。端口号是16位,是有限的,OS是需要管理它的,因此OS清楚哪些端口没有被使用。而自己去绑定是不确定的,只能是"碰运气",看那个端口没有被绑定

2.什么是本地环回

IP:127.0.0.1 叫做本地环回 -> 通常用来进行网络通信代码的测试,一般本地跑通说明本地环境以及代码基本没有大问题
image-20210516082709294

3.INADDR_ANY

**[注]**云服务器有些地方是不需要端口的,这是考虑到安全问题,因此在绑定公网IP的时候可能会失败

网络地址为INADDR_ANY,这个宏表示本地的任意ip地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个ip地址,这样设置可以在所有的IP地址上进行监听,直到与某个客户建立了链接时才确下来到底用哪个ip地址

绑定的是INADDR_ANY该主机的所有IP层收到的数据都会交给主机,否则只有绑定的IP才会将数据交给主机

image-20210517085737386
image-20210517091305619

4.四字节和点分十进制的相互转换

image-20210517102642628

#include <iostream>
using namespace std;
#include <string>
#include <vector>


typedef union IP
{
  unsigned ip;//四字节ip
  struct{
    unsigned part1:8;
    unsigned part2:8;
    unsigned part3:8;
    unsigned part4:8;
  }ip_seg;
}IP;

void GetString(string & str,IP &ip)//从part之中读取1个个字节的整形转换成字符串
{
  str+=to_string(ip.ip_seg.part1);
  str+='.';
  str+=to_string(ip.ip_seg.part2);
  str+='.';
  str+=to_string(ip.ip_seg.part3);
  str+='.';
  str+=to_string(ip.ip_seg.part4);
}

void GetNum(string &str,IP &ip)//将字符串之中的内容填入ip,读取part
{
  string temp[4];
  int sub=0;
  for(int i=0;i<(int)str.size();i++)
  {
    if(str[i]!='.')
      temp[sub]+=str[i];
    else
    {
      sub++;
    }
  }
  ip.ip_seg.part1=stoi(temp[0]);
  ip.ip_seg.part2=stoi(temp[1]);
  ip.ip_seg.part3=stoi(temp[2]);
  ip.ip_seg.part4=stoi(temp[3]);
  
}

int main()
{
//将整形ip转换成点分十进制
  IP ip;//定义一个联合结构体,用来转换字符串
  ip.ip=987654321;
  string str;
  GetString(str,ip);
  cout<<ip.ip<<"->"<<str<<endl; 
 //将点分十进制转换成整形四字节ip
  cout<<"________________"<<endl;
  IP ip2;//定义一个联合结构体,用来转换成整形
  GetNum(str,ip2);
  cout<<str<<"->"<<ip2.ip<<endl;
  return 0;
  }

效果演示:
image-20210517102507568

5.udp字典服务器

#pragma once

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <string>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <map>

class udpServer
{
  private:
    string ip;
    int port;
    int sock;
    map<string,string> dict;

  public:
    udpServer(int _port)
      :port(_port)
    {
      dict.insert(pair<string,string>("apple","苹果"));
      dict.insert(pair<string,string>("banana","香蕉"));
      dict.insert(pair<string,string>("school","学校"));
    }


    void initServer()//初始化服务器
    {
      sock=socket(AF_INET,SOCK_DGRAM,0);
      cout<<sock<<endl;

      //当前套接字文件里面只有文件信息
      //这个文件对应的是网络文件、因此需要加入ip、port信息->绑定
      
      //传入的ip、port是在用户层的,下面需要绑定在内核里面
      //因此需要填充结构体,sockaddr_in(ipv4)
      
      struct sockaddr_in local;
      local.sin_family=AF_INET;//填充协议
      local.sin_port=htons(port);//填充端口->转成网络字节序
      local.sin_addr.in_addr::s_addr = INADDR_ANY;//绑定本机上的任意IP

      if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
      {
        cerr<<"bind error"<<endl;//往显示器上打印,但是对应的文件描述符不一样
        exit(1);//绑定失败终止整个进程
      }
    }

    //服务器启动之后、永不退出
    //写一个回应服务器
    void start()
    {
      while(1)
      {
        struct sockaddr_in end_point;//保存是谁送人信息->ip 和port
        socklen_t len=sizeof(end_point);//发送人结构体大小

        char buff[88];//接收消息缓冲区
        buff[0]='\0';//清空缓冲区

        ssize_t size=recvfrom(sock,buff,sizeof(buff)-1,0,(struct sockaddr*)&end_point,&len);//接收消息
        
        if(size>0)//收到消息了
        {
          string cli=inet_ntoa(end_point.sin_addr);//,将整形转换成str,获取客户端的点分十进制ip
          cli+=":";
          cli+=to_string(ntohs(end_point.sin_port));//获取客户端的port,获得的是整形,需要转换成字符串
          cli+=':';


           buff[size]='\0';//在末尾添加\0
           cout<<cli<<buff<<endl;//打印客户端发送过来的消息
           
          // string echo_string = buff;//回显字符串
          // echo_string+="-> server echo";
          //
           
          string echo_string;
           map<string,string>::iterator it=dict.find(buff);//通过key值查找
           if(it==dict.end())//没找到
             echo_string+="not find";
           else
            echo_string+=it->second;

          sendto(sock,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&end_point,len);

        }
      }
    }

    ~udpServer()
    {
      close(sock);//关闭文件描述符
    }

};

#include "udpServer.hpp"

void Usage()
{
  cout<<"Usage:"<<"please enter server ip and port"<<endl;
}

int main(int argc,char*argv[])
{
  if(argc!=2)
  {
    Usage();
    exit(1);
  }

  udpServer *up=new udpServer(atoi(argv[1]));
  up->initServer();//初始化
  up->start();//运行起来

  return 0;
}

#pragma once

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <string>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/inet.h>

class udpClient
{
  private:
    string ip;
    int port;
    int sock;

  public:
    //连接服务器->所以填写server的ip和端口-> 下载软件的时候就是下载客户端
    udpClient(string _ip,int _port)
      :ip(_ip)
       ,port(_port)
  {}


    void initClient()//初始化客户端
    {
      sock=socket(AF_INET,SOCK_DGRAM,0);
      cout<<sock<<endl;

      //客户端不需要绑定
    }

    //服务器启动之后、永不退出
    //写一个回应服务器
    void start()
    {
      string send_string;
      //发送给谁,填充信息
      struct sockaddr_in peer;
      peer.sin_family=AF_INET;
      peer.sin_port=htons(port);
      peer.sin_addr.s_addr=inet_addr(ip.c_str());//将字符串转成4字节再转成网络字节序

      
      
      char buff[88];

      while(1)
      {
        cout<<"please enter your message#"<<endl;
        getline(cin,send_string);

        sendto(sock,send_string.c_str(),send_string.size(),0,(struct sockaddr*)&peer,sizeof(peer));
        
        buff[0]='\0';
        ssize_t size=recvfrom(sock,buff,sizeof(buff)-1,0,nullptr,nullptr);
        if(size>0)
        {
          buff[size]='\0';
          cout<<"Serve:"<<buff<<endl;
        }

      }

    }

    ~udpClient()
    {
      close(sock);//关闭文件描述符
    }

};

#include "udpClient.hpp"

void Usage()
{
  cout<<"Usage:"<<"please enter Server ip and port"<<endl;
}

int main(int argc,char *argv[])
{
  if(argc!=3)
  {
    Usage();
    exit(2);
  }

  udpClient *uc =new udpClient(argv[1],atoi(argv[2]));
  uc->initClient();
  uc->start();

  return 0;
}

image-20210517102829539

6.TCP协议

6.1监听套接字 1listen

让套接字处理监听状态listen:允许任何时刻客户端来链接我(udp可以不链接,ip+port是表示发给谁)

什么是监听状态呢?
好比餐馆,老板就处于监听你的状态,只要你去了就有饭吃

image-20210516084205266

6.2建立链接accpet

image-20210516131347579

此时的返回值也是一个文件描述符,它和socket的返回值有什么区别呢?

sock:专注于底层获取链接(监听套接字)

accept返回值:专注于进程之间的通信

比如:一个餐馆,有在外面迎宾的和在里面招待客人的,他们就是两种套接字。迎宾的只负责将客人(链接)带进餐馆,带进餐馆之后他就不管了,之后与客人进行沟通,则是在里面的服务员了 -> 类比 sock(迎宾,负责获取链接)、accept返回值(服务员,负责进程之间的通信)

6.3 文件和流的概念

一般的文件都是基于流的 -> 比如通过open、fopen、pipe等方式打开的文件都有对应的概念 stream -> 在c/c++之中打开一个文件都叫做打开一个文件流。

tcp sock也是流的概念,因此读取sock文件时,也可以用read和write,但是常用的是recv

image-20210520100127018

image-20210520100221638

6.4代码实现tcp服务器和客户端

6.4.1服务器

#ifndef _TCPSERVER_HPP_
#define _TCPSERVER_HPP_

#include <iostream>
using namespace std;
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#endif
#define BACKLOCK 5

class tcpServer
{
  private:
    int port;
    int lsock;//监听套接字
  public:
    tcpServer(int _port)
      :port(_port)
       ,lsock(-1)
    {}

    void initServer()
    {
      lsock=socket(AF_INET,SOCK_STREAM,0);
      if(lsock<0)
      {
        cerr<<"socket fail"<<endl;
        exit(2);
      }
      
      struct sockaddr_in local;
      local.sin_family=AF_INET;//填充协议
      local.sin_port=htons(port);//填充端口,转换成网网络字节序
      local.sin_addr.s_addr=INADDR_ANY;//填写ip
    
      if(bind(lsock,(struct sockaddr*)&local,sizeof(local))<0)//进行绑定
      {
        cerr<<"bind error"<<endl;
        exit(3);
      }
      if(listen(lsock,BACKLOCK)<0)
      {
        cerr<<"listen error"<<endl;
        exit(4);
      }
    }

    void service(int sock)
    {
      char buff[200];
      while(true)
      {
        size_t size=recv (sock,buff,sizeof(buff)-1,0);
        if(size>0)//读取到了
        {
          buff[size]='\0';//末尾添加\0
          cout<<"client:"<<buff<<endl;

          send(sock,buff,strlen(buff),0);//不需要+1,\0是语言的标准,文件不认识
        }
      }
    }

    void stat()
    {
      sockaddr_in end_point;//获取客户结构体信息,和udp作用一样
      while(true)
      {
        //udp直接通信,tcp第一件事是获取链接
        socklen_t len=sizeof(end_point);
        int sock=accept(lsock,(struct sockaddr*)&end_point,&len);
        if(sock<0)
        {
          cerr<<"accpet error"<<endl;
          continue;
        }
        cout<<"get a new accept"<<endl;
        service(sock);//封装一个服务接口
        
      }
    }
  ~tcpServer()
  {
	  close(lsock);
  }
};


#include "tcpServer.hpp"

void Usage()
{
  cout<<"please enter your port"<<endl;
}
int main(int argc,char *argv[])
{
  if(argc!=2)
  {
    Usage();
    exit(1);
  }
      tcpServer *tp=new tcpServer(stoi(argv[1]));//传输的是字符串 -> int
      tp->initServer();
      tp->stat();

    delete tp;
    return 0;
  }
 

image-20210517141351799

6.4.2客户端

知识点预知:

1.客户端链接服务器接口:
image-20210520102420393

2.服务器如何知道客户端退出了:
image-20210520102431694

3.将一个进程放置后台,拿到前台:
image-20210520103001199
image-20210520103936236
image-20210520104036504
4.效果展示:
image-20210520104919103

5.代码:

#ifndef _TCP_CLIENT_CPP_
#define _TCP_CLIENT_CPP_

#include <stdio.h>
using namespace std;
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#endif

class tcpClient
{
  private:
    string ip;
    int port;
    int sock;

  public:
    tcpClient(string _ip,int _port)
    :ip(_ip)
     ,port(_port)
     ,sock(-1)
    {}

    void initClient()
    {
      sock =socket(AF_INET,SOCK_STREAM,0);//绑定套接字
      if(sock==-1)
      {
        cerr<<"client sock error"<<endl;
        exit(1);
      }

    //链接服务器 -> udp不需要连接诶
      struct sockaddr_in peer;
      peer.sin_family=AF_INET;
      peer.sin_port=htons(port);//主机转成网络序列
      peer.sin_addr.s_addr=inet_addr(ip.c_str());//将字符串转成网络整形

      if(connect(sock,(struct sockaddr*)&peer,sizeof(peer))!=0)//链接服务器
      {
        cerr<<"connect fail"<<endl;
        exit(2);
      }

    }

  //链接成功...... 
  
    void start()
    {
      char msg[100];
      while(true)
      {
        cout<<"please your message"<<endl;
        size_t s=read(0,msg,sizeof(msg)-1);//从标准输入读取
       if(s>0)
       {
         msg[s-1]=0;//read会将换行读进去
         send(sock,msg,s,0);//给服务器发送信息
       
         size_t ss=recv(sock,msg,sizeof(msg)-1,0);
         if(ss>0)//表示读取到了信息
         {
           msg[ss]=0;
           cout<<"server:"<<msg<<endl;
         }
       }
      }
    }

    ~tcpClient()
    {
      close(sock);
    }
};


#include "tcpClient.hpp"
#include <stdio.h>
using namespace std;
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>

void Usage(char *arg)
{
  cout<<"usage:"<<arg<<":"<<"please enter yout ip and port"<<endl;
}

int main(int argc,char* argv[])
{
  if(argc!=3)
  {
    Usage(argv[0]);
    exit(-1);
  }
  tcpClient *tc=new tcpClient(argv[1],atoi(argv[2]));
  tc->initClient();
  tc->start();
  delete tc;

  return 0;
}

6.4tcp服务器优化

6.4.1多进程版本

#ifndef _TCPSERVER_HPP_
#define _TCPSERVER_HPP_

#include <iostream>
using namespace std;
#include <stdlib.h>
#include <signal.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>

#endif

#define BACKLOCK 5


class tcpServer
{
  private:
    int port;
    int lsock;//监听套接字
  public:
    tcpServer(int _port)
      :port(_port)
       ,lsock(-1)
    {}

    void initServer()//初始化,创建套接字
    {
      signal(SIGCHLD,SIG_IGN);//父进程忽略子进程发送过来的信号,即子进程自己清理资源

      lsock=socket(AF_INET,SOCK_STREAM,0);
      if(lsock<0)
      {
        cerr<<"socket fail"<<endl;
        exit(2);
      }
      
      struct sockaddr_in local;
      local.sin_family=AF_INET;//填充协议
      local.sin_port=htons(port);//填充端口,转换成网网络字节序
      local.sin_addr.s_addr=INADDR_ANY;//填写ip
    
      if(bind(lsock,(struct sockaddr*)&local,sizeof(local))<0)//进行绑定
      {
        cerr<<"bind error"<<endl;
        exit(3);
      }
      if(listen(lsock,BACKLOCK)<0)
      {
        cerr<<"listen error"<<endl;
        exit(4);
      }
    }

    void service(int sock)//执行任务函数
    {
      char buff[200];
      while(true)
      {
        size_t size=recv (sock,buff,sizeof(buff)-1,0);
        if(size>0)//读取到了
        {
          buff[size]='\0';//末尾添加\0
          cout<<"client:"<<buff<<endl;

          send(sock,buff,strlen(buff),0);//不需要+1,\0是语言的标准,文件不认识
        }
        else if(size==0)
        {
          cout<<"client quit..."<<endl;
          close(sock);//关闭对应的套接字
          break;
        }
        else//读取错误
        {
          cerr<<"read error"<<endl;
          close(sock);
          break;
        }
      }
    }

    void stat()
    {
      sockaddr_in end_point;//获取客户结构体信息,和udp作用一样
      while(true)
      {
        //udp直接通信,tcp第一件事是获取链接
        socklen_t len=sizeof(end_point);
        int sock=accept(lsock,(struct sockaddr*)&end_point,&len);
        if(sock<0)
        {
          cerr<<"accpet error"<<endl;
          continue;
        }

        string cl;//客户端信息
        cl+=inet_ntoa(end_point.sin_addr);//网->主,整->字符串
        cl+=':';
        cl+=to_string(ntohs(end_point.sin_port));


        cout<<"get a new accept:"<<cl<<"-"<<"sock:"<<sock<<endl;
        
        //创建多进程,可以有多个执行流
        pid_t id=fork();

        if(id==0)//子进程提供服务
        {
          //子进程不关心lsock
          close(lsock);//子进程拷贝了父进程的文件描述表
          service(sock);//封装一个服务接口
          exit(0);//执行完任务退出
        }
        //父进程的主要任务是获取链接
        //然后链接越多,对应的为服务创建的sock文件描述符也越多
        //父进程可用的资源就少了,因此不如关闭
        close(sock);//父进程不关心后面的服务
      }
    }
  ~tcpServer()
  {
  	close(lsock);
  }

};

另外一种写法___________________________________
pid_t id=fork();
if(id==0)
{
if(fork()>0)//子进程退出
	exit(0);
}
	//接下来的这一段是孙子进行在运行
          close(lsock);//子进程拷贝了父进程的文件描述表
          service(sock);//封装一个服务接口
          exit(0);//执行完任务退出

wait(id,null,0);//父进程立马可以等待子进程
-> 不推荐这样写,fork是有成本的,创建进程越多,代价越大

效果展示:
image-20210520140146228

6.4.2多线程版本

#ifndef _TCPSERVER_HPP_
#define _TCPSERVER_HPP_

#include <iostream>
using namespace std;
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>

#endif

#define BACKLOCK 5


class tcpServer
{
  private:
    int port;
    int lsock;//监听套接字
  public:
    tcpServer(int _port)
      :port(_port)
       ,lsock(-1)
    {}

    void initServer()
    {
      signal(SIGCHLD,SIG_IGN);//父进程忽略子进程发送过来的信号,即子进程自己清理资源

      lsock=socket(AF_INET,SOCK_STREAM,0);
      if(lsock<0)
      {
        cerr<<"socket fail"<<endl;
        exit(2);
      }
      
      struct sockaddr_in local;
      local.sin_family=AF_INET;//填充协议
      local.sin_port=htons(port);//填充端口,转换成网网络字节序
      local.sin_addr.s_addr=INADDR_ANY;//填写ip
    
      if(bind(lsock,(struct sockaddr*)&local,sizeof(local))<0)//进行绑定
      {
        cerr<<"bind error"<<endl;
        exit(3);
      }
      if(listen(lsock,BACKLOCK)<0)
      {
        cerr<<"listen error"<<endl;
        exit(4);
      }
    }

    static void service(int sock)
    {
      char buff[200];
      while(true)
      {
        size_t size=recv (sock,buff,sizeof(buff)-1,0);
        if(size>0)//读取到了
        {
          buff[size]='\0';//末尾添加\0
          cout<<"client:"<<buff<<endl;

          send(sock,buff,strlen(buff),0);//不需要+1,\0是语言的标准,文件不认识
        }
        else if(size==0)
        {
          cout<<"client quit..."<<endl;
          close(sock);//关闭对应的套接字
          break;
        }
        else//读取错误
        {
          cerr<<"read error"<<endl;
          close(sock);
          break;
        }
      }
    }


    static void *sevrun(void *arg)//只能有一个参数,因此必须是static
    {
      pthread_detach(pthread_self());//线程分离,不用再进行等待
      cout<<"create a new prhread"<<endl;
      int *p=(int*)arg;
      int sock=*p;
      service(sock);//执行任务
      delete p;
      return NULL;
    }

    void stat()
    {
      sockaddr_in end_point;//获取客户结构体信息,和udp作用一样
      while(true)
      {
        //udp直接通信,tcp第一件事是获取链接
        socklen_t len=sizeof(end_point);
        int sock=accept(lsock,(struct sockaddr*)&end_point,&len);
        if(sock<0)
        {
          cerr<<"accpet error"<<endl;
          continue;
        }

        string cl;//客户端信息
        cl+=inet_ntoa(end_point.sin_addr);//网->主,整->字符串
        cl+=':';
        cl+=to_string(ntohs(end_point.sin_port));

        cout<<"get a new accept:"<<cl<<"-"<<"sock:"<<sock<<endl;

        pthread_t tid;
        
        //防止新线程还未执行,老线程又创建了新的链接,sock就被改变了
        //new出来一段空间,只要传参就发生了拷贝,所以不会有影响
        int *p=new int(sock);

        pthread_create(&tid,nullptr,sevrun,(void*)p);//创建线程
      }
    }

  ~tcpServer()
  {
    close(lsock);
  }

};

//makefile
FLAG= -lpthread

.PHONY:all 
all:tcpClient tcpServer  -lpthread

tcpClient:tcpClient.cpp
	g++ -o $@ $^ $(FLAG)

tcpServer:tcpServer.cpp
	g++ -o $@ $^ $(FLAG)

.PHONY:clean
clean:
	rm -rf tcpClient tcpServer

效果展示:
image-20210520144636998

6.4.3线程池版本

#pragma once
#include <iostream>
#include <math.h>
#include <pthread.h>
#include <queue>
#include <unistd.h>
#include <map>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>

using namespace std;
#define NUM 5

class Task
{
  private:
    int sock;
    map<string,string>dict;
  public:
    Task()
    {}

    Task(int _sock)
       :sock(_sock)
    {
      dict.insert(make_pair("banana","香蕉"));
      dict.insert(make_pair("apple","苹果"));
    }

    void Run()//来一个链接,执行一个任务(打印语句)
    {
      cout<<"Task is run....."<<endl;
      
      char buff[100];
      size_t size=recv (sock,buff,sizeof(buff)-1,0);
      if(size>0)//读取到了
      {
        buff[size]='\0';//末尾添加\0
        string key=buff;

        send(sock,dict[key].c_str(),dict[key].size(),0);//不需要+1,\0是语言的标准,文件不认识
      }
      else if(size==0)
      {
        cout<<"client quit..."<<endl;
        close(sock);//关闭对应的套接字
      }
      else//读取错误
      {
        cerr<<"read error"<<endl;
        close(sock);  
      }
    }

    ~Task()
    {
      close(sock);
    }
};

class Pool
{
  private:
    int _max;//线程个数
    queue<Task*> q;//存指针,减少数据拷贝消耗
    pthread_mutex_t lock;
    pthread_cond_t cond;

  public:
    
    Pool(int max=NUM)
      :_max(max)
    {}

    void TaskLock()
    {
      pthread_mutex_lock(&lock);
    }

    void TaskUnlock()
    {
      pthread_mutex_unlock(&lock);
     }

    void ThreadWait()
    {
      pthread_cond_wait(&cond,&lock);
    }

    void ThreadWakeUp()
    {
      pthread_cond_signal(&cond);//一次唤醒一个
    }

    void AllWakeUp()
    {
      pthread_cond_broadcast(&cond);//全部唤醒
    }


    bool IsEmpty()
    {
      return q.size()==0;
    }

    void Put(Task &data)//外部放入数据
    {
      TaskLock();
      q.push(&data);
      TaskUnlock();
      ThreadWakeUp();//唤醒一个线程
    }

    void Get(Task **data)//内部线程池完成任务
    {
      Task *t=q.front();
      q.pop();
      *data=t;
    }

public:

    //必须设置为静态的,因为成员函数会有this形参
    static void *RunTask(void *arg)//处理任务函数
    {
      Pool* p_this=(Pool*)arg;
      pthread_detach(pthread_self());//分离

      while(true)
      {
         p_this->TaskLock();
         //不退出才进行判断
         while(p_this->IsEmpty())//没有任务则挂起
         {
           p_this->ThreadWait();
         }

         //在这里一定不为空,将任务拿出来
         Task *t;
         p_this->Get(&t);

         p_this->TaskUnlock();
         t->Run();
         delete t;
      }
    }
    
    //尽量不要在构造函数内做有风险的事情
    void Init()
    {
      //锁和环境变量初始化
      pthread_mutex_init(&lock,nullptr);
      pthread_cond_init(&cond,nullptr);

      //线程池的创建
      pthread_t tid;
      for(int i=0;i<_max;i++)
      {
        pthread_create(&tid,nullptr,RunTask,this);//每个线程都去处理任务,不关心线程ID
      }
    }

      ~Pool()
      {
        pthread_mutex_destroy(&lock);
        pthread_cond_destroy(&cond);
      }

};


#ifndef _TCPSERVER_HPP_
#define _TCPSERVER_HPP_

#include "pool.hpp"
#include <iostream>
using namespace std;
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>

#endif

#define BACKLOCK 5

class tcpServer
{
  private:
    int port;
    int lsock;//监听套接字
    Pool *pl;
  
  public:
    tcpServer(int _port)
      :port(_port)
       ,lsock(-1)
       ,pl(nullptr)
    {}

    void initServer()
    {
      signal(SIGCHLD,SIG_IGN);//父进程忽略子进程发送过来的信号,即子进程自己清理资源

      lsock=socket(AF_INET,SOCK_STREAM,0);
      if(lsock<0)
      {
        cerr<<"socket fail"<<endl;
        exit(2);
      }
      
      struct sockaddr_in local;
      local.sin_family=AF_INET;//填充协议
      local.sin_port=htons(port);//填充端口,转换成网网络字节序
      local.sin_addr.s_addr=INADDR_ANY;//填写ip
    
      if(bind(lsock,(struct sockaddr*)&local,sizeof(local))<0)//进行绑定
      {
        cerr<<"bind error"<<endl;
        exit(3);
      }
      if(listen(lsock,BACKLOCK)<0)
      {
        cerr<<"listen error"<<endl;
        exit(4);
      }
      pl=new Pool();
      pl->Init();//初始化线程池
    }

    static void service(int sock)
    {
      char buff[200];
      while(true)
      {
        size_t size=recv (sock,buff,sizeof(buff)-1,0);
        if(size>0)//读取到了
        {
          buff[size]='\0';//末尾添加\0
          cout<<"client:"<<buff<<endl;

          send(sock,buff,strlen(buff),0);//不需要+1,\0是语言的标准,文件不认识
        }
        else if(size==0)
        {
          cout<<"client quit..."<<endl;
          close(sock);//关闭对应的套接字
          break;
        }
        else//读取错误
        {
          cerr<<"read error"<<endl;
          close(sock);
          break;
        }
      }
    }


    static void *sevrun(void *arg)//只能有一个参数,因此必须是static
    {
      pthread_detach(pthread_self());//线程分离
      cout<<"create a new prhread"<<endl;
      int *p=(int*)arg;
      int sock=*p;
      service(sock);//执行任务
      delete p;
      return NULL;
    }

    void stat()
    {
      sockaddr_in end_point;//获取客户结构体信息,和udp作用一样
      while(true)
      {
        //udp直接通信,tcp第一件事是获取链接
        socklen_t len=sizeof(end_point);
        int sock=accept(lsock,(struct sockaddr*)&end_point,&len);
        if(sock<0)
        {
          cerr<<"accpet error"<<endl;
          continue;
        }

        string cl;//客户端信息
        cl+=inet_ntoa(end_point.sin_addr);//网->主,整->字符串
        cl+=':';
        cl+=to_string(ntohs(end_point.sin_port));

        cout<<"get a new accept:"<<cl<<"-"<<"sock:"<<sock<<endl;
      
      //此时获得了新的连接 -> 将任务put至线程池
        Task *t= new Task(sock);
        pl->Put(*t);//将任务put进去

      }
    }

  ~tcpServer()
  {
    close(lsock);
  }

};

效果展示:
image-20210520162816130

6.4.4版本特点

1.单进程版本:只有一个执行流,一般不使用,多进程/多线程版本通常小型应用(局域网,少量机)
2.多进程版本:进程之间是独立的,健壮性强,但是比较吃资源,效率低下
3.多线程版本:健壮性不强,较吃资源,效率相对较高
大量的客户端:系统会存在大量的执行流,切换有可能称为效率低下的原因

标签:string,实现,ip,sock,tcp,int,服务器,include,port
来源: https://blog.csdn.net/ych9527/article/details/117081921

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有