Socket网络编程--聊天程序(4)
上一小节讲到可以实现多客户端与服务器进行通讯,对于每一个客户端的连接请求,服务器都要分配一个进程进行处理。对于多用户连接时,服务器会受不了的,而且还很消耗资源。据说有个select函数可以用,好像还很NB的样子。
使用select多路转换处理聊天程序
下面摘取APUE 14.5小结 I/O多路转接
当从一个描述符读,然后又写到另一个描述符时,可以在下列形式的循环中循环中使用阻塞I/O:
while((n = read(STDIN_FILENO, buf, BUFFSIZ))>0)
if(write(STDOUT_FILENO, buf, n)!=n)
err_sys("write error");
这种形式的阻塞I/O到处可见。但是如果必须从两个描述符读,又将如何呢?如果仍旧使用阻塞I/O,那么就可能长时间阻塞在一个描述符上,而另一个描述符虽有很多数据却不能得到及时处理。所以为了处理这种情况显然需要另一种不同的技术。
方法一:也就是上一小节使用的方法,使用多进程。每一个进程处理一个描述符
方法二:和上面相似的,使用多线程,不同的线程处理不同的描述符
方法三:仍然使用一个进程执行该程序,但使用非阻塞I/O读取数据。然后对所有的描述符进行遍历一遍,判断对应的描述符是否有数据,如果有就读取,如果没有就立即返回。这种办法就是轮询(polling)
方法四:异步I/O。其基本的思想是进程告诉内核,当一个描述符已经准备好可以进行I/O时,用一个信号通知它。
方法五:这是一种比较好的办法。叫做I/O多路转换(I/O multiplexing)。先构造一张有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已经准备好进行I/O时,该函数才会返回。在返回时,它高数进程哪些描述符已经准备好可以进行I/O。
poll,pselect和select这三个函数使我们能够执行I/O多路转换。本程序只使用select函数。
#include <sys/select.h>
int select (int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct time val *restrict tvptr); //返回值:准备就绪的描述符数,若超时则返回0,否则出错返回-1
select 函数讲解
FD_ISSET判断描述符fd是否在给定的描述符集fdset中,通常配合select函数使用,由于select函数成功返回时会将未准备好的描述符位清零。通常我们使用FD_ISSET是为了检查在select函数返回后,某个描述符是否准备好,以便进行接下来的处理操作。
fd_set数据类型的操作
#include <sys/select.h>
int FD_ISSET(int fd, fd_set *fdset); //判断fd是否在fdset中
void FD_CLR(int fd, fd_set *fdset); //进fd从fdset中取出
void FD_SET(int fd, fd_set *fdset); //将fd放入fdset
void FD_ZERO(fd_set *fdset); //将fdset清空
timeval结构分析
struct timeval{
long tv_sec; //seconds
long tv_usec; //and microseconds
};
client.c的代码没有改
server.c的代码如下
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h> #define SERVER_PORT 12138
#define BACKLOG 20
#define MAX_CON_NO 10
#define MAX_DATA_SIZE 4096 int MAX(int a,int b)
{
if(a>b) return a;
return b;
} int main(int argc,char *argv[])
{
struct sockaddr_in serverSockaddr,clientSockaddr;
char sendBuf[MAX_DATA_SIZE],recvBuf[MAX_DATA_SIZE];
int sendSize,recvSize;
int sockfd,clientfd;
fd_set servfd,recvfd;//用于select处理用的
int fd_A[BACKLOG+];//保存客户端的socket描述符
int conn_amount;//用于计算客户端的个数
int max_servfd,max_recvfd;
int on=;
socklen_t sinSize=;
char username[];
int pid;
int i;
struct timeval timeout; if(argc != )
{
printf("usage: ./server [username]\n");
exit();
}
strcpy(username,argv[]);
printf("username:%s\n",username); /*establish a socket*/
if((sockfd = socket(AF_INET,SOCK_STREAM,))==-)
{
perror("fail to establish a socket");
exit();
}
printf("Success to establish a socket...\n"); /*init sockaddr_in*/
serverSockaddr.sin_family=AF_INET;
serverSockaddr.sin_port=htons(SERVER_PORT);
serverSockaddr.sin_addr.s_addr=htonl(INADDR_ANY);
bzero(&(serverSockaddr.sin_zero),); /*
* SOL_SOCKET.SO_REUSEADDR 允许重用本地地址
* */
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); /*bind socket*/
if(bind(sockfd,(struct sockaddr *)&serverSockaddr,sizeof(struct sockaddr))==-)
{
perror("fail to bind");
exit();
}
printf("Success to bind the socket...\n"); /*listen on the socket*/
if(listen(sockfd,BACKLOG)==-)
{
perror("fail to listen");
exit();
} timeout.tv_sec=;//1秒遍历一遍
timeout.tv_usec=;
sinSize=sizeof(clientSockaddr);//注意要写上,否则获取不了IP和端口 FD_ZERO(&servfd);//清空所有server的fd
FD_ZERO(&recvfd);//清空所有client的fd
FD_SET(sockfd,&servfd);
conn_amount=;
max_servfd=sockfd;//记录最大的server端描述符
max_recvfd=;//记录最大的client端的socket描述符
while()
{
FD_ZERO(&servfd);//清空所有server的fd
FD_ZERO(&recvfd);//清空所有client的fd
FD_SET(sockfd,&servfd);
//timeout.tv_sec=30;//可以减少判断的次数
switch(select(max_servfd+,&servfd,NULL,NULL,&timeout))//为什么要+1,是因为第一个参数是所有描述符中最大的描述符fd号加一,原因的话在APUE中有讲,因为内部是一个数组,第一个参数是要生成一个这样大小的数组
{
case -:
perror("select error");
break;
case :
//在timeout时间内,如果没有一个描述符有数据,那么就会返回0
break;
default:
//返回准备就绪的描述符数目
if(FD_ISSET(sockfd,&servfd))//sockfd 有数据表示可以进行accept
{
/*accept a client's request*/
if((clientfd=accept(sockfd,(struct sockaddr *)&clientSockaddr, &sinSize))==-)
{
perror("fail to accept");
exit();
}
printf("Success to accpet a connection request...\n");
printf(">>>>>> %s:%d join in!\n",inet_ntoa(clientSockaddr.sin_addr),ntohs(clientSockaddr.sin_port));
//每加入一个客户端都向fd_A写入
fd_A[conn_amount++]=clientfd;
max_recvfd=MAX(max_recvfd,clientfd);
}
break;
}
//FD_COPY(recvfd,servfd);
for(i=;i<MAX_CON_NO;i++)//最大队列进行判断,优化的话,可以使用链表
{
if(fd_A[i]!=)
{
FD_SET(fd_A[i],&recvfd);//对所有还连着服务器的客户端都放到fd_set中用于下面select的判断
}
} switch(select(max_recvfd+,&recvfd,NULL,NULL,&timeout))
{
case -:
//select error
break;
case :
//timeout
break;
default:
for(i=;i<conn_amount;i++)
{
if(FD_ISSET(fd_A[i],&recvfd))
{
/*receive datas from client*/
if((recvSize=recv(fd_A[i],recvBuf,MAX_DATA_SIZE,))==-)
{
//perror("fail to receive datas");
//表示该client是关闭的
printf("close\n");
FD_CLR(fd_A[i],&recvfd);
fd_A[i]=;//表示该描述符已经关闭
}
else
{
printf("Client:%s\n",recvBuf);
//可以判断recvBuf是否为bye来判断是否可以close
memset(recvBuf,,MAX_DATA_SIZE);
}
}
}
break;
} }
return ;
}
运行后的截图结果

可以看出三个客户端都可以随时连接到服务器,并且发送数据给服务器。实现的效果跟上一节的多进程实现是一样的。毕竟没有大量客户端进行连接,所以就看不出效果,从书中和网上介绍说,这样可以提高某些方面的性能。
下一节将介绍服务器端向各个还在线的客户端进行发送数据,实现交互。然后再实现聊天室功能,大概的思路就是对接收到的数据进行转发。
参考资料: http://www.cnblogs.com/gentleming/archive/2010/11/15/1877976.html
本文出处: http://www.cnblogs.com/wunaozai/p/3870338.html
Socket网络编程--聊天程序(4)的更多相关文章
- Socket网络编程--聊天程序(9)
这一节应该是聊天程序的最后一节了,现在回顾我们的聊天程序,看起来还有很多功能没有实现,但是不管怎么说,都还是不错的.这一节我们将讲多服务器问题(高大上的说法就是负载问题了.)至于聊天程序的文件发送(也 ...
- Socket网络编程--聊天程序(1)
很早的一段时间,看了APUE和UNPv1了解了网络编程,但是但是只是看而已,没有具体的实践,趁现在没有什么事做,就来实践了解一下网络编程.写博客保存下来,方便以后用到的时候可以查到. 此次的聊天程序是 ...
- Socket网络编程--聊天程序(8)
上一节已经完成了对用户的身份验证了,既然有了验证,那么接下来就能对不同的客户端进行区分了,所以这一节讲实现私聊功能.就是通过服务器对客户端的数据进行转发到特定的用户上, 实现私聊功能的聊天程序 实现的 ...
- Socket网络编程--聊天程序(6)
这一小节将增加一个用户的结构体,用于保存用户的用户名和密码,然后发给服务器,然后在服务器进行判断验证.这里就有一个问题,以前讲的就是发送字符串是使用char类型进行传输,然后在服务器进行用同样是字符串 ...
- Socket网络编程--聊天程序(7)
接上一小节,本来是计划这一节用来讲数据库的增删改查,但是在实现的过程中,出现了一点小问题,也不是技术的问题,就是在字符界面上比较不好操作.比如要注册一个帐号,就需要弄个字符界面提示,然后输入数字表示选 ...
- Socket网络编程--聊天程序(3)
上一小节,已经讲到可以每个人多说话,而且还没有限制,简单的来说,我们已经完成了聊天的功能了,那么接下来我们要实现什么功能呢?一个聊天程序至少应该支持一对多的通讯吧,接下来就实现多个客户端往服务器发送数 ...
- Socket网络编程--聊天程序(5)
上一小节我们讲了使用select来避免使用多进程的资源浪费问题.上次只是实现了从多个客户端发送数据给服务器端,接下来就要实现从服务器端发送数据给各个客户端. 使用select多路转换处理聊天程序2 c ...
- Socket网络编程--聊天程序(2)
上一节简单如何通过Socket创建一个连接,然后进行通信.只是每个人只能说一句话.而且还是必须说完才会接收到信息,总之是很不方便的事情.所以这一小节我们将对上一次的程序进行修改,修改成每个人可以多说话 ...
- Socket网络编程--小小网盘程序(5)
各位好呀!这一小节应该就是这个小小网盘程序的最后一小节了,这一节将实现最后的三个功能,即列出用户在服务器中的文件列表,还有删除用户在服务器中的文件,最后的可以共享文件给好友. 列出用户在服务器中的文件 ...
随机推荐
- 【Java】 剑指offer(53-2) 0到n-1中缺失的数字
本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集 题目 一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个 ...
- Machine Learning 学习笔记1 - 基本概念以及各分类
What is machine learning? 并没有广泛认可的定义来准确定义机器学习.以下定义均为译文,若以后有时间,将补充原英文...... 定义1.来自Arthur Samuel(上世纪50 ...
- 《Android进阶之光》--注解与依赖注入框架
No1: 标准注解: 1)@Override:覆写 2)@Deprecated:过时 3)@SuppressWarnings:取消警告 4)@SafeVarargs:申明使用了可变长度参数的方法 No ...
- 在 xilinx SDK 使用 math.h
在使用到cos sin tan等算法的时候添加了math库 #include <math.h> 但是却报错了 'Invoking: ARM gcc linker'arm-xilinx-ea ...
- web漏洞扫描工具AWVS使用
AWVS AWVS简介:Acunetix Web Vulnerability Scanner(简称AWVS)是一款知名的网络漏洞扫描工具,它通过网络爬虫测试你的网站安全,检测流行安全漏洞,如交叉站点脚 ...
- 【Java并发核心五】Future 和 Callable
默认情况下,线程Thread对象不具有返回值的功能,如果在需要取得返回值的情况下会极为不方便.jdk1.5中可以使用Future 和 Callable 来获取线程返回值. Callable 可以 看成 ...
- 用js来实现那些数据结构02(数组篇02-数组方法)
上一篇文章简单的介绍了一下js的类型,以及数组的增删方法.这一篇文章,我们一起来看看数组还有哪些用法,以及在实际工作中我们可以用这些方法来做些什么.由于其中有部分内容并不常用,所以我尽量缩小篇幅.在这 ...
- 获取AFP服务信息
获取AFP服务信息 如果苹果系统开放TCP 548端口,说明其开启了AFP服务.这个时候,可以使用Nmap的afp-serverinfo脚本获取对应的服务信息.获取的信息包括服务名.机器类型.AF ...
- bzoj4709: [Jsoi2011]柠檬 斜率优化
题目链接 bzoj4709: [Jsoi2011]柠檬 题解 斜率优化 设 \(f[i]\) 表示前 \(i\)个数分成若干段的最大总价值. 对于分成的每一段,左端点的数.右端点的数.选择的数一定是相 ...
- 洛谷.T22136.最长不下降子序列(01归并排序 分治)
题目链接 \(Description\) 给定一个长为n的序列,每次可以反转 \([l,r]\) 区间,代价为 \(r-l+1\).要求在\(4*10^6\)代价内使其LIS长度最长,并输出需要操作的 ...