当前位置: 首页 > news >正文

二、Socket 编程 TCP

Socket 编程 TCP

一、TCP 编程整体认识

TCP 是面向连接的可靠传输协议。和 UDP 不同,UDP 可以直接 sendto/recvfrom 收发数据,而 TCP 通信之前必须先建立连接。

TCP 服务端基本流程:

socket() -> bind() -> listen() -> accept() -> read/write -> close()

TCP 客户端基本流程:

socket() -> connect() -> write/read -> close()

服务端中有两个非常重要的 socket:

_listenSock:监听 socket,只负责获取新连接; sockfd:通信 socket,由 accept 返回,负责和某个客户端通信。

accept() 是 TCP 服务端非常关键的接口。监听 socket 并不直接和客户端通信,它只是负责等待连接。真正与客户端通信的是 accept() 返回的新 socket。

客户端一般不需要显式 bind(),因为客户端不需要固定端口。当客户端调用 connect() 时,操作系统会自动为客户端分配本地 IP 和临时端口。


二、TCP 常用接口说明

1. socket

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

TCP 使用:

socket(AF_INET, SOCK_STREAM, 0);

含义:

AF_INET:IPv4; SOCK_STREAM:面向字节流,也就是 TCP; 0:使用默认协议。

2. bind

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

服务器需要绑定固定 IP 和端口。否则客户端不知道连接哪里。

常见写法:

local.sin_family = AF_INET; local.sin_port = htons(port); local.sin_addr.s_addr = htonl(INADDR_ANY);

INADDR_ANY 表示绑定本机任意 IP。

3. listen

int listen(int sockfd, int backlog);

listen() 把 socket 设置为监听状态。只有调用 listen() 后,服务器才可以接收客户端连接。

4. accept

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

accept() 从监听队列中获取一个已经建立好的连接,并返回新的通信 socket。

5. connect

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

客户端通过 connect() 连接服务器。connect() 填的是服务器地址,不是自己的地址。

6. read/write

TCP 连接建立后,可以像读写文件一样读写 socket:

read(sockfd, buffer, sizeof(buffer)); write(sockfd, data.c_str(), data.size());

但是 TCP 是面向字节流的,不保证一次 write() 对应一次完整 read()。这就是后面要解决的粘包问题。


三、服务器版本演进

V1:单进程版本

主进程 accept() 一个连接后,直接调用 Service() 处理客户端。

优点是简单,适合理解 TCP 基本流程。

缺点是一次只能服务一个客户端。如果当前客户端不退出,服务器就无法继续处理其他客户端。

V2:多进程版本

父进程只负责 accept(),每来一个客户端,就创建子进程处理。

优点是多个客户端可以并发处理。

缺点是进程创建成本较高,并且需要处理子进程回收问题,否则会产生僵尸进程。

V3:多线程版本

主线程只负责 accept(),每来一个客户端,就创建一个线程处理。

优点是比多进程更轻量。

缺点是客户端太多时,线程数量会快速增加,服务器压力变大。

V4:线程池版本

提前创建固定数量的工作线程,主线程负责接收连接,然后把任务投递到线程池。

优点是避免频繁创建和销毁线程,也能控制并发数量。

缺点是如果每个连接都是长连接,一个连接会长期占用一个线程。更高并发场景还需要继续学习 select/poll/epoll。


公共代码

下面这些文件四个版本都可以共用。

Comm.hpp

#pragma once #include <sys/types.h> #include <sys/socket.h> enum { Usage_Err = 1, Socket_Err, Bind_Err, Listen_Err, Connect_Err }; // 把 sockaddr_in* 转成 sockaddr* #define CONV(addr_ptr) ((struct sockaddr *)(addr_ptr))

nocopy.hpp

#pragma once class nocopy { public: nocopy() = default; ~nocopy() = default; nocopy(const nocopy &) = delete; const nocopy &operator=(const nocopy &) = delete; };

Log.hpp

#pragma once #include <cstdio> #include <ctime> #include <cstdarg> enum LogLevel { Debug = 0, Info, Warning, Error, Fatal }; class Log { public: void LogMessage(LogLevel level, const char *format, ...) { const char *levelString[] = { "Debug", "Info", "Warning", "Error", "Fatal"}; char timeBuffer[64]; time_t curr = time(nullptr); struct tm *tm = localtime(&curr); snprintf(timeBuffer, sizeof(timeBuffer), "%04d-%02d-%02d %02d:%02d:%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); printf("[%s][%s] ", timeBuffer, levelString[level]); va_list args; va_start(args, format); vprintf(format, args); va_end(args); } }; static Log lg;

InetAddr.hpp

#pragma once #include <string> #include <cstdint> #include <netinet/in.h> class InetAddr { public: InetAddr(); explicit InetAddr(const struct sockaddr_in &addr); std::string Ip() const; uint16_t Port() const; std::string ToString() const; const struct sockaddr_in &Addr() const; private: std::string _ip; uint16_t _port; struct sockaddr_in _addr; };

InetAddr.cc

#include "InetAddr.hpp" #include <cstring> #include <arpa/inet.h> InetAddr::InetAddr() : _ip("0.0.0.0"), _port(0) { memset(&_addr, 0, sizeof(_addr)); } InetAddr::InetAddr(const struct sockaddr_in &addr) : _addr(addr) { char ipBuffer[64]; // inet_ntop 是线程安全版本,比 inet_ntoa 更推荐 inet_ntop(AF_INET, &_addr.sin_addr, ipBuffer, sizeof(ipBuffer)); _ip = ipBuffer; _port = ntohs(_addr.sin_port); } std::string InetAddr::Ip() const { return _ip; } uint16_t InetAddr::Port() const { return _port; } std::string InetAddr::ToString() const { return _ip + ":" + std::to_string(_port); } const struct sockaddr_in &InetAddr::Addr() const { return _addr; }

V1 单进程 Echo Server

TcpServer.hpp

#pragma once #include <cstdint> #include "nocopy.hpp" #include "InetAddr.hpp" const static int defaultBacklog = 6; class TcpServer : public nocopy { public: explicit TcpServer(uint16_t port); ~TcpServer(); void Init(); void Start(); private: void Service(int sockfd, InetAddr peer); private: uint16_t _port; int _listenSock; bool _isRunning; };

TcpServer.cc

#include "TcpServer.hpp" #include <iostream> #include <cstring> #include <cerrno> #include <cstdlib> #include <unistd.h> #include <signal.h> #include <sys/socket.h> #include <netinet/in.h> #include "Comm.hpp" #include "Log.hpp" TcpServer::TcpServer(uint16_t port) : _port(port), _listenSock(-1), _isRunning(false) { } void TcpServer::Init() { // 防止向已经关闭的连接写入时,进程被 SIGPIPE 杀掉 signal(SIGPIPE, SIG_IGN); // 1. 创建监听 socket _listenSock = socket(AF_INET, SOCK_STREAM, 0); if (_listenSock < 0) { lg.LogMessage(Fatal, "socket error, errno: %d, %s\n", errno, strerror(errno)); exit(Socket_Err); } // 2. 设置端口复用,方便服务器重启 int opt = 1; setsockopt(_listenSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 3. 填写服务器本地地址 struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(_port); local.sin_addr.s_addr = htonl(INADDR_ANY); // 4. 绑定 IP 和端口 if (bind(_listenSock, CONV(&local), sizeof(local)) < 0) { lg.LogMessage(Fatal, "bind error, errno: %d, %s\n", errno, strerror(errno)); exit(Bind_Err); } // 5. 设置监听状态 if (listen(_listenSock, defaultBacklog) < 0) { lg.LogMessage(Fatal, "listen error, errno: %d, %s\n", errno, strerror(errno)); exit(Listen_Err); } lg.LogMessage(Info, "server init success, port: %d\n", _port); } void TcpServer::Start() { _isRunning = true; while (_isRunning) { struct sockaddr_in peer; socklen_t len = sizeof(peer); // accept 返回的是通信 socket int sockfd = accept(_listenSock, CONV(&peer), &len); if (sockfd < 0) { lg.LogMessage(Warning, "accept error, errno: %d, %s\n", errno, strerror(errno)); continue; } InetAddr addr(peer); lg.LogMessage(Info, "new connection: %s, sockfd: %d\n", addr.ToString().c_str(), sockfd); // V1:直接在主进程中提供服务 Service(sockfd, addr); close(sockfd); } } void TcpServer::Service(int sockfd, InetAddr peer) { char buffer[1024]; while (true) { ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1); if (n > 0) { buffer[n] = '\0'; std::cout << "client[" << peer.ToString() << "] say# " << buffer << std::endl; std::string echo = "server echo# "; echo += buffer; write(sockfd, echo.c_str(), echo.size()); } else if (n == 0) { lg.LogMessage(Info, "client quit: %s\n", peer.ToString().c_str()); break; } else { lg.LogMessage(Error, "read error, errno: %d, %s\n", errno, strerror(errno)); break; } } } TcpServer::~TcpServer() { if (_listenSock >= 0) { close(_listenSock); } }

V2 多进程 Echo Server

V2 的核心变化:accept() 新连接后,创建子进程处理客户端。

TcpServer.hpp

#pragma once #include <cstdint> #include "nocopy.hpp" #include "InetAddr.hpp" const static int defaultBacklog = 6; class TcpServer : public nocopy { public: explicit TcpServer(uint16_t port); ~TcpServer(); void Init(); void Start(); private: void Service(int sockfd, InetAddr peer); void ProcessConnection(int sockfd, const struct sockaddr_in &peer); private: uint16_t _port; int _listenSock; bool _isRunning; };

TcpServer.cc

#include "TcpServer.hpp" #include <iostream> #include <cstring> #include <cerrno> #include <cstdlib> #include <unistd.h> #include <signal.h> #include <sys/socket.h> #include <sys/wait.h> #include <netinet/in.h> #include "Comm.hpp" #include "Log.hpp" TcpServer::TcpServer(uint16_t port) : _port(port), _listenSock(-1), _isRunning(false) { } void TcpServer::Init() { signal(SIGPIPE, SIG_IGN); _listenSock = socket(AF_INET, SOCK_STREAM, 0); if (_listenSock < 0) { lg.LogMessage(Fatal, "socket error, errno: %d, %s\n", errno, strerror(errno)); exit(Socket_Err); } int opt = 1; setsockopt(_listenSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(_port); local.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(_listenSock, CONV(&local), sizeof(local)) < 0) { lg.LogMessage(Fatal, "bind error, errno: %d, %s\n", errno, strerror(errno)); exit(Bind_Err); } if (listen(_listenSock, defaultBacklog) < 0) { lg.LogMessage(Fatal, "listen error, errno: %d, %s\n", errno, strerror(errno)); exit(Listen_Err); } lg.LogMessage(Info, "server init success, port: %d\n", _port); } void TcpServer::Start() { _isRunning = true; while (_isRunning) { struct sockaddr_in peer; socklen_t len = sizeof(peer); int sockfd = accept(_listenSock, CONV(&peer), &len); if (sockfd < 0) { lg.LogMessage(Warning, "accept error, errno: %d, %s\n", errno, strerror(errno)); continue; } ProcessConnection(sockfd, peer); } } void TcpServer::ProcessConnection(int sockfd, const struct sockaddr_in &peer) { InetAddr addr(peer); pid_t id = fork(); if (id < 0) { lg.LogMessage(Error, "fork error, errno: %d, %s\n", errno, strerror(errno)); close(sockfd); return; } else if (id == 0) { // 子进程不需要监听 socket close(_listenSock); Service(sockfd, addr); close(sockfd); exit(0); } else { // 父进程不负责通信,关闭自己的通信 socket close(sockfd); // 非阻塞回收,避免僵尸进程堆积 while (waitpid(-1, nullptr, WNOHANG) > 0) { } } } void TcpServer::Service(int sockfd, InetAddr peer) { char buffer[1024]; while (true) { ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1); if (n > 0) { buffer[n] = '\0'; std::cout << "client[" << peer.ToString() << "] say# " << buffer << std::endl; std::string echo = "server echo# "; echo += buffer; write(sockfd, echo.c_str(), echo.size()); } else if (n == 0) { lg.LogMessage(Info, "client quit: %s\n", peer.ToString().c_str()); break; } else { lg.LogMessage(Error, "read error, errno: %d, %s\n", errno, strerror(errno)); break; } } } TcpServer::~TcpServer() { if (_listenSock >= 0) { close(_listenSock); } }

V3 多线程 Echo Server

V3 的核心变化:每个客户端连接交给一个线程处理。

TcpServer.hpp

#pragma once #include <cstdint> #include <pthread.h> #include "nocopy.hpp" #include "InetAddr.hpp" const static int defaultBacklog = 6; class TcpServer : public nocopy { public: explicit TcpServer(uint16_t port); ~TcpServer(); void Init(); void Start(); private: class ThreadData { public: ThreadData(TcpServer *server, int sockfd, const struct sockaddr_in &peer); public: TcpServer *_server; int _sockfd; InetAddr _peer; }; private: static void *ThreadRoutine(void *args); void Service(int sockfd, InetAddr peer); void ProcessConnection(int sockfd, const struct sockaddr_in &peer); private: uint16_t _port; int _listenSock; bool _isRunning; };

TcpServer.cc

#include "TcpServer.hpp" #include <iostream> #include <cstring> #include <cerrno> #include <cstdlib> #include <unistd.h> #include <signal.h> #include <sys/socket.h> #include <netinet/in.h> #include "Comm.hpp" #include "Log.hpp" TcpServer::ThreadData::ThreadData(TcpServer *server, int sockfd, const struct sockaddr_in &peer) : _server(server), _sockfd(sockfd), _peer(peer) { } TcpServer::TcpServer(uint16_t port) : _port(port), _listenSock(-1), _isRunning(false) { } void TcpServer::Init() { signal(SIGPIPE, SIG_IGN); _listenSock = socket(AF_INET, SOCK_STREAM, 0); if (_listenSock < 0) { lg.LogMessage(Fatal, "socket error, errno: %d, %s\n", errno, strerror(errno)); exit(Socket_Err); } int opt = 1; setsockopt(_listenSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(_port); local.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(_listenSock, CONV(&local), sizeof(local)) < 0) { lg.LogMessage(Fatal, "bind error, errno: %d, %s\n", errno, strerror(errno)); exit(Bind_Err); } if (listen(_listenSock, defaultBacklog) < 0) { lg.LogMessage(Fatal, "listen error, errno: %d, %s\n", errno, strerror(errno)); exit(Listen_Err); } lg.LogMessage(Info, "server init success, port: %d\n", _port); } void TcpServer::Start() { _isRunning = true; while (_isRunning) { struct sockaddr_in peer; socklen_t len = sizeof(peer); int sockfd = accept(_listenSock, CONV(&peer), &len); if (sockfd < 0) { lg.LogMessage(Warning, "accept error, errno: %d, %s\n", errno, strerror(errno)); continue; } ProcessConnection(sockfd, peer); } } void TcpServer::ProcessConnection(int sockfd, const struct sockaddr_in &peer) { pthread_t tid; ThreadData *td = new ThreadData(this, sockfd, peer); int n = pthread_create(&tid, nullptr, ThreadRoutine, td); if (n != 0) { lg.LogMessage(Error, "pthread_create error\n"); close(sockfd); delete td; } } void *TcpServer::ThreadRoutine(void *args) { pthread_detach(pthread_self()); ThreadData *td = static_cast<ThreadData *>(args); td->_server->Service(td->_sockfd, td->_peer); close(td->_sockfd); delete td; return nullptr; } void TcpServer::Service(int sockfd, InetAddr peer) { char buffer[1024]; while (true) { ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1); if (n > 0) { buffer[n] = '\0'; std::cout << "client[" << peer.ToString() << "] say# " << buffer << std::endl; std::string echo = "server echo# "; echo += buffer; write(sockfd, echo.c_str(), echo.size()); } else if (n == 0) { lg.LogMessage(Info, "client quit: %s\n", peer.ToString().c_str()); break; } else { lg.LogMessage(Error, "read error, errno: %d, %s\n", errno, strerror(errno)); break; } } } TcpServer::~TcpServer() { if (_listenSock >= 0) { close(_listenSock); } }

V4 线程池 Echo Server

ThreadPool.hpp

#pragma once #include <queue> #include <mutex> #include <thread> #include <vector> #include <condition_variable> template <class Task> class ThreadPool { public: static ThreadPool<Task> *GetInstance() { static ThreadPool<Task> instance; return &instance; } void Start() { std::lock_guard<std::mutex> lock(_startMutex); if (_isRunning) { return; } _isRunning = true; for (int i = 0; i < _threadNum; ++i) { _threads.emplace_back([this]() { this->ThreadRun(); }); } for (auto &thread : _threads) { thread.detach(); } } void Push(const Task &task) { { std::lock_guard<std::mutex> lock(_mutex); _tasks.push(task); } _cond.notify_one(); } private: ThreadPool(int threadNum = 5) : _threadNum(threadNum), _isRunning(false) { } ThreadPool(const ThreadPool &) = delete; ThreadPool &operator=(const ThreadPool &) = delete; void ThreadRun() { while (true) { Task task; { std::unique_lock<std::mutex> lock(_mutex); _cond.wait(lock, [this]() { return !_tasks.empty(); }); task = _tasks.front(); _tasks.pop(); } task(); } } private: int _threadNum; bool _isRunning; std::vector<std::thread> _threads; std::queue<Task> _tasks; std::mutex _mutex; std::mutex _startMutex; std::condition_variable _cond; };

TcpServer.hpp

#pragma once #include <cstdint> #include "nocopy.hpp" #include "InetAddr.hpp" const static int defaultBacklog = 6; class TcpServer : public nocopy { public: explicit TcpServer(uint16_t port); ~TcpServer(); void Init(); void Start(); private: void Service(int sockfd, InetAddr peer); void ProcessConnection(int sockfd, const struct sockaddr_in &peer); private: uint16_t _port; int _listenSock; bool _isRunning; };

TcpServer.cc

#include "TcpServer.hpp" #include <iostream> #include <cstring> #include <cerrno> #include <cstdlib> #include <unistd.h> #include <signal.h> #include <functional> #include <sys/socket.h> #include <netinet/in.h> #include "Comm.hpp" #include "Log.hpp" #include "ThreadPool.hpp" TcpServer::TcpServer(uint16_t port) : _port(port), _listenSock(-1), _isRunning(false) { } void TcpServer::Init() { signal(SIGPIPE, SIG_IGN); _listenSock = socket(AF_INET, SOCK_STREAM, 0); if (_listenSock < 0) { lg.LogMessage(Fatal, "socket error, errno: %d, %s\n", errno, strerror(errno)); exit(Socket_Err); } int opt = 1; setsockopt(_listenSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(_port); local.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(_listenSock, CONV(&local), sizeof(local)) < 0) { lg.LogMessage(Fatal, "bind error, errno: %d, %s\n", errno, strerror(errno)); exit(Bind_Err); } if (listen(_listenSock, defaultBacklog) < 0) { lg.LogMessage(Fatal, "listen error, errno: %d, %s\n", errno, strerror(errno)); exit(Listen_Err); } using task_t = std::function<void()>; ThreadPool<task_t>::GetInstance()->Start(); lg.LogMessage(Info, "server init success, port: %d\n", _port); } void TcpServer::Start() { _isRunning = true; while (_isRunning) { struct sockaddr_in peer; socklen_t len = sizeof(peer); int sockfd = accept(_listenSock, CONV(&peer), &len); if (sockfd < 0) { lg.LogMessage(Warning, "accept error, errno: %d, %s\n", errno, strerror(errno)); continue; } ProcessConnection(sockfd, peer); } } void TcpServer::ProcessConnection(int sockfd, const struct sockaddr_in &peer) { using task_t = std::function<void()>; InetAddr addr(peer); // 把连接封装成任务,交给线程池处理 task_t task = std::bind(&TcpServer::Service, this, sockfd, addr); ThreadPool<task_t>::GetInstance()->Push(task); } void TcpServer::Service(int sockfd, InetAddr peer) { char buffer[1024]; while (true) { ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1); if (n > 0) { buffer[n] = '\0'; std::cout << "client[" << peer.ToString() << "] say# " << buffer << std::endl; std::string echo = "server echo# "; echo += buffer; write(sockfd, echo.c_str(), echo.size()); } else if (n == 0) { lg.LogMessage(Info, "client quit: %s\n", peer.ToString().c_str()); break; } else { lg.LogMessage(Error, "read error, errno: %d, %s\n", errno, strerror(errno)); break; } } close(sockfd); } TcpServer::~TcpServer() { if (_listenSock >= 0) { close(_listenSock); } }

客户端源码

四个版本都可以使用这个客户端测试。

TcpClient.cc

#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include "Comm.hpp" static void Usage(const std::string &process) { std::cout << "Usage: " << process << " server_ip server_port" << std::endl; } int main(int argc, char *argv[]) { if (argc != 3) { Usage(argv[0]); return Usage_Err; } std::string serverIp = argv[1]; uint16_t serverPort = std::stoi(argv[2]); // 1. 创建 socket int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { std::cerr << "socket error" << std::endl; return Socket_Err; } // 2. 填写服务器地址 struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(serverPort); if (inet_pton(AF_INET, serverIp.c_str(), &server.sin_addr) <= 0) { std::cerr << "inet_pton error" << std::endl; close(sockfd); return Connect_Err; } // 3. 连接服务器 // 客户端通常不需要显式 bind,connect 时系统会自动绑定本地端口 if (connect(sockfd, CONV(&server), sizeof(server)) < 0) { std::cerr << "connect error" << std::endl; close(sockfd); return Connect_Err; } std::cout << "connect server success" << std::endl; // 4. 通信 while (true) { std::string line; std::cout << "Please Enter# "; std::getline(std::cin, line); if (line == "quit" || line == "exit") { break; } ssize_t n = write(sockfd, line.c_str(), line.size()); if (n <= 0) { std::cerr << "write error" << std::endl; break; } char buffer[1024]; ssize_t m = read(sockfd, buffer, sizeof(buffer) - 1); if (m > 0) { buffer[m] = '\0'; std::cout << buffer << std::endl; } else if (m == 0) { std::cout << "server close connection" << std::endl; break; } else { std::cerr << "read error" << std::endl; break; } } close(sockfd); return 0; }

服务端启动入口

ServerMain.cc

#include <iostream> #include <cstdlib> #include "TcpServer.hpp" #include "Comm.hpp" static void Usage(const std::string &process) { std::cout << "Usage: " << process << " port" << std::endl; } int main(int argc, char *argv[]) { if (argc != 2) { Usage(argv[0]); return Usage_Err; } uint16_t port = std::stoi(argv[1]); TcpServer server(port); server.Init(); server.Start(); return 0; }

Makefile

.PHONY: all clean CXX=g++ CXXFLAGS=-std=c++17 -Wall -Wextra -pthread all: tcp_server tcp_client tcp_server: ServerMain.cc TcpServer.cc InetAddr.cc $(CXX) $(CXXFLAGS) -o $@ $^ tcp_client: TcpClient.cc $(CXX) $(CXXFLAGS) -o $@ $^ clean: rm -f tcp_server tcp_client

运行测试

编译:

make

启动服务端:

./tcp_server 8080

启动客户端:

./tcp_client 127.0.0.1 8080

客户端输入:

hello

服务器返回:

server echo# hello

输入:

quit

客户端退出。


最后总结

TCP 编程的主线是连接。

服务端先创建监听 socket,然后绑定端口,接着调用 listen() 进入监听状态。之后通过 accept() 获取客户端连接。accept() 返回的新 socket 才是真正用于通信的 socket。

单进程版本适合理解流程;多进程版本可以支持并发;多线程版本比多进程更轻量;线程池版本进一步减少线程频繁创建销毁的开销。

不过这些版本目前都还是简单 Echo Server,还没有解决 TCP 粘包问题。因为 TCP 是字节流协议,不保留消息边界。真正写业务服务器时,还需要设计应用层协议,例如:

报文长度 + 报文内容

后面继续学习协议设计、序列化反序列化、线程池任务处理、IO 多路复用时,TCP 服务器的结构就会越来越完整

http://www.jsqmd.com/news/874826/

相关文章:

  • 别再只用当天数据了!用Python+随机森林预测股价,试试这个加入历史数据的实战技巧
  • LLM多智能体驱动微服务自治:从架构设计到Sock Shop实战评估
  • 别再花钱买网盘了!手把手教你在Windows服务器上免费搭建个人版Filebrowser(附端口映射与防火墙配置)
  • AI 安全与对齐:幻觉、偏见、可控性与可信 AI 构建
  • 视频融合与空间计算先行者
  • Linux内核安全模块深入剖析【2.5】
  • 2026贵州区域次氯酸钠供应厂商综合排行盘点:成都次氯酸钠、液体聚合氯化铝、生产次氯酸钠、贵州次氯酸钠、贵州聚合氯化铝选择指南 - 优质品牌商家
  • 从PSCI到ATF:手把手带你拆解Linux ARM64平台CPU休眠唤醒的完整调用链
  • 2026年5月,武汉宠主的纯种马尔济斯甄选指南 - 2026年企业推荐榜
  • 2026年专业电动车停车棚厂家TOP5实力排行:充电桩停车棚/厂区停车棚/小区停车棚/汽车停车棚/膜结构体育看台/选择指南 - 优质品牌商家
  • 仅剩72小时!Midjourney即将关闭--contrast实验性参数——最后掌握原生对比度控制的窗口期
  • 2026年第二季度湖北幕墙防火漆实力厂商深度解析:昊优环保科技公司为何值得关注 - 2026年企业推荐榜
  • SVR模型可视化对比:RBF、线性、多项式核,哪个对你的数据更有效?(Python+Matplotlib实战)
  • 国内压装浮动头厂家实力排行:500kg伺服电动缸/50吨伺服电动缸/5吨伺服电动缸/C型伺服压机/exdIIBT4级防爆伺服压机/选择指南 - 优质品牌商家
  • 改性阻燃ABS技术选型全解析:绍兴,四川,河南,阻燃abs颗粒/阻燃pvc颗粒/pvc塑胶颗粒/发泡pvc颗粒/选择指南 - 优质品牌商家
  • 数字孪生与视频孪生空间智能治理技术白皮书
  • 2026现阶段屯昌工厂企业如何选择可靠的废品回收服务伙伴 - 2026年企业推荐榜
  • 如何用OpenSpeedy实现单机游戏5倍速运行:完整免费加速教程
  • 2026宜宾整装装修公司可靠性技术拆解与品牌实测:宜宾工人直管装修公司、宜宾当地装修公司、宜宾有保障装修公司、宜宾靠谱装修公司选择指南 - 优质品牌商家
  • Unity自定义碰撞与力场系统实战指南
  • 为什么92%的游戏团队在AI Agent接入阶段踩中这3个合规雷区?GDPR+未成年人保护双合规 checklist 首次披露
  • 2026年Q2供应链订货系统品牌选型技术解析:b2b供应链系统、wms仓储物流管理软件、wms仓库管理软件、wms管理系统选择指南 - 优质品牌商家
  • 2026年西安网站建设制作品牌TOP5客观盘点:西安网站制作/西安网站建设制作/西安网站建设服务/西安企业网站建设一条龙/选择指南 - 优质品牌商家
  • 2026年至今,河北地区备受推崇的悬浮地板厂家——任丘市绿美亚人造草坪厂实力解析 - 2026年企业推荐榜
  • 2026年比较好的伺服减速机/精密行星减速机优质厂家推荐榜 - 行业平台推荐
  • 别再傻傻分不清了!用DPABI和Matlab实操,带你搞懂脑影像分析里的ROI和VBM
  • 量子机器学习可解释性:基于多线性形式的SHAP值计算理论与应用
  • AI洗白:识别企业虚假AI宣传与构建真实技术能力
  • 2026企业数字化转型:从规则脚本到实在Agent智能体进化全解析
  • UE5 Engine.ini本地化配置原理与International节区深度解析