网络---

UDP

不同的套接字种类指向不同的套接字方法:多态
产生2个可执行程序,客户端可以发给别人,服务器在linux
任何1个进程有2个栈:用户栈、内核栈(操作系统在运行是形成 的临时数据)
标准错误输出cerr、标准输出,都是往显示器打印,但不同的文件描述符打印
ssize_t:有符号int;size_t:无符号int
udpServer.cc

#include"udpServer.hpp"
void Usage(std::string proc)
{//使用说明书
  std::cout<<"Usage: "<<proc<<" local_port"<<std::endl;
}
int main(int argc,char *argv[])
{
  if(argc != 2){//不传ip时,是2
    Usage(argv[0]);
    exit(1);
  }
  udpServer* up = new udpServer(atoi(argv[1]));//定义的Server对象在堆上,堆的空间大,推荐以后都这样写
  up->initServer();
  up->start();
  delete up;
}

udpServer.hpp

#include<iostream>
#include<string>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
class udpServer{
  private:
    //std::string ip;
    int port;//也可以定义为short类型,2字节,端口号
    int sock;
  public:
    //udpServer(std::string _ip = "127.0.0.1",int _port = 8080)默认构造函数
      //:ip(_ip),port(_port)初始化
    udpServer(int _port = 8080)
     :port(_port)
    {}

    void initServer()//初始化服务器
    {      sock = socket(AF_INET,SOCK_DGRAM,0);
      struct sockaddr_in local;//local:临时变量,在用户栈上所定义出来变量,写入到操作系统中,调用bind()接口,ip进入内核层
      local.sin_family = AF_INET;
      local.sin_port = htons(port);
      //local.sin_addr.s_addr = inet_addr(ip.c_str()); //ip:STL的容器、字符串,转换为c语言形式
      local.sin_addr.s_addr = htonl(INADDR_IN);
      if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
      {        std::cerr<< "bind error"<<std::endl;
        exit(1); //绑定失败,终止进程
      }
    }
 void start()//启动服务器
    {
      char msg[64]; //定义msg,接收的缓冲区
       for(;;){//服务器一直运行下去,死循环
        msg[0] = '\0';//对缓冲区进行初始化,清空字符串
       struct sockaddr_in end_point; //表示对端
        socklen_t len = sizeof(end_point);
        ssize_t s = recvfrom(sock,msg,sizeof(msg)-1,0,(struct sockaddr*)&end_point,&len);
        if(s > 0){
 //我还希望知道谁给我发的,所以需要获取到它的IP地址和端口号
          char buf[16];
          sprintf(buf,"%d",ntohs(end_point.sin_port));
 //end_point.sin_port是一个int类型的整数,此时需要把它转换为字符串,sprintf把一个整数转成字符串
          std::string cli = inet_ntoa(end_point.sin_addr);
          cli += ":";
          cli += buf;
          msg[s] = '\0';
          std::cout<< client<<"#"<< msg <<std::endl;//client发来的信息
 //接收以后在Server端打印出来,返回都去的数值再加上一定的标识,说明Server接收到了的应答
          std::string echo_string = msg;
          echo_string += " [Server echo!] ";//加后缀,客户端给回响
          sendto(sock,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&end_point,len);//参数都是要以C语言形式进行传参的
        }
      }
    }
    ~udpServer()
    {
      close(sock);
    }
};

udpClient.cc

#include"udpClient.hpp"
void Usage(std::string proc)
{//使用手册
  std::cout<<"Usage: "<<proc<<"server_ip server_port"<<std::endl;//客户端连服务器,要知道server_ip server_port
}
int main(int argc,char *argv[])
{
  if(argc != 3){
    Usage(argv[0]);
    exit(1);
  }
  udpClient uc(argv[1],atoi(argv[2]));//此时所定义出来的Client对象在栈上,但是我们知道,栈的空间是有限的,比较小,所以不推荐这种方式,但是此时代码还比较少,所以可以采用这种方式。atoi:字符串转为int
  uc.initClient();
  uc.start();

  return 0;
}

udpClient.hpp

#include<iostream>
#include<string>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
class udpClient{
  private:
    std::string ip;
    int port;//对于端口号当然也可以定义为short类型,2字节
    int sock;
  public:
    //Server端的ip和port:
    udpClient(std::string _ip = "127.0.0.1",int _port = 8080)
      :ip(_ip),port(_port)
    {}

    void initClient()
    {
      sock = socket(AF_INET,SOCK_DGRAM,0);
//客户端是不需要绑定,操作系统完成这一步
    }

    void start()
    {
      std::string msg;
      struct sockaddr_in peer; //表示服务器
      peer.sin_family = AF_INET;
      peer.sin_port = htons(port);
      peer.sin_addr.s_addr = inet_addr(ip.c_str());
//点分十进制的IP地址转换为网络的4字节,ip地址(string类型)转为C语言格式的字符串
      for(;;){
        std::cout<< "Please Enter# ";
        std::cin>>msg;//用户输入内容
       //先给Server端发送数据,然后在接收
        sendto(sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));

        char echo[128];
        ssize_t s = recvfrom(sock,echo,sizeof(echo)-1,0,nullptr,nullptr);
        if(s > 0){//读取数据成功
          echo[s] = '\0';
          std::cout<<"Server# "<<echo<<std::endl;
        }
      }
    }
    ~udpClient()
    {
      close(sock);
    }
};

Makefile

FLAG=-std=c++11

.PHONY:all
all:udpClient udpServer
udpClient:udpClient.cc
	g++ -o $@ $^ $(FLAG)
udpServer:udpServer.cc
	g++ -o $@ $^ $(FLAG)
.PHONY:clean
clean:
	rm -f udpClient udpServer 

英译汉

#include<iostream>
#include<string>
#include<map>
#include<cstdio>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
class udpServer{
  private:
    int port;
    int sock;
    std::map<std::string,std::string> dict; //字典
  public:
    udpServer(int _port = 8080)
      :port(_port)
    {
      dict.insert(std::pair<std::string,std::string>("string","字符串"));
      dict.insert(std::pair<std::string,std::string>("sort","排序"));
      dict.insert(std::pair<std::string,std::string>("student","学生"));
    }

    void initServer()
    {sock = socket(AF_INET,SOCK_DGRAM,0);
      struct sockaddr_in local;
      local.sin_family = AF_INET;
      local.sin_port = htons(port);
      local.sin_addr.s_addr = INADDR_ANY; 
      if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
        std::cerr<< "bind error"<<std::endl;
        exit(1); 
      }
    }
    void start()
    {
      char msg[64]; 
      for(;;){
        msg[0] = '\0';
        //ssize_t 是一个int类型,是一个有符号的
        struct sockaddr_in end_point; //表示对端
        socklen_t len = sizeof(end_point);
        ssize_t s = recvfrom(sock,msg,sizeof(msg)-1,0,(struct sockaddr*)&end_point,&len);
        if(s > 0){
          char buf[16];
          sprintf(buf,"%d",ntohs(end_point.sin_port));

          std::string cli = inet_ntoa(end_point.sin_addr);
          cli += ":";
          cli += buf;

          msg[s] = '\0';
          std::cout<< cli <<" # "<< msg <<std::endl;

          std::string echo = "unknow";//如果找不到则显示unknow
          //std::map<std::string,std::string>::iterator it = dict.find(msg);
          auto it = dict.find(msg);//msg:k值
          while(it != dict.end()){
            //说明找到了
            echo = dict[msg];//通过K值返回V值
            break;
          }
          sendto(sock,echo.c_str(),echo.size(),0,(struct sockaddr*)&end_point,len);
        }
      }
    }
    ~udpServer()
    {
      close(sock);
    }
};


流式服务:管道

TCP

1、

tcpServer.cc

#include"tcpServer.hpp"

void Usage(std::string proc)
{
  std::cout<<"Usage: " << proc<< "port"<<std::endl;
}

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

  tcpServer *ts = new tcpServer(atoi(argv[1]));
  ts->initServer();
  ts->start();

  delete ts;
}


tcpServer.hpp

#pragma once 

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>

#define BACKLOG 5 //一般这个的值都设置的比较小,表示的意思是此时底层链接队列中等待链接的长度
class tcpServer{
  private:
    int port;
    int lsock;//表示监听的套接字
  public:

    tcpServer(int _port = 8080,int _lsock = -1)
      :port(_port),lsock(_lsock)
    {}

    void initServer()
    {
      lsock = socket(AF_INET,SOCK_STREAM,0);
      if(lsock < 0){
        std::cerr<<"socket error"<<std::endl;
        exit(2);
      }
      struct sockaddr_in local;
      local.sin_family = AF_INET;
      local.sin_port = htons(port);
      local.sin_addr.s_addr = htonl(INADDR_ANY);

      if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0){
        std::cerr<<"bind error!"<<std::endl;
        exit(3);
      }

      //走到这里说明bind成功了,但是tcp是面向链接的,所以还需要一个接口
      if(listen(lsock,BACKLOG) < 0){
        std::cout<<"listen error!"<<std::endl;
        exit(4);
      } 
      
      //tcp必须要将套接字设置为监听状态  这里可以想夜间你去吃饭,什么时候去都可以吃到,是为什么?是因为店里面一直有人在等待着你
      //任意时间有客户端来连接我
      //成功返回0,失败返回-1
    }

    //echo服务器
    void service(int sock)
    {
      while(true){
        //打开一个文件,也叫打开一个流,所以这里其实使用read和write也是可以的,但是这里是网络,最好使用tcp的recv和send
        char buf[64];
        ssize_t s = recv(sock,buf,sizeof(buf)-1,0);
        if(s > 0){
          buf[s] = '\0';
          std::cout<<"Client# "<< buf <<std::endl;
          send(sock,buf,strlen(buf),0);//此时的网络就是文件,你往文件中发送东西的格式,可不是以\0作为结束,加入\0会出现乱码的情况
        }
        else if(s == 0){
          std::cout<<"Client quit ..."<<std::endl;
          break;
        }else{
          std::cerr<<"recv error"<<std::endl;
          break;
        }
      }
      close(sock);
    }

    void start()
    {
      struct sockaddr_in endpoint;
      while(true){
        //第一步应该获取链接
        //这里可以理解为 饭店拉客
        //lsock 是负责拉客的   accept的返回值是主要负责服务客户的
        socklen_t len = sizeof(endpoint);
        int sock = accept(lsock,(struct sockaddr*)&endpoint,&len);
        if(sock < 0){
          std::cerr<<"accept error!"<<std::endl;
          continue; //相当于拉客失败了,我需要继续拉客
        }
        std::cout<<"get a new link"<<std::endl;

        //此时就是对我拉上来的客人进行服务
        service(sock);
      }
    }

    ~tcpServer()
    {}
};



tcpClient.cc

#include"tcpClient.hpp"
void Usage(std::string proc)
{
  std::cout<< "Usage: " << "\n";
  std::cout <<"\t"<<"serv_ip serv_port"<< std::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;
}

tcpClient.hpp

#pragma once

#include<iostream>
#include<string>
#include<cstring>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
class tcpClient{
  private:
    std::string serv_ip;
    int serv_port;
    int sock;
  public:
    tcpClient(std::string _ip = "127.0.0.1",int _port = 8080,int _sock = -1)
      :serv_ip(_ip),serv_port(_port),sock(_sock)
    {}

    void initClient()
    {
      sock = socket(AF_INET,SOCK_STREAM,0);
      if(sock < 0){
        std::cerr<<"socket error"<<std::endl;
        exit(2);
      }

      struct sockaddr_in serv_point;
      serv_point.sin_family = AF_INET;
      serv_point.sin_port = htons(serv_port);
      serv_point.sin_addr.s_addr = inet_addr(serv_ip.c_str());

      //成功返回0,失败返回-1
      if(connect(sock,(struct sockaddr*)&serv_point,sizeof(serv_point)) < 0){
        std::cerr<<"connect error"<<std::endl;
      }
      
    }

    void start()
    {
      char msg[64];
      while(true){
        //对于Client端首先应该发送数据,然后在考虑接收
        ssize_t s = read(0,msg,sizeof(msg)-1);//从标准输入中读取数据
        if(s > 0){
          msg[s] = '\0';
          send(sock,msg,strlen(msg),0);
          ssize_t ss = recv(sock,msg,sizeof(msg)-1,0);
          if(ss > 0){
            msg[ss] = '\0';
            std::cout<<"Server echo # " << msg<<std::endl;
          }
        }
      }
    }

    ~tcpClient()
    {
      close(sock);
    }

};


2、多进程版

tcpServer.hpp

#pragma once 

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<signal.h>

#define BACKLOG 5 //一般这个的值都设置的比较小,表示的意思是此时底层链接队列中等待链接的长度
class tcpServer{
  private:
    int port;
    int lsock;//表示监听的套接字
  public:

    tcpServer(int _port = 8080,int _lsock = -1)
      :port(_port),lsock(_lsock)
    {}

    void initServer()
    {
      signal(SIGCHLD,SIG_IGN); //采用忽略的方式
      lsock = socket(AF_INET,SOCK_STREAM,0);
      if(lsock < 0){
        std::cerr<<"socket error"<<std::endl;
        exit(2);
      }
      struct sockaddr_in local;
      local.sin_family = AF_INET;
      local.sin_port = htons(port);
      local.sin_addr.s_addr = htonl(INADDR_ANY);

      if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0){
        std::cerr<<"bind error!"<<std::endl;
        exit(3);
      }

      //走到这里说明bind成功了,但是tcp是面向链接的,所以还需要一个接口
      if(listen(lsock,BACKLOG) < 0){
        std::cout<<"listen error!"<<std::endl;
        exit(4);
      } 
      
      //tcp必须要将套接字设置为监听状态  这里可以想夜间你去吃饭,什么时候去都可以吃到,是为什么?是因为店里面一直有人在等待着你
      //任意时间有客户端来连接我
      //成功返回0,失败返回-1
    }

    //echo服务器
    void service(int sock)
    {
      while(true){
        //打开一个文件,也叫打开一个流,所以这里其实使用read和write也是可以的,但是这里是网络,最好使用tcp的recv和send
        char buf[64];
        ssize_t s = recv(sock,buf,sizeof(buf)-1,0);
        if(s > 0){
          buf[s] = '\0';
          std::cout<<"Client# "<< buf <<std::endl;
          send(sock,buf,strlen(buf),0);//此时的网络就是文件,你往文件中发送东西的格式,可不是以\0作为结束,加入\0会出现乱码的情况
        }
        else if(s == 0){
          std::cout<<"Client quit ..."<<std::endl;
          break;
        }else{
          std::cerr<<"recv error"<<std::endl;
          break;
        }
      }
      close(sock);
    }

    void start()
    {
      struct sockaddr_in endpoint;
      while(true){
        //第一步应该获取链接
        //这里可以理解为 饭店拉客
        //lsock 是负责拉客的   accept的返回值是主要负责服务客户的
        socklen_t len = sizeof(endpoint);
        int sock = accept(lsock,(struct sockaddr*)&endpoint,&len);//父进程:获取链接lsock
        if(sock < 0){
          std::cerr<<"accept error!"<<std::endl;
          continue; //相当于拉客失败了,我需要继续拉客
        }
        //打印获取到的连接的ip和端口号
        std::string cli_info = inet_ntoa(endpoint.sin_addr);
        cli_info += ":";
        cli_info += std::to_string(endpoint.sin_port); //这里获取的端口号是一个整数,需要把该整数转换为字符串,当然这里也可以使用stringstream 比如定义一个对象 , 此时有一个int变量,一个string 对象 你把int输入stringstream 然后再把stringstream在输入string的对象中,就进行了转换
        //stringstream ss;
        //int a = 100;
        //string b ;
        //ss << a;
        //ss >> b;  这样就进行了整形转字符串的功能
        std::cout<<"get a new link "<< cli_info<<" sock: "<<sock<<std::endl;

        pid_t id = fork();
        if(id == 0){
          //说明这是子进程
          //此时我让子进程来进行对应的处理服务
          //对于子进程来说始会继承父进程的一整套东西的,那么相对应的也会继承他的lsock和sock,那么对于子进程更关心sock,所以还需要把lsock进行关闭
          close(lsock);
          service(sock);
          exit(0);//子进程运行完毕(执行完相应的任务、io任务)以后有可能会继续运行下去,所以让他退出
子进程有sock、lsock2个文件描述符,只关心sock,指向同一个文件,不同的表
        }

        //但是问题是,我还需要父进程在这里等待
        //waitpid(id,NULL,0);父进程在这里阻塞等待,当时父进程可以等待吗?不行呀,因为父进程需要去接受新来的连接。那么就不等待子进程的消息了吗?那不行,还有一种方式就是
        //对于子进程退出,其实是会给父进程发送一个SIG_CHILD的信号的,那么当父进程将收到的SIG_CHILD进行忽略子进程的信号,子进程退出的时候,系统会自动回收子进程
        //此时就是对我拉上来的客人进行服务
        
        //父进程不关心sock
        close(sock);//不要想不通,因为他们使用的是不同的文件描述符表
      }
    }

    ~tcpServer()
    {}
};


tcpClient.hpp

#pragma once

#include<iostream>
#include<string>
#include<cstring>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
class tcpClient{
  private:
    std::string serv_ip;
    int serv_port;
    int sock;
  public:
    tcpClient(std::string _ip = "127.0.0.1",int _port = 8080,int _sock = -1)
      :serv_ip(_ip),serv_port(_port),sock(_sock)
    {}

    void initClient()
    {
      sock = socket(AF_INET,SOCK_STREAM,0);
      if(sock < 0){
        std::cerr<<"socket error"<<std::endl;
        exit(2);
      }

      struct sockaddr_in serv_point;
      serv_point.sin_family = AF_INET;
      serv_point.sin_port = htons(serv_port);
      serv_point.sin_addr.s_addr = inet_addr(serv_ip.c_str());

      //成功返回0,失败返回-1
      if(connect(sock,(struct sockaddr*)&serv_point,sizeof(serv_point)) < 0){
        std::cerr<<"connect error"<<std::endl;
      }
      
    }

    void start()
    {
      char msg[64];
      while(true){
        std::cout<<"Please Enter Massage# ";
        //这里有一个用户层的c/c++的缓冲区,所以需要强制刷新,这样就不会出现第一行不打印这句的问题
        fflush(stdin);
        //对于Client端首先应该发送数据,然后在考虑接收
        ssize_t s = read(0,msg,sizeof(msg)-1);//从标准输入中读取数据
        //因为cin不会会把回车键给过滤掉,但是read又会获得回车键
        if(s > 0){
          msg[s-1] = '\0';//这一步可以把读到的回车键给过滤掉,这样显示的时候,就不会出现空行
          send(sock,msg,strlen(msg),0);
          ssize_t ss = recv(sock,msg,sizeof(msg)-1,0);
          if(ss > 0){
            msg[ss] = '\0';
            std::cout<<"Server echo # " << msg<<std::endl;
          }
        }
      }
    }

    ~tcpClient()
    {
      close(sock);
    }

};



tcpServer.hpp

#pragma once 

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<pthread.h>
#define BACKLOG 5
class tcpServer{
  private:
    int port;
    int lsock;//表示监听的套接字
  public:

    tcpServer(int _port = 8080,int _lsock = -1)
      :port(_port),lsock(_lsock)
    {}

    void initServer()
    {
      lsock = socket(AF_INET,SOCK_STREAM,0);
      if(lsock < 0){
        std::cerr<<"socket error"<<std::endl;
        exit(2);
      }
      struct sockaddr_in local;
      local.sin_family = AF_INET;
      local.sin_port = htons(port);
      local.sin_addr.s_addr = htonl(INADDR_ANY);

      if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0){
        std::cerr<<"bind error!"<<std::endl;
        exit(3);
      }

      //走到这里说明bind成功了,但是tcp是面向链接的,所以还需要一个接口
      if(listen(lsock,BACKLOG) < 0){
        std::cout<<"listen error!"<<std::endl;
        exit(4);
      } 
      
    }

    //echo服务器
    static void service(int sock)
    {
      while(true){
        //打开一个文件,也叫打开一个流,所以这里其实使用read和write也是可以的,但是这里是网络,最好使用tcp的recv和send
        char buf[64];
        ssize_t s = recv(sock,buf,sizeof(buf)-1,0);
        if(s > 0){
          buf[s] = '\0';
          std::cout<<"Client# "<< buf <<std::endl;
          send(sock,buf,strlen(buf),0);//此时的网络就是文件,你往文件中发送东西的格式,可不是以\0作为结束,加入\0会出现乱码的情况
        }
        else if(s == 0){
          std::cout<<"Client quit ..."<<std::endl;
          break;
        }else{
          std::cerr<<"recv error"<<std::endl;
          break;
        }
      }
      close(sock);
    }

    static void *serviceRoutine(void *arg)
    {
      pthread_detach(pthread_self());//采用线程分离,运行完了以后自动释放。
      std::cout<<"create a new thread"<<std::endl;
      int *p = (int*)arg;
      int sock =  *p; 
      service(sock);
      delete p;
    }

    void start()
    {
      struct sockaddr_in endpoint;
      while(true){
        socklen_t len = sizeof(endpoint);
        int sock = accept(lsock,(struct sockaddr*)&endpoint,&len);
        if(sock < 0){
          std::cerr<<"accept error!"<<std::endl;
          continue; //相当于拉客失败了,我需要继续拉客
        }
        //打印获取到的连接的ip和端口号
        std::string cli_info = inet_ntoa(endpoint.sin_addr);
        cli_info += ":";
        cli_info += std::to_string(endpoint.sin_port); 
        std::cout<<"get a new link "<< cli_info<<" sock: "<<sock<<std::endl;

        pthread_t tid;
        //得到这个sock之后,给他保存一份,然后就不怕被覆盖了
        int *p = new int(sock); //这样做这个sock就是下面所创建新线程所私有的了
        pthread_create(&tid,nullptr,serviceRoutine,(void *)p); //但是这里取sock的地址可能会出现bug,那是因为有可能正在取的时候,主线程又accept获得了一个新连接
        //pthread_create(&tid,nullptr,serviceRoutine,(void*)&sock); //但是这里取sock的地址可能会出现bug,那是因为有可能正在取的时候,主线程又accept获得了一个新连接
        //那么就有可能,对原先的sock进行覆盖,也就是没有处理前面的那个连接
      
        //主进程还需要等待线程,进行回收
        //pthread_join()但是这里如果主线程进行阻塞等待子线程执行完毕的话,那么主线程酒干不了别的事情了,所以不能这样,所以这里还有种方式就是线程分离
      }
    }

    ~tcpServer()
    {}
};

#pragma once 

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<pthread.h>
#include"ThreadPool.hpp"
#define BACKLOG 5 //一般这个的值都设置的比较小,表示的意思是此时底层链接队列中等待链接的长度
class tcpServer{
  private:
    int port;
    int lsock;//表示监听的套接字
    ThreadPool *tp;
  public:

    tcpServer(int _port = 8080,int _lsock = -1)
      :port(_port),lsock(_lsock)
    {}

    void initServer()
    {
      lsock = socket(AF_INET,SOCK_STREAM,0);
      if(lsock < 0){
        std::cerr<<"socket error"<<std::endl;
        exit(2);
      }
      struct sockaddr_in local;
      local.sin_family = AF_INET;
      local.sin_port = htons(port);
      local.sin_addr.s_addr = htonl(INADDR_ANY);

      if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0){
        std::cerr<<"bind error!"<<std::endl;
        exit(3);
      }

      //走到这里说明bind成功了,但是tcp是面向链接的,所以还需要一个接口
      if(listen(lsock,BACKLOG) < 0){
        std::cout<<"listen error!"<<std::endl;
        exit(4);
      } 
      
      //tcp必须要将套接字设置为监听状态  这里可以想夜间你去吃饭,什么时候去都可以吃到,是为什么?是因为店里面一直有人在等待着你
      //任意时间有客户端来连接我
      //成功返回0,失败返回-1
      tp = new ThreadPool();
      tp->ThreadPoolInit();//初始化线程池threadpool,创造出来一批线程
    }

    //echo服务器
    static void service(int sock)
    {
      while(true){
        //打开一个文件,也叫打开一个流,所以这里其实使用read和write也是可以的,但是这里是网络,最好使用tcp的recv和send
        char buf[64];
        ssize_t s = recv(sock,buf,sizeof(buf)-1,0);
        if(s > 0){
          buf[s] = '\0';
          std::cout<<"Client# "<< buf <<std::endl;
          send(sock,buf,strlen(buf),0);//此时的网络就是文件,你往文件中发送东西的格式,可不是以\0作为结束,加入\0会出现乱码的情况
        }
        else if(s == 0){
          std::cout<<"Client quit ..."<<std::endl;
          break;
        }else{
          std::cerr<<"recv error"<<std::endl;
          break;
        }
      }
      close(sock);
    }
类内的成员函数,传this指针
    static void *serviceRoutine(void *arg)
    {
      pthread_detach(pthread_self());//采用线程分离,运行完了以后自动释放。
      std::cout<<"create a new thread"<<std::endl;
      int *p = (int*)arg;
      int sock =  *p; 
      service(sock);
      delete p;
    }

    void start()
    {
      struct sockaddr_in endpoint;
      while(true){
        socklen_t len = sizeof(endpoint);
        int sock = accept(lsock,(struct sockaddr*)&endpoint,&len);
        if(sock < 0){
          std::cerr<<"accept error!"<<std::endl;
          continue; //相当于拉客失败了,我需要继续拉客
        }
        std::string cli_info = inet_ntoa(endpoint.sin_addr);
        cli_info += ":";
        cli_info += std::to_string(endpoint.sin_port); //这里获取的端口号是一个整数,需要把该整数转换为字符串,当然这里也可以使用stringstream 比如定义一个对象 , 此时有一个int变量,一个string 对象 你把int输入stringstream 然后再把stringstream在输入string的对象中,就进行了转换
        std::cout<<"get a new link "<< cli_info<<" sock: "<<sock<<std::endl;


        //当获得一个新链接的时候,就不要创建新线程等,只需要构建一个任务,然后把这个任务push到线程池中
        //线程池里面的线程都是从任务队列中取的,所以接收到的连接应该打包成为一个Task任务包
        Task *t = new Task(sock);
        tp->Put(*t);
      }
    }

    ~tcpServer()
    {}
};


#pragma once 

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

#define NUM 5

class Task
{
  public:
    int sock;
    std::map<std::string,std::string> dict;初始化静态成员:类外
  public:
    Task()
    {}

    Task(int _sock)
      :sock(_sock)
    {
      dict.insert(std::make_pair("apple","苹果"));
      dict.insert(std::make_pair("banana","香蕉"));
      dict.insert(std::make_pair("student","学生"));
      dict.insert(std::make_pair("boy","男孩"));
      dict.insert(std::make_pair("goat","山羊"));
    }



    
    void Run()
    {
      std::cout<< "task is running "<<std::endl;
      char buf[64];
      ssize_t s = recv(sock,buf,sizeof(buf)-1,0);
      if(s > 0){
        buf[s] = '\0';
        //让他返回翻译好的单词意思,所以还需要定义一个字典
        std::string key = buf;
        //send发送的类型必须是以C语言的形式
        send(sock,dict[key].c_str(),dict[key].size(),0);

        }
      else if(s == 0){
        std::cout<<"Client quit ..."<<std::endl;
        }
      else{
        std::cerr<<"recv error"<<std::endl;
        }
    }
    ~Task()
    {
      std::cout<<"server close sock"<<std::endl;
      close(sock);
    }
};

class ThreadPool
{
  private:
    std::queue<Task*> q; //线程池中需要一个任务队列,server端不断的发数据,线程池中的线程不断的处理数据,那么有可能你的Task越来越大,导致性能的损失,所以为了避免改用指针
    int max_num;//你需要线程池里面有多少个线程
    pthread_mutex_t lock;
    pthread_cond_t cond; //你需要server和线程池中的线程保持同步的关系,当没有任务的时候,需要让线程等待,但是不可以让server端等待,如果它等待问题就大了

  public:
    void LockQueue()
    {
      pthread_mutex_lock(&lock);
    }

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

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

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

    void ThreadWakeUp()
    {
      pthread_cond_signal(&cond);
    }
  public:
    ThreadPool(int _max = NUM):max_num(_max) 缺省
    {}
    //加了static 那么这个成员函数就属于类,只有一份,也就没有了this指针
    //且static只能够访问static函数,没有的不能访问
    static void *Routine(void *arg)// 此时他作为内部成员函数,第一个默认的参数是this指针,所以这里其实是2个参数   正常来说pthread_create的最后一个参数应该是一个,但是此时却变为了2个
    {
      ThreadPool* this_p = (ThreadPool*)arg;
      //这是线程池里面的线程,要他们一直都存在,不销毁
      while(true)
      {
        //线程池创造出来的目的就是去解决任务,但是又面临这同组线程之间的竞争以及在取任务的时候,server端放,所以需要一把锁
        this_p->LockQueue();
        while(this_p->isEmpty())//这里有可能是会被假唤醒的,所以需要循环的去判断
        {
          this_p->ThreadWait(); 
        }
        //如果任务队列中不是空,那就开始消化任务
        Task *t;
        this_p->Get(&t);
        this_p->UnlockQueue();
        t->Run();//这一步很厉害,我已经把任务从队列中拿出来了,就不需要在占有锁,在临界资源内处理了,可以先进行释放,让任务队列继续工作,我自己处理我拿出来的任务
        delete t;
      }
    }
    void ThreadPoolInit()
    {
      pthread_mutex_init(&lock,nullptr);
      pthread_cond_init(&cond,nullptr);

      pthread_t t[NUM];
      for(int i = 0;i<max_num;++i)
      {
        pthread_create(t+i,nullptr,Routine,this);
      }
    }
    //server
    void Put(Task& in)引用
    {
      //要往任务队列中塞任务
      LockQueue();
      q.push(&in);地址
      UnlockQueue();
      
      //至少要唤醒一个线程
      //pthread_cond_signal   pthread_cond_broadcast()唤醒所有的线程
      ThreadWakeUp();唤醒,去执行任务
    }

    void Get(Task **out)
    {任务队列里:1级指针,用out把任务指针拿出来
      Task *t = q.front();
      q.pop();
      *out = t;
    }


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


数据从套接字里取,
得到新链接,构造任务,把任务放到任务队列,多线程取任务,run函数处理任务,处理完任务之后,析构、关闭链接、delete任务,任务太多时,塞到任务队列里缓存,处理完之后再拿出来队列里的处理
服务器接收请求,
TCP:全双工、字节流、面向链接的通信方案
tcpServer.hpp

#pragma once 

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<pthread.h>
#include"ThreadPool.hpp"
#define BACKLOG 5 //一般这个的值都设置的比较小,表示的意思是此时底层链接队列中等待链接的长度
class tcpServer{
  private:
    int port;
    int lsock;//表示监听的套接字
    ThreadPool *tp;
  public:

    tcpServer(int _port = 8080,int _lsock = -1)
      :port(_port),lsock(_lsock)
    {}

    void initServer()
    {
      lsock = socket(AF_INET,SOCK_STREAM,0);
      if(lsock < 0){
        std::cerr<<"socket error"<<std::endl;
        exit(2);
      }
      struct sockaddr_in local;
      local.sin_family = AF_INET;
      local.sin_port = htons(port);
      local.sin_addr.s_addr = htonl(INADDR_ANY);

      if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0){
        std::cerr<<"bind error!"<<std::endl;
        exit(3);
      }

      //走到这里说明bind成功了,但是tcp是面向链接的,所以还需要一个接口
      if(listen(lsock,BACKLOG) < 0){
        std::cout<<"listen error!"<<std::endl;
        exit(4);
      } 
      
      //tcp必须要将套接字设置为监听状态  这里可以想夜间你去吃饭,什么时候去都可以吃到,是为什么?是因为店里面一直有人在等待着你
      //任意时间有客户端来连接我
      //成功返回0,失败返回-1
      tp = new ThreadPool();创建
      tp->ThreadPoolInit();//初始化threadpool,创造出来一批线程
    }

    //echo服务器
    static void service(int sock)
    {
      while(true){
        //打开一个文件,也叫打开一个流,所以这里其实使用read和write也是可以的,但是这里是网络,最好使用tcp的recv和send
        char buf[64];
        ssize_t s = recv(sock,buf,sizeof(buf)-1,0);
        if(s > 0){
          buf[s] = '\0';
          std::cout<<"Client# "<< buf <<std::endl;
          send(sock,buf,strlen(buf),0);//此时的网络就是文件,你往文件中发送东西的格式,可不是以\0作为结束,加入\0会出现乱码的情况
        }
        else if(s == 0){
          std::cout<<"Client quit ..."<<std::endl;
          break;
        }else{
          std::cerr<<"recv error"<<std::endl;
          break;
        }
      }
      close(sock);
    }

    static void *serviceRoutine(void *arg)
    {
      pthread_detach(pthread_self());//采用线程分离,运行完了以后自动释放。
      std::cout<<"create a new thread"<<std::endl;
      int *p = (int*)arg;
      int sock =  *p; 
      service(sock);
      delete p;
    }

    void start()
    {
      struct sockaddr_in endpoint;
      while(true){
        socklen_t len = sizeof(endpoint);
        int sock = accept(lsock,(struct sockaddr*)&endpoint,&len);
        if(sock < 0){
          std::cerr<<"accept error!"<<std::endl;
          continue; //相当于拉客失败了,我需要继续拉客
        }
        std::string cli_info = inet_ntoa(endpoint.sin_addr);
        cli_info += ":";
        cli_info += std::to_string(endpoint.sin_port); //这里获取的端口号是一个整数,需要把该整数转换为字符串,当然这里也可以使用stringstream 比如定义一个对象 , 此时有一个int变量,一个string 对象 你把int输入stringstream 然后再把stringstream在输入string的对象中,就进行了转换
        std::cout<<"get a new link "<< cli_info<<" sock: "<<sock<<std::endl;


        //当获得一个新链接的时候,就不要创建新线程等,只需要构建一个任务,然后把这个任务push到线程池中
        //线程池里面的线程都是从任务队列中取的,所以接收到的连接应该打包成为一个Task任务包
        Task *t = new Task(sock);栈上开任务
        tp->Put(*t);解引用,开的任务放到任务队列
      }
    }

    ~tcpServer()
    {}
};


tcpClient.hpp

#pragma once

#include<iostream>
#include<string>
#include<cstring>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
class tcpClient{
  private:
    std::string serv_ip;
    int serv_port;
    int sock;
  public:
    tcpClient(std::string _ip = "127.0.0.1",int _port = 8080,int _sock = -1)
      :serv_ip(_ip),serv_port(_port),sock(_sock)
    {}

    void initClient()
    {
      sock = socket(AF_INET,SOCK_STREAM,0);
      if(sock < 0){
        std::cerr<<"socket error"<<std::endl;
        exit(2);
      }

      struct sockaddr_in serv_point;
      serv_point.sin_family = AF_INET;
      serv_point.sin_port = htons(serv_port);
      serv_point.sin_addr.s_addr = inet_addr(serv_ip.c_str());

      //成功返回0,失败返回-1
      if(connect(sock,(struct sockaddr*)&serv_point,sizeof(serv_point)) < 0){
        std::cerr<<"connect error"<<std::endl;
      }
      
    }

    void start()
    {
      char msg[64];
     // while(true){
        std::cout<<"Please Enter Massage# ";
        //这里有一个用户层的c/c++的缓冲区,所以需要强制刷新,这样就不会出现第一行不打印这句的问题
        fflush(stdout);
        //对于Client端首先应该发送数据,然后在考虑接收
        ssize_t s = read(0,msg,sizeof(msg)-1);//从标准输入中读取数据
        //因为cin不会会把回车键给过滤掉,但是read又会获得回车键
        if(s > 0){
          msg[s-1] = '\0';//这一步可以把读到的回车键给过滤掉,这样显示的时候,就不会出现空行
          send(sock,msg,strlen(msg),0);发到服务器
          ssize_t ss = recv(sock,msg,sizeof(msg)-1,0);读取翻译之后的信息
          if(ss > 0){
            msg[ss] = '\0';
            std::cout<<"Server echo # " << msg<<std::endl;
          }
          else if(ss == 0){
            //break;
          }else{
            //break;
          }
        }
     // }
    }

    ~tcpClient()
    {
      close(sock);
    }

};


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值