阻塞:
阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。
函数只有在得到结果之后才会返回。
非阻塞:
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
/**
参 数 :int nfds -- maxfd是要监视的最大的文件描述符值+1;
参 数 : fd_set *readfds -- 读文件描述符的集合
参 数 : fd_set *writefds -- 可写文件描述符集合
参 数 : fd_set *exceptfds -- 异常文件描述符集合
参 数 : struct timeval *timeout --超时描述,在一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。
返回值 :
说 明 :select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型。
*/
int select(_In_ int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval * timeout);
FD_ZERO(fd_set *fdset); --将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。
FD_SET(fd_set *fdset); --用于在文件描述符集合中增加一个新的文件描述符。
FD_CLR(fd_set *fdset); --用于在文件描述符集合中删除一个文件描述符。
FD_ISSET(int fd,fd_set *fdset); --用于测试指定的文件描述符是否在该集合中。
跨平台-简易TCP服务端
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <windows.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <cerrno>
#include <cstring>
#define SOCKET int
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define closesocket(s) close(s)
#endif
#include <stdio.h>
#include <thread>
#include <vector>
#include <algorithm> // std::remove_if
// 缓冲区大小
#define BUFFER_SIZE 4096
// 全局客户端列表
std::vector<SOCKET> g_clients;
// 设置非阻塞
void setNonBlocking(SOCKET sock) {
#ifdef _WIN32
unsigned long mode = 1;
ioctlsocket(sock, FIONBIO, &mode);
#else
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
#endif
}
// 处理客户端数据
int processor(SOCKET client_sock) {
char buffer[BUFFER_SIZE];
ssize_t n;
#ifdef _WIN32
n = recv(client_sock, buffer, BUFFER_SIZE - 1, 0);
#else
n = recv(client_sock, buffer, BUFFER_SIZE - 1, 0);
#endif
if (n > 0) {
buffer[n] = '\0';
printf("Received from %d: %s", (int)client_sock, buffer);
// 回显
if (send(client_sock, buffer, n, 0) != n) {
perror("send failed");
return -1;
}
return 0;
}
else if (n == 0) {
printf("Client %d disconnected.\n", (int)client_sock);
return -1; // 客户端断开
}
else {
// 非阻塞下 EWOULDBLOCK/EAGAIN 是正常的
#ifdef _WIN32
if (WSAGetLastError() != WSAEWOULDBLOCK) {
printf("recv error on %d: %d\n", (int)client_sock, WSAGetLastError());
return -1;
}
#else
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("recv error");
return -1;
}
#endif
return 0; // 无数据可读,正常
}
}
// 关闭客户端 socket 并从列表移除
void closeClient(SOCKET sock) {
#ifdef _WIN32
closesocket(sock);
#else
close(sock);
#endif
}
int main() {
#ifdef _WIN32
// 启动 Windows socket 环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
if (WSAStartup(ver, &dat) != 0) {
printf("WSAStartup failed.\n");
return -1;
}
#endif
// 1. 创建 socket
SOCKET server_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (server_sock == INVALID_SOCKET) {
printf("Failed to create socket.\n");
return -1;
}
// 设置端口复用(避免 Address already in use)
int opt = 1;
#ifdef _WIN32
setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
#else
setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#endif
// 绑定地址
sockaddr_in server_addr = {};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(4567);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_sock, (sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
printf("Bind failed.\n");
#ifdef _WIN32
closesocket(server_sock);
WSACleanup();
#else
close(server_sock);
#endif
return -1;
} else {
printf("Bind successful.\n");
}
// 监听
if (listen(server_sock, SOMAXCONN) == SOCKET_ERROR) {
printf("Listen failed.\n");
closesocket(server_sock);
return -1;
} else {
printf("Listening on port 4567...\n");
}
// 设置服务器 socket 为非阻塞(accept 也需要非阻塞)
setNonBlocking(server_sock);
bool running = true;
fd_set read_set, write_set, except_set;
struct timeval tv;
while (running) {
FD_ZERO(&read_set);
FD_ZERO(&write_set);
FD_ZERO(&except_set);
// 总是监听服务器 socket
FD_SET(server_sock, &read_set);
FD_SET(server_sock, &except_set); // 异常
SOCKET max_sock = server_sock;
// 添加所有客户端
for (SOCKET client : g_clients) {
FD_SET(client, &read_set);
FD_SET(client, &except_set);
if (client > max_sock) max_sock = client;
}
// 设置超时:1秒(避免无限阻塞)
tv.tv_sec = 1;
tv.tv_usec = 0;
int activity = select((int)max_sock + 1, &read_set, nullptr, &except_set, &tv);
if (activity < 0) {
perror("select error");
break;
}
// 处理异常(通常表示连接断开)
for (int i = (int)g_clients.size() - 1; i >= 0; i--) {
SOCKET sock = g_clients[i];
if (FD_ISSET(sock, &except_set)) {
printf("Exception on socket %d, closing.\n", (int)sock);
closeClient(sock);
g_clients.erase(g_clients.begin() + i);
}
}
// 处理客户端读事件
for (int i = (int)g_clients.size() - 1; i >= 0; i--) {
SOCKET sock = g_clients[i];
if (FD_ISSET(sock, &read_set)) {
if (processor(sock) == -1) {
closeClient(sock);
g_clients.erase(g_clients.begin() + i);
}
}
}
// 处理服务器 socket:新连接
if (FD_ISSET(server_sock, &read_set)) {
sockaddr_in client_addr = {};
int addr_len = sizeof(client_addr);
SOCKET client_sock = accept(server_sock, (sockaddr*)&client_addr, &addr_len);
if (client_sock != INVALID_SOCKET) {
setNonBlocking(client_sock); // 必须非阻塞!
g_clients.push_back(client_sock);
printf("New client: fd=%d, IP=%s\n",
(int)client_sock, inet_ntoa(client_addr.sin_addr));
}
else {
#ifdef _WIN32
if (WSAGetLastError() != WSAEWOULDBLOCK) {
printf("accept error: %d\n", WSAGetLastError());
}
#else
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("accept error");
}
#endif
}
}
// 可在此处处理其他业务(如定时任务)
// printf("Idle processing...\n");
}
// 清理资源
for (SOCKET sock : g_clients) {
closeClient(sock);
}
closesocket(server_sock);
#ifdef _WIN32
WSACleanup();
#endif
printf("Server exited gracefully.\n");
return 0;
}
简易TCP客户端
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <windows.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <cerrno>
#include <cstring>
#define SOCKET int
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define closesocket(s) close(s)
#endif
#include <stdio.h>
#include <thread>
#include <atomic>
#include <string>
// 全局运行标志(原子操作,线程安全)
std::atomic<bool> g_bRun(true);
// 设置非阻塞
void setNonBlocking(SOCKET sock) {
#ifdef _WIN32
unsigned long mode = 1;
ioctlsocket(sock, FIONBIO, &mode);
#else
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
#endif
}
// 接收数据处理器
int processor(SOCKET client_sock) {
char buffer[4096];
ssize_t n;
#ifdef _WIN32
n = recv(client_sock, buffer, sizeof(buffer) - 1, 0);
#else
n = recv(client_sock, buffer, sizeof(buffer) - 1, 0);
#endif
if (n > 0) {
buffer[n] = '\0';
printf("Received: %s", buffer);
return 0;
}
else if (n == 0) {
printf("Server disconnected.\n");
g_bRun = false;
return -1;
}
else {
// 非阻塞下 EWOULDBLOCK/EAGAIN 是正常的
#ifdef _WIN32
if (WSAGetLastError() != WSAEWOULDBLOCK) {
printf("recv error: %d\n", WSAGetLastError());
g_bRun = false;
return -1;
}
#else
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("recv error");
g_bRun = false;
return -1;
}
#endif
return 0; // 无新数据
}
}
// 发送线程函数
void sendThread(SOCKET sock) {
char hello[] = "Hello from client!\n";
send(sock, hello, strlen(hello), 0);
printf("Sent: %s", hello);
while (g_bRun.load()) { // 检查全局标志
printf("Enter message to send (or 'exit' to quit): ");
fflush(stdout);
char cmdBuf[256] = {};
if (!fgets(cmdBuf, sizeof(cmdBuf), stdin)) {
break; // EOF
}
// 移除换行符
cmdBuf[strcspn(cmdBuf, "\n")] = 0;
if (strcmp(cmdBuf, "exit") == 0) {
g_bRun = false;
break;
}
if (strlen(cmdBuf) > 0) {
std::string msg = std::string(cmdBuf) + "\n";
#ifdef _WIN32
send(sock, msg.c_str(), (int)msg.length(), 0);
#else
send(sock, msg.c_str(), msg.length(), 0);
#endif
}
}
printf("Send thread exiting...\n");
}
int main() {
#ifdef _WIN32
// 启动 Windows socket 环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
if (WSAStartup(ver, &dat) != 0) {
printf("WSAStartup failed.\n");
return -1;
}
#endif
// 1. 创建 socket
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET) {
printf("Failed to create socket.\n");
return -1;
}
// 设置为非阻塞(确保 accept 和 recv 不阻塞)
setNonBlocking(sock);
// 2. 连接服务器
sockaddr_in server_addr = {};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(4567);
// 统一使用本地回环地址
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
int ret = connect(sock, (sockaddr*)&server_addr, sizeof(server_addr));
if (ret == SOCKET_ERROR) {
#ifdef _WIN32
if (WSAGetLastError() != WSAEWOULDBLOCK && WSAGetLastError() != WSAEINPROGRESS) {
printf("Connect failed.\n");
closesocket(sock);
WSACleanup();
return -1;
}
#else
if (errno != EINPROGRESS) {
perror("Connect failed");
close(sock);
return -1;
}
#endif
} else {
printf("Connected to server.\n");
}
// 启动发送线程(使用 join,确保主线程等待)
std::thread t1(sendThread, sock);
fd_set read_set;
struct timeval tv;
while (g_bRun) {
FD_ZERO(&read_set);
FD_SET(sock, &read_set);
// 每次 select 最多等待 1 秒
tv.tv_sec = 1;
tv.tv_usec = 0;
int activity = select((int)sock + 1, &read_set, nullptr, nullptr, &tv);
if (activity < 0) {
perror("select error");
break;
}
if (activity == 0) {
// 超时,可做其他事(如心跳)
continue;
}
// 有数据可读
if (FD_ISSET(sock, &read_set)) {
if (processor(sock) == -1) {
break;
}
}
}
// 通知发送线程退出
g_bRun = false;
// 等待发送线程结束
if (t1.joinable()) {
t1.join();
}
// 关闭 socket
closesocket(sock);
#ifdef _WIN32
WSACleanup();
#endif
printf("Client exited gracefully.\n");
return 0;
}