1. 阻塞模型
2. 阻塞+多线程
3. select
4. 异步IO
5. IOCP
6. epoll
7. Reactor和Proactor
基础知识
最常见的socket tcp编程模型就是阻塞模型,方法也很简单:
客户端:
- connect
- send
- recv
- closesocket
服务端:
- bind
- listen
- accept
- recv
- send
各种socket编程模型都是对服务端来说的。下面上代码:
客户端
// sock_client.cpp
#include <stdio.h>
#include <WinSock2.h>
#include <iostream>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
#define SERVERADDR "127.0.0.1"
#define SERVERPORT 9000
#define DATA_MAXSIZE 128
int main(int argc, char **argv)
{
cout << "this is sock client" << endl;
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData); //初始化Winsock DLL 与WSACleanup成对出现
SOCKET sockClient; //客户端socket
SOCKADDR_IN addrServer; //服务端地址
//创建客户端socket
sockClient = socket(AF_INET, SOCK_STREAM, 0);
//定义服务器地址
addrServer.sin_addr.S_un.S_addr = inet_addr(SERVERADDR);
addrServer.sin_family = AF_INET; // 协议族 ipv4
addrServer.sin_port = htons(SERVERPORT);
//连接到服务器
connect(sockClient, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));
//发送数据
char message[DATA_MAXSIZE ] = "hello world";
char buff[DATA_MAXSIZE ];
int ret = send(sockClient, message, DATA_MAXSIZE , 0);
printf("recv send return : %d\r\n", ret);
ret = recv(sockClient, buff, DATA_MAXSIZE, 0);
printf("recv from server : %s\r\n", buff);
//关闭socket
closesocket(sockClient);
WSACleanup(); // 解除socket库的绑定
return 0;
}
这种比较简单的代码我喜欢用MinGW编译:
> g++ sock_client.cpp -lws2_32 -o sock_client.exe
服务端
/*
sock_server_block.cpp
阻塞模型
服务端同时只能处理一个客户端连接
*/
/* 阻塞模型
服务端同时只能处理一个客户端连接
*/
#include <iostream>
using namespace std;
#include <winsock2.h>
#include <stdio.h>
#include "utility.h"
#pragma comment(lib, "ws2_32.lib")
#define SERVERPORT 9000
#define DATA_MAXSIZE 128
#define BACK_LOG 20 // 经过三次握手,但还没有accept的最大连接数,达到最大值后将拒绝连接
int main(int argc, char **argv)
{
WSADATA wsaData;
SOCKET sockServer;
SOCKADDR_IN addrServer;
SOCKET sockClient;
SOCKADDR_IN addrClient;
WSAStartup(MAKEWORD(2, 2), &wsaData);
//创建服务端socket
sockServer = socket(AF_INET, SOCK_STREAM, 0);
addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //INADDR_ANY表示任何IP
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(SERVERPORT); //绑定端口
//绑定socket
bind(sockServer, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));
//Listen监听端
listen(sockServer, BACK_LOG); //BACK_LOG 为最大等待连接数目
printf("server start:\nlistening...\n");
int len = sizeof(SOCKADDR);
char sendBuf[DATA_MAXSIZE]; //发送至客户端的字符串
char recvBuf[DATA_MAXSIZE]; //接受客户端返回的字符串
char stime[64];
while (true)
{
//会阻塞进程,直到有客户端连接上来为止
sockClient = accept(sockServer, (SOCKADDR *)&addrClient, &len);
get_time(stime, sizeof(stime));
printf("[%s] accept client : %#x\n", stime, &sockClient);
while (true)
{
//阻塞等待接收数据
int recv_size = recv(sockClient, recvBuf, DATA_MAXSIZE, 0);
get_time(stime, sizeof(stime));
if (recv_size > 0)
{
//打印客户端数据
printf("[%s] recv from client[%#x] : %s\n", stime, &sockClient, recvBuf);
sprintf(sendBuf, "this is sock server %#x", &sockClient);
send(sockClient, sendBuf, DATA_MAXSIZE, 0);
}
else
{
cout << "[" << stime << "] client[" << &sockClient << "] socket closed" << endl;
closesocket(sockClient);
break;
}
}
}
//关闭socket
closesocket(sockClient);
WSACleanup();
return 0;
}
编译:
>g++ sock_server_block.cpp utility.cpp -lws2_32 -o sock_server_block.exe -fpermissive
测试结果:
多客户端测试
上面一次只有一个客户端连接,看不出效果,我们重新写一个客户端代码,多个客户端同时连接和发送数据,看看会发生什么情况。
// sock_client_bulk.cpp
// 批量创建client,并发送数据
#include <stdio.h>
#include <WinSock2.h>
#include <iostream>
#include <process.h>
#include <windows.h>
#include "utility.h"
#pragma comment(lib, "ws2_32.lib")
using namespace std;
#define SERVERADDR "127.0.0.1"
#define SERVERPORT 9000
#define MSG_BUF_SIZE 128
#define CLIENT_NUM 10
unsigned int CreateClient(void *args);
int send_times = 1;
int client_num = 1;
int sendFailedTimes = 0;
int connFailedTimes = 0;
CRITICAL_SECTION mCriticalSection_Send; // 定义临界区
CRITICAL_SECTION mCriticalSection_Conn; // 定义临界区
CRITICAL_SECTION mCriticalSection_Num; // 定义临界区
// 通过在main函数中传入参数设置客户端数和发送次数
// sock_client_bulk.exe [client_num] [send_times]
int main(int argc, char **argv)
{
// cout << argc << argv[0] << endl;
client_num = CLIENT_NUM;
send_times = 2;
if (argc > 1)
{
// cout << argv[0] << endl;
client_num = atoi(argv[1]);
}
if (argc > 2)
{
send_times = atoi(argv[2]);
}
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
// printf("1");
int cnum = client_num;
char stime[64];
get_time(stime, sizeof(stime));
printf("[%s] client count %d start\r\n", stime, cnum);
InitializeCriticalSection(&mCriticalSection_Send); // 初始化
InitializeCriticalSection(&mCriticalSection_Conn); // 初始化
InitializeCriticalSection(&mCriticalSection_Num); // 初始化
// 创建客户端
for (int i = 0; i < client_num; i++)
{
// printf("1");
_beginthreadex(0, 0, CreateClient, 0, 0, 0);
// printf("1");
// Sleep(200);
}
cin.get();
return 0;
}
unsigned int CreateClient(void *args)
{
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
//定义服务器地址
SOCKADDR_IN addrServer;
addrServer.sin_addr.S_un.S_addr = inet_addr(SERVERADDR);
addrServer.sin_family = AF_INET; // 协议族 ipv4
addrServer.sin_port = htons(SERVERPORT);
char stime[64];
int ret = 0;
ret = connect(sockClient, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));
if (ret >= 0)
{
char buf[MSG_BUF_SIZE];
int i = 0;
char buf2[MSG_BUF_SIZE];
while (i < send_times)
{
sprintf(buf, "this is sock client %#x , send times %d", &sockClient, i);
// printf("%d sock %d\r\n", &sockClient, i);
// printf("%s\r\n", buf);
get_time(stime, sizeof(stime));
printf("[%s] client[%#x] start send %d\r\n", stime, &sockClient, i);
ret = send(sockClient, buf, MSG_BUF_SIZE, 0);
if (ret > 0)
{
// get_time(stime, sizeof(stime));
// printf("[%s] recv send return : %d\r\n", stime, ret);
ret = recv(sockClient, buf2, MSG_BUF_SIZE, 0);
// printf("[%s] recv from server : %s\r\n", stime, buf);
}
else
{
EnterCriticalSection(&mCriticalSection_Send); // 进入临界区
sendFailedTimes++;
LeaveCriticalSection(&mCriticalSection_Send); // 离开临界区
set_console_text_color_red();
printf("[%s] client[%#x] send failed , return value %d\r\n", stime, &sockClient, ret);
set_console_text_color_white();
}
// Sleep(1000);
i++;
}
}
else
{
EnterCriticalSection(&mCriticalSection_Conn); // 进入临界区
connFailedTimes++;
LeaveCriticalSection(&mCriticalSection_Conn); // 进入临界区
get_time(stime, sizeof(stime));
set_console_text_color_red();
printf("[%s] client[%#x] connect failed, return value %d\r\n", stime, &sockClient, ret);
set_console_text_color_white();
}
get_time(stime, sizeof(stime));
printf("[%s] client[%#x] closed\r\n", stime, &sockClient);
closesocket(sockClient);
EnterCriticalSection(&mCriticalSection_Num); // 进入临界区
client_num--;
LeaveCriticalSection(&mCriticalSection_Num); // 进入临界区
if (client_num <= 0)
{
WSACleanup();
DeleteCriticalSection(&mCriticalSection_Send); // 删除
DeleteCriticalSection(&mCriticalSection_Conn); // 删除
DeleteCriticalSection(&mCriticalSection_Num); // 删除
get_time(stime, sizeof(stime));
cout << "[" << stime << "] client work complete ,connect failed times : " << connFailedTimes << ", send failed times : " << sendFailedTimes << endl
<< "press any keys to continue..." << endl;
}
}
编译:
> g++ sock_client_bulk.cpp utility.cpp -lws2_32 -o sock_client_bulk.exe -fpermissive
运行测试:
创建100个连接,每个连接发送2次数据。
> sock_client_bulk.exe 100 2
可以反复测试,固定服务端listen的最大连接队列,调整客户端数量。
在设置服务端listen最大值为1时,客户端数为20,发送次数为10时,结果如下:
总结
- 阻塞型的socket服务端每次只能处理一个连接
- 虽然可以通过调整listen的最大连接队列值,但是依旧很容易达到客户端数量上限,导致连接或发送数据失败
utility.h、utility.cpp
// utility.h
// 获取当前时间
int get_time(char ** time);
int get_time(char * timeBuf,int bufSize);
// 设置控制台文字颜色为红色
void set_console_text_color_red();
// 恢复控制台文字为白色(默认颜色)
void set_console_text_color_white();
// utility.cpp
#include "utility.h"
#include <stdio.h>
#include <time.h>
#include <sys/timeb.h>
#include <Windows.h>
int get_time(char **t)
{
time_t timep;
time(&timep);
*t = new char[64];
struct timeb tb;
ftime(&tb);
size_t sz = strftime(*t, 64, "%Y-%m-%d %H:%M:%S", localtime(&timep));
sprintf(*t + sz, " %d", tb.millitm);
// printf("%s \r\n", t);
return 0;
}
int get_time(char *timeBuf, int bufSize)
{
if (nullptr == timeBuf)
return -1;
time_t timep;
time(&timep);
struct timeb tb;
ftime(&tb);
size_t sz = strftime(timeBuf, bufSize, "%Y-%m-%d %H:%M:%S", localtime(&timep));
sprintf(timeBuf + sz, " %d", tb.millitm);
// printf("%s \r\n", timeBuf);
return 0;
}
void set_console_text_color_red()
{
HANDLE handle;
handle = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED); //设置为红色
}
void set_console_text_color_white()
{
HANDLE handle;
handle = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); //恢复默认的白色
}