【Socket】:实现一个简单通用的Tcp和Udp服务器及客户端

前言:本篇博客利用Socket API实现一个简单通用的Tcp及Udp服务器及客户端。并且将Tcp版本的服务器改为多进程版本多线程版本以及引入线程池

网络编程套接字:https://blog.csdn.net/hansionz/article/details/85226345

1. 实现一个通用简单Udp服务器/客户端

  • 封装udpsocket接口
//udp_socket.hpp
#pragma once 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;

class UdpSockect{

public:
  UdpSockect()
    :_fd(-1)
  {}

  //创建套接字
  void Sockect()
  {
    //AF_INET代表IPV4协议族
    //SOCK_DGRAM代表数据报
    _fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(_fd < 0)
    {
      perror("use socket");
      return;
    }
  }

  //关闭套接字,相当于关闭文件
  void Close()
  {
    close(_fd);
  }

  //绑定端口号(服务器)
  void Bind(uint16_t port)
  {
    sockaddr_in addr;//IPv4的地址类型
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);//主机字节序转网络字节序
    //inet_addr可以将点分十进制的字符串转为in_addrIP地址
    //INADDR_ANY代表0.0.0.0
    addr.sin_addr.s_addr = INADDR_ANY;
    int ret = bind(_fd, (sockaddr*)&addr, sizeof(addr));
    if(ret < 0)
    {
      perror("use bind");
      return;
    }
  }

  //接收消息(客户端、服务器)
  void ReceFrom(std::string* buf, std::string* ip = NULL, uint16_t* port = NULL)
  {
    char tmp[1024] = {0};
    sockaddr_in addr;
    socklen_t len = sizeof(addr);
    //返回值为实际接收到的字符个数
    ssize_t rs = recvfrom(_fd, tmp, sizeof(tmp)-1, 0, (sockaddr*)&addr, &len);

    if(rs < 0)
    {
      perror("use recefrom");
      return;
    }
  
    //assign是使用c类指针tmp的前rs个字符重新赋值buf
    buf->assign(tmp, rs);

    //如果外部要使用addr中的ip地址和端口号,则赋值,否则直接跳过
    if(ip != NULL)
    {
      *ip = inet_ntoa(addr.sin_addr);
    }
    if(port != NULL)
    {
      *port = ntohs(addr.sin_port);
    }
  }

  //发送消息(客户端、服务器)
  void SendTo(const std::string& buf, const std::string& ip, uint16_t port)
  {
   sockaddr_in addr;
   addr.sin_addr.s_addr = inet_addr(ip.c_str());
   addr.sin_port = htons(port);
   addr.sin_family = AF_INET;

   ssize_t ws = sendto(_fd, buf.data(), buf.size(), 0, (sockaddr*)&addr, sizeof(addr));
   if(ws < 0)
   {
     perror("use sendto");
     return;
   }
  }
private:
  int _fd;
};

注:网络地址为INADDR_ANY, 这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址, 这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址,当然这是针对TCP的。

  • 实现通用的udp服务器
#pragma once
#include "udp_sockect.hpp"

//函数指针
typedef void (*Handler)(const std::string& req, std::string* res);
// C++ 11 式写法
//#include  
//typedef std::function Handler;
 
class UdpServer{

public:
  UdpServer()
  {
    //创建套接字
     _sock.Sockect();
  }

  /* 1.创建套接字
   * 2.绑定端口号
   * 3.开始循环读取客户端请求
   * 4.读取到请求后做请求处理
   * 5.得到响应
   * 6.向客户端发回响应
   **/

  //开启服务器
  void Start( uint16_t port, Handler handler)
  {
    _sock.Bind( port);
    while(1)
    {
      //不断读取客户端的请求
      std::string req;
      std::string re_ip;//获取远程主机的ip和端口号
      uint16_t re_port;
      _sock.ReceFrom(&req, &re_ip, &re_port);
      
      //处理请求得到响应结果
      std::string res;
      handler(req, &res);

      //返回响应给客户端
      _sock.SendTo(res, re_ip, re_port);//re_ip代表目的ip和port
      std::cout << "ip: " << re_ip << "port: "<< re_port << " | req: " << req << " res: " << res <<std::endl; 
    } 
    _sock.Close();
  }
private:
  UdpSockect _sock;
};
  • 实现一个通用的udp客户端
#pragma once
#include "udp_sockect.hpp"

class UdpClient{
public:
  UdpClient(std::string& ip, uint16_t port)
    :_ip(ip)
     ,_port(port)
  {
    //创建套接字
    _sock.Sockect();
  }

  ~UdpClient()
  {
    _sock.Close();
  }

  void RecvFrom(std::string* buf)
  {
     _sock.ReceFrom(buf);
  }

  void SendTo(std::string& buf)
  {
     _sock.SendTo(buf, _ip, _port);
  }
private:
  UdpSockect _sock;
  std::string _ip;
  uint16_t _port;
};

以上述的通用服务器和客户端实现一个英译汉词典给出英文,发送给服务器,然后服务器返回该英文单词的汉语意思。

  • 实现英译汉词典服务器
#include "udp_server.hpp"
#include 

std::unordered_map<std::string, std::string> dict;

void Translate(const std::string& req, std::string* res)
{
  //std::cout << "translate" << std::endl;
  auto it = dict.find(req);
  if(it == dict.end())
  {
    *res = "词典中没有该词汇";
    return;
  }
  *res = it->second;
}

int main(int argc, char* argv[])
{
  if(argc != 2)
  {
    std::cout << "Usage:./dict_server[port]" << std::endl;
    return 1;
  }

  //向词典中插入一些键值对
  dict.insert(std::make_pair("sport","运动"));
  dict.insert(std::make_pair("computer","电脑"));
  dict.insert(std::make_pair("english","英语"));
  dict.insert(std::make_pair("math","数学"));

  UdpServer server;
  server.Start(atoi(argv[1]), Translate);

  return 0;
}
  • 实现英译汉词典客户端
#include "udp_client.hpp"

int main(int argc, char* argv[])
{
  if(argc != 3)
  {
    std::cout << "Usage:./dict_client[ip][port]" << std::endl;
    return 1;
  }
  std::string ip(argv[1]);
  UdpClient client(ip, atoi(argv[2]));

  while(1)
  {
    std::string word;
    std::cout <<"请输入要查的单词:";
    std::cin >> word;
    if(word == "quit")
    {
      std::cout << "good bye" << std::endl;
      break;
    }
    client.SendTo(word);
    std::string res;
    client.RecvFrom(&res);
    std::cout << res << std::endl;
  }
  return 0;
}

代码github链接:https://github.com/hansionz/Linux_Code/tree/master/Socket/UdpCode

2.实现一个简单通用的TCP服务器/客户端

  • 封装Tcp sockect接口
//tcp_socket.hpp
#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include  
#include  
#include 

using namespace std;

typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;

class TcpSocket
{
public:
  TcpSocket(){}
  TcpSocket(int fd)
  {
    _fd = fd;
  }
  //创建套接字
  void Socket()
  {
    _fd = socket(AF_INET, SOCK_STREAM, 0);
    if(_fd < 0){
      perror("use socket");
      return;
    }
  }
  //关闭套接字
  void Close() const
  {
    close(_fd);
  }
  //绑定ip和端口号
  void Bind(const string& ip, uint16_t port)
  {
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(ip.c_str());
    addr.sin_port = htons(port);
    if(bind(_fd, (sockaddr*)&addr, sizeof(addr)) < 0){
      perror("use bind");
      return;
    }
  }
  //监听
  void Listen(int backlog)
  {
    if(listen(_fd, backlog) < 0){
      perror("use listen");
      return;
    }
  }
  //服务器接受连接
  void Accept(TcpSocket* obj, string* ip = NULL, uint16_t* port = NULL )
  {
    sockaddr_in peer;
    socklen_t len = sizeof(peer);
    int new_sock = accept(_fd, (sockaddr*)&peer, &len);
    if(new_sock < 0){
      perror("use accept");
      return;
    }
    obj->_fd = new_sock;

    if(ip != NULL)
    {
      *ip = inet_ntoa(peer.sin_addr);
    }
    if(port != NULL)
    {
      *port = ntohs(peer.sin_port);
    }
  }
 //接受消息
void Recv(string* buf)
{
  buf->clear();
  char peer[1024];
  ssize_t read_size = read(_fd, peer, sizeof(peer) - 1);
  if(read_size < 0){
    perror("use read");
    return;
  }
  buf->assign(peer, read_size);
}
//发送消息
void Send(const string& buf)
{
  ssize_t write_size = write(_fd, buf.c_str(), buf.size());
  if(write_size < 0){
    perror("use wirte");
    return;
  }
}
//客户端建立连接
void Connect(string& ip, uint16_t port)
{
  sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = inet_addr(ip.c_str());
  addr.sin_port = htons(port);

  if(connect(_fd, (sockaddr*)&addr, sizeof(addr)) < 0)
  {
    perror("use connect");
    return;
  }
}
int Getfd() const 
{
  return _fd;
}
private:
  int _fd = -1;
};
  • 实现通用Tcp服务器
#pragma once 

#include "tcp_socket.hpp"

typedef void (*Handler)(string& req, string* res);

class TcpServer
{
public:
  TcpServer(string ip, uint16_t port)
    :_ip(ip)
     ,_port(port)
  {}

  void Start(Handler handler)
  {
    //1.创建socket
    listen_sock.Socket();
    //2.绑定ip和端口号
    listen_sock.Bind(_ip, _port);
    //3.监听
    listen_sock.Listen(5);

    while(1)
    {
      TcpSocket new_sock;
      string ip;
      uint16_t port;
      //4.接收连接
      listen_sock.Accept(&new_sock, &ip, &port);
      cout <<"client:" << ip.c_str() << " connect" << endl;
      while(1)
      {
        //5.连接成功读取客户端请求
        string req;
        new_sock.Recv(&req);
        //6.处理请求
        string res;
        handler(req, &res);

        //写回处理结果
        new_sock.Send(res);
        cout << "客户:" << ip.c_str() << " REQ:" << req << ". RES:" << res << endl;
      }
    }
  }
private:
  TcpSocket listen_sock;
  string _ip;
  uint16_t _port;
};
  • 实现通用Tcp客户端
#pragma once 

#include "tcp_socket.hpp"

class TcpClient
{
public:
  TcpClient(string ip, uint16_t port)
    :_ip(ip)
     ,_port(port)
  {
    sock.Socket();
  }
  void Connect()
  {
    sock.Connect(_ip, _port);
  } 
  void Recv(string* buf)
  {
    sock.Recv(buf);
  }
  void Send(const string& buf)
  {
    sock.Send(buf);
  }
  ~TcpClient()
  {
    sock.Close();
  }
private:
  TcpSocket sock;
  string _ip;
  uint16_t _port;
};

以上面通用服务器和客户端为基础实现一个简单网络翻译器。客户端输入要查询的单词,发送给服务器然后服务器经过查询项客户端。

  • 实现翻译器的服务器
#include "tcp_proc_server.hpp"
#include 

unordered_map<string, string> dict;
void Translate(string& req, string* res)
{
  auto it = dict.find(req);
  if(it == dict.end())
  {
    *res = "词典中没有该单词";
    return;
  }
  *res = it->second;
}
int main(int argc, char* argv[])
{
  if(argc != 3)
  {
    cout << "Usage: ./tcp_server [ip][port]" << endl;
    return 1;
  }

  //初始化词典
  dict.insert(make_pair("book","书"));
  dict.insert(make_pair("tree", "树"));
  dict.insert(make_pair("dream","梦想"));

  string ip(argv[1]);
  TcpProcServer server(ip,atoi(argv[2])); 
  server.Start(Translate);

  return 0;
}
  • 实现翻译器的客户端
#include "tcp_client.hpp"

int main(int argc, char* argv[])
{
  if(argc != 3)
  {
    cout << "Usage: ./dict_client [ip][port]" << endl;
    return 1;
  }

  TcpClient client(argv[1], atoi(argv[2]));
  client.Connect();

  while(1)
  {
    cout << "请输入查询的单词:" << endl;
    string word;
    cin >> word;

    if(word == "quit")
      break;

    client.Send(word);
    
    string res;
    client.Recv(&res);

    cout << "意思是:" << res << endl;
  }
  return 0;
}
  • Makfile写法
.PHONY:all
all:dict_client dict_server

dict_client:dict_client.cc tcp_client.hpp tcp_socket.hpp
	g++ -o $@ -std=c++11 $^
dict_server:dict_server.cc tcp_server.hpp  tcp_socket.hpp
	g++ -o $@ -std=c++11 $^
.PHONY:clean
clean:
	rm -f dict_client dict_server

代码github链接:https://github.com/hansionz/Linux_Code/tree/master/Socket

3.Tcp服务器多进程版本

#pragma once 

#include "tcp_socket.hpp"
#include 
#include 

typedef void (*Handler)(string& req, string* res);

class TcpProcServer
{
public:
  TcpProcServer(string& ip, uint16_t port)
    :_ip(ip)
     ,_port(port)
  {
    signal(SIGCHLD, SIG_IGN);
  }
  void ProcConnect(TcpSocket& newsock, string& ip, uint16_t port, Handler hanler)
  {
    pid_t id = fork();
    if(id < 0)//fork失败
    {
      perror("use fork");
      exit(1);
    }
    else if(id == 0){//child
      while(1){
        string req;
        newsock.Recv(&req);
        string res;
        hanler(req, &res);
        newsock.Send(res);
        cout << ip << "-" << port << " req:" << req << " | res:" << res <<endl; 
      }
    }
    else{//father
      //父进程直接结束即可,不需要wait子进程,因为已经将SIGCHILD信号捕捉为忽略
      //父进程也不能wait,如果wait不能再次快速调用accept
      //父进程需要关闭newsock
      newsock.Close();
      return;
    }
  }
  void Start(Handler handler)
  {
    listen_sock.Socket();
    listen_sock.Bind(_ip, _port);
    listen_sock.Listen(5);
    while(1)
    {
      TcpSocket newsock;
      string ip;
      uint16_t port;
      listen_sock.Accept(&newsock, &ip, &port);

      cout << "client:" << ip.c_str() << "-" << port << " connect" << endl;
      ProcConnect(newsock, ip, port, handler);
    }
  }

private:
  TcpSocket listen_sock;
  string _ip;
  uint16_t _port;
};

4.Tcp服务器多线程版本

#pragma once 

#include "tcp_socket.hpp"
#include 
#include 

typedef void (*Handler)(string& req, string* res);
typedef struct ThreadArg
{
  TcpSocket new_sock;
  string ip;
  uint16_t port;
  Handler handler;
}ThreadArg;

class TcpPthServer
{
public:
  TcpPthServer(string& ip, uint16_t port)
    :_ip(ip)
     ,_port(port)
  {}
  //这个函数也必须是静态函数,才能被Routine函数调用
  static void SingleDeal(ThreadArg* arg)
  {
    while(1)
    {
      string req;
      arg->new_sock.Recv(&req);

      string res;
      arg->handler(req, &res);

      arg->new_sock.Send(res);
      cout << "client:" << arg->ip << "--" << arg->port << "req:" << req << " .res:" << res << endl;
    }
  }
  //这个函数必须是静态函数,负责会多出一个this参数
  static void* Routine(void* arg)
  {
    pthread_detach(pthread_self());
    //reinterpret_cast是C++中的强制类型转换
    ThreadArg* ptr = reinterpret_cast<ThreadArg*>(arg);

    SingleDeal(ptr);
    //当处理完时,要释放套接字描述符
    ptr->new_sock.Close();
    //传入的arg参数是new出来的
    delete ptr;
    ptr = NULL;
  }
  void Start(Handler handler)
  {
    listen_sock.Socket();
    listen_sock.Bind(_ip, _port);
    listen_sock.Listen(5);
    while(1)
    {
      ThreadArg* arg = new ThreadArg();
      arg->handler = handler;
      listen_sock.Accept(&arg->new_sock, &arg->ip, &arg->port);
      cout << "client:" << arg->ip.c_str() << "--" << arg->port << " connect" << endl;

      pthread_t tid;
      pthread_create(&tid, NULL, Routine, (void*)arg);
    }
  }
private:
  TcpSocket listen_sock;
  string _ip;
  uint16_t _port;
};

5.线程池版本服务器

  • 实现一个线程池
#include "tcp_socket.hpp"

#include 
#include 

using namespace std;

typedef void (*Handler_t)(TcpSocket);

class Task
{
public:
  Task(TcpSocket newsock, Handler_t handler = NULL)
    :_sock(newsock)
    ,_handler(handler)
  {}

  void Run()
  {
    _handler(_sock);
  }
private:
  TcpSocket _sock;
  Handler_t _handler;

};

class ThreadPool
{
public:
  ThreadPool(int nums)
    :_nums(nums)
    ,idle_nums(0)
  {
    pthread_cond_init(&_cond, NULL);
    pthread_mutex_init(&_mutex, NULL);
  }

  void LockQueue()
  {
    pthread_mutex_lock(&_mutex);
  }
  void UnlockQueue()
  {
    pthread_mutex_unlock(&_mutex);
  }

  void IdlePthread()
  {
    pthread_cond_wait(&_cond, &_mutex);
  }

  void NotiyPthread()
  {
    pthread_cond_signal(&_cond);
  }

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

  Task PopTask()
  {
    Task t = q.front();
    q.pop();
    return t;
  }

  static void* ThreadRoutine(void* arg)
  {
    ThreadPool* tp = reinterpret_cast<ThreadPool*>(arg);
    pthread_detach(pthread_self());//分离线程
    while(1)
    {
      tp->LockQueue();
      while(tp->IsEmpty())//使用while轮询防止假唤醒
      {
        tp->IdlePthread();
      }
      Task t = tp->PopTask();
      tp->UnlockQueue();

      t.Run();
    }
  }
  void InitPool()
  {
    pthread_t pid;
    for(int i = 0; i < _nums; i++)
    {
      pthread_create(&pid, NULL, ThreadRoutine, this);
    }
  }
  void AddTask(const Task& t)
  {
    LockQueue();
    q.push(t);
    NotiyPthread();
    UnlockQueue();
  }
  ~ThreadPool()
  {
    pthread_cond_destroy(&_cond);
    pthread_mutex_destroy(&_mutex);
  }
private:
  TcpSocket _sock;
  int _nums;
  int idle_nums;
  queue<Task> q;
  pthread_cond_t _cond;
  pthread_mutex_t _mutex;
};
  • 线程池版本的服务器
#pragma once 

#include "tcp_socket.hpp"
#include "threadpool.hpp"

#include 

unordered_map<string, string> dict;
void Translate(string& req, string* res)
{
  auto it = dict.find(req);
  if(it == dict.end())
  {
    *res = "词典中没有该单词";
    return;
  }
  *res = it->second;
}

//typedef void (*Handler)(string& req, string* res);

class TcpServer
{
public:
  TcpServer(string ip, uint16_t port)
    :_pool(5)
    ,_ip(ip)
     ,_port(port)
  {}

  static void service(TcpSocket sock)
  {
    
    //初始化词典
    dict.insert(make_pair("book","书"));
    dict.insert(make_pair("tree", "树"));
    dict.insert(make_pair("dream","梦想"));

    while(1)
    {
      string req;
      sock.Recv(&req);
      string res;
      Translate(req, &res);

      cout << req << "-" << res << endl;
      sock.Send(res);
    }
    sock.Close();
  }
  void Start()
  {
    //1.创建socket
    listen_sock.Socket();
    //2.绑定ip和端口号
    listen_sock.Bind(_ip, _port);
    //3.监听
    listen_sock.Listen(5);
    //初始化线程池
    _pool.InitPool();
    while(1)
    {
      TcpSocket new_sock;
      string ip;
      uint16_t port;
      //4.接收连接
      listen_sock.Accept(&new_sock, &ip,&port); 
      cout <<"client:" << ip.c_str() << " connect" << endl;

      Task t(new_sock, service);
      _pool.AddTask(t);
    }
  }
private:
  TcpSocket listen_sock;
  ThreadPool _pool;
  string _ip;
  uint16_t _port;
};

你可能感兴趣的:(网络编程)