程序员薪水有高有低,有的人一个月可能拿30K、50K,有的人可能只有2K、3K。同样有五年工作经验的程序员,可能一个人每月拿20K,一个拿5K。是什么因素导致了这种差异?我特意总结了容易导致薪水低的九大行为表现,避开这些大坑,你就离高薪不远了。

1. 习惯即刻回报

他不懂得只有春天播种,秋天才会有收获。刚刚付出一点点,甚至还没有付出,就想要得到回报。技术刚刚掌握,能一边百度一边干活了就觉得该拿到多少多少钱了。找工作先想着多少多少钱,入职了没干几个月就想着要加多少多少钱,干了没几个月,薪水要是没涨,就放弃了,准备通过跳槽加薪,不跳槽的话,往往也会因为没加薪而牢骚满腹,工作敷衍了事。

一个程序员的价值,是通过他带给公司的价值体现的。先给公司带来了价值,然后才会反过来在薪水上体现出自己的价值。公司都很现实,很少会为你的潜力买单,在你还没有体现出价值时就给你很高的薪水。

在生活和工作中,一定要懂得付出,不要那么急功近利,马上想得到回报。天下没有白吃的午餐,你想获得什么,就得先付出什么。唯有种下种子,然后浇水、施肥、除草、杀虫,然后才能等来收获。

2. 缺乏学习热情

很少有哪个岗位的人像程序员这样需要持续不断的学习,软件开发的技术日新月异,而每一项技术又往往博大精深,不持续、深入钻研是很难掌握的,更别谈精通了。如果你对一项技术不能深刻理解熟练应用,表现出来的水准仅仅是能干活、还行,那很难说会有公司愿意为“还行”付出大的代价,只有脱颖而出,才可能备受重视。

假如你对学习、掌握、精通技术没有兴趣,面对不断涌现的新语言新技术新框架没有学习欲望,那但就软件开发这个工作而言,你不但眼下不太可能拿到高薪,将来也不会。在这样一个快速变化的时代,只有不断地学习才不会被抛弃。

3. 不够努力

虽然我们都知道努力学习可以改变我们的技能水平,持续努力不懈坚持可以让自己有所建树,可还是有很多人浅尝辄止,三天打鱼两天晒网,搞两下能Run就放下了。

人和人在聪明才智上的差距并没有想象中大,甚至很多时候,从大多数人的努力程度之低来看,根本还轮不到拼天赋。如果两个人的实力半斤八两的话,热情工作努力坚持的人,一定比较容易成功。

4. 畏难

做事拈轻怕重,不愿挑战。殊不知能力就是在不断挑战不断突破自己的过程中历练出来的。在一个公司里面,经常承担高难度任务的程序员,一定是成长比较快的,薪水增长也一定是比较快的。越是困难的事情,越能体现出个人价值,也越能带给个人成长。

万事起头难,不要害怕困难。事情做不好往往不是因为没有能力,大都是由于缺乏恒心。只要不怕困难,坚持前行,一定会有不一样的收获。

事业就像女人,谁去追求,谁就能得手。金钱也一样。

5. 缺乏责任心

工作上不管什么事儿,反正不是自己的事儿,缺乏责任心,干好干不好都无所谓,对交付承诺、对产品质量都不在意,没什么事儿能让他上心。

一个人的责任心如何,决定着他在工作中的态度,决定着其事业的好坏和成败。如果一个人没有责任心,即使他有再大的能耐,也不一定能做出好的成绩来。

6. 消极,抱怨

工作稍有不顺,就怨气沸腾,这个怎么怎么样,那个怎么怎么样,而我怎么就这样,任务不公平,资源不公平,那谁谁不支持我,那谁谁不配合……

抱怨不能使事情变好,反之,它会让负面情绪蔓延,蚕食你的精力和时间,让你产出更低。成功者永不抱怨,抱怨者永不成功。立刻停止抱怨,早一分钟停止,你就离目标近一分钟。

7. 没有时间管理观念

每个人的一天都只有24小时,人和人的差别就在于如何利用时间上。

有的人每周都有目标,每天都有计划,早上起来会想今天要做的几件重要的事,晚上会回顾今天完成的事,总结干成了什么干坏了什么,还会有计划的学习新知识新技能,这样日积月累不断坚持,每一天都是高效的,每一天都朝着更丰富更完美的自己前进。

而有的人则漫无目的,走哪算哪,到了公司,上午基本做不成事儿,到下午了还不知道要做什么,晚上也发愁如何消磨时间……

8. 为薪水工作

虽然工作的一大目的是获取薪水,养活自己以及供给家庭所需;但是,这只是工作最直接的报偿,同时也是最低级的目标。

如果我们为薪水而工作,将注定我们是短视的,也将注定受到最深的伤害。假如你看不到工资以外的东西,斤斤计较于薪水、福利、职位等,那外界的些微风吹草动就可能让你像浮萍一样飘来荡去,你很快就会失去平衡,失去信心,失去热情,失去平和,进而在工作时总是采取一种应付了事的态度,能少做就少做,能躲避就躲避,觉得只要对得起自己的那份薪水就成了。长此以往,你追求的高薪水反倒得不到。

我们进入一个公司工作,是为了自己,不是薪水也不是别人,比薪水更重要的,是成长和成就自己的机会。我们一定要明白,公司、企业、组织,都是我们锻炼自己、修炼自我的平台,我们不是为薪水工作、不是为老板工作、不是为家人工作,是为实现自我而工作,是为更完美的自己而工作。

唯有志存高远,方能风行天下。

9. 其实不喜欢软件开发

有一部分人从事软件开发工作,并不是因为喜欢,也没有干着干着从不喜欢变成喜欢。他们可能是喜欢软件开发附带的高薪水——平均薪水比其他行业高。人做一件自己不喜欢的事情时,心理上没有亲近感,不会想着怎样把事情做得更好,往往是差不多就成了,不太可能有精益求精积极向上的追求。因为在做不喜欢的事情时,情感上是拒绝的,情绪上是想逃离的,总想着早点儿完事儿拉倒,每一天去单位时不是充满期待,而是各种担忧、烦躁、畏惧,到了单位,稍有困难或不顺心,就会消极、抱怨、抵触、拒绝……

做喜欢的事,能最大可能发挥一个人的潜能和热情,会最快速地通向成功成就自己。而做不喜欢的事,一开始就注定了事倍功半,最后也往往会是痛苦不堪或半途而废。

转载自:

月薪3万的程序员都避开了哪些坑

本文通过一个实例来讲解如何使用 WinDbg 来调试 Windows CriticalSection 死锁的问题。

演示示例

这里有一个关键区锁死问题的程序,运行之后依次点击“CS 锁死”按钮、右上角退出按钮,程序就会卡死。

对于眼下的这个问题,界面完全失去响应,这说明负责消息处理的 UI 线程阻塞了。

对于几乎所有的 windows GUI 程序,编号为 0 的初始线程就是 UI 线程,windows 发现该界面一段时间没有消息响应之后就会在标题后面加上“(未响应)”。

阅读全文 »

在进行数据库开发时,我们可能需要写很多存储过程,本文提供一个存储过程的模板,通过该模板可以简化存储过程的开发。

阅读全文 »

写在前面

前面的文章已经介绍了套接字 I/O 的同步模型、WSAAsyncSelect模型、WSAEventSelect模型,到目前为止套接字I/O还剩下2个模型没有介绍:重叠模型,完成端口模型。

如果程序对性能和并发要求不高,可以使用前面介绍的WSAEventSelect模型;如果对性能和并发有要求,可以使用本文介绍的完成端口模型。

因为完成端口模型是基于重叠模型的,且在易用性、可伸缩性等方面都高于重叠模型,在一般选择重叠模型的场合,都可以用完成端口模型来替代,强烈建议使用完成端口模型。

“完成端口模型”是 Windows 系统上面套接字 I/O 的终极模型,可以用它代替前面的所有模型。如果对完成端口模型有一个好的封装,基本上可以“一招鲜,吃遍天”,免去重复造轮子的麻烦。所以这里对完成端口的模型的介绍和比前面的几篇篇幅更长,示例代码也更加复杂和全面。

Reactor和Proactor

在网络编程中,我们常听到的两种I/O多路复用的模式:reactor 和 proactor。
对于这两种模式的区别通俗来说就是:

1
2
Reactor: 能收了你跟我说一声。
Proactor: 你帮我最多收十个字节,收好了跟我说一声。

Windows提供的完成端口模型就是Proactor模式;而对于Linux系统,由于没有操作系统层面的支持,只能使用Reactor模式,如epoll等。

完成端口模型介绍

完成端口模型说白了就是,您要做什么事情(如接收连接AcceptEx、发送数据WSASend、接收数据WSARecv、连接服务端ConnectEx),您告诉我,我做完了通知您。这里的“我”指的是操作系统,“您”指的是应用程序。
如应用程序需要接收其他端发来的数据,可以调用WSARecv,并指定接收数据的缓冲区及大小,等其他端发来数据时,操作系统自动将数据放入到应用程序指定的缓冲区中,然后通知应用程序数据来啦。 这个和WSAEventSelect模型最大的不同就是,WSAEventSelect模型只是通知程序数据来了,并没有将数据接收,还需要程序调用recv来接收数据。

完成端口创建和绑定

1
2
3
4
5
6
HANDLE WINAPI CreateIoCompletionPort(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE ExistingCompletionPort,
_In_ ULONG_PTR CompletionKey,
_In_ DWORD NumberOfConcurrentThreads
);

CreateIoCompletionPort这个函数比较特殊,根据传入的参数不同,它可以实现2个功能:创建一个完成端口;将完成端口和设备(套接字)相绑定。一般对该函数进行如下封装来实现这2个功能:

1
2
3
4
5
6
7
8
HANDLE CreateNewCompletionPort() {
return CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
}

BOOL AssociateDeviceWithCompletionPort(HANDLE completion_port, HANDLE device, DWORD completion_key) {
HANDLE h = CreateIoCompletionPort(device, completion_port, completion_key, 0);
return (h == completion_port);
}

线程池

前面说到了,完成端口模型就是应用程序等着操作系统把事情做完了通知它。那既然是的操作肯定是阻塞住了的,所以不能在主线程中,我们需要启动子线程去等。可是启动多个子线程去了,一个连接一个线程吗?我们知道,线程越多,占用的系统资源也就越多,而且线程的切换也是消耗CPU时间的。所以线程不是越多越好,这里有一个经验法则就是:线程数量 = CPU数量 * 2

线程数量和CPU数量相同是最理想的环境,这样免去了CPU在各个线程之间切换,但现实情况下,难免某些线程执行某些任务耗时较长,导致CPU将时间片从该线程分拨出去。所以这里用CPU数量乘以2,最大限度的利用CPU资源。这也是完成端口的目标,即最大限度的利用CPU资源。

获取CPU数量的方式:

1
2
3
4
5
int GetNumberOfProcesser() {
SYSTEM_INFO si;
GetSystemInfo(&si);
return si.dwNumberOfProcessors;
}

AcceptEx等

1
2
3
4
5
6
7
8
9
10
BOOL AcceptEx(
_In_ SOCKET sListenSocket,
_In_ SOCKET sAcceptSocket,
_In_ PVOID lpOutputBuffer,
_In_ DWORD dwReceiveDataLength,
_In_ DWORD dwLocalAddressLength,
_In_ DWORD dwRemoteAddressLength,
_Out_ LPDWORD lpdwBytesReceived,
_In_ LPOVERLAPPED lpOverlapped
);

AcceptEx和WSARecv、WSASend、ConnectEx等函数类似,最后一个参数都是LPOVERLAPPED,需要调用者提供一个重叠结构。
但这个AcceptEx、ConnectEx等函数比较特别,他们是微软专门在Windows操作系统里面提供的扩展函数,不是在Winsock2标准里面提供的,是微软为了方便使用重叠I/O机制,额外提供的一些函数。

以AcceptEx为例,微软的实现是通过mswsock.dll中提供的,所以我们可以通过静态链接mswsock.lib来直接调用AcceptEx。但不推荐使用这种方式,因为每次直接调用AcceptEx时,Service Provider都得要通过WSAIoctl()获取一次该函数指针,这样效率比较低。所以我们一般都是在直接代码中先获取到这个函数指针,并保存下来,后面直接使用这个函数指针就好了。

获取AcceptEx等函数的指针的方式:

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
LPFN_ACCEPTEX GetAcceptExFnPointer(SOCKET s)
{
LPFN_ACCEPTEX fn = NULL;
GUID GuidAcceptEx = WSAID_ACCEPTEX;
DWORD bytes = 0;

if (SOCKET_ERROR == WSAIoctl(
s,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidAcceptEx,
sizeof(GuidAcceptEx),
&fn,
sizeof(fn),
&bytes,
NULL,
NULL)) {
return NULL;
}
return fn;
}

LPFN_CONNECTEX GetConnectExFnPointer(SOCKET s)
{
LPFN_CONNECTEX fn = NULL;
GUID GuidConnectEx = WSAID_CONNECTEX;
DWORD bytes = 0;

if (SOCKET_ERROR == WSAIoctl(
s,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidConnectEx,
sizeof(GuidConnectEx),
&fn,
sizeof(fn),
&bytes,
NULL,
NULL)) {
return NULL;
}
return fn;
}

LPFN_GETACCEPTEXSOCKADDRS GetAcceptExSockAddrsFnPointer(SOCKET s)
{
LPFN_GETACCEPTEXSOCKADDRS fn = NULL;
GUID GuidGetAcceptExSockAddrs = WSAID_GETACCEPTEXSOCKADDRS;
DWORD bytes = 0;

if (SOCKET_ERROR == WSAIoctl(
s,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidGetAcceptExSockAddrs,
sizeof(GuidGetAcceptExSockAddrs),
&fn,
sizeof(fn),
&bytes,
NULL,
NULL)) {
return NULL;
}
return fn;
}

GetQueuedCompletionStatus

函数原型如下:

1
2
3
4
5
6
7
BOOL WINAPI GetQueuedCompletionStatus(
_In_ HANDLE CompletionPort,
_Out_ LPDWORD lpNumberOfBytes,
_Out_ PULONG_PTR lpCompletionKey,
_Out_ LPOVERLAPPED *lpOverlapped,
_In_ DWORD dwMilliseconds
);

前面说到了,完成端口模型需要在子线程中等待操作系统做完事情之后的通知。而GetQueuedCompletionStatus函数就是用来等待这个通知的。通过该函数可以获取的本次传输的字节数lpNumberOfBytes、一个用户绑定在套接字上的自定义整数lpCompletionKey、用户调用WSASend等函数时指定的OVERLAPPED结构的指针lpOverlapped

我们比较关注的是lpCompletionKey、lpOverlapped这2个参数:
lpCompletionKey是调用CreateIoCompletionPort函数绑定完成端口和套接字时指定的,每个套接字(SOCKET)对应一个lpCompletionKey。lpCompletionKey可以是包括指针在内的任何整数。

lpOverlapped是每次调用WSASend等函数时指定的,每一次操作(也就是每一次调用,如WSASend, WSARecv, AcceptEx, ConnectEx)对应的lpOverlapped都不一样,所以一次操作对应一个lpOverlapped。一个SOCKET可以有多次操作,多以对应多个lpOverlapped。

对应关系如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+-----------+           +--------------------+
| | 1 --> 1 | |
| SOCKET +-----------> lpCompletionKey |
| | | |
+-----+-----+ +--------------------+
|
|1 --> n
|
+-----v------------------+
| |
| Send,Recv,Accept... |
| |
+-----+------------------+
|
|1 --> 1
|
+-----v------------------+
| |
| lpOverlapped |
| |
+------------------------+

注:示例代码中的PER_SOCKET_CONTEXT结构对应图中的SOCKET,PER_IO_CONTEXT结构对应图中的lpOverlapped。知道这个对理解示例代码会有很大的帮助。

Windows还提供了一个辅助宏CONTAINING_RECORD,该宏可以根据结构体中的某成员的地址来推算出该结构体整体的地址。
知道了这个功能,我们就可以在lpOverlapped参数上做文章了(扩展),具体见示例。

上面对完成端口模型只做了一个简单的介绍,关于完成端口的详细介绍可以参考《windows核心编程 第5版》 10.3节。

CONTAINING_RECORD宏的实现原理

该宏的作用就是:根据结构体中的某成员的地址来推算出该结构体整体的地址,相当于一个万能公式。

见: 如何通过结构体成员地址获取父地址

示例

示例代码实现如下功能:

  1. 服务端和客户端都使用完成端口模型来实现。
  2. 服务端和客户端之间通过发送消息来模拟TCP的三次握手机制。

3.1 辅助函数

iocp.h和iocp.cpp中实现了IOCP相关的结构体定义和一些通用的辅助函数:
iocp.h

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
// iocp.h
#ifndef IOCP_H_
#define IOCP_H_

#include <winsock2.h>
#include <MSWSock.h>
#include <vector>

#define MAX_BUFFER_LEN 8192
#define EXIT_CODE 0

namespace IOCP {

typedef enum _OPERATION_TYPE
{
ACCEPT_POSTED,
CONNECT_POSTED,
SEND_POSTED,
RECV_POSTED,
NULL_POSTED
}OPERATION_TYPE;


typedef struct _PER_IO_CONTEXT
{
OVERLAPPED overlapped;
SOCKET socket;
WSABUF wsa_buffer;
char buffer[MAX_BUFFER_LEN];
OPERATION_TYPE operation_type;


_PER_IO_CONTEXT() {
ZeroMemory(&overlapped, sizeof(overlapped));
ZeroMemory(buffer, MAX_BUFFER_LEN);
socket = INVALID_SOCKET;
wsa_buffer.buf = buffer;
wsa_buffer.len = MAX_BUFFER_LEN;
operation_type = NULL_POSTED;
}

~_PER_IO_CONTEXT() {
if (socket != INVALID_SOCKET) {
closesocket(socket);
socket = INVALID_SOCKET;
}
}

void ResetBuffer() {
ZeroMemory(buffer, MAX_BUFFER_LEN);
}

} PER_IO_CONTEXT;



typedef struct _PER_SOCKET_CONTEXT {
SOCKET socket;
SOCKADDR_IN client_addr;
std::vector<_PER_IO_CONTEXT*> io_ctx_array;

_PER_SOCKET_CONTEXT() {
socket = INVALID_SOCKET;
memset(&client_addr, 0, sizeof(client_addr));
}

~_PER_SOCKET_CONTEXT()
{
if (socket != INVALID_SOCKET) {
closesocket(socket);
socket = INVALID_SOCKET;
}

for (size_t i = 0; i < io_ctx_array.size(); i++) {
delete io_ctx_array[i];
}
io_ctx_array.clear();
}


_PER_IO_CONTEXT* GetNewIoContext() {
_PER_IO_CONTEXT* p = new _PER_IO_CONTEXT;

io_ctx_array.push_back(p);

return p;
}

void RemoveContext(_PER_IO_CONTEXT* pContext) {
for (std::vector<_PER_IO_CONTEXT*>::iterator it = io_ctx_array.begin();
it != io_ctx_array.end(); it++) {
if (pContext == *it) {
delete pContext;
pContext = NULL;
io_ctx_array.erase(it);
break;
}
}
}
} PER_SOCKET_CONTEXT;

int GetNumberOfProcesser();
HANDLE CreateNewCompletionPort();
BOOL AssociateDeviceWithCompletionPort(HANDLE completion_port, HANDLE device, DWORD completion_key);

LPFN_ACCEPTEX GetAcceptExFnPointer(SOCKET s);
LPFN_CONNECTEX GetConnectExFnPointer(SOCKET s);
LPFN_GETACCEPTEXSOCKADDRS GetAcceptExSockAddrsFnPointer(SOCKET s);
};

#endif // IOCP_H_

iocp.cpp

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
// iocp.cpp
#include "iocp.h"

namespace IOCP {

int GetNumberOfProcesser() {
SYSTEM_INFO si;
GetSystemInfo(&si);
return si.dwNumberOfProcessors;
}

HANDLE CreateNewCompletionPort() {
return CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
}

BOOL AssociateDeviceWithCompletionPort(HANDLE completion_port, HANDLE device, DWORD completion_key) {
HANDLE h = CreateIoCompletionPort(device, completion_port, completion_key, 0);
return (h == completion_port);
}


LPFN_ACCEPTEX GetAcceptExFnPointer(SOCKET s) {
LPFN_ACCEPTEX fn = NULL;
GUID GuidAcceptEx = WSAID_ACCEPTEX;
DWORD bytes = 0;

if (SOCKET_ERROR == WSAIoctl(
s,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidAcceptEx,
sizeof(GuidAcceptEx),
&fn,
sizeof(fn),
&bytes,
NULL,
NULL)) {
return NULL;
}
return fn;
}

LPFN_CONNECTEX GetConnectExFnPointer(SOCKET s) {
LPFN_CONNECTEX fn = NULL;
GUID GuidConnectEx = WSAID_CONNECTEX;
DWORD bytes = 0;

if (SOCKET_ERROR == WSAIoctl(
s,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidConnectEx,
sizeof(GuidConnectEx),
&fn,
sizeof(fn),
&bytes,
NULL,
NULL)) {
return NULL;
}
return fn;
}

LPFN_GETACCEPTEXSOCKADDRS GetAcceptExSockAddrsFnPointer(SOCKET s) {
LPFN_GETACCEPTEXSOCKADDRS fn = NULL;
GUID GuidGetAcceptExSockAddrs = WSAID_GETACCEPTEXSOCKADDRS;
DWORD bytes = 0;

if (SOCKET_ERROR == WSAIoctl(
s,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidGetAcceptExSockAddrs,
sizeof(GuidGetAcceptExSockAddrs),
&fn,
sizeof(fn),
&bytes,
NULL,
NULL)) {
return NULL;
}
return fn;
}
}

3.1 服务端

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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#include <winsock2.h>
#include <iostream>
#include <assert.h>
#include <vector>
#include <process.h>
#include "iocp.h"

using namespace std;

const u_short kPort = 10001;
const std::string kSYN = "(SYN) hello server, I'm client. Can you hear me?";
const std::string kSYN_ACK = "(SYN+ACK) hello client, I'm server. I can hear you, can you hear me?";
const std::string kACK = "(ACK) hello server, I'm client. I can hear you!";

#pragma comment(lib, "Ws2_32.lib")

HANDLE g_IOCP = INVALID_HANDLE_VALUE;
HANDLE g_exit = NULL;

int g_work_thread_num = 0;
HANDLE *g_work_threads = NULL;

IOCP::PER_SOCKET_CONTEXT *g_listen_ctx = NULL;

CRITICAL_SECTION g_cs_socket_ctx_array;
std::vector<IOCP::PER_SOCKET_CONTEXT*> g_socket_ctx_array;

LPFN_ACCEPTEX g_AcceptExFn = NULL;
LPFN_GETACCEPTEXSOCKADDRS g_AcceptExSockAddrsFn = NULL;

// 管理g_socket_ctx_array
//
void AddSocketContext(IOCP::PER_SOCKET_CONTEXT *socket_ctx) {
EnterCriticalSection(&g_cs_socket_ctx_array);
g_socket_ctx_array.push_back(socket_ctx);
LeaveCriticalSection(&g_cs_socket_ctx_array);
}

void RemoveSocketContext(IOCP::PER_SOCKET_CONTEXT *socket_ctx) {
EnterCriticalSection(&g_cs_socket_ctx_array);
for (std::vector<IOCP::PER_SOCKET_CONTEXT*>::iterator it = g_socket_ctx_array.begin(); it != g_socket_ctx_array.end(); it++) {
if (*it == socket_ctx) {
delete *it;
g_socket_ctx_array.erase(it);
break;
}
}
LeaveCriticalSection(&g_cs_socket_ctx_array);
}

void ClearSocketContextArray() {
EnterCriticalSection(&g_cs_socket_ctx_array);
for (std::vector<IOCP::PER_SOCKET_CONTEXT*>::iterator it = g_socket_ctx_array.begin(); it != g_socket_ctx_array.end(); it++) {
closesocket((*it)->socket);
delete *it;
}
g_socket_ctx_array.clear();
LeaveCriticalSection(&g_cs_socket_ctx_array);
}

// 发送Accept、Recv、Send请求
//
bool PostAccept(IOCP::PER_IO_CONTEXT* io_ctx) {
if (io_ctx == NULL)
return false;

io_ctx->operation_type = IOCP::ACCEPT_POSTED;
io_ctx->ResetBuffer();
io_ctx->socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);

if (io_ctx->socket == INVALID_SOCKET) {
printf("WSASocket failed with code: %d\n", WSAGetLastError());
return false;
}

DWORD bytes = 0;
if (g_AcceptExFn(g_listen_ctx->socket,
io_ctx->socket,
io_ctx->wsa_buffer.buf,
0,
sizeof(SOCKADDR_IN) + 16,
sizeof(SOCKADDR_IN) + 16,
&bytes,
&io_ctx->overlapped) == FALSE) {
int gle = WSAGetLastError();
if (gle != WSA_IO_PENDING) {
printf("AcceptEx failed with code: %d\n", gle);
return false;
}
}

return true;
}

bool PostRecv(IOCP::PER_IO_CONTEXT* io_ctx) {
if (io_ctx == NULL)
return false;

io_ctx->operation_type = IOCP::RECV_POSTED;
io_ctx->ResetBuffer();

DWORD recv_bytes = 0;
DWORD flags = 0;
int ret = WSARecv(io_ctx->socket, &io_ctx->wsa_buffer, 1, &recv_bytes, &flags, &io_ctx->overlapped, NULL);
if (ret == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
return false;
}

return true;
}

bool PostSend(IOCP::PER_IO_CONTEXT* io_ctx, const char* msg, int msg_len) {
if (io_ctx == NULL)
return false;

io_ctx->operation_type = IOCP::SEND_POSTED;
memcpy(io_ctx->wsa_buffer.buf, msg, msg_len);
io_ctx->wsa_buffer.len = msg_len;

DWORD sent_bytes = 0;
int ret = WSASend(io_ctx->socket, &io_ctx->wsa_buffer, 1, &sent_bytes, 0, &io_ctx->overlapped, NULL);
if (ret == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
return false;
}

return true;
}

// 处理Accept、Recv、Send完成之后的通知
//
bool DoAccept(IOCP::PER_SOCKET_CONTEXT *socket_ctx, IOCP::PER_IO_CONTEXT *io_ctx) {
SOCKADDR_IN* ClientAddr = NULL;
SOCKADDR_IN* LocalAddr = NULL;
int remoteLen = sizeof(SOCKADDR_IN);
int localLen = sizeof(SOCKADDR_IN);

g_AcceptExSockAddrsFn(io_ctx->wsa_buffer.buf, io_ctx->wsa_buffer.len - ((sizeof(SOCKADDR_IN) + 16) * 2),
sizeof(SOCKADDR_IN) + 16, sizeof(SOCKADDR_IN) + 16, (LPSOCKADDR*)&LocalAddr, &localLen, (LPSOCKADDR*)&ClientAddr, &remoteLen);

printf("* new connection(%s:%d): %s\n", inet_ntoa(ClientAddr->sin_addr), ntohs(ClientAddr->sin_port), io_ctx->wsa_buffer.buf);

// 此次创建一个新的PER_SOCKET_CONTEXT,之前老的PER_SOCKET_CONTEXT继续用作接收客户端连接
//
IOCP::PER_SOCKET_CONTEXT *new_socket_ctx = new IOCP::PER_SOCKET_CONTEXT();
new_socket_ctx->socket = io_ctx->socket;

if (!IOCP::AssociateDeviceWithCompletionPort(g_IOCP, (HANDLE)new_socket_ctx->socket, (DWORD)new_socket_ctx)) {
printf("AssociateDeviceWithCompletionPort failed\n");
delete new_socket_ctx;
new_socket_ctx = NULL;
return false;
}

AddSocketContext(new_socket_ctx);

// post recv
IOCP::PER_IO_CONTEXT *new_io_ctx = new_socket_ctx->GetNewIoContext();
new_io_ctx->socket = new_socket_ctx->socket;
if (!PostRecv(new_io_ctx)) {
printf("PostRecv failed\n");
return false;
}

// post new accept
if (!PostAccept(io_ctx)) {
printf("PostAccept failed\n");
return false;
}

return true;
}

bool DoRecv(IOCP::PER_SOCKET_CONTEXT *socket_ctx, IOCP::PER_IO_CONTEXT *io_ctx) {
printf("recv: %s\n", io_ctx->wsa_buffer.buf);

if (strcmp(io_ctx->wsa_buffer.buf, kSYN.c_str()) == 0) {
// SYN+ACK
IOCP::PER_IO_CONTEXT * new_io_ctx = socket_ctx->GetNewIoContext();
new_io_ctx->socket = socket_ctx->socket;

if (!PostSend(new_io_ctx, kSYN_ACK.c_str(), kSYN_ACK.length())) {
printf("PostSend failed\n");
return false;
}
}

// post new recv
if (!PostRecv(io_ctx)) {
printf("PostRecv failed\n");
return false;
}

return true;
}

bool DoSend(IOCP::PER_SOCKET_CONTEXT *socket_ctx, IOCP::PER_IO_CONTEXT *io_ctx) {
printf("send: %s\n", io_ctx->wsa_buffer.buf);
return true;
}

// 工作线程
unsigned int __stdcall WorkThreadProc(void *arg) {
DWORD transferred_bytes = 0;
IOCP::PER_SOCKET_CONTEXT *socket_ctx = NULL;
OVERLAPPED *overlapped = NULL;
DWORD gle;

while (WaitForSingleObject(g_exit, 0) != WAIT_OBJECT_0) {
BOOL ret = GetQueuedCompletionStatus(g_IOCP, &transferred_bytes, (PULONG_PTR)&socket_ctx, &overlapped, INFINITE);
gle = GetLastError();

if (socket_ctx == EXIT_CODE) {
break;
}

if (ret == FALSE) {
if (gle == WAIT_TIMEOUT) {
continue;
}
else if (gle == ERROR_NETNAME_DELETED) {
printf("client exit\n");

RemoveSocketContext(socket_ctx);

continue;
}
else {
RemoveSocketContext(socket_ctx);
break;
}
}
else {
// http://blog.csdn.net/china_jeffery/article/details/78801331
IOCP::PER_IO_CONTEXT *io_ctx = CONTAINING_RECORD(overlapped, IOCP::PER_IO_CONTEXT, overlapped);

if ((transferred_bytes == 0) && (io_ctx->operation_type == IOCP::RECV_POSTED || io_ctx->operation_type == IOCP::SEND_POSTED)) {
printf("client disconnect\n");
RemoveSocketContext(socket_ctx);
continue;
}

switch (io_ctx->operation_type)
{
case IOCP::ACCEPT_POSTED:
DoAccept(socket_ctx, io_ctx);

break;
case IOCP::RECV_POSTED:
DoRecv(socket_ctx, io_ctx);

break;
case IOCP::SEND_POSTED:
DoSend(socket_ctx, io_ctx);

break;
default:
assert(false);
}
}
}
return 0;
}

int main()
{
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);

do
{
InitializeCriticalSection(&g_cs_socket_ctx_array);
g_IOCP = IOCP::CreateNewCompletionPort();
g_exit = CreateEvent(NULL, FALSE, FALSE, NULL);

g_work_thread_num = IOCP::GetNumberOfProcesser() * 2;

g_work_threads = new HANDLE[g_work_thread_num];
for (int i = 0; i < g_work_thread_num; i++) {
g_work_threads[i] = (HANDLE)_beginthreadex(NULL, 0, WorkThreadProc, NULL, 0, NULL);
}

g_listen_ctx = new IOCP::PER_SOCKET_CONTEXT;
g_listen_ctx->socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

if (!IOCP::AssociateDeviceWithCompletionPort(g_IOCP, (HANDLE)g_listen_ctx->socket, (DWORD)g_listen_ctx)) {
printf("AssociateDeviceWithCompletionPort failed with code: %d\n", GetLastError());
break;
}

struct sockaddr_in addr = { 0 };
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(kPort);
if (bind(g_listen_ctx->socket, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)) == SOCKET_ERROR) {
printf("bind failed with code: %d\n", WSAGetLastError());
break;
}

if (listen(g_listen_ctx->socket, SOMAXCONN) == SOCKET_ERROR) {
printf("listen failed with code: %d\n", WSAGetLastError());
break;
}

g_AcceptExFn = IOCP::GetAcceptExFnPointer(g_listen_ctx->socket);
if (g_AcceptExFn == NULL) {
printf("GetAcceptExFnPointer failed\n");
break;
}

g_AcceptExSockAddrsFn = IOCP::GetAcceptExSockAddrsFnPointer(g_listen_ctx->socket);
if (g_AcceptExSockAddrsFn == NULL) {
printf("GetAcceptExSockAddrsFnPointer failed\n");
break;
}

int i = 0;
for (; i < 10; i++) {
IOCP::PER_IO_CONTEXT *io_ctx = g_listen_ctx->GetNewIoContext();
if (PostAccept(io_ctx) == FALSE) {
break;
}
}
if(i != 10)
break;


} while (FALSE);


printf("\npress any ket to stop server...\n");
getchar();

SetEvent(g_exit);
for (int i = 0; i < g_work_thread_num; i++) {
PostQueuedCompletionStatus(g_IOCP, 0, (DWORD)EXIT_CODE, NULL);
}
WaitForMultipleObjects(g_work_thread_num, g_work_threads, TRUE, INFINITE);

ClearSocketContextArray();

printf("\npress any ket to exit...\n");
getchar();

DeleteCriticalSection(&g_cs_socket_ctx_array);
WSACleanup();
return 0;
}

3.2 客户端

客户端代码和服务端类似,唯一需要注意的是ConnectEx函数调用之前,需要将SOCKET进行bind操作。

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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#include <winsock2.h>
#include <iostream>
#include <assert.h>
#include <vector>
#include <process.h>
#include "iocp.h"

using namespace std;

const std::string kIP = "127.0.0.1";
const u_short kPort = 10001;

const std::string kSYN = "(SYN) hello server, I'm client. Can you hear me?";
const std::string kSYN_ACK = "(SYN+ACK) hello client, I'm server. I can hear you, can you hear me?";
const std::string kACK = "(ACK) hello server, I'm client. I can hear you!";

#pragma comment(lib, "Ws2_32.lib")

HANDLE g_IOCP = INVALID_HANDLE_VALUE;
HANDLE g_exit = NULL;

int g_work_thread_num = 0;
HANDLE *g_work_threads = NULL;

IOCP::PER_SOCKET_CONTEXT *g_client_ctx = NULL;


LPFN_CONNECTEX g_ConnectExFn = NULL;


bool PostConnect(IOCP::PER_IO_CONTEXT* io_ctx, const std::string &ip, int port) {
if (io_ctx == NULL)
return false;
io_ctx->operation_type = IOCP::CONNECT_POSTED;
io_ctx->ResetBuffer();

// ConnectEx requires the socket to be initially bound.
struct sockaddr_in addr0 = { 0 };
addr0.sin_family = AF_INET;
addr0.sin_addr.s_addr = INADDR_ANY;
addr0.sin_port = 0;
int ret = bind(io_ctx->socket, (SOCKADDR*)&addr0, sizeof(addr0));
if (ret != 0) {
printf("bind failed: %d\n", WSAGetLastError());
return false;
}

struct sockaddr_in addr1 = { 0 };
addr1.sin_family = AF_INET;
addr1.sin_addr.s_addr = inet_addr(ip.c_str());
addr1.sin_port = htons(port);

ret = g_ConnectExFn(io_ctx->socket,
reinterpret_cast<const sockaddr*>(&addr1),
sizeof(addr1),
NULL,
0,
NULL,
&io_ctx->overlapped);
int gle = WSAGetLastError();
if (ret == SOCKET_ERROR && gle != WSA_IO_PENDING) {
return false;
}

return true;
}

bool PostRecv(IOCP::PER_IO_CONTEXT* io_ctx) {
if (io_ctx == NULL)
return false;

io_ctx->operation_type = IOCP::RECV_POSTED;
io_ctx->ResetBuffer();

DWORD recv_bytes = 0;
DWORD flags = 0;
int ret = WSARecv(io_ctx->socket, &io_ctx->wsa_buffer, 1, &recv_bytes, &flags, &io_ctx->overlapped, NULL);
if (ret == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
return false;
}

return true;
}

bool PostSend(IOCP::PER_IO_CONTEXT* io_ctx, const char* msg, int msg_len) {
if (io_ctx == NULL)
return false;

io_ctx->operation_type = IOCP::SEND_POSTED;
memcpy(io_ctx->wsa_buffer.buf, msg, msg_len);
io_ctx->wsa_buffer.len = msg_len;

DWORD sent_bytes = 0;
int ret = WSASend(io_ctx->socket, &io_ctx->wsa_buffer, 1, &sent_bytes, 0, &io_ctx->overlapped, NULL);
int gle = WSAGetLastError();
if (ret == SOCKET_ERROR && gle != WSA_IO_PENDING) {
printf("WSASend failed with code: %d\n", gle);
return false;
}

return true;
}

bool DoConnect(IOCP::PER_SOCKET_CONTEXT *socket_ctx, IOCP::PER_IO_CONTEXT *io_ctx) {
printf("connect to server\n");

if (!PostRecv(io_ctx)) {
printf("PostRecv failed\n");
return false;
}

IOCP::PER_IO_CONTEXT* new_io_ctx = socket_ctx->GetNewIoContext();
new_io_ctx->socket = socket_ctx->socket;

if (!PostSend(new_io_ctx, kSYN.c_str(), kSYN.length())) {
printf("PostSend failed\n");
return false;
}

return true;
}

bool DoRecv(IOCP::PER_SOCKET_CONTEXT *socket_ctx, IOCP::PER_IO_CONTEXT *io_ctx) {
printf("recv: %s\n", io_ctx->wsa_buffer.buf);

if (strcmp(io_ctx->wsa_buffer.buf, kSYN_ACK.c_str()) == 0) {
// ACK
IOCP::PER_IO_CONTEXT * new_io_ctx = socket_ctx->GetNewIoContext();
new_io_ctx->socket = socket_ctx->socket;

if (!PostSend(new_io_ctx, kACK.c_str(), kACK.length())) {
printf("PostSend failed\n");
return false;
}
}

// post new recv
if (!PostRecv(io_ctx)) {
printf("PostRecv failed\n");
return false;
}

return true;
}

bool DoSend(IOCP::PER_SOCKET_CONTEXT *socket_ctx, IOCP::PER_IO_CONTEXT *io_ctx) {
printf("send: %s\n", io_ctx->wsa_buffer.buf);
return true;
}

unsigned int __stdcall WorkThreadProc(void *arg) {
DWORD transferred_bytes = 0;
IOCP::PER_SOCKET_CONTEXT *socket_ctx = NULL;
OVERLAPPED *overlapped = NULL;
DWORD gle;

while (WaitForSingleObject(g_exit, 0) != WAIT_OBJECT_0) {
BOOL ret = GetQueuedCompletionStatus(g_IOCP, &transferred_bytes, (PULONG_PTR)&socket_ctx, &overlapped, INFINITE);
gle = GetLastError();

if (socket_ctx == EXIT_CODE) {
break;
}

if (ret == FALSE) {
if (gle == WAIT_TIMEOUT) {
continue;
}
else if (gle == ERROR_NETNAME_DELETED) {
printf("server exit\n");
closesocket(socket_ctx->socket);
socket_ctx->socket = INVALID_SOCKET;
break;
}
else {
closesocket(socket_ctx->socket);
socket_ctx->socket = INVALID_SOCKET;
break;
}
}
else {
IOCP::PER_IO_CONTEXT *io_ctx = CONTAINING_RECORD(overlapped, IOCP::PER_IO_CONTEXT, overlapped);

switch (io_ctx->operation_type)
{
case IOCP::CONNECT_POSTED:
DoConnect(socket_ctx, io_ctx);

break;
case IOCP::RECV_POSTED:
DoRecv(socket_ctx, io_ctx);

break;
case IOCP::SEND_POSTED:
DoSend(socket_ctx, io_ctx);

break;
default:
assert(false);
}
}
}
return 0;
}


int main()
{
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);

do
{
g_IOCP = IOCP::CreateNewCompletionPort();
g_exit = CreateEvent(NULL, FALSE, FALSE, NULL);

g_work_thread_num = IOCP::GetNumberOfProcesser() * 2;

g_work_threads = new HANDLE[g_work_thread_num];
for (int i = 0; i < g_work_thread_num; i++) {
g_work_threads[i] = (HANDLE)_beginthreadex(NULL, 0, WorkThreadProc, NULL, 0, NULL);
}

g_client_ctx = new IOCP::PER_SOCKET_CONTEXT;
g_client_ctx->socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

if (!IOCP::AssociateDeviceWithCompletionPort(g_IOCP, (HANDLE)g_client_ctx->socket, (DWORD)g_client_ctx)) {
printf("AssociateDeviceWithCompletionPort failed with code: %d\n", GetLastError());
break;
}

g_ConnectExFn = IOCP::GetConnectExFnPointer(g_client_ctx->socket);
if (g_ConnectExFn == NULL) {
printf("GetConnectExFnPointer failed\n");
break;
}

IOCP::PER_IO_CONTEXT* io_ctx = g_client_ctx->GetNewIoContext();
io_ctx->socket = g_client_ctx->socket;
if (!PostConnect(io_ctx, kIP, kPort)) {
printf("PostConnect failed\n");
}

} while (FALSE);


printf("press any key to exit client...\n");
getchar();

SetEvent(g_exit);
closesocket(g_client_ctx->socket);

getchar();
WSACleanup();
return 0;
}

📌限于篇幅,不在此提供完整的示例代码,如需获取完整的示例代码,可以联系我

WSAEventSelect模型介绍

WSAEventSelect模型和WSAAsyncSelect模型类似,但WSAEventSelect模型允许应用程序在一个或多个套接字上面接收以事件为基础的网络事件通知。该模型和WSAAsyncSelect模型的最主要的区别在于网络事件是由事件对象句柄完成的,而不是通过窗口消息完成的。

阅读全文 »

WSAAsyncSelect模型介绍

利用 WSAAsyncSelect 模型结合 Windows 窗口消息循环,应用程序可以在一个套接字上接收以 Windows 消息为基础的网络事件通知。

在使用 WSAAsyncSelect 模型之前,首先需要创建一个 Windows 窗口,并为该窗口提供一个窗口过程支持函数(WndProc)。

阅读全文 »

Select模型介绍

套接字Select模型的中心思想是利用 select 函数实现对I/O的管理。

可以利用select函数判断一个或多个套接字上是否存在待接收数据,或者能否向一个或多个套接字发送数据。

select函数是同步的,也会阻塞,但和阻塞模型不同的是,Select模型可以同时管理多个套接字。

阅读全文 »

套接字I/O的阻塞模型是最常见的网络模型,也是在网络编程中最早接触的一个模型。因为它是阻塞的,所以我们一般都会结合线程一起使用,如将acceptrecv等操作放到单独的线程,避免程序主线程被阻塞。

阅读全文 »
0%