TCP聊天程序linux,【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;

}

执行效果

690b5b69f3bc875a792f2ce15bc45cf9.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值