一 核心步骤
服务端步骤
- 创建套接字 →
socket() - 绑定 IP / 端口 →
bind() - 监听连接 →
listen() - 接收客户端连接 →
accept() - 读写数据 →
read()/write() - 关闭套接字 →
close()
客户端步骤
- 创建套接字 →
socket() - 连接服务端 →
connect() - 读写数据 →
read()/write() - 关闭套接字 →
close()
二 核心函数
1. 创建套接字:socket ()

2. 绑定地址端口:bind ()(仅服务端)

3. 监听连接:listen ()(仅服务端)

4. 接收连接:accept ()(仅服务端)

5. 发起连接:connect ()(仅客户端)

三 tcp编程实现服务器回显
仅展示主要接口,封装函数PthreadPool可看前面文章,主函数可自己调用
version1
tcpServer.hpp
cpp
#include <iostream>
#include "log.hpp"
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> // 定义 sockaddr_in、in_addr、in_port_t 等
#include <arpa/inet.h> // 提供 inet
#include <pthread.h>
#include <functional>
#include "PthreadPool.hpp"
#define MAX_NUM 1024
namespace Server
{
static uint16_t deport = 8080; // 默认端口
static int gbacklog = 5;
enum
{
SOCkET_ERR = 0,
BIND_ERR = 1,
LISTEN_ERR = 2
}; // 错误值
//服务端io可放在类外
void serverIO(int sock)
{
char buffer[MAX_NUM];
while (1) // 一直读,类似管道
{
memset(&buffer,0,sizeof(buffer));
ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
if (n <= 0) // 读不到东西,代表客户端退出
{
logMessage(NORMAL, "client quit! server too");
close(sock);
break;
}
buffer[n] = 0;
cout << "rec message:" << buffer << endl;
// 把接收到的消息转回客户端
string outbuffer = buffer;
outbuffer += "<-servre[echo]";
write(sock,outbuffer.c_str(), outbuffer.size());
}
}
class tcpServer
{
public:
tcpServer(uint16_t port = deport) : _listensockfd(-1), _port(port)
{
}
void init()
{
// 1.创建套接字
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
logMessage(FATAL, "create socket error");
exit(SOCkET_ERR);
}
logMessage(NORMAL, "create socket success,listenfd:%d",_listensockfd);
// 端口复用:解决程序重启时端口TIME_WAIT问题
int opt = 1;
setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 2.bind绑定port与ip(ANY)
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY);
local.sin_port = htons(_port);
int n = bind(_listensockfd, (struct sockaddr *)&local,sizeof(local));
if (n < 0)
{
logMessage(FATAL, "bind socket error");
exit(BIND_ERR);
}
logMessage(NORMAL, "bind socket success");
// 3.设置监听状态
if (listen(_listensockfd, gbacklog) < 0) // gbacklog后面讲
{
logMessage(FATAL, "listen socket error");
exit(LISTEN_ERR);
}
logMessage(NORMAL, "listen socket success");
// 4.在start中
PthreadPool<calTask>::getInstance()->run();
}
void start()
{
// 4.获取新链接
while (1)
{
struct sockaddr_in peer; // 用于获取客户端ip和port
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
int sock = accept(_listensockfd, (struct sockaddr *)&peer, &len);
if (sock < 0)
{
logMessage(ERROR, "accpet error,again");
continue;
}
logMessage(NORMAL, "accept a new link success,sock:%d",sock);
// 5.获取到新的sock后,就用新sock进行未来通信(面向字节流,后续都是文件操作)
//version_1(单进程阻塞一对一通信)
serverIO(sock);
close(sock);
}
}
~tcpServer() {}
private:
int _listensockfd;
// 这里的sock不同于udp的sock,这个sock只负责监听
uint16_t _port;
};
}
tcpClient.hpp
cpp
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> // 定义 sockaddr_in、in_addr、in_port_t 等
#include <arpa/inet.h> // 提供 inet
using namespace std;
namespace Client
{
class tcpClient
{
public:
tcpClient(string ip,uint16_t port):_sockfd(-1),_ip(ip),_port(port)
{}
void init()
{
//1.创建socket
_sockfd = socket(AF_INET,SOCK_STREAM,0);
if(_sockfd < 0)
{
cerr<<"create socket error"<<endl;
exit(0);
}
//2.bind(客户端为系统隐式绑定)
}
void run()
{
//3.发起链接
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(_ip.c_str());
server.sin_port = htons(_port);
socklen_t len = sizeof(server);
if (connect(_sockfd, (struct sockaddr *)&server, len) != 0)
{
cerr << "connect socket error" << endl;
exit(0);
}
else
{
while (1)
{
string buffer;
cout << "Please Enter##";
getline(cin,buffer);
write(_sockfd,buffer.c_str(),buffer.size());
//接收server返回的数据
char buf[1024];
int n = read(_sockfd,buf,sizeof(buf)-1);
if (n > 0)
{
buf[n] = 0;
cout << "server回显##" << buf << endl;
}
else
{
break;
}
}
}
}
~tcpClient()
{
if(_sockfd >= 0) close(_sockfd);
}
private:
int _sockfd;
string _ip;
uint16_t _port;
};
}
log.hpp
cpp
//服务端日志函数
#include <iostream>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>
using namespace std;
#define NUM 1024
#define DEBUG 0
#define NORMAL 1
#define WARNNING 2
#define ERROR 3
#define FATAL 4
string retStr(int key)
{
switch(key)
{
case 0 : return "DEBUG";
break;
case 1 :return "NORMAL";
break;
case 2 :return "WAENNING";
break;
case 3 :return "ERROR";
break;
case 4 :return "FATAL";
break;
default: return "UNKNOW";
break;
}
}
void logMessage(int level,const char *format,...)
//void logMessage(int level,const string&message)
{
//cout<<level<<":"<<message<<endl;
//[日志等级][时间戳/时间][pid][messge]
//[WARNING][2023-05-1118:09:08][123][创建socket失败]
char logprefix[NUM];
snprintf(logprefix,sizeof(logprefix),"[%s][%ld][%d]",retStr(level).c_str(),(long int)time(nullptr),getpid());
char logcontent[NUM];
va_list arg;
va_start(arg,format);
vsnprintf(logcontent,sizeof(logcontent),format,arg);
cout<<logprefix<<logcontent<<endl;
}
version2(多进程版)
仅修改start()函数即可
cpp
#include <iostream>
#include "log.hpp"
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> // 定义 sockaddr_in、in_addr、in_port_t 等
#include <arpa/inet.h> // 提供 inet
#include <pthread.h>
#include <functional>
#include "PthreadPool.hpp"
#define MAX_NUM 1024
namespace Server
{
static uint16_t deport = 8080; // 默认端口
static int gbacklog = 5;
enum
{
SOCkET_ERR = 0,
BIND_ERR = 1,
LISTEN_ERR = 2
}; // 错误值
//服务端io可放在类外
void serverIO(int sock)
{
char buffer[MAX_NUM];
while (1) // 一直读,类似管道
{
memset(&buffer,0,sizeof(buffer));
ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
if (n <= 0) // 读不到东西,代表客户端退出
{
logMessage(NORMAL, "client quit! server too");
close(sock);
break;
}
buffer[n] = 0;
cout << "rec message:" << buffer << endl;
// 把接收到的消息转回客户端
string outbuffer = buffer;
outbuffer += "<-servre[echo]";
write(sock,outbuffer.c_str(), outbuffer.size());
}
}
class tcpServer
{
public:
tcpServer(uint16_t port = deport) : _listensockfd(-1), _port(port)
{
}
void init()
{
// 1.创建套接字
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
logMessage(FATAL, "create socket error");
exit(SOCkET_ERR);
}
logMessage(NORMAL, "create socket success,listenfd:%d",_listensockfd);
// 端口复用:解决程序重启时端口TIME_WAIT问题
int opt = 1;
setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 2.bind绑定port与ip(ANY)
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY);
local.sin_port = htons(_port);
int n = bind(_listensockfd, (struct sockaddr *)&local,sizeof(local));
if (n < 0)
{
logMessage(FATAL, "bind socket error");
exit(BIND_ERR);
}
logMessage(NORMAL, "bind socket success");
// 3.设置监听状态
if (listen(_listensockfd, gbacklog) < 0) // gbacklog后面讲
{
logMessage(FATAL, "listen socket error");
exit(LISTEN_ERR);
}
logMessage(NORMAL, "listen socket success");
// 4.在start中
PthreadPool<calTask>::getInstance()->run();
}
void start()
{
// 4.获取新链接
while (1)
{
struct sockaddr_in peer; // 用于获取客户端ip和port
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
int sock = accept(_listensockfd, (struct sockaddr *)&peer, &len);
if (sock < 0)
{
logMessage(ERROR, "accpet error,again");
continue;
}
logMessage(NORMAL, "accept a new link success,sock:%d",sock);
// 5.获取到新的sock后,就用新sock进行未来通信(面向字节流,后续都是文件操作)
// version_2(多进程版)
int k = fork();
if (k < 0)
{
logMessage(FATAL, "fork error");
exit(0);
}
if(k == 0)
{
logMessage(NORMAL, "fork success");
serverIO(sock);
}
close(sock);
// 非阻塞回收已退出的子进程(写在父进程中)
pid_t ret_pid;
// 循环回收所有已退出的子进程,避免僵尸进程堆积
while ((ret_pid = waitpid(-1, NULL, WNOHANG)) > 0) {
logMessage(NORMAL, "child process %d exited, recycled", ret_pid);
}
}
~tcpServer() {}
private:
int _listensockfd;
// 这里的sock不同于udp的sock,这个sock只负责监听
uint16_t _port;
};
}
version3(多线程版)
cpp
#include <iostream>
#include "log.hpp"
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> // 定义 sockaddr_in、in_addr、in_port_t 等
#include <arpa/inet.h> // 提供 inet
#include <pthread.h>
#include <functional>
#include "PthreadPool.hpp"
#define MAX_NUM 1024
namespace Server
{
static uint16_t deport = 8080; // 默认端口
static int gbacklog = 5;
enum
{
SOCkET_ERR = 0,
BIND_ERR = 1,
LISTEN_ERR = 2
}; // 错误值
//服务端io可放在类外
void serverIO(int sock)
{
char buffer[MAX_NUM];
while (1) // 一直读,类似管道
{
memset(&buffer,0,sizeof(buffer));
ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
if (n <= 0) // 读不到东西,代表客户端退出
{
logMessage(NORMAL, "client quit! server too");
close(sock);
break;
}
buffer[n] = 0;
cout << "rec message:" << buffer << endl;
// 把接收到的消息转回客户端
string outbuffer = buffer;
outbuffer += "<-servre[echo]";
write(sock,outbuffer.c_str(), outbuffer.size());
}
}
void *threadIO(void *args)
{
// 设置线程分离:结束后自动释放资源,无需主线程join
pthread_detach(pthread_self());
int *p_sock = static_cast<int *>(args);
int sock = *p_sock;
delete p_sock;
// 处理客户端通信
serverIO(sock);
close(sock);
return nullptr;
}
class tcpServer
{
public:
tcpServer(uint16_t port = deport) : _listensockfd(-1), _port(port)
{
}
void init()
{
// 1.创建套接字
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
logMessage(FATAL, "create socket error");
exit(SOCkET_ERR);
}
logMessage(NORMAL, "create socket success,listenfd:%d",_listensockfd);
// 端口复用:解决程序重启时端口TIME_WAIT问题
int opt = 1;
setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 2.bind绑定port与ip(ANY)
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY);
local.sin_port = htons(_port);
int n = bind(_listensockfd, (struct sockaddr *)&local,sizeof(local));
if (n < 0)
{
logMessage(FATAL, "bind socket error");
exit(BIND_ERR);
}
logMessage(NORMAL, "bind socket success");
// 3.设置监听状态
if (listen(_listensockfd, gbacklog) < 0) // gbacklog后面讲
{
logMessage(FATAL, "listen socket error");
exit(LISTEN_ERR);
}
logMessage(NORMAL, "listen socket success");
// 4.在start中
PthreadPool<calTask>::getInstance()->run();
}
void start()
{
// 4.获取新链接
while (1)
{
struct sockaddr_in peer; // 用于获取客户端ip和port
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
int sock = accept(_listensockfd, (struct sockaddr *)&peer, &len);
if (sock < 0)
{
logMessage(ERROR, "accpet error,again");
continue;
}
logMessage(NORMAL, "accept a new link success,sock:%d",sock);
// 5.获取到新的sock后,就用新sock进行未来通信(面向字节流,后续都是文件操作)
// version_3(多线程版)
int *p_sock = new int(sock);
pthread_t t;
pthread_create(&t,nullptr,threadIO,p_sock);
//pthread_join(t,nullptr);//这里不能进行线程等待因为我们的目的并行,如果等待就会阻塞变成串行
//close(sock);//这里因为是线程他们的资源描述符是共享的,所以应该在子线程内部去关闭描述符
}
}
~tcpServer() {}
private:
int _listensockfd;
// 这里的sock不同于udp的sock,这个sock只负责监听
uint16_t _port;
};
}
version4(线程池版)
cpp
#include <iostream>
#include "log.hpp"
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> // 定义 sockaddr_in、in_addr、in_port_t 等
#include <arpa/inet.h> // 提供 inet
#include <pthread.h>
#include <functional>
#include "PthreadPool.hpp"
#define MAX_NUM 1024
namespace Server
{
static uint16_t deport = 8080; // 默认端口
static int gbacklog = 5;
enum
{
SOCkET_ERR = 0,
BIND_ERR = 1,
LISTEN_ERR = 2
}; // 错误值
//服务端io可放在类外
void serverIO(int sock)
{
char buffer[MAX_NUM];
while (1) // 一直读,类似管道
{
memset(&buffer,0,sizeof(buffer));
ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
if (n <= 0) // 读不到东西,代表客户端退出
{
logMessage(NORMAL, "client quit! server too");
close(sock);
break;
}
buffer[n] = 0;
cout << "rec message:" << buffer << endl;
// 把接收到的消息转回客户端
string outbuffer = buffer;
outbuffer += "<-servre[echo]";
write(sock,outbuffer.c_str(), outbuffer.size());
}
}
void *threadIO(void *args)
{
// 设置线程分离:结束后自动释放资源,无需主线程join
pthread_detach(pthread_self());
int *p_sock = static_cast<int *>(args);
int sock = *p_sock;
delete p_sock;
// 处理客户端通信
serverIO(sock);
close(sock);
return nullptr;
}
class tcpServer
{
public:
tcpServer(uint16_t port = deport) : _listensockfd(-1), _port(port)
{
}
void init()
{
// 1.创建套接字
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
logMessage(FATAL, "create socket error");
exit(SOCkET_ERR);
}
logMessage(NORMAL, "create socket success,listenfd:%d",_listensockfd);
// 端口复用:解决程序重启时端口TIME_WAIT问题
int opt = 1;
setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 2.bind绑定port与ip(ANY)
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY);
local.sin_port = htons(_port);
int n = bind(_listensockfd, (struct sockaddr *)&local,sizeof(local));
if (n < 0)
{
logMessage(FATAL, "bind socket error");
exit(BIND_ERR);
}
logMessage(NORMAL, "bind socket success");
// 3.设置监听状态
if (listen(_listensockfd, gbacklog) < 0) // gbacklog后面讲
{
logMessage(FATAL, "listen socket error");
exit(LISTEN_ERR);
}
logMessage(NORMAL, "listen socket success");
// 4.在start中
PthreadPool<calTask>::getInstance()->run();
}
void start()
{
// 4.获取新链接
while (1)
{
struct sockaddr_in peer; // 用于获取客户端ip和port
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
int sock = accept(_listensockfd, (struct sockaddr *)&peer, &len);
if (sock < 0)
{
logMessage(ERROR, "accpet error,again");
continue;
}
logMessage(NORMAL, "accept a new link success,sock:%d",sock);
// 5.获取到新的sock后,就用新sock进行未来通信(面向字节流,后续都是文件操作)
//version_4(线程池版)
calTask t(sock,serverIO);
PthreadPool<calTask>::getInstance()->push(t);
}
}
~tcpServer() {}
private:
int _listensockfd;
// 这里的sock不同于udp的sock,这个sock只负责监听
uint16_t _port;
};
}
Task.hpp
cpp
#include <iostream>
#include <functional>
using namespace std;
class calTask
{
typedef std::function<void(int)>func_t;
public:
calTask()
{}
calTask(int x,func_t func):_x(x),_callback(func)
{}
string operator()()
{
_callback(_x);
}
~calTask()
{}
private:
int _x;
func_t _callback;
};