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

 

一、关于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

            {

                     //其他的处理

            }

        }

    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383634.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL Server【四】

identity 主键自动增长&#xff0c;用户不需要为identity修饰的主键赋值 create table student (std_id int primary key identity(10,5),--(10,5)可以省略&#xff0c;默认为(1,1)std_name nvarchar(200) not null ) select * from student insert into student values (张三…

计算机网络【4】传输层

概述 传输层是只有主机才有的层次 传输层的功能&#xff1a; 传输层提供进程和进程之间的逻辑通信&#xff08;网络层提供主机与主机之间的逻辑通信&#xff09;复用和分用传输层对收到的报文进行差错检测 传输层有两个协议&#xff1a; 面向连接的传输层控制协议TCP&…

计算机网络【0】概述

计算机网络概念和功能 概念 是一个将分散的、具有独立功能的计算机系统&#xff0c;通过通信设备与线路连接起来&#xff0c;由功能完善的软件实现资源共享和信息传递的系统。 计算机网络是互连的、自治&#xff08;无主从关系&#xff09;的计算机集合。 功能 数据通信&am…

计算机网络【1】物理层

物理层解决如何在连接各种计算机的传输媒体上传输数据比特流&#xff0c;而不是指具体的传输媒体。 确定与传输媒体接口有关的特性 机械特性&#xff1a;定义物理连接的特性&#xff0c;如规格、接口形状、引线数目、引脚数目、排列电气特性&#xff1a;规定传输二进制位时的电…

计算机网路【2】数据链路层

结点&#xff1a;主机、路由器 链路&#xff1a;两个节点的物理通道 数据链路&#xff1a;逻辑通道&#xff0c;把实现 控制数据传输协议的硬件和软件加到链路上就构成数据链路 帧&#xff1a;链路层的协议数据单元&#xff0c;封装网络层数据报 数据链路层在物理层提供服务的…

计算机网络【5】应用层

应用层对应用程序的通信提供服务 应用层协议定义&#xff1a; 应用层的功能&#xff1a; 文件传输、访问和管理电子邮件虚拟终端查询服务和远程作业登录 重要协议&#xff1a;FTP、SMTP、POP3、HTTP、DNS 网络应用模型 客户/服务器模型&#xff08;Client/Server&#x…

操作系统【八】文件管理

文件&#xff1a;一组有意义的信息/数据集合 文件的属性&#xff1a; 文件名&#xff1a;由创建文件的用户决定文件名&#xff0c;主要是为了方便用户找到文件。同一个目录下不允许有重名文件标识符&#xff1a;一个系统内的个文件标识符唯一&#xff0c;对用户来说毫无可读性…

数据库原理及应用【六】数据库设计

数据依赖 函数依赖FD&#xff1a;一个属性或者一组属性的值可以决定另一个属性的值 多值依赖MVD&#xff1a;一个属性或者一组属性的值可以决定另一个属性的值的集合。FD是MVD的特例 符号表示&#xff1a;Name->->Course&#xff0c;课程多值依赖于姓名 连接依赖&#x…

数据库原理及应用【二】数据模型

层次模型 tree Record and fieldParent-Child relationship(PCR) 每个记录类型只有一个父节点 无法表达多对多信息 采用虚记录解决多对多 网状数据模型 系&#xff1a;主记录->属记录 主记录和属记录都可以有好多个 关系模型 表&#xff1a;table/relation 拥有更高的…

数据可视化【二】HTML+CSS+SVG+D3

HTML、CSS和SVG学习实现代码&#xff1a;https://vizhub.com/Edward-Elric233/89185eb96bc64a9d81777873a0ccd0b9 index.html <!DOCTYPE html> <html><head><title>Shapes with SVG and CSS</title><link rel"stylesheet" href&qu…

数据可视化【三】基本概念

Visualization is suitable when there is a need to augment human capabilities rather than replace people with computational decision-making methods. 当可以信赖的智能化的解决方案存在的时候&#xff0c;可视化是不必要的。 当不知道需要分析的问题是什么的时候&…

数据可视化【四】Bar Chart

Make a Bar Chart Representing a data table in JavaScriptCreating rectangles for each rowUsing linear and band scalesThe margin conventionAdding axes 以下学习内容参考博客&#xff1a;传送门 select()选择所有指定元素的第一个 selectAll()选择指定元素的全部 上…

数据库原理及应用【三】DBMS+SQL

DBMS Query LanguagesInterface and maintaining tools(GUI)APIsClass Library QL 不是图灵完备的&#xff0c;不是一种编程语言。 QL SQL是一种非过程化的查询语言。 DDL数据定义语言&#xff1a;表&#xff0c;视图QL 查询语言DML 数据操纵语言DCL 数据控制语言 Base t…

数据可视化【五】 Scatter Plot

Scatter Plot vizhub上实现的代码&#xff1a; https://vizhub.com/Edward-Elric233/53807a1b35d94329b3689081cd2ea945 https://vizhub.com/Edward-Elric233/b9647d50899a4a0e8e917f913cd0a53a https://vizhub.com/Edward-Elric233/8c6b50cd81a04f048f490f48e4fe6264 由前…

数据可视化【六】Line Chart Area Chart

Line Chart vizhub代码&#xff1a; https://vizhub.com/Edward-Elric233/094396fc7a164c828a4a8c2e13045308 实现效果&#xff1a; 这里先使用d3.line()设置每个点的x坐标和y坐标&#xff0c;然后再用这个东西设置path的d属性&#xff0c;就可以得到曲线。 const lineGen…

数据可视化【七】 更新模式

Enter 以下面这个简单的代码进行分析 const svg d3.select(svg); // svg.style(background-color, red); testconst height svg.attr(height); // equals paresFloat() const width svg.attr(width);const makeFruit type >( {type} ); //这种写法好像能够直接得到一个…

数据可视化【八】根据数据类型选择可视化方式

Marks:Rows PointsLinesAreas Channels:Columns PositionColorShape

数据可视化【九】单向数据流交互

我们使用一下上上篇博客的代码。 例如我们想要当鼠标点击水果的时候会出现黑色的框&#xff0c;再点击一下黑色的框就会消失。 首先&#xff0c;我们应该给组件添加点击事件&#xff1a; fruitBowl.js gruopAll.on(click, d > onClick(d.id));这个on函数第一个参数是事件…

数据库原理及应用【四】数据库管理系统

查询优化 数据库管理系统中非常重要的一部分。 代数优化 按照一定的规则将语句变化成关系代数以后进行优化 操作优化 对代数优化后的查询树使用比较好的方法进行查询。 主要是对连接运算进行优化 嵌套循环归并扫描索引优化哈希连接 恢复机制 备份&#xff08;完整备份差…

递归式复杂度求解

代换法 猜测复杂度验证是否满足递归式&#xff08;使用归纳法&#xff09;找到常数应该满足的条件针对基本情况&#xff0c;常数足够大时总是成立的 需要注意的是&#xff0c;我们猜测的复杂度有可能不满足递归式&#xff0c;这个时候就要通过减去一些低阶项来使得归纳成立。…