Linux下网络socket编程——实现服务器(select)与多个客户端通信

88 篇文章 1 订阅
订阅专栏

 

一、关于socket通信

服务器端工作流程:

  • 调用 socket() 函数创建套接字 用 bind() 函数将创建的套接字与服务端IP地址绑定
  • 调用listen()函数监听socket() 函数创建的套接字,等待客户端连接 当客户端请求到来之后
  • 调用 accept()函数接受连接请求,返回一个对应于此连接的新的套接字,做好通信准备
  • 调用 write()/read() 函数和 send()/recv()函数进行数据的读写,通过 accept() 返回的套接字和客户端进行通信 关闭socket(close)

客户端工作流程:

  • 调用 socket() 函数创建套接字
  • 调用 connect() 函数连接服务端
  • 调用write()/read() 函数或者 send()/recv() 函数进行数据的读写
  • 关闭socket(close)

 

二、用select实现服务器端编程:

select函数楼主在之前文章中(select函数用法)已经提及,不在多做缀述。下面贴上服务器端代码servce.c

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

#include <stdio.h>

#include <netinet/in.h>   //for souockaddr_in

#include <sys/types.h>     

#include <sys/socket.h>

#include <errno.h>

#include <stdlib.h>

 

#include <arpa/inet.h>

 

//for select

#include <sys/time.h>

#include <sys/types.h>

#include <unistd.h>

#include <sys/select.h>

 

#include <strings.h>   //for bzero

#include <string.h>

 

#define BUFF_SIZE 1024

#define backlog 7

#define ser_port 11277

#define CLI_NUM 3

 

 

int client_fds[CLI_NUM];

 

int main(int agrc,char **argv)

{

    int ser_souck_fd;

    int i;  

    char input_message[BUFF_SIZE];

    char resv_message[BUFF_SIZE];

 

 

    struct sockaddr_in ser_addr;

    ser_addr.sin_family= AF_INET;    //IPV4

    ser_addr.sin_port = htons(ser_port);

    ser_addr.sin_addr.s_addr = INADDR_ANY;  //指定的是所有地址

 

    //creat socket

    if( (ser_souck_fd = socket(AF_INET,SOCK_STREAM,0)) < 0 )

    {

        perror("creat failure");

        return -1;

    }

 

    //bind soucket

    if(bind(ser_souck_fd, (const struct sockaddr *)&ser_addr,sizeof(ser_addr)) < 0)

    {

        perror("bind failure");

        return -1;

    }

 

    //listen

    if(listen(ser_souck_fd, backlog) < 0)

    {

        perror("listen failure");

        return -1;

    }

 

 

    //fd_set

    fd_set ser_fdset;

    int max_fd=1;

    struct timeval mytime;

    printf("wait for client connnect!\n");

 

    while(1)

    {

        mytime.tv_sec=27;

        mytime.tv_usec=0;

 

        FD_ZERO(&ser_fdset);

 

        //add standard input

        FD_SET(0,&ser_fdset);

        if(max_fd < 0)

        {

            max_fd=0;

        }

 

        //add serverce

        FD_SET(ser_souck_fd,&ser_fdset);

        if(max_fd < ser_souck_fd)

        {

            max_fd = ser_souck_fd;

        }

 

        //add client

        for(i=0;i<CLI_NUM;i++)  //用数组定义多个客户端fd

        {

            if(client_fds[i]!=0)

            {

                FD_SET(client_fds[i],&ser_fdset);

                if(max_fd < client_fds[i])

                {

                    max_fd = client_fds[i];

                }

            }

        }

 

        //select多路复用

        int ret = select(max_fd + 1, &ser_fdset, NULL, NULL, &mytime);

 

        if(ret < 0)   

        {   

            perror("select failure\n");   

            continue;   

        }   

 

        else if(ret == 0)

        {

            printf("time out!");

            continue;

        }

 

        else

        {

            if(FD_ISSET(0,&ser_fdset)) //标准输入是否存在于ser_fdset集合中(也就是说,检测到输入时,做如下事情)

            {

                printf("send message to");

                bzero(input_message,BUFF_SIZE);

                fgets(input_message,BUFF_SIZE,stdin);

 

                for(i=0;i<CLI_NUM;i++)

                {

                    if(client_fds[i] != 0)

                    {

                        printf("client_fds[%d]=%d\n", i, client_fds[i]);

                        send(client_fds[i], input_message, BUFF_SIZE, 0);

                    }

                }

 

            }

 

            if(FD_ISSET(ser_souck_fd, &ser_fdset))

            {

                struct sockaddr_in client_address;

                socklen_t address_len;

                int client_sock_fd = accept(ser_souck_fd,(struct sockaddr *)&client_address, &address_len);

                if(client_sock_fd > 0)

                {

                    int flags=-1;

                    //一个客户端到来分配一个fd,CLI_NUM=3,则最多只能有三个客户端,超过4以后跳出for循环,flags重新被赋值为-1

                    for(i=0;i<CLI_NUM;i++)

                    {

                        if(client_fds[i] == 0)

                        {

                            flags=i;

                            client_fds[i] = client_sock_fd;

                            break;

                        }

                    }

 

 

                    if (flags >= 0)

                    {

                        printf("new user client[%d] add sucessfully!\n",flags);

 

                    }

 

                    else //flags=-1

                    {  

                        char full_message[]="the client is full!can't join!\n";

                        bzero(input_message,BUFF_SIZE);

                        strncpy(input_message, full_message,100);

                        send(client_sock_fd, input_message, BUFF_SIZE, 0);

 

                    }

                }   

            }

 

        }

 

        //deal with the message

 

        for(i=0; i<CLI_NUM; i++)

        {

            if(client_fds[i] != 0)

            {

                if(FD_ISSET(client_fds[i],&ser_fdset))

                {

                    bzero(resv_message,BUFF_SIZE);

                    int byte_num=read(client_fds[i],resv_message,BUFF_SIZE);

                    if(byte_num > 0)

                    {

                        printf("message form client[%d]:%s\n", i, resv_message);

                    }

                    else if(byte_num < 0)

                    {

                        printf("rescessed error!");

                    }

 

                    //某个客户端退出

                    else  //cancel fdset and set fd=0

                    {

                        printf("clien[%d] exit!\n",i);

                        FD_CLR(client_fds[i], &ser_fdset);

                        client_fds[i] = 0;

                       // printf("clien[%d] exit!\n",i);

                        continue;  //这里如果用break的话一个客户端退出会造成服务器也退出。 

                    }

                }

            }

        }   

    }

    return 0;

}

select实现多路复用,多路复用,顾名思义,就是说各做各的事,标准输入事件到来,有相关函数处理。服务器处理服务器的事件,客户端到来时有相关函数对其进行处理,通过select遍历各fd的读写情况,就不用担心阻塞了。

三、用epoll实现客户端编程:

1、客户端程序(epoll_client.c):

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

#include<stdio.h>   

#include<stdlib.h>   

#include<netinet/in.h>   

#include<sys/socket.h>   

#include<arpa/inet.h>   

#include<string.h>   

#include<unistd.h>   

 

#include <sys/epoll.h>

#include <errno.h>

#include <fcntl.h>

 

#define BUFFER_SIZE 1024   

 

int main(int argc, const char * argv[])   

{  

    int i,n;

    int connfd,sockfd;

    struct epoll_event ev,events[20]; //ev用于注册事件,数组用于回传要处理的事件

    int epfd=epoll_create(256);//创建一个epoll的句柄,其中256为你epoll所支持的最大句柄数

 

    struct sockaddr_in client_addr;

    struct sockaddr_in server_addr;   

 

    server_addr.sin_family = AF_INET;   

    server_addr.sin_port = htons(11277);   

    server_addr.sin_addr.s_addr =INADDR_ANY;   

    bzero(&(server_addr.sin_zero), 8);   

 

    int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0); 

 

    ev.data.fd=server_sock_fd;//设置与要处理的事件相关的文件描述符

    ev.events=EPOLLIN|EPOLLET;//设置要处理的事件类型

    epoll_ctl(epfd,EPOLL_CTL_ADD,server_sock_fd,&ev);//注册epoll事件

 

    if(server_sock_fd == -1)   

    {   

        perror("socket error");   

        return 1;   

    }   

 

    char recv_msg[BUFFER_SIZE];   

    char input_msg[BUFFER_SIZE];   

 

    if(connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) == 0)   

    {   

        for(;;)

        {

            int nfds=epoll_wait(epfd,events,20,500);//等待epoll事件的发生

            for(i=0;i<nfds;++i)

            {   

                if(events[i].events&EPOLLOUT) //有数据发送,写socket

                {

                    bzero(input_msg, BUFFER_SIZE);   

                    fgets(input_msg, BUFFER_SIZE, stdin);   

 

                    sockfd = events[i].data.fd;

                    write(sockfd, recv_msg, n);

 

                    ev.data.fd=sockfd;

                    ev.events=EPOLLIN|EPOLLET;

                    epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);

                }  

 

                else if(events[i].events&EPOLLIN)//有数据到来,读socket

                {

                    bzero(recv_msg, BUFFER_SIZE);

                    if((n = read(server_sock_fd, recv_msg, BUFFER_SIZE)) <0 )

                    {

                        printf("read error!");

                    }

 

                    ev.data.fd=server_sock_fd;

                    ev.events=EPOLLOUT|EPOLLET;

                    printf("%s\n",recv_msg);

                }

 

            }       

        }

    }   

    return 0;   

}  

2、关于epoll函数:

相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明: 
#define __FD_SETSIZE 1024 
表示select最多同时监听1024个fd

一共三个函数:

1

2

1、 int epoll_create (int size);

创建一个epoll的句柄

size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

1

2、 int epoll_ctl (int epfd , int op, int fd, struct epoll_event *event);

epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的fd到epfd中;

EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

EPOLL_CTL_DEL:从epfd中删除一个fd;

第三个参数是需要监听的fd

第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

1

2

3

4

5

struct epoll_event

 {

    __uint32_t events;    /* Epoll events */

    epoll_data_t data;     /* User data variable */

};

  

1

2

3

4

5

6

7

typedef union epoll_data

 {

    void *ptr;

    int fd;

    __uint32_t u32;

    __uint64_t u64;

} epoll_data_t;

events可以是以下几个宏的集合:

  • EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
  • EPOLLOUT:表示对应的文件描述符可以写;
  • EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
  • EPOLLERR:表示对应的文件描述符发生错误;
  • EPOLLHUP:表示对应的文件描述符被挂断;
  • EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
  • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

1

3、 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

等待事件的产生,类似于select()调用。

参数events用来从内核得到事件的集合,

maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,

参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

使用步骤:

<1>首先通过create_epoll(int maxfds)来创建一个epoll的句柄,其中maxfds为你epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。

<2>然后每一帧的调用epoll_wait (int epfd, epoll_event events, int max events, int timeout) 来查询所有的网络接口。

<3>kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件范围,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则返回。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。 epoll_wait返回之后应该是一个循环,遍历所有的事件。

基本上都是如下的框架:

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

for( ; ; )

    {

        nfds = epoll_wait(epfd,events,20,500);

        for(i=0;i<nfds;++i)

        {

            if(events[i].data.fd==listenfd) //有新的连接

            {

                     connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);    //accept这个连接

                     ev.data.fd=connfd;

                     ev.events=EPOLLIN|EPOLLET;

                     epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中

            }

 

           else if( events[i].events&EPOLLIN ) //接收到数据,读socket

            {

                     n = read(sockfd, line, MAXLINE)) < 0    //读

                     ev.data.ptr = md;     //md为自定义类型,添加数据

                     ev.events=EPOLLOUT|EPOLLET;

                     epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓

            }

            else if(events[i].events&EPOLLOUT) //有数据待发送,写socket

            {

                     struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取数据

                     sockfd = md->fd;

                     send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //发送数据

                     ev.data.fd=sockfd;

                     ev.events=EPOLLIN|EPOLLET;

                     epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据

            }

            else

            {

                     //其他的处理

            }

        }

    }

写文章

热门文章

  • 成为C++高手之实战项目 20125
  • TCP第四次挥手为什么要等待2MSL 17466
  • c++继承父类的子类,如何调用父类的同名函数? 13500
  • 链栈基本操作 13354
  • c语言实现配置文件的读写 11614

分类专栏

  • Linux网络编程 88篇
  • Linux API 31篇
  • Linux 进程间通信 1篇
  • C++ 75篇
  • 数据结构与算法 65篇
  • Linux 线程同步 30篇
  • 一些配置 4篇
  • 数据库 8篇
  • C++ 11 21篇
  • 小小项目 8篇
  • IO复用 23篇
  • 各种坑 2篇
  • C Language 19篇
  • C++ 设计模式
  • Interesting 7篇
  • Linux系统编程 4篇
  • Shell编程 2篇
  • 面试珠玑 7篇
  • Python 2篇

最新评论

  • TCP第四次挥手为什么要等待2MSL

    不愿透露姓名的Y同学: 第2点:客户端在发送最后一个ACK之后,再经过经过2MSL,就可以使本链接持续时间内所产生的所有报文段都从网络中消失。从保证在关闭连接后不会有还在网络中滞留的报文段去骚扰服务器。 为什么2MSL之后上一个连接的数据就全消失了啊?

  • C语言实现单链表(带头结点)的基本操作(创建,头插法,尾插法,删除结点,打印链表)

    『ßengi』: 感谢!非常实用!

  • linux 网络编程:使用两线程实现socket同时收发数据

    lnn123456789: 非常帅的代码

  • TCP第四次挥手为什么要等待2MSL

    怎么这么帅啊: 由于TIME_WAIT状态的持续时间是MSL的两倍,因此这使得一个方向上的数据包丢失了MSL秒,而丢失的回复则丢失了另一个MSL秒。 通过执行此规则,可以确保成功建立TCP连接时,该连接的先前版本中的所有旧副本都已在网络中过期。

  • (C语言版)链表(一)——实现单向链表创建、插入、删除等简单操作(包含个人理解说明及注释,新手跟着写代码)

    都别吵了,快打起来呀: p_new->data = data; p_new->pNext = NULL; pTail->pNext = p_new; pTail = p_new;这第一个的这段没有看懂耶

您愿意向朋友推荐“博客详情页”吗?

  • 强烈不推荐
  • 不推荐
  • 一般般
  • 推荐
  • 强烈推荐
提交

最新文章

  • C语言实现单链表操作
  • 实现Linux select IO复用C/S服务器代码
  • TCP服务器/客户端实例(C/C )
2020年1篇
2019年5篇
2018年136篇
2017年238篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值

深圳SEO优化公司铜川网站推广工具多少钱吕梁企业网站建设多少钱渭南建站公司龙岗营销网站推荐南昌营销型网站建设价格临夏设计网站推荐大同网站搭建公司忻州优秀网站设计公司大丰外贸网站制作多少钱潜江外贸网站建设哪家好福州网站设计报价那曲网站搜索优化运城网页制作鹰潭模板制作多少钱临夏百度网站优化排名报价新乡网站改版多少钱昆明模板推广哪家好驻马店百度竞价价格营口网站制作哪家好朝阳网站优化排名多少钱镇江关键词排名成都网站优化排名报价海南网站优化按天收费哪家好长沙企业网站建设报价濮阳网站优化按天扣费推荐龙岩网站搭建报价亳州推广网站推荐广元企业网站改版报价许昌英文网站建设哪家好镇江百度竞价包年推广公司歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化