• 网络编程06-服务器编程非阻塞IO、多路复用


    目录

    一、服务器编程中四种高性能IO模型

    1、阻塞IO

    2、非阻塞 IO

    3、多路复用

    4、信号驱动

    二、阻塞IO

    三、非阻塞IO 

    1、阻塞IO与非阻塞IO之间的差异

     2、如何给文件描述符设置非阻塞属性----fcntl() ----- man 2 fcntl

    四、多路复用

    1、同时监听多个套接字IO口

    2、什么是多路复用

    3、特点

    4、多路复用的函数接口-------select -----man 2 select

    Client.c


    一、服务器编程中四种高性能IO模型

     

    1、阻塞IO

    1)read(fd,buf) ; recv(fd) ; recvfrom(fd)
    这些函数本身是不具有阻塞属性,而是这个文件描述符的本身阻塞属性导致这个函数执行表现出来的形式是阻塞。
    2)在默认的情况下,Linux下建立的socket套接字 都是 阻塞的。

    2、非阻塞 IO

    1)给文件描述符添加非阻塞属性
    2)由于非阻塞属性,所以不断询问套接字中是否有数据到达

    3、多路复用

    1)同时对多个IO口进行操作(也就是同时监听几个套接字)
    2)可以在规定的时间内检测数据是否到达

    4、信号驱动

    1)属于异步通信方式
    2)当socket中有数据到达时,通过发送信号告知用户

    二、阻塞IO

    读阻塞:当数据缓冲区中没有数据可以读取时,调用read/recv/recvfrom就会无限阻塞。
    写阻塞:当缓冲区剩余的大小 小于 写入的数据量,就会发生写阻塞,直到缓冲区中的数据被读取了

    三、非阻塞IO 

    1、阻塞IO与非阻塞IO之间的差异

     

     2、如何给文件描述符设置非阻塞属性----fcntl() ----- man 2 fcntl

    1. #include
    2. #include
    3. int fcntl(int fd, int cmd, ... /* arg */ );
    参数
            fd : 你要 设置 哪个 文件,将这个文件的文件描述符传递进来
            cmd: 请求控制的命令字
            arg:这个参数要不要填,取决于第二个参数
            第二个参数:

     

    比如:

    1. int fd = open("xxx");
    2. int status = fcntl(fd, F_GETFL ); //得到文件描述符的状态
    3. status |= O_NONBLOCK ;//在原来的基础上新增非阻塞属性
    4. fcntl(fd, F_SETFL,status); //把变量status的状态设置到文件描述符中

     

    返回值
            F_GETFL Value of file status flags.
            F_SETFL
            成功返回0
            失败返回 -1
    例子1:设置一个非阻塞属性给套接字,看看这个套接字还会不会阻塞等待客户端连接?

     

    例子2: 使用TCP通信 将其修改为非阻塞属性

    1. 1 #include<stdio.h>
    2. 2 #include <sys/socket.h>
    3. 3 #include <sys/types.h> /* See NOTES */
    4. 4 #include <netinet/in.h>
    5. 5 #include <arpa/inet.h>
    6. 6 #include <string.h>
    7. 7 #include <unistd.h>
    8. 8 #include <fcntl.h>
    9. 9 #define OWNADDR "192.168.14.3" //我自己电脑的ip地址
    10. 10 #define OWNPORT 20000 //我自己电脑的该程序的端口号
    11. 12 int main()
    12. 13 {
    13. printf("当前是服务器 IP:%s Port:%u\n",OWNADDR,OWNPORT); 14
    14. //1、买手机(建立套接字) 15
    15. int socketfd = socket(AF_INET, SOCK_STREAM, 0); 16
    16. if(socketfd == -1) 17
    17. { 18
    18. printf("没钱了....,失败\n"); 19
    19. return -1; 20
    20. } 21
    21. //因为服务器立马退出之后,端口号不会及时释放
    22. //此时如果服务器又马上运行,那么端口号会被占用,导致服务器分配端口号失败,连接失败 23
    23. //所以设置端口号可以复用 24
    24. int optval = 1; 25
    25. setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR,&optval, sizeof(optval)); 26
    26. 27
    27. //2、绑定自己的电话号码(绑定自己的IP地址 和端口号) 28
    28. //定义一个IPV4结构体变量,初始化自己的IP地址和端口号 29
    29. struct sockaddr_in ownAddr; 30
    30. ownAddr.sin_family = AF_INET;/*地址族 IPV4*/ 31
    31. ownAddr.sin_port = htons(OWNPORT); //htons 将本地端口号转为网络端口号 32
    32. ownAddr.sin_addr.s_addr = inet_addr(OWNADDR); //将本地IP地址转为网络IP地址 33
    33. 34
    34. bind(socketfd, (struct sockaddr *)&ownAddr,sizeof(struct sockaddr_in)); 35
    35. 36
    36. //3、设置铃声(监听) listen 37
    37. listen(socketfd,5); 38
    38. 39
    39. //4、坐等电话(阻塞接收客户端的连接) 40
    40. printf("等待客户端连接.......\n"); 41
    41. //定义一个IPV4结构体变量,存储连接上来的客户端的IP地址 和 端口号 42
    42. struct sockaddr_in clientAddr; 43
    43. //如果你要想要获取对方的IP地址和端口号,第三个参数必须把结构体的大小传递进去 44
    44. int len = sizeof(struct sockaddr_in); 45
    45. 46
    46. //给 socketfd设置非阻塞属性 47
    47. int status = fcntl(socketfd,F_GETFL);//得到这个套接字文件描述符的属性 48
    48. //将得到的文件描述符的全部属性 中的 其中一个属性设置成 非阻塞 49
    49. status |= O_NONBLOCK; 50
    50. int ret = fcntl(socketfd,F_SETFL,status);//把变量status的状态设置到文件描述符中 51
    51. if(ret == -1) 52
    52. { 53
    53. printf("fcntl error\n"); 54
    54. 55
    55. } 56
    56. while(1) 57
    57. { 58
    58. //此时是非阻塞,会一直不断地循环 59
    59. int newClientFd = accept(socketfd,(struct sockaddr*)&clientAddr,&len); 60
    60. if(newClientFd != -1) 61
    61. {
    62. //printf("有客户端连接上来了............\n"); 63
    63. //打印连接上来的客户端的IP地址和端口号,将网络字节序转为 本地字节序 64
    64. printf("连接上来的客户端IP:%s 端口
    65. 号:%u\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
    66. 65
    67. } 66
    68. //printf("11111111111\n"); 67
    69. } 68
    70. 69
    71. //5、关闭 70
    72. close(socketfd); 71
    73. //close(newClientFd); 72
    74. 73
    75. return 0; 74
    76. 75 }

     

    例题1: 写一个服务器,最多可以接受1000个客户端连接,可以随时接受连接。
    只要连接上服务器的客户端有数据达到,那么就把数据打印出来。要求非阻塞IO实现。不能开线程
    1. 思路: 最多可以接受20个客户端连接
    2. ---> 用户连接成功 ---> 判断满人 --> 拒绝连接 --> 断开该用户 2
    3. ---> 用户连接成功 ---> 未满人 --> 保存套接字到数组 3
    4. 4
    5. 5 struct ciient{
    6. int clifd[1000]; //已连接的用户的套接字 6
    7. int count; //已连接用户的总数 7
    8. 8 }
    9. 9
    10. 10
    11. 非阻塞IO:不断询问这些已连接的用户以及是否有新的人连接过来! 11
    12. while(1) 12
    13. { 13
    14. int clifd[20]; ??? 有没有数据来? 14
    15. }
    1. 1 #include<stdio.h>
    2. 2 #include<stdio.h>
    3. 3 #include <sys/types.h> /* See NOTES */
    4. 4 #include <sys/socket.h>
    5. 5 #include <sys/socket.h>
    6. 6 #include <arpa/inet.h>
    7. 7 #include <unistd.h>
    8. 8 #include<stdlib.h>
    9. 9 #include <string.h>
    10. 10 #include <errno.h>
    11. 11 #include <signal.h>
    12. 12 #include <sys/wait.h>
    13. 13 #include <fcntl.h>
    14. 14
    15. 15 //#include <pthread.h>
    16. 16
    17. 17 #define SERVER_PORT 9999
    18. 18
    19. 19
    20. 20 struct client{
    21. int clientFd_Array[1000] ;//存储所有连接上来的客户端的套接字文件描述符 21
    22. int clientCount; //连接上来的客户端的总数 22
    23. 23 };
    24. 24
    25. 25
    26. 26 void sys_err(const char*err)
    27. 27 {
    28. fprintf(stderr,"%s\n",strerror(errno)); 28
    29. exit(0); 29
    30. 30 }
    31. 31
    32. 32
    33. 33 int main()
    34. 34 {
    35. int ret; 35
    36. struct sockaddr_in clientAddr;//存储连接上来的客户端的IP地址和端口号 36
    37. int len = sizeof(struct sockaddr_in); 37
    38. struct client clientManager; //客户端 结构体管理变量 38
    39. 39
    40. //初始化结构体 40
    41. clientManager.clientCount = 0; 41
    42. 42
    43. printf("服务器 Port:%d PID:%d \n",SERVER_PORT,getpid()); 43
    44. 44
    45. //1、建立套接字 45
    46. int socketFd = socket(AF_INET,SOCK_STREAM, 0); 46
    47. if(socketFd == -1){ 47
    48. sys_err("socket error"); 48
    49. } 49
    50. //端口复用 50
    51. int optval = 1; 51
    52. setsockopt(socketFd,SOL_SOCKET,SO_REUSEADDR,&optval, sizeof(optval)); 52
    53. 53
    54. //2、绑定自己的IP地址和端口号 54
    55. struct sockaddr_in serverAddr; 55
    56. serverAddr.sin_family = AF_INET; 56
    57. serverAddr.sin_port = htons(SERVER_PORT);//short 57
    58. serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); 58
    59. 59
    60. ret = bind(socketFd,(struct sockaddr*)&serverAddr,sizeof(struct sockaddr_in)); 60
    61. if(ret == -1){ 61
    62. sys_err("bind error"); 62
    63. } 63
    64. 64
    65. //3、设置监听 65
    66. listen(socketFd,128); 66
    67. 67
    68. //设置该监听套接字 为 非阻塞属性 68
    69. //第一步 先得到原来该套接字原来的属性 69
    70. int state = fcntl(socketFd,F_GETFL); 70
    71. //第二步 在原来的基础上新增 一个非阻塞 属性 71
    72. state |= O_NONBLOCK; // state = state | O_NONBLOCK 72
    73. //第三步 将 新的属性 设置回 文件描述符中 73
    74. fcntl(socketFd,F_SETFL,state); 74
    75. 75
    76. while(1){ 76
    77. //4、接收新的客户端连接.... 77
    78. int newClientFd = accept(socketFd, (struct sockaddr*)&clientAddr,&len); 78
    79. if(newClientFd > 0) //说明有客户端连接上来了 79
    80. { 80
    81. printf("有新的客户端连接上来 IP:%s Port:%hu newClientFd:%d\n", 81
    82. inet_ntoa(clientAddr.sin_addr), 82
    83. ntohs(clientAddr.sin_port), 83
    84. newClientFd); 84
    85. 85
    86. //将客户端文件描述符的属性 设置成 非阻塞属性 86
    87. //第一步 先得到原来该套接字原来的属性 87
    88. int state = fcntl(newClientFd,F_GETFL); 88
    89. //第二步 在原来的基础上新增 一个非阻塞 属性 89
    90. state |= O_NONBLOCK; // state = state | O_NONBLOCK 90
    91. //第三步 将 新的属性 设置回 文件描述符中 91
    92. fcntl(newClientFd,F_SETFL,state); 92
    93. 93
    94. 94
    95. //将每一个连接上来的客户端存储到结构体变量中 95
    96. clientManager.clientFd_Array[clientManager.clientCount] =
    97. newClientFd;
    98. 96
    99. clientManager.clientCount++; 97
    100. } 98
    101. 99
    102. //挨个轮询查看 连接上来的客户端 是否有数据到达 100
    103. for(int i=0; i<clientManager.clientCount; i++){ 101
    104. char buf[1024] = {0}; 102
    105. int ret = recv(clientManager.clientFd_Array[i],buf, sizeof(buf),
    106. 0);
    107. 103
    108. if(ret == 0) //客户端断开了 104
    109. { 105
    110. //printf("客户端退出了....\n"); 106
    111. //sleep(1); 107
    112. break; 108
    113. } 109
    114. else if(ret > 0) //有数据 来了,可以进行打印 110
    115. { 111
    116. printf("recv:%s\n",buf); 112
    117. } 113
    118. } 114
    119. 115
    120. 116
    121. } 117
    122. 118
    123. //关闭 119
    124. close(socketFd);
    125. return 0; 123
    126. 124 }

    四、多路复用

    1、同时监听多个套接字IO口

    阻塞IO ---->只能同时监听一个套接字
    非阻塞IO--->一直轮询 问IO口有没有数据到达,非常浪费CPU资源

    2、什么是多路复用

    就是预先把需要监听的文件描述符加入到一个集合,然后再规定的时间内或者无限时间进行等待。如果在规定的时间内,集合中文件描述符没有数据变化,则说明超时接收,并进入下一次规定的时间再次等待。一旦集合中的文件描述符有数据变化,则其他的没有数据变化的文件描述符就会被剔除到集合之外,并且再次进入下一次的等待状态。

    3、特点

    同时对多个IO口进行监听。

    4、多路复用的函数接口-------select -----man 2 select

    1. #include <sys/select.h>
    2. 2 #include <sys/time.h>
    3. 3 #include <sys/types.h>
    4. 4 #include <unistd.h>
    5. int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct
    6. timeval *timeout); //poll epoll
    参数:
            nfds: 三个集合中 最大的文件描述符的值 + 1,因为此参数会告诉内核检测前多少个文件描述符的状态
            readfds:监控有读数据到达文件描述符集合,传入传出参数
            writefds: 监控写数据到达文件描述符集合,传入传出参数
            exceptfds: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
            timeout:设置阻塞等待时间,3种情况
                    1.设置NULL,永远等下去,这个函数是阻塞(无限等待,直到有文件描述符的状态发生变化)
                    2.设置timeval,等待固定时间
                    3.设置timeval里时间均为0,检查描述字后立即返回,轮询
                    ---> 如果这个参数填NULL,则说明这个函数是阻塞(无限等待,直到有文件描述符的状
    态发生变化)
    1. struct timeval {
    2. 2 long tv_sec; /* seconds 秒*/
    3. 3 long tv_usec; /* microseconds 微秒*/
    4. 4 };
    5. 5
    6. void FD_CLR(int fd, fd_set *set); //把文件描述符集合里fd0 6
    7. int FD_ISSET(int fd, fd_set *set); //测试文件描述符集合里fd是否置1 7
    8. void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1 8
    9. void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0
    1. 返回值:
    2. 成功
    3. 有数据到达 ---->状态发生变化的文件描述符的总数
    4. 没有数据到达,超时-->0
    5. 失败 -1
    6. 注意:
    7. 1select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符
    8. 个数并不能改变select监听文件个数
    9. 2、解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模
    10. 型,会大大降低服务器响应效率
    11. 3、可以进行跨平台
    1. /*4_accept非阻塞IO轮训服务器*/
    2. #include <stdio.h>
    3. #include <unistd.h>
    4. #include <string.h>
    5. /*socket*/
    6. #include <sys/types.h> /* See NOTES */ //man 2 socket
    7. /*inet_addr*/
    8. #include <sys/socket.h> //man 3 inet_addr
    9. #include <netinet/in.h>
    10. #include <arpa/inet.h>
    11. /*fcntl*/
    12. #include <fcntl.h> //man 2 fcntl
    13. #define SERVER_IP "192.168.11.2"
    14. #define SERVER_PORT 60000
    15. int main(int argc,char **argv)
    16. {
    17. //创建套接字
    18. int socket_fd = socket(AF_INET,SOCK_STREAM,0);
    19. if(socket_fd < 0)
    20. {
    21. perror("socket fail");
    22. return -1;
    23. }
    24. //所以设置端口号可以复用,这两条语句放在 绑定bind 之前
    25. int optval = 1;
    26. setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,&optval, sizeof(optval));
    27. //绑定
    28. struct sockaddr_in server_addr;
    29. server_addr.sin_family = AF_INET;
    30. server_addr.sin_port = htons(SERVER_PORT);
    31. server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
    32. bind(socket_fd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    33. //监听
    34. listen(socket_fd,20);
    35. //添加非阻塞属性到socket_fd套接字中
    36. int status = fcntl(socket_fd, F_GETFL); //得到文件描述符的状态-->F_GETFL
    37. status |= O_NONBLOCK ;//在原来的基础上新增非阻塞属性
    38. fcntl(socket_fd, F_SETFL,status); //把变量status的状态设置到文件描述符中
    39. //接收
    40. int socket_client;
    41. int client_cnt[20],cnt=0,j=0;
    42. //初始化客户端的套接字
    43. for(j=0;j<20;j++)
    44. {
    45. client_cnt[j] = -1;
    46. }
    47. struct sockaddr_in client_addr;
    48. socklen_t addrlen = sizeof(client_addr);
    49. int ret;
    50. char buf[1024] = {0};
    51. //在不开进程和线程的情况,同时能够接受多个客户端
    52. while(1)
    53. {
    54. //由于socket_fd套接字带有阻塞属性,所以导致accept函数阻塞
    55. socket_client = accept(socket_fd,(struct sockaddr *)&client_addr,&addrlen);
    56. if(socket_client != -1)
    57. {
    58. char *ip = inet_ntoa(client_addr.sin_addr);
    59. int port = ntohs(client_addr.sin_port);
    60. printf("有新的客户端上线 [ip:%s port:%d]\n",ip,port);
    61. //将socket_client设置为非阻塞属性存放在数组中
    62. int status = fcntl(socket_client, F_GETFL); //得到文件描述符的状态-->F_GETFL
    63. status |= O_NONBLOCK ;//在原来的基础上新增非阻塞属性
    64. fcntl(socket_client, F_SETFL,status); //把变量status的状态设置到文件描述符中
    65. //将所有的客户端套接字存放在数组中
    66. client_cnt[cnt++] = socket_client;
    67. //cnt表示的是数组里面有效的客户端套接字
    68. //printf("cnt = %d\n",cnt);
    69. }
    70. #if 0 //查看数组里面所有的客户端套接字(调试用)
    71. for(j=0;j<20;j++)
    72. {
    73. printf("%d ",client_cnt[j]);
    74. }
    75. printf("\n");
    76. printf("等待连接中....\n");
    77. sleep(1);
    78. #endif
    79. //遍历数组里面所有的套接字(循环接受每一个客户端发过来的数据)
    80. for(j=0;j<cnt;j++) //cnt是数组里面有效的套接字
    81. {
    82. memset(buf,0,sizeof(buf));
    83. //默认状态下recv这个函数是阻塞属性,但是现在的recv是非阻塞的
    84. ret = recv(client_cnt[j],buf,sizeof(buf),0);
    85. if(ret > 0) //只打印有效的数据
    86. printf("buf:%s ret:%d\n",buf,ret);
    87. }
    88. }
    89. //关闭套接字
    90. close(socket_fd);
    91. }
    92. /*5_accept非阻塞IO轮训服务器ip与端口*/
    93. #include <stdio.h>
    94. #include <unistd.h>
    95. #include <string.h>
    96. /*socket*/
    97. #include <sys/types.h> /* See NOTES */ //man 2 socket
    98. /*inet_addr*/
    99. #include <sys/socket.h> //man 3 inet_addr
    100. #include <netinet/in.h>
    101. #include <arpa/inet.h>
    102. /*fcntl*/
    103. #include <fcntl.h> //man 2 fcntl
    104. #define SERVER_IP "192.168.11.2"
    105. #define SERVER_PORT 60000
    106. struct client_info
    107. {
    108. int cli_socket;
    109. struct sockaddr_in cli_addr;
    110. };
    111. int main(int argc,char **argv)
    112. {
    113. //创建套接字
    114. int socket_fd = socket(AF_INET,SOCK_STREAM,0);
    115. if(socket_fd < 0)
    116. {
    117. perror("socket fail");
    118. return -1;
    119. }
    120. //所以设置端口号可以复用,这两条语句放在 绑定bind 之前
    121. int optval = 1;
    122. setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,&optval, sizeof(optval));
    123. //绑定
    124. struct sockaddr_in server_addr;
    125. server_addr.sin_family = AF_INET;
    126. server_addr.sin_port = htons(SERVER_PORT);
    127. server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
    128. bind(socket_fd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    129. //监听
    130. listen(socket_fd,20);
    131. //添加非阻塞属性到socket_fd套接字中
    132. int status = fcntl(socket_fd, F_GETFL); //得到文件描述符的状态-->F_GETFL
    133. status |= O_NONBLOCK ;//在原来的基础上新增非阻塞属性
    134. fcntl(socket_fd, F_SETFL,status); //把变量status的状态设置到文件描述符中
    135. //初始化客户端的套接字
    136. int cnt = 0,j=0;
    137. struct client_info info[100];
    138. for(j=0;j<100;j++)
    139. info[j].cli_socket = -1;
    140. struct sockaddr_in client_addr;
    141. socklen_t addrlen = sizeof(client_addr);
    142. //接收
    143. int socket_client;
    144. int ret;
    145. char buf[1024] = {0};
    146. //在不开进程和线程的情况,同时能够接受多个客户端
    147. while(1)
    148. {
    149. //由于socket_fd套接字带有阻塞属性,所以导致accept函数阻塞
    150. socket_client = accept(socket_fd,(struct sockaddr *)&client_addr,&addrlen);
    151. if(socket_client != -1)
    152. {
    153. char *ip = inet_ntoa(client_addr.sin_addr);
    154. int port = ntohs(client_addr.sin_port);
    155. printf("有新的客户端上线 [ip:%s port:%d]\n",ip,port);
    156. //将socket_client设置为非阻塞属性存放在数组中
    157. int status = fcntl(socket_client, F_GETFL); //得到文件描述符的状态-->F_GETFL
    158. status |= O_NONBLOCK ;//在原来的基础上新增非阻塞属性
    159. fcntl(socket_client, F_SETFL,status); //把变量status的状态设置到文件描述符中
    160. //将所有的客户端套接字存放在数组中
    161. //cnt表示的是数组里面有效的客户端套接字
    162. info[cnt].cli_socket = socket_client;
    163. info[cnt].cli_addr = client_addr;
    164. cnt++;
    165. }
    166. #if 0 //查看数组里面所有的客户端套接字(调试用)
    167. for(j=0;j<20;j++)
    168. {
    169. printf("%d ",info[j].cli_socket);
    170. }
    171. printf("\n");
    172. printf("等待连接中....\n");
    173. sleep(1);
    174. #endif
    175. //遍历数组里面所有的套接字(循环接受每一个客户端发过来的数据)
    176. for(j=0;j<cnt;j++) //cnt是数组里面有效的套接字
    177. {
    178. memset(buf,0,sizeof(buf));
    179. //默认状态下recv这个函数是阻塞属性,但是现在的recv是非阻塞的
    180. /* ret = recv(client_cnt[j],buf,sizeof(buf),0);
    181. if(ret > 0) //只打印有效的数据
    182. printf("buf:%s ret:%d\n",buf,ret); */
    183. ret = recv(info[j].cli_socket,buf,sizeof(buf),0);
    184. if(ret > 0) //只打印有效的数据
    185. printf("[ip:%s port:%d]buf:%s ret:%d\n",
    186. inet_ntoa(info[j].cli_addr.sin_addr),
    187. ntohs(info[j].cli_addr.sin_port),
    188. buf,ret);
    189. }
    190. }
    191. //关闭套接字
    192. close(socket_fd);
    193. }
    194. /*6_使用多路复用select实现TCP服务器*/
    195. #include<stdio.h>
    196. #include<stdio.h>
    197. #include <sys/types.h> /* See NOTES */
    198. #include <sys/socket.h>
    199. #include <sys/socket.h>
    200. #include <arpa/inet.h>
    201. #include <unistd.h>
    202. #include<stdlib.h>
    203. #include <string.h>
    204. #include <errno.h>
    205. #include <signal.h>
    206. #include <sys/wait.h>
    207. #include <fcntl.h>
    208. #include <sys/time.h>
    209. #include <sys/types.h>
    210. #include <unistd.h>
    211. #define SERVER_IP "192.168.11.2"
    212. #define SERVER_PORT 60000
    213. void sys_err(const char*err)
    214. {
    215. fprintf(stderr,"%s\n",strerror(errno));
    216. exit(0);
    217. }
    218. int main()
    219. {
    220. int ret,max_fd,i=0,imax=0;
    221. int client[FD_SETSIZE]; //存储所有客户端的文件描述符//该宏系统已经定义好了 文件描述符的总数 1024
    222. struct sockaddr_in clientAddr;//存储连接上来的客户端的IP地址和端口号
    223. int len = sizeof(struct sockaddr_in);
    224. printf("服务器 Port:%d PID:%d \n",SERVER_PORT,getpid());
    225. //1、建立套接字(监听套接字)
    226. int socket_fd = socket(AF_INET,SOCK_STREAM, 0);
    227. if(socket_fd == -1)
    228. {
    229. sys_err("socket error");
    230. //perror("bind"); //两种错误条释放作用一样
    231. }
    232. //端口复用
    233. int optval = 1;
    234. setsockopt(socket_fd,SOL_SOCKET,SO_REUSEADDR,&optval, sizeof(optval));
    235. //2、绑定自己的IP地址和端口号
    236. struct sockaddr_in server_addr;
    237. server_addr.sin_family = AF_INET;
    238. server_addr.sin_port = htons(SERVER_PORT);//short
    239. //server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //表示本机任意IP地址
    240. server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
    241. ret = bind(socket_fd,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in));
    242. if(ret == -1)
    243. {
    244. sys_err("bind error");
    245. }
    246. //3、设置监听
    247. listen(socket_fd,128);
    248. //定义文件描述符集合
    249. fd_set rset,allset;
    250. //清空集合
    251. FD_ZERO(&allset);
    252. FD_SET(socket_fd, &allset); //把监听套接字 加入到集合中
    253. //最大的文件描述符,在没有客户端进来之前,最大的文件描述符最大值就是socket_fd
    254. max_fd = socket_fd;
    255. //初始化为 -1 给存放客户端套接字的数组初始化
    256. for(int k=0; k<FD_SETSIZE; k++)
    257. {
    258. client[k] = -1;
    259. }
    260. //设置阻塞的时间
    261. struct timeval t;
    262. t.tv_sec = 5;
    263. t.tv_usec = 0;
    264. while(1)
    265. {
    266. rset = allset; //每次循环的时候都需要重新设置select的集合
    267. //多路复用 ,同时监听 多个套接字文件描述符的状态 --阻塞监听
    268. //nready表示的是状态发生变化的文件描述符的总数,(不是里面存放了多少个描述符)
    269. int nready = select(max_fd+1,&rset, NULL,NULL, NULL);
    270. if(nready == -1)
    271. {
    272. perror("select error");
    273. break;
    274. }
    275. //程序走到这里,说明集合中的文件描述符的状态一定发生变化了
    276. //如果是监听套接字文件描述符发生变化了,说明一定是有新客户端连接上来了
    277. if(FD_ISSET(socket_fd, &rset))
    278. {
    279. //接收新的客户端
    280. int new_clientfd = accept(socket_fd, (struct sockaddr*)&clientAddr,&len);
    281. printf("有新的客户端连接上来 IP:%s Port:%hu new_clientfd:%d\n",
    282. inet_ntoa(clientAddr.sin_addr),
    283. ntohs(clientAddr.sin_port),
    284. new_clientfd);
    285. //把新的客户端文件描述符加入到集合中
    286. FD_SET(new_clientfd, &allset);
    287. //更新文件描述符最大值
    288. if(new_clientfd > max_fd)
    289. max_fd = new_clientfd;
    290. //将连接上来的客户端文件描述符 加入到 数组中
    291. for(i=0; i<FD_SETSIZE; i++)
    292. {
    293. if(client[i] <0 )
    294. {
    295. client[i] = new_clientfd;
    296. break;
    297. }
    298. }
    299. //imax = i;//存放客户端套接字数组里面的最大的下标值(可以通过这个值来记录你客户端数目)
    300. //说明该集合中只有监听套接字文件描述符发生变化
    301. if(--nready == 0)
    302. continue;
    303. }
    304. //程序走到这里,说明有客户端发送数据过来
    305. for(i=0; i<FD_SETSIZE; i++)
    306. {
    307. if(client[i] <0)
    308. continue;
    309. //说明该客户端发送数据过来了,
    310. //客户端套接字的状态就发生变化这句话if(FD_ISSET(client[i], &rset))就成立
    311. if(FD_ISSET(client[i], &rset))
    312. {
    313. char buf[1024] = {0};
    314. int ret = read(client[i],buf,sizeof(buf));
    315. if(ret == 0) //该客户端断开了
    316. {
    317. printf("有客户端断开了.....\n");
    318. close(client[i]);//关闭文件描述符
    319. FD_CLR(client[i],&allset);//将该客户端从 文件集合中删除
    320. client[i] = -1; //该文件描述符对应的数组位置置 -1
    321. }
    322. printf("recv:%s\n",buf);
    323. //说明所有发生变化的文件描述符 已经被我们处理完了,则退出
    324. if(--nready == 0)
    325. break;
    326. }
    327. }
    328. }
    329. //关闭
    330. close(socket_fd);
    331. return 0;
    332. }

    Client.c

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. #include <unistd.h>
    5. /*socket*/
    6. #include <sys/types.h> /* See NOTES */
    7. #include <sys/socket.h>
    8. /*ip*/
    9. #include <netinet/in.h> //man 3 inet_addr
    10. #include <arpa/inet.h>
    11. //服务器的IP地址
    12. #define SERVER_IP "192.168.11.2"
    13. #define SERVER_PORT 60000
    14. int main(int argc,char **argv)
    15. {
    16. //建立套接字--socket
    17. int socket_fd;
    18. //AF_INET-->ipv4 SOCK_STREAM-->tcp
    19. socket_fd = socket(AF_INET,SOCK_STREAM,0);
    20. if(socket_fd < 0)
    21. {
    22. perror("socket fail");
    23. return -1;
    24. }
    25. printf("socket_fd = %d\n",socket_fd);
    26. //填充IP地址(服务器)--新结构体
    27. struct sockaddr_in server_addr;
    28. server_addr.sin_family = AF_INET; //ipv4
    29. server_addr.sin_port = htons(SERVER_PORT);//host to net(本机端口号转网络端口号)
    30. server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);//将本机IP转换为网络IP
    31. //连接服务器
    32. int ret;
    33. ret = connect(socket_fd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    34. if(ret < 0)
    35. {
    36. printf("connect fail\n");
    37. return -1;
    38. }
    39. //给服务器发送数据
    40. char buf[1024] = {0};
    41. while(1)
    42. {
    43. memset(buf,0,sizeof(buf));
    44. scanf("%s",buf);
    45. ret = send(socket_fd,buf,strlen(buf),0);
    46. printf("send success ret:%d\n",ret);
    47. }
    48. //关闭套接字
    49. close(socket_fd);
    50. return 0;
    51. }

  • 相关阅读:
    好用的截图软件Snipaste2.7.3
    [附源码]SSM计算机毕业设计血库管理系统JAVA
    C语言的程序流程控制
    njs最详细的入门手册:Nginx JavaScript Engine
    SQL注入漏洞(MSSQL注入)
    【人工智能基础】机器学习
    clickhouse在执行alter table update delete等命令后数据没有更新
    自定义mvc增删改查
    【网络安全】网站中间件存在的解析漏洞
    如何在 Linux 上安装 MySQL
  • 原文地址:https://blog.csdn.net/weixin_48102054/article/details/127737249