【Linux网络编程】基于TCP协议 I/O多路转接(select) 的高性能回显服务器客户端模型
程序员文章站
2022-06-06 09:25:51
...
服务端代码:
myselect.c
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define ARRAY_SIZE 1024
/* 使用说明 */
void usage(const char* proc)
{
printf("%s usage : [server_ip] [ server_port]\n", proc);
}
/* 初始化一个socket连接 */
int init_sock(const char* _ip, int _port)
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
perror("socket");
exit(2);
}
int flg = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flg, sizeof(flg));
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = inet_addr(_ip);
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
perror("bind");
exit(3);
}
if(listen(sock , 10) < 0)
{
perror("listen");
exit(4);
}
return sock;
}
/* 等待事件发生并处理 */
void run_select(int listen_sock)
{
int array_fds[ARRAY_SIZE]; /* 保存所有关心的描述符 */
array_fds[0] = listen_sock; /* 将listen_sock 保存*/
/* 初始化为 array_fds */
int idx_init = 1;
for(idx_init = 1; idx_init < ARRAY_SIZE; ++idx_init)
array_fds[idx_init] = -1;
fd_set rfds; /* 关心的读事件描述符集 */
fd_set wfds; /* 关心的写事件描述符集 */
int max_fd = -1;
struct timeval _timeout = {1, 0};
while(1)
{
max_fd = -1;
_timeout.tv_sec = 1;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
/* 一. 将数组中存储的描述符,添加到关心的集合中
* 并找出其中最大的描述符,
*/
int idx_add = 1;
for(idx_add = 0; idx_add < ARRAY_SIZE; idx_add++)
{
if(array_fds[idx_add] > 0)
{
FD_SET(array_fds[idx_add], &rfds);
FD_SET(array_fds[idx_add], &wfds);
if(array_fds[idx_add] > max_fd)
max_fd = array_fds[idx_add];
}
}
/* 二. 检测select的返回情况*/
int select_ret = select(max_fd+1, &rfds, &wfds, NULL, &_timeout);
switch( select_ret )
{
case 0:
printf("timeout\n");
break;
case -1:
perror("select\n");
break;
default:
{
/* 遍历数组,检测事件发生的描述符,并响应 */
int idx_check = 0;
for(idx_check = 0; idx_check < ARRAY_SIZE; ++idx_check)
{
if(array_fds[idx_check] < 0)
continue;
if(idx_check == 0 && FD_ISSET(array_fds[idx_check], &rfds) )
{ /* listensock 有数据来,表示有新的连接请求 */
struct sockaddr_in client;
socklen_t len = sizeof(client);
int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
if(new_sock > 0)
{
printf("新的连接请求来自: ip = %s, port = %d \n",inet_ntoa(client.sin_addr), ntohs(client.sin_port));
/* 在数组中找到未被占用的描述符位置,并保存新连接的描述符 */
int idx_find = 0;
for(idx_find = 1; idx_find < ARRAY_SIZE; ++idx_find)
{
if(array_fds[idx_find] < 0)
{
array_fds[idx_find] = new_sock;
break;
}
}
if(idx_find == ARRAY_SIZE)
close(new_sock);
}
} // << end if idx_check == 0 && FD_ISSET -- rfds>>
else if( idx_check != 0 && FD_ISSET(array_fds[idx_check], &rfds) )
{ /* 其余描述符有数据来*/
char buf[ 1024];
memset(buf, '\0', sizeof(buf));
ssize_t s = read(array_fds[idx_check], buf, sizeof(buf) - 1);
if(s > 0)
{
printf("client say : %s\n", buf);
if(FD_ISSET(array_fds[idx_check], &wfds))
write(array_fds[idx_check], buf, sizeof(buf)-1);
}
else if(s == 0)
{
printf("client quit\n");
close(array_fds[idx_check]);
array_fds[idx_check] = -1;
}
else
{
perror("read fail");
close(array_fds[idx_check]);
array_fds[idx_check] = -1;
}
} // << end else if idx_check != 0 && FD_ISSET -- rfds >>
} // << end for idx_check = 0; idx_check<ARRAY_SIZE; ++idx_check >>
} // << end default >>
break;
} // << end switch select >>
} // << end while 1 >>
}
int main(int argc, char **argv)
{
if(3 != argc)
{
usage(argv[0]);
exit(1);
}
int sock = init_sock(argv[1], atoi(argv[2]));
printf("start run_select\n");
run_select(sock);
return 0;
}
客户端代码:
为了练习dup 和 dup2 函数的使用,在客户端中,使用了这两个函数进行标准输出的重定向以及恢复,使用printf 函数向sockfd 中写数据,并提示用户输入。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
if(argc != 3)
{
printf("Usage [server_ip] [ server_port]\n");
exit(1);
}
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
perror("socket");
exit(2);
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(argv[1]);
server.sin_port = htons(atoi(argv[2]));
if(connect(sock,(struct sockaddr*)&server, sizeof(server))<0)
{
perror("connect");
exit(3);
}
// 保存标准输出的描述符
int oldfd = dup(STDOUT_FILENO);
char buf[1024];
while(1)
{
printf("please input #");
fflush(stdout);
memset(buf, '\0', sizeof(buf));
/* 重定向输出到sock */
dup2(sock, STDOUT_FILENO);
ssize_t s = read(0, buf, sizeof(buf)-1);
if(s > 0)
{
/* 处理客户端只输入一个回车而程序挂起的bug */
if( buf[0] == '\n' )
{
dup2(oldfd, STDOUT_FILENO);
continue;
}
if(strncmp("quit", buf, 4) == 0)
break;
buf[s - 1] = 0;
/* 用printf 向sock写 */
printf("%s", buf);
fflush(stdout);
/* 恢复 标准输出 */
dup2(oldfd, STDOUT_FILENO);
/* 从sock读服务端回显的数据, */
ssize_t _s = read(sock, buf, sizeof(buf) - 1);
if(_s >0)
{
buf[_s] = 0;
printf("server echo # %s\n", buf);
}
else if (s <= 0)
{
continue;
}
}
}
close(sock);
close(oldfd);
return 0;
}
下一篇: 中西式快餐网站建设的区别