I/O复用之Select模型

73 篇文章 1 订阅
69 篇文章 0 订阅

    I/O复用之Select模型

I/O复用使得程序能够同时监听多个文件描述符,但是,他本身也是阻塞的,并且当一个或多个文件描述符准备就绪时,如果不采用其他措施,程序只能按顺序处理其中的每个文件描述符。如果要使程序能够并行运行,只能使用多进程或多线程的方式。

Linux下实现I/O复用的系统调用主要有select、poll和epoll,下面将详细介绍select的系统调用。

Select系统调用的用途:在一段时间内,监听用户感兴趣的文件描述符上面的可读、可写和异常等事件。

相关API的介绍:

1

2

#include <sys/select.h>

int select(int nfds, fd_set * readfds, fd_set * writefdds, fd_set * exceptfds, struct timeval * timeout);

 

①、nfds参数:所有监听的文件描述符的最大值 + 1

②、readfds、writefds和exceptfds参数分别为可读、可写和异常等事件对应的文件描述符集合;程序只需要传入自己感兴趣的文件描述符,内核将修改他们来通知程序那些文件描述符已经准备就绪;fd_set结构体仅包含一个整形数组,该数组的每一个元素的每一位标志一个文件描述符,下面的一组宏用来操作fd_set的每一位:

1

2

3

4

FD_ZERO(fd_set * fdset);//清除fd_set的所有位

FD_SET(int fd, fd_set * fdset);//设置fdset的位fd

FD_CLR(int fd, fd_set * fdset);//清除fdset的位fd

int FD_ISSET(int fd, fd_set * fdset);//测试fdset的位fd是否被设置

 

③、ttimeout参数:timeout参数是一个timeval结构体,timeval的结构体的定义如下:

1

2

3

4

5

struct timeval

{

long tv_sec;//秒数

long tv_usec;//微秒数

};

 

如果tv_sec和tv_usec都设置为0,则select立即返回, 如果timeout参数设置为NULL,则select一直阻塞直到某个文件描述符准备就绪。

返回值:成功返回就绪的文件描述符的总数,如果超过时间内没有任何文件描述符准备就绪,select将返回0,失败则返回-1并设置errno;若在select等待事件内程序接收到信号,则select立即返回-1,并设置errno为EINTER。

文件描述符的就绪条件:

可读描述符就绪条件:

①、接收缓冲区中的字节数大于或等于SO_RCVLOWAT标志

‚、有新的连接请求

ƒ、对方关闭连接

④、有未处理的错误

可写描述符的就绪条件:

①、发送缓冲区中的可用字节数大于或等于SO_SENLOWAT标志

‚、写操作被关闭

ƒ、有未处理的错误

异常文件描述符的就绪条件:

①、socket上接收带外数据

 

代码示例:

 

[1].[文件] command_define.h ~ 613B    下载(71) 跳至 [1] [2] [3] [4]

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

/*************************************************************************

    > File Name: command_define.h

    > Author: cjj

    > Created Time: 2013年10月27日 星期日 00时57分04秒

 ************************************************************************/

#ifndef COMMAND_DEFINE_H

#define COMMAND_DEFINE_H

#include <stddef.h>

 

const int MAX_LISTEN = 3000;

const int SERVER_LISTEN_PORT = 8768;

const int MAX_DATA_LEN = 4096;

 

typedef struct

{

    unsigned char buf[MAX_DATA_LEN];

    size_t nLen;

    int socket;

}ServerData, *pServerData;

 

typedef void (*pCallBack)(const char * szBuf, size_t nLen, int socket);

 

#endif

[2].[文件] TcpServer.h ~ 1020B    下载(64) 跳至 [1] [2] [3] [4]

?

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

/*************************************************************************

    > File Name: TcpServer.h

    > Author: cjj

    > Created Time: 2013年10月27日 星期日 01时01分23秒

 ************************************************************************/

#ifndef TCP_SERVER_H

#define TCP_SERVER_H

 

#include "command_define.h"

#include <set>

#include <list>

#include <sys/select.h>

#include <pthread.h>

 

class TcpServer

{

public:

    TcpServer();

    virtual ~TcpServer();

    bool Initialize(unsigned int nPort, unsigned long recvFunc);

    bool SendData(const unsigned char * szBuf, size_t nLen, int socket);

    bool UnInitialize();

 

private:

    static void * AcceptThread(void * pParam);

    static void * OperatorThread(void * pParam);   

    static void * ManageThread(void * pParam);

private:

    int m_server_socket;

    fd_set m_fdReads;

    pthread_mutex_t m_mutex;

    pCallBack m_operaFunc;

    //int m_client_socket[MAX_LISTEN];

    std::set<int> m_client_socket;

    std::list<ServerData> m_data;

    pthread_t m_pidAccept;

    pthread_t m_pidManage;

};

 

#endif

[3].[文件] TcpServer.cpp ~ 6KB    下载(66) 跳至 [1] [2] [3] [4]

?

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

/*************************************************************************

    > File Name: TcpServer.cpp

    > Author: cjj

    > Created Time: 2013年10月27日 星期日 01时29分54秒

 ************************************************************************/

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include "TcpServer.h"

#include <stdio.h>

#include <unistd.h>

#include <string.h>

 

TcpServer::TcpServer()

{

    pthread_mutex_init(&m_mutex, NULL);

    FD_ZERO(&m_fdReads);

    m_client_socket.clear();

    m_data.clear();

    m_operaFunc = 0;

    m_pidAccept = 0;

    m_pidManage = 0;

}

 

TcpServer::~TcpServer()

{

    FD_ZERO(&m_fdReads);

    m_client_socket.clear();

    m_data.clear();

    m_operaFunc = NULL;

    pthread_mutex_destroy(&m_mutex);

}

 

bool TcpServer::Initialize(unsigned int nPort, unsigned long recvFunc)

{

    if(0 != recvFunc)

    {

        //设置回调函数

        m_operaFunc = (pCallBack)recvFunc;

    }

    //先反初始化

    UnInitialize();

    //创建socket

    m_server_socket = socket(AF_INET, SOCK_STREAM, 0);

    if(-1 == m_server_socket)

    {

        printf("socket error:%m\n");

        return false;

    }

    //绑定IP和端口

    sockaddr_in serverAddr = {0};

    serverAddr.sin_family = AF_INET;

    serverAddr.sin_port = htons(nPort);

    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    int res = bind(m_server_socket, (sockaddr*)&serverAddr, sizeof(serverAddr));

    if(-1 == res)

    {

        printf("bind error:%m\n");

        return false;

    }

    //监听

    res = listen(m_server_socket, MAX_LISTEN);

    if(-1 == res)

    {

        printf("listen error:%m\n");

        return false;

    }

    //创建线程接收socket连接

    if(0 != pthread_create(&m_pidAccept, NULL, AcceptThread, this))

    {

        printf("create accept thread failed\n");

        return false;

    }

    //创建管理线程

    if(0 != pthread_create(&m_pidManage, NULL, ManageThread, this))

    {

        printf("create manage thread failed\n");

        return false;

    }

    return true;

}

 

//接收socket连接线程

void * TcpServer::AcceptThread(void * pParam)

{

    if(!pParam)

    {

        printf("param is null\n");

        return 0;

    }

    TcpServer * pThis = (TcpServer*)pParam;

    int nMax_fd = 0;

    int i = 0;

    while(1)

    {

        FD_ZERO(&pThis->m_fdReads);

        //把服务器监听的socket添加到监听的文件描述符集合

        FD_SET(pThis->m_server_socket, &pThis->m_fdReads);

        //设置监听的最大文件描述符

        nMax_fd = nMax_fd > pThis->m_server_socket ? nMax_fd : pThis->m_server_socket;

        std::set<int>::iterator iter = pThis->m_client_socket.begin();

        //把客户端对应的socket添加到监听的文件描述符集合

        for(; iter != pThis->m_client_socket.end(); ++iter)

        {

            FD_SET(*iter, &pThis->m_fdReads);

        }

        //判断最大的文件描述符

        if(!pThis->m_client_socket.empty())

        {

            --iter;

            nMax_fd = nMax_fd > (*iter) ? nMax_fd : (*iter);

        }

        //调用select监听所有文件描述符

        int res = select(nMax_fd + 1, &pThis->m_fdReads, 0, 0, NULL);

        if(-1 == res)

        {

            printf("select error:%m\n");

            continue;

        }

        printf("select success\n");

        //判断服务器socket是否可读

        if(FD_ISSET(pThis->m_server_socket, &pThis->m_fdReads))

        {

            //接收新的连接

            int fd = accept(pThis->m_server_socket, 0,0);

            if(-1 == fd)

            {

                printf("accept error:%m\n");

                continue;

            }

            //添加新连接的客户

            pThis->m_client_socket.insert(fd);

            printf("连接成功\n");

        }

        for(iter = pThis->m_client_socket.begin(); iter != pThis->m_client_socket.end(); ++iter)

        {

            //判断客户是否可读

            if(-1 != *iter && FD_ISSET(*iter, &pThis->m_fdReads))

            {

                unsigned char buf[MAX_DATA_LEN] = {0};

                res = recv(*iter, buf, sizeof(buf), 0);

                if(res > 0)

                {

                    ServerData serverData = {0};

                    memcpy(serverData.buf, buf, res);

                    serverData.nLen = res;

                    serverData.socket = *iter;

                    pthread_mutex_lock(&pThis->m_mutex);

                    pThis->m_data.push_back(serverData);

                    pthread_mutex_unlock(&pThis->m_mutex);

                }

                else if(0 == res)

                {

                    printf("客户端退出\n");

                    pThis->m_client_socket.erase(iter);

                }

                else

                {

                    printf("recv error\n");

                }  

            }

        }

    }

}

 

//发送数据到指定的socket

bool TcpServer::SendData(const unsigned char * buf, size_t len, int sock)

{

    if(NULL == buf)

    {

        return false;

    }

    int res = send(sock, buf, len, 0);

    if(-1 == res)

    {

        printf("send error:%m\n");

        return false;

    }

    return true;

}

 

//管理线程,用于创建处理线程

void * TcpServer::ManageThread(void * pParam)

{

    if(!pParam)

    {

        return 0;

    }

    pthread_t pid;

    TcpServer * pThis = (TcpServer *)pParam;

    while(1)

    {

        //使用互斥量

        pthread_mutex_lock(&pThis->m_mutex);

        int nCount = pThis->m_data.size();

        pthread_mutex_unlock(&pThis->m_mutex);

        if(nCount > 0)

        {

            pid = 0;

            //创建处理线程

            if( 0 != pthread_create(&pid, NULL, OperatorThread, pParam))

            {

                printf("creae operator thread failed\n");

            }

        }

        //防止抢占CPU资源

        usleep(100);

    }

}

 

//数据处理线程

void * TcpServer::OperatorThread(void * pParam)

{

    if(!pParam)

    {

        return 0;

    }

    TcpServer * pThis = (TcpServer*)pParam;

    pthread_mutex_lock(&pThis->m_mutex);

    if(!pThis->m_data.empty())

    {

        ServerData data = pThis->m_data.front();

        pThis->m_data.pop_front();

        if(pThis->m_operaFunc)

        {

            //把数据交给回调函数处理

            pThis->m_operaFunc((char *)&data, sizeof(data), data.socket);

        }

    }

    pthread_mutex_unlock(&pThis->m_mutex);

}

 

bool TcpServer::UnInitialize()

{

    close(m_server_socket);

    for(std::set<int>::iterator iter = m_client_socket.begin(); iter != m_client_socket.end(); ++iter)

    {

        close(*iter);

    }

    m_client_socket.clear();

    if(0 != m_pidAccept)

    {

        pthread_cancel(m_pidAccept);

    }

    if(0 != m_pidManage)

    {

        pthread_cancel(m_pidManage);

    }

}

[4].[文件] test.cpp ~ 825B    下载(66) 跳至 [1] [2] [3] [4]

?

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

/*************************************************************************

    > File Name: test.cpp

    > Author: cjj

    > Created Time: 2013年10月27日 星期日 02时23分51秒

 ************************************************************************/

#include "TcpServer.h"

#include <stdio.h>

#include <unistd.h>

 

TcpServer tcpServer;

void printMessage(const char * buf, size_t nlen, int sock)

{

    if(NULL == buf)

    {

        return;

    }

    ServerData * serverData = (ServerData*)buf;

    printf("Message:%s\n", serverData->buf);

    tcpServer.SendData(serverData->buf, sizeof(serverData->buf), serverData->socket);

}

 

 

 

int main()

{

    if(false == tcpServer.Initialize(8768, (unsigned long)printMessage))

    {

        printf("Initialize failed\n");

        return -1;

    }

    printf("tcpserver:%d\n", sizeof(tcpServer));

    while(1)

    {

        sleep(2);

    }

    return 0;

}

 

 

 

分享的代码主要是使用select系统调用和多线程编程实现一个简单的并行的TCP服务器

select 模型的有缺点:

优点:select模型的优点之一就是良好的跨平台支持

缺点:

1、单个进程能够监测的文件描述符有限,一般为1024,可以通过修改内核修改

2、select维护了存储大量文件描述符的数据结构,随着文件描述符的增加,其复制的开销及遍历造成的开销影响系统效率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值