Linux网络编程之TCP

文章目录

Linux网络编程之TCP

1、TCP协议的简单认识

  • 传输层协议

  • 有连接

  • 可靠传输

  • 面向字节流


2、TCP网络编程接口

2.1、socket

cpp 复制代码
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

该接口和UDP一样,首先我们得先创建socket套接字。

用法不一样的是传入参数不同。

UDP传入的type是SOCK_DGRAM,表示无连接不可靠数据报的传输层协议。

那TCP传入的type是SOCK_STREAM,表示有序、可靠、有连接字节流的传输层协议。


2.2、bind

cpp 复制代码
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

和UDP的用法一致。

绑定本地服务器信息。


2.3、listen

cpp 复制代码
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

这个接口是UDP没有的。

listen接口是监听是否有客户端发器连接,如果有,则给accept接口说可以建立连接。


2.4、accept

cpp 复制代码
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

这个接口是UDP没有的。

accept接口是和客户端建立连接,成功建立连接则返回一个新的文件描述符!

这里举一个生活例子来说明listen和accept的关系:

假设有一家饭馆,有一个招揽客人的服务员张三,还有多个带客人进饭店某个桌子的服务员李四王五等。服务员张三只负责招揽客人,如果有客人想进来吃饭,那么张三就会叫李四王五等服务员带客人去指定桌子吃饭,然后张三再继续招揽客人,李四王五等人只管服务他们带进来的指定客人,知道他们吃完。当然客人进饭店了看了环境可能不想吃了,然后又不吃了。那么这里的张三就是listen,李四王五等人就是accept后的新文件描述符newsockfd1,newsockfd2等。


2.5、connect

cpp 复制代码
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

这个接口是UDP没有的。

connect是客户端向服务器发起连接的接口。


3、简单的TCP网络程序

3.1、服务器响应程序

和UDP服务器响应程序一样,客户端发什么就回什么,只不过多了建立连接的步骤。
注意 :在TcpServer.hpp文件中,有处理任务的多个版本。

  • Comm.hpp文件
cpp 复制代码
#pragma once
#include "InetAddr.hpp"

enum errorcode
{
   CREATE_ERROR = 1,
   BIND_ERROR,
   LISTEN_ERROR,
   SEND_ERROR,
   RECV_ERROR,
   CONNECT_ERROR,
   FORK_ERROR,
   USAGE_ERROR
};

#define CONV(ADDR) ((struct sockaddr *)ADDR)

std::string CombineIpAndPort(InetAddr addr)
{
   return "[" + addr.Ip() + ":" + std::to_string(addr.Port()) + "] ";
}
  • InetAddr.hpp文件
cpp 复制代码
#pragma once

#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>

class InetAddr
{
   void GetAddress(std::string *ip, uint16_t *port)
   {
       // char *inet_ntoa(struct in_addr in);
       *ip = inet_ntoa(_addr.sin_addr);
       *port = ntohs(_addr.sin_port);
   }

public:
   InetAddr(const struct sockaddr_in &addr) : _addr(addr)
   {
       GetAddress(&_ip, &_port);
   }

   std::string Ip()
   {
       return _ip;
   }

   uint16_t Port()
   {
       return _port;
   }
   bool operator==(InetAddr &addr)
   {
       return _ip == addr.Ip() && _port == addr.Port();
   }

   const struct sockaddr_in& GetAddr()
   {
       return _addr;
   }

   ~InetAddr() {}

private:
   struct sockaddr_in _addr;
   std::string _ip;
   uint16_t _port;
};
  • LockGuard.hpp文件
cpp 复制代码
# pragma once

#include <pthread.h>


class LockGuard
{
public:
   LockGuard(pthread_mutex_t *mutex) : _mutex(mutex)
   {
       pthread_mutex_lock(_mutex); // 构造加锁
   }
   ~LockGuard()
   {
       pthread_mutex_unlock(_mutex); // 析构解锁
   }

private:
   pthread_mutex_t *_mutex;
};
  • Log.hpp文件
cpp 复制代码
#pragma once

#include <string>
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include "LockGuard.hpp"

using namespace std;

bool isSave = false; // 默认向显示器打印
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
#define FILEPATH "./log.txt"

enum level
{
   DEBUG = 0,
   INFO,
   WARNING,
   ERROR,
   FATAL
};

void SaveToFile(const string &message)
{
   ofstream out(FILEPATH, ios_base::app);
   if (!out.is_open())
       return;
   out << message;
   out.close();
}

std::string LevelToString(int level)
{
   switch (level)
   {
   case DEBUG:
       return "Debug";
   case INFO:
       return "Info";
   case WARNING:
       return "Warning";
   case ERROR:
       return "Error";
   case FATAL:
       return "Fatal";
   default:
       return "Unknow";
   }
}

std::string GetTimeString()
{
   time_t curr_time = time(nullptr);
   struct tm *format_time = localtime(&curr_time);
   if (format_time == nullptr)
       return "None";
   char buff[1024];
   snprintf(buff, sizeof(buff), "%d-%d-%d %d:%d:%d",
            format_time->tm_year + 1900,
            format_time->tm_mon + 1,
            format_time->tm_mday,
            format_time->tm_hour,
            format_time->tm_min,
            format_time->tm_sec);
   return buff;
}

void LogMessage(const std::string filename, int line, bool issave, int level, const char *format, ...)
{
   std::string levelstr = LevelToString(level);
   std::string timestr = GetTimeString();
   pid_t pid = getpid();

   char buff[1024];
   va_list arg;
   // int vsnprintf(char *str, size_t size, const char *format, va_list ap); // 使用可变参数
   va_start(arg, format);
   vsnprintf(buff, sizeof(buff), format, arg);
   va_end(arg);

   LockGuard lock(&mutex);
   std::string message = "[" + timestr + "]" + "[" + levelstr + "]" + "[pid:" + std::to_string(pid) + "]" + "[" + filename + "]" + "[" + std::to_string(line) + "] " + buff + '\n';
   if (issave == false)
       std::cout << message;
   else
       SaveToFile(message);
}

// 固定文件名和行数
#define LOG(level, format, ...)                                               \
   do                                                                        \
   {                                                                         \
       LogMessage(__FILE__, __LINE__, isSave, level, format, ##__VA_ARGS__); \
   } while (0)

#define EnableScreen()  \
   do                  \
   {                   \
       isSave = false; \
   } while (0)

#define EnableFile()   \
   do                 \
   {                  \
       isSave = true; \
   } while (0)

void Test(int num, ...)
{
   va_list arg;
   va_start(arg, num);
   while (num--)
   {
       int data = va_arg(arg, int);
       std::cout << data << " ";
   }
   std::cout << std::endl;
   va_end(arg);
}
  • Main.cc文件
cpp 复制代码
#include <iostream>
#include <memory>
#include "TcpServer.hpp"

void Usage()
{
   // printf("./udp_server serverip serverport\n");
   printf("Usage : ./udp_server serverport\n"); // ip 已经设置为0
}

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

   uint16_t serverport = std::stoi(argv[1]);
   std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(serverport);
   tsvr->InitServer();
   tsvr->Start();

   return 0;
}
  • Makefile文件
makefile 复制代码
.PHONY:all
all:tcp_client tcp_server

tcp_client:TcpClient.cc
	g++ -o $@ $^ -std=c++14 -lpthread
tcp_server:Main.cc
	g++ -o $@ $^ -std=c++14 -lpthread

.PHONY:clean
clean:
	rm -f tcp_server tcp_client
  • TcpClient.cc文件
cpp 复制代码
#include <iostream>

#include <strings.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "Comm.hpp"
#include "Log.hpp"

void Usage()
{
   printf("Usage : ./udp_client serverip serverport\n");
}
int main(int argc, char *argv[])
{
   if (argc != 3)
   {
       Usage();
       exit(USAGE_ERROR);
   }
   std::string serverip = argv[1];
   uint16_t serverport = std::stoi(argv[2]);

   int sockfd = socket(AF_INET, SOCK_STREAM, 0);
   if (sockfd < 0)
   {
       LOG(FATAL, "create sockfd error, error code : %d, error string : %s", errno, strerror(errno));
       exit(CREATE_ERROR);
   }
   LOG(INFO, "create sockfd success");

   struct sockaddr_in local;
   bzero(&local, sizeof(local));
   local.sin_family = AF_INET;
   local.sin_port = htons(serverport);
   local.sin_addr.s_addr = inet_addr(serverip.c_str());

   // 发起连接
   int n = ::connect(sockfd, CONV(&local), sizeof(local));
   if (n < 0)
   {
       LOG(WARNING, "create connect error, error code : %d, error string : %s", errno, strerror(errno));
       exit(CONNECT_ERROR);
   }
   LOG(INFO, "create connect success");

   // 发收数据
   while (true)
   {
       std::cout << "Please Enter# ";
       // 发送数据
       std::string message;
       std::getline(cin, message);

       int ret = ::send(sockfd, message.c_str(), message.size(), 0);
       if (ret < 0)
       {
           LOG(FATAL, "send message error, error code : %d, error string : %s", errno, strerror(errno));
           exit(SEND_ERROR);
       }

       char buff[1024];
       // 接收数据
       int m = ::recv(sockfd, buff, sizeof(buff) - 1, 0);
       if (m > 0)
       {
           buff[m] = 0;
           std::cout << "Server Echo$ " << buff << std::endl;
       }
   }
}
  • TcpServer.hpp文件
cpp 复制代码
#pragma once

#include <sys/types.h> /* See NOTES */
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <error.h>
#include <string.h>
#include <pthread.h>
#include <functional>

#include "Log.hpp"
#include "InetAddr.hpp"
#include "Comm.hpp"
#include "Threadpool.hpp"

const int defaultsockfd = -1;
int gbacklog = 16; // 暂时先用

using task_t = std::function<void()>;

// 声明
class TcpServer;

class ThreadData
{
public:
   ThreadData(int sockfd, InetAddr addr, TcpServer *self)
       : _sockfd(sockfd), _addr(addr), _self(self) {}
   ~ThreadData() = default;

public:
   int _sockfd;
   InetAddr _addr;
   TcpServer *_self;
};

class TcpServer
{
public:
   TcpServer(uint16_t port) : _port(port), _listensock(defaultsockfd), _isrunning(false)
   {
   }

   void InitServer()
   {
       // 创建
       _listensock = socket(AF_INET, SOCK_STREAM, 0); // 这个就是文件描述符
       if (_listensock < 0)
       {
           LOG(FATAL, "create sockfd error, error code : %d, error string : %s", errno, strerror(errno));
           exit(CREATE_ERROR);
       }
       LOG(INFO, "create sockfd success");

       struct sockaddr_in local;
       bzero(&local, sizeof(local));
       local.sin_family = AF_INET;
       local.sin_port = htons(_port);
       local.sin_addr.s_addr = INADDR_ANY;
       // 绑定
       int n = ::bind(_listensock, CONV(&local), sizeof(local));
       if (n < 0)
       {
           LOG(FATAL, "bind sockfd error, error code : %d, error string : %s", errno, strerror(errno));
           exit(BIND_ERROR);
       }
       LOG(INFO, "bind sockfd success");
   }

   void Service(int sockfd, InetAddr client)
   {
       while (true)
       {
           // TCP是字节流(可以使用write和read接口),UDP是数据报
           char buff[1024];
           // 接收消息
           int n = ::read(sockfd, buff, sizeof(buff)); // bug,接收数据可能收到的不完整,比如1+100,可能先收到1+1,再收到00 -- 按序到达
           std::string clientAddr = CombineIpAndPort(client);

           if (n > 0)
           {
               buff[n] = 0;
               std::string message = clientAddr + buff;
               LOG(INFO, "get message : \n %s", message.c_str());

               // 发送消息
               int m = ::write(sockfd, buff, strlen(buff)); 
               if (m < 0)
               {
                   LOG(FATAL, "send message error ,error code : %d , error string : %s", errno, strerror(errno));
                   exit(SEND_ERROR);
               }
           }
           else if (n == 0)
           {
               // 发送端不发送数据了
               LOG(INFO, "%s quit", clientAddr.c_str());
               break;
           }
           else
           {
               LOG(FATAL, "recv message error ,error code : %d , error string : %s", errno, strerror(errno));
               exit(RECV_ERROR);
           }
       }
       ::close(sockfd); // 服务结束,关闭文件描述符,避免文件描述符泄漏
   }

   static void *HandlerService(void *args)
   {
       pthread_detach(pthread_self()); // 分离线程
       ThreadData *td = static_cast<ThreadData *>(args);
       td->_self->Service(td->_sockfd, td->_addr);
       delete td;
       return nullptr;
   }

   void Start()
   {
       _isrunning = true;
       while (_isrunning)
       {
           // 监听
           int ret = ::listen(_listensock, gbacklog);
           if (ret < 0)
           {
               LOG(FATAL, "listen error, error code : %d , error string : %s", errno, strerror(errno));
               exit(LISTEN_ERROR);
           }
           LOG(INFO, "listen success!");

           struct sockaddr_in peer;
           socklen_t len = sizeof(peer);
           // 获取新连接
           int sockfd = accept(_listensock, CONV(&peer), &len); // 建立连接成功,创建新文件描述符进行通信
           if (sockfd < 0)
           {
               LOG(WARNING, "accept error, error code : %d , error string : %s", errno, strerror(errno));
               continue;
           }
           LOG(INFO, "accept success! new sockfd : %d", sockfd);

           InetAddr addr(peer); // 给后面提供传入的ip、port
           // 服务 -- 发送和接收数据
           // V0 -- 单进程
           // Service(sockfd, addr); // 这里是while死循环,没有运行完就一直运行,下一个请求来的时候得这个while退出才能执行

           // v1 -- 多进程
           // int id = fork();
           // if (id == 0)
           // {
           //     // 子进程
           //     ::close(_listensock); // 子进程对监听文件描述符不关心
           //     if (fork() > 0)
           //         exit(0);           // 子进程创建进程后退出,孙子进程被系统领养,不用等待
           //     Service(sockfd, addr); // 孙子进程执行任务
           //     exit(0);
           // }
           // else if (id > 0)
           // {
           //     ::close(sockfd); // 这里每次关闭的文件描述符都是4,使得每次accept创建的文件描述符都是4,这个4是留个各个子进程(子进程再给孙子进程)的(互不影响)
           //     // 父进程
           //     pid_t rid = waitpid(id, nullptr, 0); // 虽然是阻塞等待,但是子进程是刚创建就退出来了,让孙子进程(孤儿进程)执行任务,接下来继续监听和建立连接
           //     if (rid == id)
           //     {
           //         LOG(INFO, "wait child process success");
           //     }
           // }
           // else
           // {
           //     // 异常
           //     LOG(FATAL, "fork error ,error code : %d , error string : %s", errno, strerror(errno));
           //     exit(FORK_ERROR);
           // }

           // v2 -- 多线程
           // pthread_t tid;
           // ThreadData *td = new ThreadData(sockfd, addr, this); // 传指针
           // pthread_create(&tid, nullptr, HandlerService, td); // 这里创建线程后,线程去做执行任务,主线程继续向下执行 , 并且线程不能关闭sockf,线程和进程共享文件描述符表

           // v3 -- 线程池
           task_t t = std::bind(&TcpServer::Service, this, sockfd, addr);
           Threadpool<task_t>::GetInstance()->Enqueue(t);

           // v4 -- 进程池 -- 不推荐,需要传递文件描述符!
       }
       _isrunning = false;
   }

   ~TcpServer()
   {
       if (_listensock > defaultsockfd)
           ::close(_listensock); // 不用了关闭监听
   }

private:
   uint16_t _port;
   int _listensock;
   bool _isrunning;
};
  • Thread.hpp文件
cpp 复制代码
#ifndef __THREAD_HPP__
#define __THREAD_HPP__

#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>

using namespace std;

// 封装Linux线程
namespace ThreadModule
{
   using func_t = function<void(string &)>;
   class Thread
   {
   public:
       // /* ThreadData* */Thread(func_t<T> func, T data, const string& name = "default name") : _func(func), _data(data), _threadname(name), _stop(true) {}
       Thread(func_t func, const string &name = "default name") : _func(func), _threadname(name), _stop(true) {}

       void Execute()
       {
           _func(_threadname);

           // _func(_data);
       }

       //  隐含this
       static void *threadroutine(void *arg)
       {
           Thread *self = static_cast<Thread *>(arg);
           self->Execute(); // static 访问不了成员变量
           return nullptr;
       }
       bool Start()
       {
           int n = pthread_create(&_tid, nullptr, threadroutine, this);
           if (!n)
           {
               _stop = false;
               return true;
           }
           else
           {
               return false;
           }
       }

       void Detach()
       {
           if (!_stop)
           {
               pthread_detach(_tid);
           }
       }

       void Join()
       {
           if (!_stop)
           {
               pthread_join(_tid, nullptr);
           }
       }

       string name()
       {
           return _threadname;
       }

       void Stop()
       {
           _stop = true;
       }
       // ~Thread() {}

   private:
       pthread_t _tid;
       string _threadname;
       func_t _func;
       bool _stop;
   };

} // namespace ThreadModule

#endif
  • Threadpool.hpp文件
cpp 复制代码
#pragma once

#include <vector>
#include <queue>
#include <queue>
#include "Thread.hpp"
#include <pthread.h>
#include "LockGuard.hpp"

using namespace ThreadModule;

const int NUM = 3;

template <typename T>
class Threadpool
{
   void LockQueue(pthread_mutex_t &mutex)
   {
       pthread_mutex_lock(&mutex);
   }

   void UnLockQueue(pthread_mutex_t &mutex)
   {
       pthread_mutex_unlock(&mutex);
   }

   void SleepThread(pthread_cond_t &cond, pthread_mutex_t &mutex)
   {
       pthread_cond_wait(&cond, &mutex);
   }
   void WakeUpThread(pthread_cond_t &cond)
   {
       pthread_cond_signal(&cond);
   }
   void WakeUpAll(pthread_cond_t &cond)
   {
       pthread_cond_broadcast(&_cond);
   }
   Threadpool(const int threadnum = NUM) : _threadnum(threadnum), _waitnum(0), _isrunning(false)
   {
       pthread_mutex_init(&_mutex, nullptr);
       pthread_cond_init(&_cond, nullptr);
       LOG(INFO, "Threadpool Constructor successful ! ");
   }

   void TaskHandler(string &name)
   {
       // sleep(1);
       // cout  << name << " : hh " << endl;
       // sleep(1);
       LOG(DEBUG, "%s is running", name.c_str());
       while (true)
       {
           LockQueue(_mutex);
           while (_task_queue.empty() && _isrunning)
           {
               // 等待
               ++_waitnum;
               SleepThread(_cond, _mutex);
               --_waitnum;
           }
           // 此时一定大于一个线程没有休眠
           if (_task_queue.empty() && !_isrunning)
           {
               // 此时任务队列已经没有内容,且此时线程池已经停止
               UnLockQueue(_mutex);
               cout << name << " quit ... " << endl;
               break;
           }
           LOG(DEBUG, "%s get task sucessful !", name.c_str());
           //  其他情况就得处理任务
           T t = _task_queue.front();
           _task_queue.pop();
           UnLockQueue(_mutex);

           // 处理任务
           t();
           // cout << name << " : " << t.stringResult() << endl;
           // LOG(DEBUG, "%s handler task sucessful ! Result is %s", name.c_str(), t.stringResult().c_str());
           sleep(1);
       }
   }
   void InitThreadPool()
   {
       for (int i = 0; i < _threadnum; ++i)
       {
           string name = "Thread - " + to_string(i + 1);
           _threads.emplace_back(bind(&Threadpool::TaskHandler, this, placeholders::_1), name);
       }
       _isrunning = true;
       LOG(INFO, "Init Threadpool successful !");
   }

public:
   static Threadpool<T> *GetInstance(int threadnum = NUM)
   {

       if (_instance == nullptr)
       {
           LockGuard lockguard(&_lock);
           if (_instance == nullptr)
           {
               // pthread_mutex_lock(&_lock);
               // 第一次创建线程池
               _instance = new Threadpool<T>(threadnum);
               _instance->InitThreadPool();
               _instance->Start();
               LOG(DEBUG, "第一次创建线程池");
               // pthread_mutex_unlock(&_lock);
               return _instance;
           }
       }

       LOG(DEBUG, "获取线程池");
       return _instance;
   }

   bool Enqueue(const T &in)
   {
       bool ret = false;
       LockQueue(_mutex);
       if (_isrunning)
       {
           _task_queue.push(in);
           if (_waitnum > 0)
               WakeUpThread(_cond);
           LOG(DEBUG, "enqueue sucessful...");
           ret = true;
       }

       UnLockQueue(_mutex);
       return ret;
   }

   void Stop()
   {
       LockQueue(_mutex);
       _isrunning = false;
       if (_waitnum > 0)
           WakeUpAll(_cond);
       UnLockQueue(_mutex);
   }

   void Start()
   {
       for (auto &thread : _threads)
       {
           thread.Start();
           LOG(INFO, "%s is start sucessful...", thread.name().c_str());
       }
   }

   void Wait()
   {
       for (auto &thread : _threads)
       {
           thread.Join();
           LOG(INFO, "%s is quit...", thread.name().c_str());
       }
   }
   ~Threadpool()
   {
       pthread_mutex_destroy(&_mutex);
       pthread_cond_destroy(&_cond);
       LOG(INFO, "delete mutex sucessful !");
   }

private:
   vector<Thread> _threads;
   queue<T> _task_queue;
   int _threadnum;
   int _waitnum;

   pthread_mutex_t _mutex; // 互斥访问任务队列
   pthread_cond_t _cond;

   bool _isrunning;

   // 懒汉模式
   static Threadpool<T> *_instance;
   static pthread_mutex_t _lock;
};

template <typename T>
Threadpool<T> *Threadpool<T>::_instance = nullptr;
template <typename T>
pthread_mutex_t Threadpool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;
  • 运行结果:

3.2、服务器执行命令行

  • Comm.hpp文件
cpp 复制代码
#pragma once
#include "InetAddr.hpp"

enum errorcode
{
   CREATE_ERROR = 1,
   BIND_ERROR,
   LISTEN_ERROR,
   SEND_ERROR,
   RECV_ERROR,
   CONNECT_ERROR,
   FORK_ERROR,
   USAGE_ERROR
};

#define CONV(ADDR) ((struct sockaddr *)ADDR)

std::string CombineIpAndPort(InetAddr addr)
{
   return "[" + addr.Ip() + ":" + std::to_string(addr.Port()) + "] ";
}
  • CommandExecute.hpp文件
cpp 复制代码
#pragma once

#include <stdio.h>
#include <string>
#include <vector>

bool CheckCommand(std::string command)
{
   std::vector<std::string> cmd = {
       "kill",
       "rm",
       "dd",
       "top",
       "reboot",
       "shutdown",
       "mv",
       "cp",
       "halt",
       "unlink",
       "exit",
       "chmod"};
   for (auto &e : cmd)
   {
       if (command.find(e) != std::string::npos)
           return false;
   }

   return true;
}

std::string OnCommand(std::string command)
{
   if (!CheckCommand(command))
   {
       return "bad man!";
   }
   // FILE *popen(const char *command, const char *type);
   FILE *pp = popen(command.c_str(), "r");
   if (!pp)
   {
       return "popen error!";
   }

   std::string response;
   char buff[1024];
   while (true)
   {
       // char *fgets(char *s, int size, FILE *stream);
       char *s = fgets(buff, sizeof(buff), pp);
       if (!s)
           break;
       else
           response += buff;
   }
   pclose(pp);
   return response.empty() ? "not command" : response;
}
  • InetAddr.hpp文件
cpp 复制代码
#pragma once

#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>

class InetAddr
{
   void GetAddress(std::string *ip, uint16_t *port)
   {
       // char *inet_ntoa(struct in_addr in);
       *ip = inet_ntoa(_addr.sin_addr);
       *port = ntohs(_addr.sin_port);
   }

public:
   InetAddr(const struct sockaddr_in &addr) : _addr(addr)
   {
       GetAddress(&_ip, &_port);
   }

   std::string Ip()
   {
       return _ip;
   }

   uint16_t Port()
   {
       return _port;
   }
   bool operator==(InetAddr &addr)
   {
       return _ip == addr.Ip() && _port == addr.Port();
   }

   const struct sockaddr_in& GetAddr()
   {
       return _addr;
   }

   ~InetAddr() {}

private:
   struct sockaddr_in _addr;
   std::string _ip;
   uint16_t _port;
};
  • LockGuard.hpp文件
cpp 复制代码
# pragma once

#include <pthread.h>


class LockGuard
{
public:
   LockGuard(pthread_mutex_t *mutex) : _mutex(mutex)
   {
       pthread_mutex_lock(_mutex); // 构造加锁
   }
   ~LockGuard()
   {
       pthread_mutex_unlock(_mutex); // 析构解锁
   }

private:
   pthread_mutex_t *_mutex;
};
  • Log.hpp文件
cpp 复制代码
#pragma once

#include <string>
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include "LockGuard.hpp"

using namespace std;

bool isSave = false; // 默认向显示器打印
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
#define FILEPATH "./log.txt"

enum level
{
   DEBUG = 0,
   INFO,
   WARNING,
   ERROR,
   FATAL
};

void SaveToFile(const string &message)
{
   ofstream out(FILEPATH, ios_base::app);
   if (!out.is_open())
       return;
   out << message;
   out.close();
}

std::string LevelToString(int level)
{
   switch (level)
   {
   case DEBUG:
       return "Debug";
   case INFO:
       return "Info";
   case WARNING:
       return "Warning";
   case ERROR:
       return "Error";
   case FATAL:
       return "Fatal";
   default:
       return "Unknow";
   }
}

std::string GetTimeString()
{
   time_t curr_time = time(nullptr);
   struct tm *format_time = localtime(&curr_time);
   if (format_time == nullptr)
       return "None";
   char buff[1024];
   snprintf(buff, sizeof(buff), "%d-%d-%d %d:%d:%d",
            format_time->tm_year + 1900,
            format_time->tm_mon + 1,
            format_time->tm_mday,
            format_time->tm_hour,
            format_time->tm_min,
            format_time->tm_sec);
   return buff;
}

void LogMessage(const std::string filename, int line, bool issave, int level, const char *format, ...)
{
   std::string levelstr = LevelToString(level);
   std::string timestr = GetTimeString();
   pid_t pid = getpid();

   char buff[1024];
   va_list arg;
   // int vsnprintf(char *str, size_t size, const char *format, va_list ap); // 使用可变参数
   va_start(arg, format);
   vsnprintf(buff, sizeof(buff), format, arg);
   va_end(arg);

   LockGuard lock(&mutex);
   std::string message = "[" + timestr + "]" + "[" + levelstr + "]" + "[pid:" + std::to_string(pid) + "]" + "[" + filename + "]" + "[" + std::to_string(line) + "] " + buff + '\n';
   if (issave == false)
       std::cout << message;
   else
       SaveToFile(message);
}

// 固定文件名和行数
#define LOG(level, format, ...)                                               \
   do                                                                        \
   {                                                                         \
       LogMessage(__FILE__, __LINE__, isSave, level, format, ##__VA_ARGS__); \
   } while (0)

#define EnableScreen()  \
   do                  \
   {                   \
       isSave = false; \
   } while (0)

#define EnableFile()   \
   do                 \
   {                  \
       isSave = true; \
   } while (0)

void Test(int num, ...)
{
   va_list arg;
   va_start(arg, num);
   while (num--)
   {
       int data = va_arg(arg, int);
       std::cout << data << " ";
   }
   std::cout << std::endl;
   va_end(arg);
}
  • Main.cc文件
cpp 复制代码
#include <iostream>
#include <memory>

#include "TcpServer.hpp"
#include "CommandExecute.hpp"

void Usage()
{
   // printf("./udp_server serverip serverport\n");
   printf("Usage : ./udp_server serverport\n"); // ip 已经设置为0
}

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

   uint16_t serverport = std::stoi(argv[1]);
   std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(serverport, OnCommand);
   tsvr->InitServer();
   tsvr->Start();

   return 0;
}
  • Makefile文件
makefile 复制代码
.PHONY:all
all:tcp_client tcp_server

tcp_client:TcpClient.cc
	g++ -o $@ $^ -std=c++14 -lpthread
tcp_server:Main.cc
	g++ -o $@ $^ -std=c++14 -lpthread

.PHONY:clean
clean:
	rm -f tcp_server tcp_client
  • TcpClient.cc文件
cpp 复制代码
#include <iostream>

#include <strings.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "Comm.hpp"
#include "Log.hpp"

void Usage()
{
   printf("Usage : ./udp_client serverip serverport\n");
}
int main(int argc, char *argv[])
{
   if (argc != 3)
   {
       Usage();
       exit(USAGE_ERROR);
   }
   std::string serverip = argv[1];
   uint16_t serverport = std::stoi(argv[2]);

   int sockfd = socket(AF_INET, SOCK_STREAM, 0);
   if (sockfd < 0)
   {
       LOG(FATAL, "create sockfd error, error code : %d, error string : %s", errno, strerror(errno));
       exit(CREATE_ERROR);
   }
   LOG(INFO, "create sockfd success");

   struct sockaddr_in local;
   bzero(&local, sizeof(local));
   local.sin_family = AF_INET;
   local.sin_port = htons(serverport);
   local.sin_addr.s_addr = inet_addr(serverip.c_str());

   // 发起连接
   int n = ::connect(sockfd, CONV(&local), sizeof(local));
   if (n < 0)
   {
       LOG(WARNING, "create connect error, error code : %d, error string : %s", errno, strerror(errno));
       exit(CONNECT_ERROR);
   }
   LOG(INFO, "create connect success");

   // 发收数据
   while (true)
   {
       std::cout << "Please Enter# ";
       // 发送数据
       std::string message;
       std::getline(cin, message);

       int ret = ::send(sockfd, message.c_str(), message.size(), 0);
       if (ret < 0)
       {
           LOG(FATAL, "send message error, error code : %d, error string : %s", errno, strerror(errno));
           exit(SEND_ERROR);
       }

       char buff[1024];
       // 接收数据
       int m = ::recv(sockfd, buff, sizeof(buff) - 1, 0);
       if (m > 0)
       {
           buff[m] = 0;
           std::cout << "Server Echo$ " << buff << std::endl;
       }
   }
}
  • TcpServer.hpp文件
cpp 复制代码
#pragma once

#include <sys/types.h> /* See NOTES */
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <error.h>
#include <string.h>
#include <pthread.h>
#include <functional>

#include "Log.hpp"
#include "InetAddr.hpp"
#include "Comm.hpp"


const int defaultsockfd = -1;
int gbacklog = 16; // 暂时先用

using callback_t = std::function<std::string(std::string)>;

// 声明
class TcpServer;

class ThreadData
{
public:
   ThreadData(int sockfd, InetAddr addr, TcpServer *self)
       : _sockfd(sockfd), _addr(addr), _self(self) {}
   ~ThreadData() = default;

public:
   int _sockfd;
   InetAddr _addr;
   TcpServer *_self;
};

class TcpServer
{
public:
   TcpServer(uint16_t port, callback_t cb) : _port(port), _listensock(defaultsockfd), _isrunning(false), _cb(cb)
   {
   }

   void InitServer()
   {
       // 创建
       _listensock = socket(AF_INET, SOCK_STREAM, 0); // 这个就是文件描述符
       if (_listensock < 0)
       {
           LOG(FATAL, "create sockfd error, error code : %d, error string : %s", errno, strerror(errno));
           exit(CREATE_ERROR);
       }
       LOG(INFO, "create sockfd success");

       struct sockaddr_in local;
       bzero(&local, sizeof(local));
       local.sin_family = AF_INET;
       local.sin_port = htons(_port);
       local.sin_addr.s_addr = INADDR_ANY;
       // 绑定
       int n = ::bind(_listensock, CONV(&local), sizeof(local));
       if (n < 0)
       {
           LOG(FATAL, "bind sockfd error, error code : %d, error string : %s", errno, strerror(errno));
           exit(BIND_ERROR);
       }
       LOG(INFO, "bind sockfd success");
   }

   void Service(int sockfd, InetAddr client)
   {
       while (true)
       {
           // TCP是字节流(可以使用write和read接口),UDP是数据报
           char buff[1024];
           // 接收消息
           int n = ::read(sockfd, buff, sizeof(buff)); // bug,接收数据可能收到的不完整,比如1+100,可能先收到1+1,再收到00 -- 按序到达
           std::string clientAddr = CombineIpAndPort(client);

           if (n > 0)
           {
               buff[n] = 0;
               std::string message = clientAddr + buff;
               LOG(INFO, "get message : \n %s", message.c_str());

               // 发送消息
               std::string response = _cb(buff); // 回调
               int m = ::write(sockfd, response.c_str(), response.size());
               if (m < 0)
               {
                   LOG(FATAL, "send message error ,error code : %d , error string : %s", errno, strerror(errno));
                   exit(SEND_ERROR);
               }
           }
           else if (n == 0)
           {
               // 发送端不发送数据了
               LOG(INFO, "%s quit", clientAddr.c_str());
               break;
           }
           else
           {
               LOG(FATAL, "recv message error ,error code : %d , error string : %s", errno, strerror(errno));
               exit(RECV_ERROR);
           }
       }
       ::close(sockfd); // 服务结束,关闭文件描述符,避免文件描述符泄漏
   }

   static void *HandlerService(void *args)
   {
       pthread_detach(pthread_self()); // 分离线程
       ThreadData *td = static_cast<ThreadData *>(args);
       td->_self->Service(td->_sockfd, td->_addr);
       delete td;
       return nullptr;
   }

   void Start()
   {
       _isrunning = true;
       while (_isrunning)
       {
           // 监听
           int ret = ::listen(_listensock, gbacklog);
           if (ret < 0)
           {
               LOG(FATAL, "listen error, error code : %d , error string : %s", errno, strerror(errno));
               exit(LISTEN_ERROR);
           }
           LOG(INFO, "listen success!");

           struct sockaddr_in peer;
           socklen_t len = sizeof(peer);
           // 获取新连接
           int sockfd = accept(_listensock, CONV(&peer), &len); // 建立连接成功,创建新文件描述符进行通信
           if (sockfd < 0)
           {
               LOG(WARNING, "accept error, error code : %d , error string : %s", errno, strerror(errno));
               continue;
           }
           LOG(INFO, "accept success! new sockfd : %d", sockfd);

           InetAddr addr(peer); // 给后面提供传入的ip、port
           // 服务 -- 发送和接收数据

           // v2 -- 多线程
           pthread_t tid;
           ThreadData *td = new ThreadData(sockfd, addr, this); // 传指针
           pthread_create(&tid, nullptr, HandlerService, td);   // 这里创建线程后,线程去做执行任务,主线程继续向下执行 , 并且线程不能关闭sockf,线程和进程共享文件描述符表
       }
       _isrunning = false;
   }

   ~TcpServer()
   {
       if (_listensock > defaultsockfd)
           ::close(_listensock); // 不用了关闭监听
   }

private:
   uint16_t _port;
   int _listensock;
   bool _isrunning;

   callback_t _cb;
};
  • 运行结果:

4、TCP客户端connect断线重连

这里是服务器响应程序的断线重连的实现细节:TCP客户端connect断线重连


5、应用层自定义协议以及序列化和反序列化

这里是应用层自定义协议以及序列化和反序列化的实现细节:应用层自定义协议以及序列化和反序列化

里面还包含复杂的TCP网络程序:服务器网络计算器


OKOK,Linux网络编程之TCP就到这里,如果你对Linux和C++也感兴趣的话,可以看看我的主页哦。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。

Xpccccc的github主页

相关推荐
爱和冰阔落1 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨1 小时前
WSL2的安装与使用
linux·wsl
微小的鱼2 小时前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng2 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器
严肃小白兔2 小时前
Cloudflare 更新 API Token 管理:从最小权限到自动化凭据安全
网络·安全·自动化
Android系统攻城狮3 小时前
Linux FFmpeg深度解析之av_write_frame调用流程与实战(三十三)
linux·运维·服务器·ffmpeg·音视频进阶·ffmpeg音视频
Linux-lucky14 小时前
45-Linux学习之旅之防火墙与WEB加固
linux·前端·网络·学习·ubuntu
禁默15 小时前
启元 Q1 二次开发实践:Ubuntu 部署与仿真验证,从 PrimeConsole 到 ROS 2 Humble + RViz
linux·运维·ubuntu
skywalk816315 小时前
把 去也网站 从 Ubuntu 搬到 FreeBSD:一次 Service Jail + nginx 的部署实录
linux·运维·nginx·ubuntu·freebsd
爱莉希雅&&&17 小时前
GitLab 完整学习笔记
linux·运维·服务器