文章目录
Linux网络编程之TCP
1、TCP协议的简单认识
传输层协议
有连接
可靠传输
面向字节流
2、TCP网络编程接口
2.1、socket
#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
#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
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog);
这个接口是UDP没有的。
listen接口是监听是否有客户端发器连接,如果有,则给accept接口说可以建立连接。
2.4、accept
#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
#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
文件#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
文件#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
文件# 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
文件#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
文件#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
文件.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
文件#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
文件#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
文件#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
文件#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
文件#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
文件#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
文件#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
文件# 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
文件#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
文件#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
文件.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
文件#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
文件#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的一些题的题解,有兴趣的可以看看。