C++:socket服务端模型

首先解释下socket含义。网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。 Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。
下面我列举几个常用的服务端TCP socket实现代码

1、首先是最原始的阻塞模型。这种模型简单易懂,[以下代码基于Windows]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <winsock2 .h>
#pragma comment(lib, "ws2_32.lib")
 
int mian (int argc, char* argv []) {
    //Winsock环境初始化
    WSAData wd;
    WSAStartup (MAKEWORD (2, 2), &wd);
 
    //创建套接字
    SOCKET sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);//对于UDP协议,第二个参数填 SOCK_DGRAM
 
    //绑定前的操作,UDP可以不用绑定
    sockaddr_in addr;
    addr.sin_family = AF_INET;//需要绑定的本地地址
    addr.sin_addr.s_addr = 0;
    addr.sin_port = htons (51423);//需要绑定的本地端口
 
    //执行绑定
    bind (sock, (struct sockaddr*)&addr, sizeof (addr));
 
    //监听
    listen (sock, SOMAXCONN);
 
    while (true) {
        //如果有链接请求,则接受链接
        SOCKET session = accept (sock, NULL, NULL);
        if (INVALID_SOCKET == session) break;
 
        //接收数据
        char buf [1024];
        int len = recv (session, buf, sizeof (buf), 0);
 
        //发送数据
        send (session, buf, len, 0);
 
        //关闭链接
        closesocket (session);
    }
 
    //关闭套接字
    closesocket (sock);
 
    //Winsock环境释放
    WSACleanup ();
    return 0;
}</winsock2>


这种模型原理是,通过阻塞程序来一直读取socket数据,直到读取数据时才进行处理,否则一直处于阻塞状态。当然以上逻辑完全不可能作为服务器,估摸web体系刚建立时的程序猿都是使用这种架构+多线程实现并发模式。以上代码仅做了解即可,不用深究。

2、然后是比较原始的select模型,特殊场合使用,[以下代码基于Windows]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
 
#include <vector>
using namespace std;
 
std::vector<int> connect_sockets;
 
int mian (int argc, char* argv []) {
    //Winsock环境初始化
    WSAData wd;
    WSAStartup (MAKEWORD (2, 2), &wd);
 
    //创建套接字
    SOCKET sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);//对于UDP协议,第二个参数填 SOCK_DGRAM
 
    //绑定前的操作,UDP可以不用绑定
    sockaddr_in addr;
    addr.sin_family = AF_INET;//需要绑定的本地地址
    addr.sin_addr.s_addr = 0;
    addr.sin_port = htons (51423);//需要绑定的本地端口
 
    //执行绑定
    bind (sock, (struct sockaddr*)&addr, sizeof (addr));
 
    //监听
    listen (sock, SOMAXCONN);
 
    while (true) {
        int maxfd;
        fd_set readfds;
        //fd_set writefds;
        //fd_set exceptfds; 
        struct timeval tv;
 
        FD_ZERO (&readfds);
 
        FD_SET (sock, &readfds);
        maxfd = sock;
        //要将所有的client也加入到集合
        std::vector<int>::iterator it;
        for (it = connect_sockets.begin (); it != connect_sockets.end (); ++it) {
            int fd = *it;
            FD_SET (fd, &readfds);
            if (fd > maxfd) maxfd = fd;
        }
        maxfd++;
 
        // 等待1秒钟
        tv.tv_sec = 1;
        tv.tv_usec = 0;
 
        int count = select (maxfd, &readfds, NULL, NULL, &tv);
        if (count == 0) continue;
        if (SOCKET_ERROR == count) break;
 
        // count > 0
        if (FD_ISSET (sock, &readfds)) {
            SOCKET session = accept (sock, NULL, NULL);
            // 将fd保存到全局的一个变量中,方便下一次去出来,放入fd_set
            connect_sockets.push_back (session);
        }
 
        for (it = connect_sockets.begin (); it != connect_sockets.end ();) {
            SOCKET session = *it;
            if (FD_ISSET (session, &readfds))  // 如果session是在readfds中,说明fd有人发消息过来了,应该调用recv
            {
                // 接收数据
                char buf [1024];
                int len = recv (session, buf, sizeof (buf), 0);
 
                // 回应
                send (session, buf, len, 0);
                closesocket (session);
 
                it = connect_sockets.erase (it);
            } else {
                ++it;
            }
        }
    }
 
    //关闭套接字
    closesocket (sock);
 
    //Winsock环境释放
    WSACleanup ();
    return 0;
}

Linux的select代码和Windows的差不多,就不重写一遍了。这种模型需要定义一个fd表,然后一直不停的迭代扫描这个表;当有事件产生时,系统会给fd表置一个标识,程序扫描到标识时根据定义执行不同的操作。这种模型优点是效率高,但缺点也很明显,效率随着链接数增加而直线下降。这种模型用的非常少了,但还有一些链接少但要求实时数据更新的软件还在用。效率在特定场合比WSAAsyncSelect或epoll高,但高的这一点完全可以忽略。

3、接下来是Windows环境稍微先进些的IOCP模型,[以下代码基于Windows]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <iostream>
using namespace std;
 
#define RECV_POSTED 1001
#define SEND_POSTED 1002
 
HANDLE hCompletionPort;
typedef struct _PER_HANDLE_DATA {
    SOCKET sock;
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
 
typedef struct _PER_IO_OPERATION_DATA {
    OVERLAPPED Overlapped;
    WSABUF DataBuff [1];
    char Buff [24];
    BOOL OperationType;
} PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;
 
#define _PROGRAM_EXIT system ("pause");WSACleanup ();return 0;
 
DWORD WINAPI ServerWorkerThread (LPVOID CompletionPort) {
    DWORD dw;
    LPPER_HANDLE_DATA PerHandleData;
    LPPER_IO_OPERATION_DATA PerIoData;
    BOOL bT;
 
    while (TRUE) {
        //等待完成端口上SOCKET的完成
        cout << "等待完成端口上SOCKET的完成" << endl;
        bT = GetQueuedCompletionStatus ((HANDLE) CompletionPort, &dw, (LPDWORD) &PerHandleData, (LPOVERLAPPED *) &PerIoData, INFINITE);
 
        //检查是否有错误产生
        if (dw == 0 && (PerIoData->OperationType == RECV_POSTED || PerIoData->OperationType == SEND_POSTED)) {
            //关闭SOCKET
            cout << PerHandleData->sock << "SOCKET关闭" << endl;
            closesocket (PerHandleData->sock);
            free (PerHandleData);
            free (PerIoData);
            continue;
        }
 
        //为请求服务
        if (PerIoData->OperationType == RECV_POSTED) {
            //处理
            cout << "接收处理" << endl;
            cout << PerHandleData->sock << "SOCKET :" << PerIoData->Buff << endl;
            //回应客户端
            ZeroMemory (PerIoData->Buff, 24);
            strcpy (PerIoData->Buff, "OK");
            ZeroMemory ((LPVOID) &(PerIoData->Overlapped), sizeof (OVERLAPPED));
            PerIoData->DataBuff [0].len = 2;
            PerIoData->DataBuff [0].buf = PerIoData->Buff;
            PerIoData->OperationType = SEND_POSTED;
            WSASend (PerHandleData->sock, PerIoData->DataBuff, 1, &dw, 0, &(PerIoData->Overlapped), NULL);
        } else /*if(PerIoData->OperationType == SEND_POSTED)*/ {
            //发送时的处理
            cout << "发送处理" << endl;
            ZeroMemory ((LPVOID) &(PerIoData->Overlapped), sizeof (OVERLAPPED));
            ZeroMemory (PerIoData->Buff, 24);
            PerIoData->DataBuff [0].len = 24;
            PerIoData->DataBuff [0].buf = PerIoData->Buff;
            PerIoData->OperationType = RECV_POSTED;
            DWORD flags = 0;
            WSARecv (PerHandleData->sock, PerIoData->DataBuff, 1, &dw, &flags, &(PerIoData->Overlapped), NULL);
        }
    }
}
 
int main (int argc, char* argv []) {
    cout << "初始环境..." << endl;
    WSAData wsaData;
    if (WSAStartup (MAKEWORD (2, 2), &wsaData) != 0) {
        cout << "WSAStartup失败" << endl;
        return 0;
    }
 
    //创建一个IO完成端口
    cout << "创建一个IO完成端口" << endl;
    hCompletionPort = CreateIoCompletionPort (INVALID_HANDLE_VALUE, NULL, 0, 0);
    if (hCompletionPort == INVALID_HANDLE_VALUE) {
        cout << "创建IO完成端口失败" << endl;
        _PROGRAM_EXIT
    }
    //获取CPU数目
    SYSTEM_INFO si;
    GetSystemInfo (&si);
    //创建一定数目的工作者线程,本例中以一个处理器一个线程搭配
    for (int i = 0; i<(int) si.dwNumberOfProcessors * 2; i++) {//NumberOfProcessors
        DWORD thread_id;
        HANDLE hThread = CreateThread (NULL, 0, ServerWorkerThread, (LPVOID) hCompletionPort, 0, &thread_id);
        cout << "创建工作者线程" << i << endl;
        CloseHandle (hThread);
    }
    //创建监听SOCKET
    cout << "创建监听SOCKET" << endl;
    SOCKET sockListen = WSASocket (AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (sockListen == SOCKET_ERROR) {
        cout << "WSASocket错误" << endl;
        _PROGRAM_EXIT
    }
 
    int reuse_addr = 1;
    if (setsockopt (sockListen, SOL_SOCKET, SO_REUSEADDR, (const char *) &reuse_addr, sizeof (int)) != 0) {
        cout << "setsockopt错误" << endl;
        _PROGRAM_EXIT
    }
 
    struct sockaddr_in addrLocal;
    addrLocal.sin_family = AF_INET;
    addrLocal.sin_addr.s_addr = htonl (INADDR_ANY);
    addrLocal.sin_port = htons (9090);
    if (bind (sockListen, (struct sockaddr *)&addrLocal, sizeof (sockaddr_in)) != 0) {
        cout << "bind错误" << endl;
        _PROGRAM_EXIT
    }
    //准备监听
    cout << "准备监听" << endl;
    if (listen (sockListen, 5) != 0) {
        cout << "listen错误" << endl;
        _PROGRAM_EXIT
    }
    while (true) {
        //接收用户连接,被和完成端口关联
        SOCKET session = WSAAccept (sockListen, NULL, NULL, NULL, 0);
        LPPER_HANDLE_DATA perHandleData = (LPPER_HANDLE_DATA) malloc (sizeof (PER_HANDLE_DATA));
        if (perHandleData == NULL) continue;
        cout << "socket number " << session << "接入" << endl;
        perHandleData->sock = session;
 
        LPPER_IO_OPERATION_DATA ioperdata = (LPPER_IO_OPERATION_DATA) malloc (sizeof (PER_IO_OPERATION_DATA));
        memset (&(ioperdata->Overlapped), 0, sizeof (OVERLAPPED));
        (ioperdata->DataBuff [0]).len = 24;
        (ioperdata->DataBuff [0]).buf = ioperdata->Buff;
        ioperdata->OperationType = RECV_POSTED;
        if (ioperdata == NULL) { free (perHandleData); continue; }
        //关联
        cout << "关联SOCKET和完成端口" << endl;
        if (CreateIoCompletionPort ((HANDLE) session, hCompletionPort, (DWORD) perHandleData, 1) == NULL) {
            cout << session << "createiocompletionport错误" << endl;
            free (perHandleData);
            free (ioperdata);
            continue;
        }
        //投递接收操作
        cout << "投递接收操作" << endl;
        DWORD flags, n_recv;
        WSARecv (perHandleData->sock, ioperdata->DataBuff, 1, &n_recv, &flags, &(ioperdata->Overlapped), NULL);
    }
    _PROGRAM_EXIT
}

这种模型在比较大的链接的情况下比select效率高很多,使用多线程来并发处理访问数据。但它也有几个坑,首先是模型结构比较复杂,基本框架就是上面一堆代码;其次是这种模型通过多线程来并发处理数据,多线程的坑你们都懂得。所以这种模型我也不推荐使用。慢慢来不要急,好的模型都在后面呢。

4、接下来是先进的WSAAsyncSelect模型,[以下代码基于Windows]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#pragma comment(linker, "/subsystem:console /entry:WinMain")
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <tchar.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
 
#define PORT      10086
#define MSGSIZE   1024
#define WM_SOCKET WM_USER+1
 
//单线程异步模型通过窗口消息循环进行通讯,所以这儿需要创建一个窗口
 
//窗口消息循环
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    static SOCKET sock;
    switch (message) {
    case WM_CREATE:
        //初始化WinSock
        WSAData wd;
        WSAStartup (MAKEWORD(2, 2), &wd);
 
        //创建socket
        sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
 
        //绑定
        SOCKADDR_IN local;
        local.sin_addr.S_un.S_addr = htonl (INADDR_ANY);
        local.sin_family = AF_INET;
        local.sin_port = htons (PORT);
        bind (sock, (struct sockaddr *)&local, sizeof (local));
 
        //监听
        listen (sock, 100);
        //设置网络模型
        WSAAsyncSelect (sock, hwnd, WM_SOCKET, FD_ACCEPT);
        return 0;
    case WM_DESTROY:
        closesocket (sock);
        WSACleanup ();
        PostQuitMessage (0);
        return 0;
 
    case WM_SOCKET:
        //遇到什么错误,,这时候其实可以WSAGetLastError查看错误详情
        if (WSAGETSELECTERROR (lParam)) {
            closesocket (wParam);
            break;
        }
 
        switch (WSAGETSELECTEVENT (lParam)) {
        case FD_ACCEPT:
            //接受链接请求
            SOCKADDR_IN client;
            int iAddrSize = sizeof (client);
            SOCKET sClient = accept (wParam, (struct sockaddr *)&client, &iAddrSize);
            WSAAsyncSelect (sClient, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
            break;
        case FD_READ:
            //读取数据
            char szMessage [MSGSIZE];
            int ret = recv ((SOCKET) wParam, szMessage, MSGSIZE, 0);
            if (ret == 0 || ret == SOCKET_ERROR && WSAGetLastError () == WSAECONNRESET) {
                closesocket ((SOCKET) wParam);
            } else {
                szMessage [ret] = '\0';
                send ((SOCKET) wParam, szMessage, strlen (szMessage), 0);
                printf ("%s\n", szMessage);
            }
            break;
 
        case FD_CLOSE:
            //关闭链接
            closesocket ((SOCKET) wParam);
            break;
        }
        return 0;
    }
    //默认消息循环
    return DefWindowProc (hwnd, message, wParam, lParam);
}
 
//主函数
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
    //注册窗口类
    static TCHAR szAppName [] = _T ("AsyncSelect Model");
    HWND         hwnd;
    WNDCLASS     wndclass;
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;
    if (!RegisterClass (&wndclass)) {
        MessageBox (NULL, _T ("This program requires Windows NT!"), szAppName, MB_ICONERROR);
        return 0;
    }
 
    //创建窗口
    hwnd = CreateWindow (szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
    ShowWindow (hwnd, iCmdShow);
    UpdateWindow (hwnd);
 
    //消息处理
    MSG msg;
    while (GetMessage (&msg, NULL, 0, 0)) {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
 
    UnregisterClass (szAppName, hInstance);
    return msg.wParam;
}

看起来好像这种模型也不简单,实际上这种模型设计架构非常先进,使用单线程异步模式,通过创建一个窗口,然后使用窗口消息循环来处理网络事件,在单线程处理大量链接的同时还能不用自己管理数据结构。这种模型也有缺点(有人说这是缺点,仁者见仁智者见智)就是,一旦使用这种模型就必须得创建一个窗口。不过对于现在这么大的内存来说,创建一个窗口也不是个什么事。网络上一些不太靠谱的砖家说什么什么TCP数据包可以直接发给窗口之类的,指的就是这种模型。连外行都能说个大概(虽然完全没说到点上2333)由此可见这种模型的知名度。
除此之外,还有一种类似的模型:WSPAsyncSelect,用法和上面差不多,对程序猿来说差异仅仅为函数名不同而已,这儿就不重写一遍了。

5、接下来是epoll模型,[以下代码基于Linux]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
// inet相关的头文件
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/epoll.h>
#include <stdlib.h>
 
void set_nonblock (int sock) {
    int flags = fcntl (sock, F_GETFL, 0);
    flags |= O_NONBLOCK;
    fcntl (sock, F_SETFL, flags);
}
 
int main (int argc, char* argv []) {
    // 1. 创建socket对象
    // 第一个参数是协议族 AF_INET表示以太网
    // 第二个参数是协议类型 SOCK_STREAM TCP
    // 第三个是协议 对于以太网来说,永远都是0
    int sock = socket (AF_INET, SOCK_STREAM, 0);
    set_nonblock (sock);
 
    // Linux下使用int表示socket对象,它的地位和open出来的文件是一样的
 
    // 2.给socket指定端口
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;     // 绑定本地地址
    addr.sin_addr.s_addr = 0;
    addr.sin_port = htons (51423); // 绑定本地端口
    bind (sock, (struct sockaddr*)&addr, sizeof (addr));
 
    // 3. 启动监听
    listen (sock, 200);
 
    // 创建epoll对象
    int epollfd = epoll_create (1024);
 
    // 把sock加入到epollfd
    struct epoll_event ev;
    ev.data.fd = sock;
    ev.events = EPOLLIN;
    epoll_ctl (epollfd, EPOLL_CTL_ADD, sock, &ev);
 
    struct epoll_event* ev_out = malloc (sizeof (*ev_out) * 8);
    int count = 8;
 
    while (1) {
        //struct epoll_event ev_out[8];
        int ret = epoll_wait (epollfd, ev_out, count, 1000);
 
        // 没有socket有消息
        if (ret == 0) continue;
        if (ret == -1) {
            if (errno == EINTR) continue;
            break;
        }
 
        int i;
        for (i = 0; i<ret; ++i) {
            struct epoll_event* p = ev_out + i;
            if (p->data.fd == sock) {
                int session = accept (p->data.fd, NULL, NULL);
 
                struct epoll_event ev1;
                ev1.data.fd = session;
                ev1.events = EPOLLIN;
                epoll_ctl (epollfd, EPOLL_CTL_ADD, session, &ev1);
            } else {
                char buf [1024];
                int len = recv (p->data.fd, buf, sizeof (buf), 0);
                send (p->data.fd, buf, len, 0);
 
                close (p->data.fd);
 
            }
        }
    }
 
    // 关闭socket
    close (sock);
 
    return 0;
}

实际上在讨论epoll模型前应该先说说poll模型,但poll模型只是在select模型架构之上,极大提高select模型(网上说基于Linux的select模型最大并发链接数是1024)的并发链接数,然而这并没什么卵用因为这种模型也是迭代处理结构体消息,意味着poll模型也和select模型一样,链接数越大效率越低。所以,链接数少还能用用select,链接数一多那就epoll,这种模型已经被淘汰所以这儿不贴示例代码了。
然后正式说说epoll模型。这种模型与Windows环境下的WSAAsyncSelect模型相似,基于单线程异步模型,但它有个好处就是连窗口都给省了,消息全部通过while循环进行处理,没有事件就等待,有事件就处理,并且几乎无最大链接数限制,可以说是Linux上面最完美的模型。

6、Boost.asio阻塞模型,[以下代码跨平台]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <boost/asio.hpp>
 
using boost::asio::ip::tcp;
 
int main (int argc, char* argv []) {
    try {
        boost::asio::io_service serv;
        tcp::acceptor acpt (serv, tcp::endpoint (tcp::v4 (), 51423));
 
        while (true) {
            tcp::socket session (serv);
            acpt.accept (session);
            session.write_some ("hello world");
            session.close ();
        }
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what () << "\n";
    }
    return 0;
}

可能有人问,已经讲了各平台最完美的网络模型了,为啥还在继续?道理很简单嘛,之前的模型都不跨平台。通过编写跨平台代码可以极大减少代码对平台的相关依赖,可以很方便的将代码移植到不同的操作系统。这个可以说是优势也可以说是劣势,因为有人觉得它们开发的代码不需要跨平台,跨平台代码效率也不能达到最佳(跨平台代码本质也是通过调用平台框架模型而实现),里面大部分代码为了解决平台依赖而涉及很多中间转换过程,相比之下写跨平台代码效率比写epoll这类模型效率要低一部分。不过嘛有必要这么纠结嘛?现代计算机CPU频率这么高内存这么大,提升那几毫秒的时间也几乎看不出任何效果。还是那句话,除了在特定场合外,尽量不要写平台相关代码。
下面开始介绍介绍这个模型,哦不,框架。模型也就那样,阻塞模型,不过asio这框架用来开发网络相当方便。上面的阻塞模型代码看到了吧?一大堆不懂啥玩意的代码还都是定式,为啥不能省略之?在此环境下asio诞生了, 它的诞生不仅仅是一场革命,更是方便了广大程序猿2333
另外,asio基于boost,这个库被人们称之为“C++准标准库”,里面很多特性、类,都很可能成为C艹的标准。

7、Boost.asio异步模型,[以下代码跨平台]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>
#include <boost/asio.hpp>
 
using boost::asio::ip::tcp;
 
//会话类,每个链接都是一个会话
class session : public std::enable_shared_from_this<session> {
public:
    session (tcp::socket socket) : socket_ (std::move (socket)) {}
 
    void start () {
        do_read ();
    }
 
private:
    //读数据,异步事件代表完成
    void do_read () {
        auto self (shared_from_this ());
        socket_.async_read_some (boost::asio::buffer (data_, max_length), [this, self] (boost::system::error_code ec, std::size_t length) {
            if (!ec) do_write (length);
        });
    }
 
    //写数据,异步事件代表完成
    void do_write (std::size_t length) {
        auto self (shared_from_this ());
        boost::asio::async_write (socket_, boost::asio::buffer (data_, length), [this, self] (boost::system::error_code ec, std::size_t length) {
            if (!ec) do_read ();
        });
    }
 
    //会话句柄
    tcp::socket socket_;
    enum {
        max_length = 1024
    };
 
    //会话数据缓冲区
    char data_ [max_length];
};
 
//服务器类,用于控制会话
class server {
public:
    //初始化异步模型
    server (boost::asio::io_service& io_service, short port) : acceptor_ (io_service, tcp::endpoint (tcp::v4 (), port)), socket_ (io_service) {
        do_accept ();
    }
 
private:
    //相应链接事件
    void do_accept () {
        acceptor_.async_accept (socket_, [this] (boost::system::error_code ec) {
            if (!ec) std::make_shared<session> (std::move (socket_))->start ();
            do_accept ();
        });
    }
 
    tcp::acceptor acceptor_;
    tcp::socket socket_;
};
 
int main (int argc, char* argv []) {
    try {
        boost::asio::io_service io_service;
        server s (io_service, 8877);
        io_service.run ();
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what () << "\n";
    }
    return 0;
}

终于进入正题了,其实我想说的也就这个模型,之前的模型看看就得了O(∩_∩)O2333~。asio的异步网络模型使用了先进的C++11规范,通过lambda表达式实现异步方法调用,感觉用着挺爽的,不用手工管理session,另外对于每个session封装成了一个类,可以自己手工管理结构体属性等,用起来比WSAAsyncSelect、epoll更爽,且代码更加清晰直观,乃服务端模型上上之选。代码逻辑太过简单我都不知道怎么写注释了。
以上服务端模型基于TCP。由于麻烦我这儿就不写UDP了反正这东西也没链接写起来也简单

发布者

fawdlstty

又一只萌萌哒程序猿~~

发表评论

电子邮件地址不会被公开。 必填项已用*标注