【Linux网络编程】TCP select聊天程序

实现目标

【1】创建TCP服务器和客户端,实现简易聊天程序;
【2】单一进程,通过I/O复用select函数实现;
【3】客户端/服务器任一结束,结束连接和对方进程。


select函数

  Linux系统中I/O复用实现方式有selece()、poll()、epoll()。I/O复用可以使单一进程可以同时监听多个文件描述符(句柄)事件的发生,而不用建立多个进程,在特殊情况下可以节约系统资源,但牺牲一定的效率。 常用的I/O复用场景有:

【1】服务端同时处理监听socket和连接socket ;
【2】服务端同时处理TCP请求和UDP请求 ;
【3】服务端同时监听多个端口或者处理多种服务请求;
【4】客户端同时处理多个socket ;
【5】客户端同时处理用户输入(如STDIN)和网络连接。

  select函数功能是在指定超时时间内,或者阻塞,监听进程指定的文件描述符上的事件状态,在可读、可写、异常事件发生后,唤醒进程进行相关事件处理。


函数原型

#include 

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

形参

参数 含义
nfds 监听文件描述符数目+1
readfds 监听可读文件描述符集合
writefds 监听可写文件描述符集合
exceptfds 监听异常文件描述符集合
timeout 超时时间,传入NULL时为不超时阻塞

返回值

返回值 含义
负值 select函数执行错误,错误原因存于errno中
正值 文件可读/写,异常
0 等待超时,没有可读/写,或异常的文件

select执行失败时,常见错误码

错误码 含义
EBADF 文件描述符无效或文件已关闭
EINTR 调用被信号所中断
EINVAL 参数nfds为负值
ENOMEM 内存不足

相关函数

函数 作用
FD_CLR(inr fd,fd_set* set); 清除文件描述符组set中相关fd的位
FD_ISSET(int fd,fd_set *set); 检查文件描述符组set中相关fd的位是否为真(bool)
FD_SET(int fd,fd_set*set); 设置文件描述符组set中相关fd的位
FD_ZERO(fd_set *set); 清除文件描述符组set的全部位

使用注意事项

【1】select函数调用后,会清空它所监听的文件描述符集合,所以每次调用select()之前必须把描述符重新加入到监听集合中;
【2】最大监听文件描述符有限制,一般是1024。


实现代码

服务器端(server)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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;
	fd_set s_fds;
	int max_fd;            	/* 监控文件描述符中最大的文件号 */
	struct timeval tv;      /* 超时返回时间 */
	int ret = 0;
	
	if(argc != 3)
	{
		printf("format error!\n");
		printf("usage: server 
\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); int set = 0; for(;;) { FD_ZERO(&s_fds); /* 清空s_fds集合 */ FD_SET(STDIN_FILENO, &s_fds); /* 加入标准输入集合 */ FD_SET(s_fd, &s_fds); /* 加入服务器fd集合 */ if(c_fd != 0) { FD_SET(c_fd, &s_fds); /* 加入客户端fd集合 */ } if(c_fd >= max_fd) { max_fd = c_fd; } else { max_fd = s_fd; } tv.tv_sec = 10; tv.tv_usec = 0; ret = select(max_fd+1, &s_fds, NULL, NULL, &tv); if(ret < 0) { perror("select error"); break; } else if(ret == 0) { continue; /* time out */ } if(FD_ISSET(s_fd,&s_fds)) { if(c_fd) { continue; } c_fd = accept(s_fd, (struct sockaddr*)&c_addr, (socklen_t *)&addr_len); if(c_fd < 0) { perror("accept error"); close(s_fd); exit(1); } 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)); } printf("waiting client connecting\r\n"); } if(c_fd <= 0) { continue; /* 无客户端连接 */ } if(FD_ISSET(c_fd,&s_fds)) { 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; } } if(FD_ISSET(STDIN_FILENO,&s_fds)) { fflush(stdout); memset(buf, 0, sizeof(buf)); size = read(STDIN_FILENO, buf, sizeof(buf) - 1); if(size > 0) { buf[size-1] = '\0'; } else if(size == 0) { printf("read is done...\n"); break; } 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)); //printf("write %s\n,size %d",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"); } } close(s_fd); close(c_fd); return 0; }

客户端(client)

#include 
#include 
#include 
#include 
#include  
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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;
	int max_fd; 			/* 监控文件描述符中最大的文件号 */
	struct timeval tv;		/* 超时返回时间 */

	if(argc != 3)
	{
		printf("format error!\n");
		printf("usage: client 
\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); } for(;;) { FD_ZERO(&s_fds); /* 清空s_fds集合 */ FD_SET(STDIN_FILENO, &s_fds); /* 加入标准输入集合 */ FD_SET(c_fd, &s_fds); /* 加入客户端fd集合 */ max_fd = c_fd; tv.tv_sec = 30; tv.tv_usec = 0; ret = select(max_fd+1, &s_fds, NULL, NULL, &tv); if(ret < 0) { perror("select error"); break; } else if(ret == 0) { continue; /* time out */ } if(FD_ISSET(STDIN_FILENO,&s_fds)) /* 终端消息 */ { fflush(stdout); memset(buf, 0, sizeof(buf)); size = read(STDIN_FILENO, buf, sizeof(buf) - 1); if(size > 0) { buf[size-1] = '\0'; } else if(size == 0) { printf("read is done...\n"); break; } 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)); //printf("write %s\n,size %d",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(FD_ISSET(c_fd,&s_fds)) /* 服务器消息 */ { memset(buf, 0, sizeof(buf)); size = read(c_fd, buf, sizeof(buf)); 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; }

执行效果
在这里插入图片描述

你可能感兴趣的:(C,linux网络编程,Linux应用编程)