Linux 网络编程——TCP poll聊天程序
实现目标
【1】创建TCP服务器和客户端,实现简易聊天程序;
【2】单一进程,通过I/O复用poll函数实现;
【3】客户端/服务器任一结束,结束连接和对方进程。
poll函数
poll函数和select函数调用的本质一样的,也是对所有监听文件描述符进行轮询,有事件发生则返回。与select不同的是,poll监听文件描述符数目没有限制,poll执行完不会清空文件描述符集合,也就是不需每次都重新装载文件描述符。因此,如果监听描述符数目大时,poll体现出来的效率要比select高。
函数原型
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
形参
参数 | 含义 |
---|---|
fds | 监听文件描述符事件集合 |
nfds | 监听文件描述符数目 |
timeout | 超时时间(毫秒),传入0立即返回;传入-1(INFTIM)为不超时阻塞 |
其中fds是一个“struct pollfd”结构体,是实现poll I/O复用的关键。
struct pollfd
{
int fd; /* 被监听文件描述符 */
short events; /* 监听事件 */
short revents; /* 监听返回的事件 */
};
监听事件是对需要监听的文件描述符进行事件注册,由用户设置;返回事件是用于存放poll返回的事件结果,如果poll返回值为负值(执行出错),返回事件是无效的,即使有相关事件置位,也不能使用该事件作为判断条件。
事件 | 含义 | 可作为设定事件(events) | 可作为返回事件(revents) |
---|---|---|---|
POLLIN | 普通或优先级带数据可读 | 是 | 是 |
POLLRDNORM | 普通数据可读 | 是 | 是 |
POLLRDBAND | 优先级带数据可读 | 是 | 是 |
POLLPRI | 高优先级数据可读(比如TCP带外数据) | 是 | 是 |
POLLOUT | 数据可写 | 是 | 是 |
POLLWRNORM | 普通数据可写 | 是 | 是 |
POLLWRBAND | 优先级带数据可写 | 是 | 是 |
POLLRDHUP | TCP连接被对端关闭,或者关闭了写操作 | 是 | 是 |
POPPHUP | 发生挂起 | 否 | 是 |
POLLERR | 发生错误 | 否 | 是 |
POLLNVAL | 文件描述符没有打开 | 否 | 是 |
返回值
返回值 | 含义 |
---|---|
负值 | poll函数执行错误,错误原因存于errno中 |
正值 | 事件就绪文件描述符的总数量 |
0 | 等待超时,没有可读/写,或异常的文件 |
实现代码
服务器端(server)
#define _GNU_SOURCE 1 /* 使用“POLLRDHUP”宏需定义该宏 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/prctl.h>
#include <arpa/inet.h>
#include <errno.h>
#include <resolv.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <poll.h>
int main (int argc, char * argv[])
{
int s_fd = 0, c_fd = 0, pid;
socklen_t addr_len;
struct sockaddr_in s_addr, c_addr;
char buf[1024];
ssize_t size = 0;
struct pollfd p_fds[5]; /* 最大监听5个句柄 */
int max_fd;
int ret = 0;
if(argc != 3)
{
printf("format error!\n");
printf("usage: server <address> <port>\n");
exit(1);
}
/* 服务器端地址 */
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
if(!inet_aton(argv[1], (struct in_addr *) &s_addr.sin_addr.s_addr))
{
perror("invalid ip addr:");
exit(1);
}
/* 创建socket */
if((s_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket create failed:");
exit(1);
}
/* 端口重用,调用close(socket)一般不会立即关闭socket,而经历“TIME_WAIT”的过程 */
int reuse = 0x01;
if(setsockopt(s_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(int)) < 0)
{
perror("setsockopt error");
close(s_fd);
exit(1);
}
/* 绑定地址 */
if(bind(s_fd, (struct sockaddr*)&s_addr, sizeof(s_addr)) < 0)
{
perror("bind error");
close(s_fd);
exit(1);
}
/* 监听socket */
if(listen(s_fd, 5) < 0)
{
perror("listen error");
close(s_fd);
exit(1);
}
addr_len = sizeof(struct sockaddr);
/* poll 监听参数 */
for(ret = 0;ret < 5;ret++)
{
p_fds[ret].fd = -1;
}
p_fds[0].fd = STDIN_FILENO;
p_fds[0].events = POLLIN;
p_fds[0].revents = 0;
p_fds[1].fd = s_fd;
p_fds[1].events = POLLIN;
p_fds[1].revents = 0;
max_fd = 1;
for(;;)
{
ret = poll(p_fds,max_fd+1,2000); /* 监听5个句柄;-1表示阻塞,不超时 */
if(ret < 0)
{
perror("poll error");
break;
}
else if(ret == 0)
{
continue;
}
if(((p_fds[1].revents&POLLIN) == POLLIN) && (p_fds[1].fd == s_fd))
{
if(c_fd)
{
continue;
}
printf("waiting client connecting\r\n");
c_fd = accept(s_fd, (struct sockaddr*)&c_addr, (socklen_t *)&addr_len);
if(c_fd < 0)
{
perror("accept error");
close(s_fd);
break;
}
else
{
printf("connected with ip: %s and port: %d\n", inet_ntop(AF_INET,&c_addr.sin_addr, buf, 1024), ntohs(c_addr.sin_port));
}
/* 将客户端加入监听集合中 */
p_fds[2].fd = c_fd;
p_fds[2].events = POLLIN | POLLRDHUP | POLLPRI;
p_fds[2].revents = 0;
max_fd = 2;
}
if((p_fds[0].revents&POLLIN) == POLLIN && p_fds[0].fd == STDIN_FILENO)
{
fflush(stdout);
memset(buf, 0, sizeof(buf));
size = read(STDIN_FILENO, buf, sizeof(buf) - 1);
if(size > 0)
{
buf[size-1] = '\0';
}
else
{
perror("read stdin error");
break;
}
if(!strncmp(buf, "quit", 4))
{
printf("close the connect!\n");
break;
}
if(buf[0] == '\0')
{
printf("please enter message to send:\n");
continue;
}
size = write(c_fd, buf, strlen(buf));
if(size <= 0)
{
printf("message'%s' send failed!errno code is %d,errno message is '%s'\n",buf, errno, strerror(errno));
break;
}
printf("please enter message to send:\n");
}
if((p_fds[2].revents&POLLRDHUP) == POLLRDHUP && p_fds[2].fd == c_fd)
{/* disconnect */
printf("client disconnect!\n");
break;
}
if((p_fds[2].revents&POLLIN) == POLLIN && p_fds[2].fd == c_fd)
{
memset(buf, 0, sizeof(buf));
size = read(c_fd, buf, sizeof(buf) - 1);
if(size > 0)
{
printf("message recv %dByte: \n%s\n",size,buf);
}
else if(size < 0)
{
printf("recv failed!errno code is %d,errno message is '%s'\n",errno, strerror(errno));
break;
}
else
{
printf("client disconnect!\n");
break;
}
}
}
close(s_fd);
close(c_fd);
return 0;
}
客户端(client)
#define _GNU_SOURCE 1 /* 使用“POLLRDHUP”宏需定义该宏 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/prctl.h>
#include <poll.h>
int main (int argc, char * argv[])
{
int c_fd,pid;
int ret = 0;
struct sockaddr_in s_addr;
socklen_t addr_len;
char buf[1024];
ssize_t size;
fd_set s_fds;
struct pollfd p_fds[5]; /* 最大监听5个句柄 */
int max_fd; /* 监听文件描述符中最大的文件号 */
if(argc != 3)
{
printf("format error!\n");
printf("usage: client <address> <port>\n");
exit(1);
}
/* 创建socket */
c_fd = socket(AF_INET, SOCK_STREAM, 0);
if(c_fd < 0)
{
perror("socket create failed");
return -1;
}
/* 服务器端地址 */
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
if(!inet_aton(argv[1], (struct in_addr *) &s_addr.sin_addr.s_addr))
{
perror("invalid ip addr");
exit(1);
}
/* 连接服务器*/
addr_len = sizeof(s_addr);
ret = connect(c_fd, (struct sockaddr*)&s_addr, addr_len);
if(ret < 0)
{
perror("connect server failed");
exit(1);
}
/* poll 监听参数 */
for(ret = 0;ret < 5;ret++)
{
p_fds[ret].fd = -1;
}
p_fds[0].fd = STDIN_FILENO;
p_fds[0].events = POLLIN;
p_fds[0].revents = 0;
p_fds[1].fd = c_fd;
p_fds[1].events = POLLIN | POLLRDHUP | POLLPRI;
p_fds[1].revents = 0;
max_fd = 1;
for(;;)
{
ret = poll(p_fds,max_fd+1,-1); /* 监听5个句柄;-1表示阻塞,不超时 */
if(ret < 0)
{
perror("poll error");
break;
}
else if(ret == 0)
{
continue;
}
if((p_fds[0].revents&POLLIN) == POLLIN && p_fds[0].fd == STDIN_FILENO)
{
fflush(stdout);
memset(buf, 0, sizeof(buf));
size = read(STDIN_FILENO, buf, sizeof(buf) - 1);
if(size > 0)
{
buf[size-1] = '\0';
}
else
{
perror("read stdin error");
break;
}
if(!strncmp(buf, "quit", 4))
{
printf("close the connect!\n");
break;
}
if(buf[0] == '\0')
{
printf("please enter message to send:\n");
continue;
}
size = write(c_fd, buf, strlen(buf));
if(size <= 0)
{
printf("message'%s' send failed!errno code is %d,errno message is '%s'\n",buf, errno, strerror(errno));
break;
}
printf("please enter message to send:\n");
}
if((p_fds[1].revents&POLLRDHUP) == POLLRDHUP && p_fds[1].fd == c_fd)
{/* disconnect */
printf("server disconnect!\n");
break;
}
if((p_fds[1].revents&POLLIN) == POLLIN && p_fds[1].fd == c_fd)
{
memset(buf, 0, sizeof(buf));
size = read(c_fd, buf, sizeof(buf) - 1);
if(size > 0)
{
printf("message recv %dByte: \n%s\n",size,buf);
}
else if(size < 0)
{
printf("recv failed!errno code is %d,errno message is '%s'\n",errno, strerror(errno));
break;
}
else
{
printf("server disconnect!\n");
break;
}
}
}
close(c_fd);
return 0;
}
执行效果