03 tcp服务端和客户端的实现

本文详细介绍了如何实现单线程TCP服务端和客户端,随后探讨了多进程、多线程和线程池版本的优化,以及如何将进程置于后台和前台操作。最后展示了基于TCP的简单多人聊天系统,包括服务端的线程池处理和客户端的交互过程。
摘要由CSDN通过智能技术生成


一、本地环回

IP:127.0.0.1 叫做本地环回。通常用来进行网络通信代码的测试,一般本地跑通说明本地环境以及代码基本没有大问题。
在这里插入图片描述


二、将sin_addr设置为INADDR_ANY的含义

INADDR_ANY转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,因为有些机器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上进行监听,直到与某个客户建立了链接时才确下来到底用哪个IP地址。

绑定的是INADDR_ANY,则该主机的所有IP层收到的数据都会交给主机,且只需要管理一个套接字。


三、TCP协议

3.1. 监听套接字 listen

由于TCP是有连接的,所以必须将套接字设置为监听状态。

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

int listen(int sockfd, int backlog);

socket:创建套接字返回的文件描述符
backlog:底层队列的链接长度,长度是有限制的,不宜设置太大一般分为全链接队列和半链接队列

返回值:成功返回0,失败返回-1

在这里插入图片描述

3.2. 建立连接accpet

udp在启动时直接recvfrom读取数据。tcp由于已经建立了连接,所以需要从已完成连接队列中返回一个已完成连接,如果队列为空,则会进入阻塞状态

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

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

sockfd是由socket函数返回的套接字描述符。
addr和addrlen用来返回已连接的客户端的协议地址。
如果我们对客户端的协议地址不感兴趣,可以把arrd和addrlen均置为空指针

返回值:失败返回-1,成功返回一个新的文件描述符

一个服务器通常仅仅创建一个监听套接字(listen_sock),它在该服务器生命周期内一直存在。

accept成功后,内核为每个由服务器进程接收的客户端连接创建一个已连接套接字,accept的返回值是由内核自动生成的该套接字的文件描述符(sock),代表与客户端的TCP连接,后续的接收数据都使用这个sock来进行。当服务器完成对某个给定的客户端的服务器时,相应的已连接套接字就被关闭。

listen_sock和sock的关系有点类似于餐馆接待员和服务员的关系,listen_sock只有一个,accept可以调用多次产生多个sock。

在这里插入图片描述

3.3. 连接服务端connect

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

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

sockfd是由socket函数返回的套接字描述符。
addr和addrlen用是要连接服务端的协议地址。

返回值:失败返回-1,成功返回0
在通信时,仍然使用我们创建的套接字,也就是sockfd

在这里插入图片描述

3.4. 文件和流的概念

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

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

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

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd:accept返回的文件描述符
buf:读数据缓冲区
len:期望读取的数据长度
flags:读数据是IO、不一定有数据让你读,如果读取条件不成立,就挂起等待(默认为0,阻塞等待)

返回值:>0读取到了,<0未读取到,=0表示对端关闭了

在这里插入图片描述

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

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd:accept返回的文件描述符
buf:发送的数据
len:发送的长度
flags:如果发送条件不成立,就挂起等待(默认为0,阻塞等待)

在这里插入图片描述

udp中的recvfromsendto是用于无连接通信,在有连接的tcp通信中,一般采用recvsend
在这里插入图片描述


四、单线程tcp服务端和客户端的实现

4.1. 服务端

tcpServer.hpp:

#pragma once
#include <iostream>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>
class tcpServer
{
  private:
    int port;
    int listen_sock;//监听套接字
  public:
    tcpServer(int _port)
      :port(_port)
       ,listen_sock(-1)
    {}

    void initServer()
    {
      listen_sock=socket(AF_INET,SOCK_STREAM,0);
      if(listen_sock<0)
      {
        std::cerr<<"socket fail"<<std::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(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)//进行绑定
      {
        std::cerr<<"bind error"<<std::endl;
        exit(3);
      }
      if(listen(listen_sock,5)<0)
      {
        std::cerr<<"listen error"<<std::endl;
        exit(4);
      }
    }

    void service(int sock)
    {
      char buff[200];
      while(true)
      {
        //由于accept已经获取客户端结构体信息,所以直接使用recv读取信息即可
        size_t size=recv (sock,buff,sizeof(buff)-1,0);
        if(size>0)//读取到数据
        {
          buff[size]='\0';//末尾添加\0
          std::cout<<"client:"<<buff<<std::endl;
          //由于已经获取连接,所以在send的时候不需要客户端的sockaddr,直接往sock里发送即可

          send(sock,buff,strlen(buff),0);//不需要+1,\0是语言的标准,文件不认识
        }
        //客户端退出了
        else if(size==0)
        {
          std::cout<<"client quit..."<<std::endl;
          close(sock);
          break;
        }
        else
        {
          std::cout<<"client error..."<<std::endl;
          close(sock);
          break;
        }
      }
    }

    void start()
    {
      sockaddr_in endpoint;//获取客户端结构体信息,和udp作用一样
      while(true)
      {
        //udp直接通信,tcp第一件事是获取链接
        socklen_t len=sizeof(endpoint);
        int sock=accept(listen_sock,(struct sockaddr*)&endpoint,&len);
        if(sock<0)
        {
          std::cerr<<"accpet error"<<std::endl;
          //获取失败后继续获取
          continue;
        }
        std::string cli_info=inet_ntoa(endpoint.sin_addr);
        cli_info+=":";

        cli_info+=std::to_string(ntohs(endpoint.sin_port));
        std::cout<<"get a new link..."<<cli_info<<std::endl;

        service(sock);//调用封装的服务接口
      }
    }
  ~tcpServer()
  {
	  close(listen_sock);
  }
};

tcpServer.cpp:

#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 *tp=new tcpServer(atoi(argv[1]));
  tp->initServer();//将服务器初始化
  tp->start();

  delete tp;
  return 0;
}

4.2. 客户端

tcpClient.hpp:

#pragma once
#include<iostream>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string>
#include<cstring>
class tcpClient{
private:
    int svr_port;
    std::string svr_ip;
    int sock;
public:
    tcpClient(std::string _ip="127.0.0.1",int _port=8080)
        :svr_port(_port)
        ,svr_ip(_ip){}
    void initClient()
    {
        sock=socket(AF_INET,SOCK_STREAM,0);
        if(sock<0)
        {
            std::cerr<<"socket error"<<std::endl;
            exit(2);
        }
        //连接,不需要绑定
        struct sockaddr_in svr;
        svr.sin_family=AF_INET;
        svr.sin_port=htons(svr_port);
        svr.sin_addr.s_addr=inet_addr(svr_ip.c_str());
        if(connect(sock,(struct sockaddr*)&svr,sizeof(svr))!=0)
        {
            std::cerr<<"connect error"<<std::endl;
        }
        //connect success;
    }
    void start()
    {
        char msg[64];
        while(true)
        {
            std::cout<<"Please Enter Message:";
            fflush(stdout);
            //获取从键盘输入的数据
            size_t s=read(0,msg,sizeof(msg)-1);
            if(s>0)
            {
                msg[s-1]=0;
                //发送给服务端
                send(sock,msg,strlen(msg),0);
                //从服务端接收发回来的数据
                size_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);
    }
};

tcpClient.cpp:

#include "tcpClient.hpp"

void Usage(std::string proc)
{
  std::cout<<"Usage:"<<proc<<"port"<<std::endl;
}
int main(int argc,char* argv[])
{
  if(argc!=3)
  {
    Usage(argv[0]);
    exit(1);
  }
  tcpClient *tp=new tcpClient(argv[1],atoi(argv[2]));
  tp->initClient();//将服务器初始化
  tp->start();
  
  delete tp;
  return 0;
}

4.3. 效果展示

在这里插入图片描述


五、tcp服务端优化

单进程版本的服务端只有一个进程,当第二个客户端连接时,由于服务端的执行流正在处理第一个客户端的数据,所以会导致第二个客户端无法连接的情况。

5.1. 多进程版本

#pragma once
#include <iostream>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>
#include<signal.h>
class tcpServer
{
  private:
    int port;
    int listen_sock;//监听套接字
  public:
    tcpServer(int _port)
      :port(_port)
       ,listen_sock(-1)
    {}

    void initServer()
    {
      signal(SIGCHLD,SIG_IGN);//父进程忽略子进程发送过来的信号,即子进程自己清理资源
      listen_sock=socket(AF_INET,SOCK_STREAM,0);
      if(listen_sock<0)
      {
        std::cerr<<"socket fail"<<std::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(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)//进行绑定
      {
        std::cerr<<"bind error"<<std::endl;
        exit(3);
      }
      if(listen(listen_sock,5)<0)
      {
        std::cerr<<"listen error"<<std::endl;
        exit(4);
      }
    }

    void service(int sock)
    {
      char buff[200];
      while(true)
      {
        //由于accept已经获取客户端结构体信息,所以直接使用recv读取信息即可
        size_t size=recv (sock,buff,sizeof(buff)-1,0);
        if(size>0)//读取到数据
        {
          buff[size]='\0';//末尾添加\0
          std::cout<<"client:"<<buff<<std::endl;
          //由于已经获取连接,所以在send的时候不需要客户端的sockaddr,直接往sock里发送即可

          send(sock,buff,strlen(buff),0);//不需要+1,\0是语言的标准,文件不认识
        }
        //客户端退出了
        else if(size==0)
        {
          std::cout<<"client quit..."<<std::endl;
          close(sock);
          break;
        }
        else
        {
          std::cout<<"client error..."<<std::endl;
          close(sock);
          break;
        }
      }
    }

    void start()
    {
      sockaddr_in endpoint;//获取客户端结构体信息,和udp作用一样
      while(true)
      {
        //udp直接通信,tcp第一件事是获取链接
        socklen_t len=sizeof(endpoint);
        int sock=accept(listen_sock,(struct sockaddr*)&endpoint,&len);
        if(sock<0)
        {
          std::cerr<<"accpet error"<<std::endl;
          //获取失败后继续获取
          continue;
        }
        std::string cli_info=inet_ntoa(endpoint.sin_addr);
        cli_info+=":";

        cli_info+=std::to_string(ntohs(endpoint.sin_port));
        std::cout<<"get a new link..."<<cli_info<<std::endl;
        //创建新进程,让子进程执行服务
        pid_t id=fork();
        if(id==0)//子进程提供服务
        {
          //子进程关闭listen_sock不会影响父进程
          close(listen_sock);//子进程拷贝了父进程的文件描述表
          service(sock);//封装一个服务接口
          exit(0);//执行完任务退出
        }
        //父进程的主要任务是获取链接
        //然后链接越多,对应的为服务创建的sock文件描述符也越多
        //父进程可用的资源就少了,因此不如关闭
        //父进程关闭sock不会影响子进程
        close(sock);
        //由于signal(SIGCHLD,SIG_IGN); 父进程不必等待子进程
      }
    }
  ~tcpServer()
  {
	  close(listen_sock);
  }
};

在这里插入图片描述

5.2. 多线程版本

多进程的版本创建进程开销还是太大,所以可以改成多线程。

#pragma once
#include <iostream>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
class tcpServer
{
  private:
    int port;
    int listen_sock;//监听套接字
  public:
    tcpServer(int _port)
      :port(_port)
       ,listen_sock(-1)
    {}

    void initServer()
    {
      listen_sock=socket(AF_INET,SOCK_STREAM,0);
      if(listen_sock<0)
      {
        std::cerr<<"socket fail"<<std::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(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)//进行绑定
      {
        std::cerr<<"bind error"<<std::endl;
        exit(3);
      }
      if(listen(listen_sock,5)<0)
      {
        std::cerr<<"listen error"<<std::endl;
        exit(4);
      }
    }

   static void service(int sock)
    {
        while(true)
        {
            char buffer[1024];
            size_t s=recv(sock,buffer,sizeof(buffer)-1,0);
            if(s>0)
            {
                buffer[s]=0;
                std::cout<<"client#"<<buffer<<std::endl;
                send(sock,buffer,strlen(buffer),0);
            }
            else if(s==0)
            {
                std::cout<<"client quit..."<<std::endl;
                close(sock);
                break;
            }
            else{
                std::cout<<"recv client data error..."<<std::endl;
                break;
            }
        }
        close(sock);
    }
    static void* serviceRoutine(void* arg)
    {
      //线程分离,主线程不等子线程,子线程执行完自己释放
      pthread_detach(pthread_self());
      std::cout<<"create a new thread for IO"<<std::endl;
      int* p=(int*)arg;
      int sock=*p;
      service(sock);
      delete p;
    }
    void start()
    {
      sockaddr_in endpoint;//获取客户端结构体信息,和udp作用一样
      while(true)
      {
        //udp直接通信,tcp第一件事是获取链接
        socklen_t len=sizeof(endpoint);
        int sock=accept(listen_sock,(struct sockaddr*)&endpoint,&len);
        if(sock<0)
        {
          std::cerr<<"accpet error"<<std::endl;
          //获取失败后继续获取
          continue;
        }
        std::string cli_info=inet_ntoa(endpoint.sin_addr);
        cli_info+=":";

        cli_info+=std::to_string(ntohs(endpoint.sin_port));
        std::cout<<"get a new link..."<<cli_info<<std::endl;
        //创建新进程,让子进程执行服务
        pthread_t tid;
        int* p=new int(sock);
        //传入要执行的函数serviceRoutine和套接字p
        pthread_create(&tid,NULL,serviceRoutine,(void*)p);

      }
    }
  ~tcpServer()
  {
	  close(listen_sock);
  }
};

在这里插入图片描述

5.3. 线程池版本

ThreadPool.hpp:

#pragma once 

#include <iostream>
#include <queue>
#include<math.h>
#include<unistd.h>
#define NUM 5
class Task{
public:
    int sock; 
    Task(int _sock):sock(_sock){
    }
    void Run(){
        while(true)
        {
            char buffer[1024];
            size_t s=recv(sock,buffer,sizeof(buffer)-1,0);
            if(s>0)
            {
                buffer[s]=0;
                std::cout<<"client#"<<buffer<<std::endl;
                send(sock,buffer,strlen(buffer),0);
            }
            else if(s==0)
            {
                std::cout<<"client quit..."<<std::endl;
                close(sock);
                break;
            }
            else{
                std::cout<<"recv client data error..."<<std::endl;
                break;
            }
        }
        close(sock);
    }
    ~Task(){
        std::cout<<"server close sock:"<<sock<<std::endl;
        close(sock);
    }
};

class ThreadPool{
private:
  std::queue<Task*> q;//任务队列
  int max_num;//线程的总数
  pthread_mutex_t lock;
  pthread_cond_t cond;//只能消费者等待,因为生产者要从外部获取任务
  
  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);
  }
  void ThreadsWakeup(){
    //唤醒所有的线程
    pthread_cond_broadcast(&cond);
  }
public:
  ThreadPool(int _max=NUM):max_num(_max){

  }

  static void*Routine(void*arg){//必须设置为静态的,因为成员函数会有this形参
    ThreadPool*this_p=(ThreadPool*)arg;
    while(true){//从任务队列中拿任务
      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,NULL);
    pthread_cond_init(&cond,NULL);  
    pthread_t t;//创建一批线程
    for(int i=0;i<max_num;i++){
      pthread_create(&t,NULL,Routine,this);
    }
  }

  void Put(Task &in){//往任务队列中放任务
    LockQueue();
    q.push(&in);
    UnlockQueue();
    ThreadsWakeup();
  }
  void Get(Task **out){
    Task*t=q.front();
    q.pop();
    *out=t;
  }

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

tcpServer.hpp:

#pragma once

#include<iostream>
#include<string>
#include<cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include<cstring>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<pthread.h>
#include"ThreadPool.hpp"
//全链接队列长度
#define BACKLOG 5
class tcpServer{
private:
    int port;//端口号
    int listen_sock;//监听套接字
    ThreadPool *tp;//线程池
public:
    tcpServer(int _port)
    :port(_port)
    ,listen_sock(-1)
    ,tp(NULL)
    {}

    void initServer()
    {
        signal(SIGCHLD,SIG_IGN);
        listen_sock=socket(AF_INET,SOCK_STREAM,0);
        if(listen_sock<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(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)
        {
            std::cerr<<"bind error"<<std::endl;
            exit(3);
        }
        //套接字必须设定为监听状态
        if(listen(listen_sock,BACKLOG)<0)
        {
            std::cerr<<"bind error"<<std::endl;
            exit(4);
        }
        //创建并初始化线程池
        tp=new ThreadPool();
        tp->ThreadPoolInit();
        
    }
    void start()
    {
        sockaddr_in endpoint;
        while(true)
        {
            socklen_t len=sizeof(endpoint);
            int sock=accept(listen_sock,(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(ntohs(endpoint.sin_port));
            std::cout<<"get a new link..."<<cli_info<<std::endl;
            Task* t=new Task(sock);
            tp->Put(*t);
        }
    }
    ~tcpServer()
    {}
};

在这里插入图片描述

5.4. 各个版本特点

  1. 单进程版本:只有一个执行流,一般不使用,多进程/多线程版本通常是小型的应用(局域网,少量机)。
  2. 多进程版本:进程之间是独立的,健壮性强,但是比较吃资源,效率低下
  3. 多线程版本:健壮性不强,较吃资源,效率相对较高。如果有大量的客户端,则系统会存在大量的执行流,执行流之间的切换会使效率低下。
  4. 线程池版本:可以减轻一大批客户端来进行链接时,服务器的压力。同时、由于线程池的线程数量有限,可以有效防止外部非法请求攻击、也在一定程度上保护了计算机

六、将一个进程放置后台,拿到前台

  • &
    这个用在一个命令的最后,可以把这个命令放到后台执行
  • ctrl+ z
    可以将一个正在前台执行的命令放到后台,并且暂停
  • jobs
    查看当前有多少在后台运行的命令
  • fg
    将后台中的命令调至前台继续运行
  • bg
    将一个在后台暂停的命令,变成继续执行

七、基于tcp的简单的多人聊天

7.1. 服务端

ThreadPool.hpp:

#pragma once 
#include <iostream>
#include <queue>
#include<math.h>
#include<unistd.h>
using namespace std;
#define NUM 10
class Task{
public:
    int sock; 
    Task(int _sock):sock(_sock){
    }
    void Run(){
        while(true)
        {
          //接收到发来的数据,然后发给除了自己的所有人
          char buffer[1024];
          size_t s=recv(sock,buffer,sizeof(buffer)-1,0);
          if(s>0)
          {
              buffer[s]=0;
              string sendStr(buffer);
              sendStr.insert(0,userInfMap[sock]+">");
              int sendSock;
              for(auto e:userInfMap)
              {
                  sendSock=e.first;
                  if(sendSock!=sock)
                  {
                      send(sendSock,sendStr.c_str(),sendStr.size(),0);
                  }
              }
          }
          else if(s==0)
          {
              std::cout<<userInfMap[sock]<<"退出..."<<std::endl;
              close(sock);
              break;
          }
          else{
              std::cout<<"recv"<<userInfMap[sock]<<"data error..."<<std::endl;
              break;
          }
        }
        close(sock);
    }
    ~Task(){
        std::cout<<"server close sock:"<<sock<<std::endl;
        close(sock);
    }
};

class ThreadPool{
private:
  std::queue<Task*> q;//任务队列
  int max_num;//线程的总数
  pthread_mutex_t lock;
  pthread_cond_t cond;//只能消费者等待,因为生产者要从外部获取任务
  
  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);
  }
  void ThreadsWakeup(){
    //唤醒所有的线程
    pthread_cond_broadcast(&cond);
  }
public:
  ThreadPool(int _max=NUM):max_num(_max){

  }

  static void*Routine(void*arg){//必须设置为静态的,因为成员函数会有this形参
    ThreadPool*this_p=(ThreadPool*)arg;
    while(true){//从任务队列中拿任务
      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,NULL);
    pthread_cond_init(&cond,NULL);  
    pthread_t t;//创建一批线程
    for(int i=0;i<max_num;i++){
      pthread_create(&t,NULL,Routine,this);
    }
  }

  void Put(Task &in){//往任务队列中放任务
    LockQueue();
    q.push(&in);
    UnlockQueue();
    ThreadsWakeup();
  }
  void Get(Task **out){
    Task*t=q.front();
    q.pop();
    *out=t;
  }

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

tcpServer.hpp:

#pragma once
#include<iostream>
#include<string>
#include<cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include<cstring>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<pthread.h>
#include<map>
using namespace std;
map<int,string> userInfMap;
#include"ThreadPool.hpp"
//全链接队列长度
#define BACKLOG 5
class tcpServer{
private:
    int port;//端口号
    int listen_sock;//监听套接字
    ThreadPool *tp;//线程池
public:
    tcpServer(int _port)
    :port(_port)
    ,listen_sock(-1)
    ,tp(NULL)
    {}
    void initServer()
    {
        signal(SIGCHLD,SIG_IGN);
        listen_sock=socket(AF_INET,SOCK_STREAM,0);
        if(listen_sock<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(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)
        {
            std::cerr<<"bind error"<<std::endl;
            exit(3);
        }
        //套接字必须设定为监听状态
        if(listen(listen_sock,BACKLOG)<0)
        {
            std::cerr<<"bind error"<<std::endl;
            exit(4);
        }
        //创建并初始化线程池
        tp=new ThreadPool();
        tp->ThreadPoolInit();
        
    }
    void start()
    {
        sockaddr_in endpoint;
        while(true)
        {
            socklen_t len=sizeof(endpoint);
            int sock=accept(listen_sock,(struct sockaddr*)&endpoint,&len);
            if(sock<0)
            {
                std::cerr<<"accept error"<<std::endl;
                continue;
            }
            char userName[64]={'\0'};
            size_t s=recv(sock,userName,sizeof(userName)-1,0);
            string cli_info(userName);
            userInfMap[sock]=cli_info;
            std::cout<<cli_info<<"已连接"<<std::endl;
            Task* t=new Task(sock);
            tp->Put(*t);
        }
    }
    ~tcpServer()
    {}
};

tcpServer.cpp:

#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 *tp=new tcpServer(atoi(argv[1]));
  tp->initServer();//将服务器初始化
  tp->start();

  delete tp;
  return 0;
}

7.2. 客户端

tcpClient.hpp:

#pragma once
#include<iostream>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string>
#include<cstring>
#include<pthread.h>
using namespace std;
class tcpClient{
private:
    int svr_port;
    std::string svr_ip;
    int sock;
    char userName[100];
public:
    tcpClient(std::string _ip="127.0.0.1",int _port=8080)
        :svr_port(_port)
        ,svr_ip(_ip){}
    void initClient()
    {
        sock=socket(AF_INET,SOCK_STREAM,0);
        if(sock<0)
        {
            std::cerr<<"socket error"<<std::endl;
            exit(2);
        }
        //连接,不需要绑定
        struct sockaddr_in svr;
        svr.sin_family=AF_INET;
        svr.sin_port=htons(svr_port);
        svr.sin_addr.s_addr=inet_addr(svr_ip.c_str());
        if(connect(sock,(struct sockaddr*)&svr,sizeof(svr))!=0)
        {
            std::cerr<<"connect error"<<std::endl;
        }
        //connect success;
        cout<<"请输入你的用户名:";
        fflush(stdout);
        size_t s=read(0,userName,sizeof(userName)-1);
        userName[s-1]=0;
        send(sock,userName,strlen(userName),0);
    }
    static void readService(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<<buff<<endl;
        }
        else if(size==0)
        {
          cout<<"服务器关闭..."<<endl;
          close(sock);//关闭对应的套接字
          break;
        }
        else//读取错误
        {
          cerr<<"read error"<<endl;
          close(sock);
          break;
        }
      }
    }
    static void *sevrun(void *arg)//只能有一个参数,因此必须是static
    {
      pthread_detach(pthread_self());//线程分离,不用再进行等待
      int *p=(int*)arg;
      int sock=*p;
      readService(sock);//执行任务
      delete p;
      return NULL;
    }

    void start()
    {
        //创建子线程去读服务端发来的数据
        pthread_t tid;
        int *p=new int(sock);
        pthread_create(&tid,NULL,sevrun,(void*)p);
        
        char msg[64];
        std::cout<<"请在下面输入内容>"<<endl;;
        while(true)
        {
            fflush(stdout);
            size_t s=read(0,msg,sizeof(msg)-1);
            if(s>0)
            {
                msg[s-1]=0;
                send(sock,msg,strlen(msg),0);
            }
        }
        
    }
    ~tcpClient()
    {
        close(sock);
    }
};

tcpClient.cpp:

#include "tcpClient.hpp"

void Usage(std::string proc)
{
  std::cout<<"Usage:"<<proc<<"port"<<std::endl;
}
int main(int argc,char* argv[])
{
  // if(argc!=3)
  // {
  //   Usage(argv[0]);
  //   exit(1);
  // }
  tcpClient *tp=new tcpClient("49.232.92.201",atoi(argv[1]));
  tp->initClient();//将服务器初始化
  tp->start();
  
  delete tp;
  return 0;
}

7.3. 效果展示

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今天也要写bug、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值