其他关联文章@丶4ut15m:
IO复用
我的作业照片之前删掉了.并且之前也没有特地给IO复用写复习用的东西,只剩下一个给别人讲IO复用时候写的草稿和代码(所幸我注释还挺多)了,看的重点放代码上吧.
select函数,fd_set
fd_set存放所有的套接字
fd_set all_set;
listenfd
FD_SET(listenfd,&all_set);
already = select(nfds,read_set,write_set,exception_set,timeout);
fd_set存放所有的套接字
用select来获取可操作的套接字
当listenfd可读时候,说明有新连接到达
当connectfd可读的时候,说明有新的数据到达
typedef struct {
int connectfd;
struct sockaddr_in client;
char *cli_name;
char *cli_data;
}CLIENT;
while(1){
read_set =all_set;
对于监听套接字:
if(FD_ISSET(listenfd,&read_set)){
//接收连接
FD_SET(connectfd,&all_set);
}
for(i=0;i<FD_SETSIZE;i++){//遍历所有的套接字
if(FD_ISSET(i,&read_set)){
//和客户端交互
process_cli();
}
}
}
实例如下
服务器:
服务器等待接收客户的连接请求,一旦连接成功则显示客户地址;
接着接收客户端的名称并显示;
然后接收来自该客户的字符串,对接收的字符串按分组进行加密(分组长度为个人学号,密钥为个人序号,分组不够补0),再将加密后的字符发回客户端;
之后继续等待接收该客户的信息,直到客户关闭连接,服务器将每个连接的用户所发来的所有数据存储起来;
当连接终止后,服务器将显示客户的名字及相应的所有数据。
要求服务器具有同时处理多个客户请求的能力。
客户端:
客户首先与相应的服务器建立连接;
接着接收用户输入的客户端名称,并将其发送给服务器;
然后继续接收用户输入的字符,再将字符串发送给服务器,同时接收服务器发回的加密后的字符串并显示。
之后,继续等待用户输入字符串,指导用户输入的是quit,则关闭连接并退出。
使用IO复用实现
服务器代码如下
#include <stdio.h> //基本输入输出
#include <stdlib.h> //exit函数所在头文件
#include <string.h> //字符串处理函数
#include <sys/socket.h> //套接字函数所在头文件
#include <arpa/inet.h> //地址结构所在头文件
#include <unistd.h> //close函数所在头文件
#include <sys/time.h> //select函数所在头文件
#include <netinet/in.h>
#define PORT 3333
#define MAXDATASIZE 1000
typedef struct {
int connectfd; //存放已连接套接字
char *cli_name; //char指针类型,用以指向后面malloc的区域
char *cli_data;
struct sockaddr_in addr; //客户端地址结构
}CLIENT;
//声明函数
void process_cli(CLIENT *client,char *recvbuf,int len);
void savedata(char *recvbuf,int len,char *data);
void encode(char *data,int len,int connectfd);
int main(void){
int listenfd,connectfd,sockfd; //前者为监听套接字,中者为已连接套接字,后者用来暂存已连接套接字
int maxfd,maxi,i; //前者始终存放最大文件描述符(套接字),中者存放已连接数量,后者用来作为遍历结构体的索引
int already,num; //前者用来存放select返回的可操作文件描述符数量,后者用来存放接收数据的大小
char recvbuf[100]; //暂存客户端发送的数据
struct sockaddr_in server; //地址结构
fd_set all_set,read_set; //前者为文件描述符集合,后者为可读文件描述符集合
CLIENT client[FD_SETSIZE]; //客户端结构体,新连接的客户端信息存放在该结构体中
socklen_t addrlen;
//创建套接字
if((listenfd = socket(AF_INET,SOCK_STREAM,0)) ==-1){
perror("Socket error!\n");
exit(0);
}
//地址重用
int opt = SO_REUSEADDR;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
//配置地址结构体
memset(&server,'\0',sizeof(server));
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
server.sin_family = AF_INET;
//绑定地址结构
if(bind(listenfd,(struct sockaddr *)&server,sizeof(server))){
perror("Bind error!\n");
exit(0);
}
//监听套接字
if(listen(listenfd,5)){
perror("Listen error!\n");
exit(0);
}
//初始化select函数参数
maxfd = listenfd; //maxfd始终等于最大的文件描述符
maxi = -1; //还未取得任何连接,故使maxi等于-1
for(i = 0; i < FD_SETSIZE; i++){
//遍历所有客户端结构体,并将之已连接套接字置为-1以代表该结构体未被使用
client[i].connectfd = -1;
}
FD_ZERO(&all_set); //将文件描述符集合清零
FD_SET(listenfd,&all_set); //将listenfd添加进all_set中
addrlen = sizeof(struct sockaddr_in); //存放结构体sockaddr_in的大小,下面取得连接时有用
while(1){
struct sockaddr_in addr; //用来暂存取得连接时候的客户端地址结构
read_set = all_set; //拷贝all_set的内容给read_set,使用read_set取监听文件描述符的可读情况
/*select函数原型
* int select(nfds,readfds,writefds,exceptfds,timeout);
* 第一个参数为最大文件描述符的值+1,第二个参数为可读文件描述符集合,
* 第三个为可写文件描述符集合,第四个参数为异常文件描述符集合
* 最后一个参数为select函数超时时间
*/
already = select(maxfd+1, &read_set,NULL,NULL,NULL);
/*select的作用为:调用select函数之后它会阻塞,时长为timeout,
* 当readfds中任一(也可以多个)文件描述符可读,或者
* writefds文件描述符集合中任一(可以多个)文件描述符可写,或者
* exceptfds文件描述符集合中任一(可以多个)文件描述符异常之时
* select函数结束阻塞并且返回可操作(可操作便是可读或者可写或者异常)文件描述符的个数
* 如果设置timeout为NULL则select函数不会阻塞,调用之后便会直接返回结果
* 正是select函数这个可以判断多个描述符的功能,才使得我们可以单进程单线程的处理多个请求
*/
printf("haode\n");
//先判断listenfd是否可读
if(FD_ISSET(listenfd,&read_set)){
//如果listenfd可读则代表有新连接到达,可以处理新的连接请求
if((connectfd = accept(listenfd,(struct sockaddr *)&addr,&addrlen)) ==-1){
//如果出错了,不退出,进行下一次循环
perror("Accept error!\n");
continue;
}
for(i = 0; i < FD_SETSIZE; i++){
//接受了新连接,将取得连接的套接字和客户端地址结构存放在CLIENT结构体中
if(client[i].connectfd < 0){
//如果client[i].connectfd <0就代表这个结构体还未被使用,可以存放新连接数据
client[i].connectfd = connectfd;
client[i].cli_name = (char *)malloc(sizeof(char)*MAXDATASIZE); //malloc一段空间用来存放客户端名称
client[i].cli_data = (char *)malloc(sizeof(char)*MAXDATASIZE); //malloc一段空间用来存放客户端发送的所有数据
client[i].addr = addr; //存放客户端地址结构
client[i].cli_name[0] = '\0'; //对申请的空间初始化
client[i].cli_data[0] = '\0';
//打印客户端的ip
printf("Got a connection from %s.\n",inet_ntoa(client[i].addr.sin_addr));
//存放好数据之后不用再遍历客户端结构体,退出循环
break;
}
}//for遍历所有客户端结构体 end
if(i == FD_SETSIZE){
//说明已经达到连接上限
printf("Too many connections\n");
}
//将已连接套接字添加进all_set中
FD_SET(connectfd,&all_set);
if(connectfd > maxfd){
//maxfd始终等于最大文件描述符
maxfd = connectfd;
}
if(i > maxi){
//maxi为已连接个数
maxi = i;
}
if(--already <= 0){
//说明可操作套接字已经处理完毕,开始下一次while循环
continue;
}
}//listenfd可读判断 end
//如果listenfd不可读或者listenfd套接字已经处理完毕,则说明有客户端发送消息过来
for(i = 0;i<= maxi;i++){
//遍历当前所有已连接客户端
if((sockfd = client[i].connectfd) < 0){
//说明这个客户端没有存放连接,继续查找下一个客户端
continue;
}
if(FD_ISSET(sockfd,&read_set)){
if((num = recv(sockfd,recvbuf,MAXDATASIZE,0)) == 0){
//关闭客户端
close(sockfd);
//打印客户端信息
printf("Client %s closed connection. All message as follow:%s\n",client[i].cli_name,client[i].cli_data);
//将已连接套接字从文件描述符集合中清除
FD_CLR(sockfd,&all_set);
//将该客户端结构体中的已连接套接字置为-1说明该客户端结构体现在是空的
client[i].connectfd = -1;
//释放前面malloc的两段空间
free(client[i].cli_name);
free(client[i].cli_data);
}
else process_cli(&client[i], recvbuf, num);
if(--already <=0){
//说明可操作套接字已经处理完毕,不再遍历
break;
}
}//判断已连接套接字是否可读
}//for遍历当前已连接的客户端
}//while end
close(listenfd); //关闭监听套接字
return 0;
}
void process_cli(CLIENT *client,char *recvbuf,int len){
recvbuf[len] = '\0';
if(strlen(client->cli_name) == 0){
//如果当前结构体的cli_name为空则说明是新建立的连接,客户端第一次发送过来的数据为客户端名称
memcpy(client->cli_name,recvbuf,len);
//打印客户端名称
printf("The client's name is %s.\n",client->cli_name);
//名称处理完毕回到main函数
return;
}
if(strcmp(recvbuf,"quit")){
//执行到此处说明客户端发送的不是名称而是消息,将消息进行保存
//不是quit就保存
savedata(recvbuf,len,client->cli_data);
}
//调用加密函数
encode(recvbuf,len,client->connectfd);
}
void savedata(char *recvbuf,int len,char *data){
//上次存放的结束为这次存放的开始
int start = strlen(data);
for(int i=0;i<len;i++){
data[start +i] = recvbuf[i];
}
}
void encode(char *data,int len,int connectfd){
int i,j;
int snum[] = {2,0,1,7,1,2,2,1,1,9}; //密钥
char miwen[100]; //data用以接收用户输入的数据,miwen用以存放密文
j = 0;
//清空密文数组
memset(&miwen,'\0',sizeof(miwen));
//密钥长度为学号长度,能整除该长度就说明不需要在字符串后面补零可以直接加密
for(i = 0; i<strlen(data); i++){
if((data[i] >= 'a' && data[i] <='z') || (data[i] >= 'A' && data[i] <='Z')){
//字符加密与数字加密需要分开,因为字符移位可能会越过字母的区域,需要另做操作
if(((data[i]+snum[j]) > 'z') && (data[i] >= 'a') && (data[i] <= 'z')){
//越界情况一
miwen[i] = data[i] + snum[j]-26;
}
else if(((data[i]+snum[j])>'Z') && (data[i] >= 'A') && (data[i] <= 'Z')){
//越界情况二
miwen[i] = data[i] + snum[j] -26;
}
else{
//这是正常情况执行的代码
miwen[i] = data[i] + snum[j];
}
//每次加密结束,j增加移至下一个密钥
j++;
if(j %10 == 0){
//如果j%10等于0,就说明已经加密完一轮,该让密钥回到第一个值的位置了
j = 0;
}
}
}
if(strlen(data) % 10 != 0){
//如果不能整除则需要填补。
for(i=0;i<(10-strlen(data)%10);i++){
miwen[strlen(data)+i] += 48;
miwen[strlen(data)+i] += snum[j];
j++;
if(j %10 == 0){
//如果j%10等于0,就说明已经加密完一轮,该让密钥回到第一个值的位置了
j = 0;
}
}
}
send(connectfd,miwen,sizeof(miwen),0); //发送密文
}
客户端代码如下
#include <stdio.h> //基本输入输出
#include <netinet/in.h> //sockaddr_in结构体所在头文件
#include <sys/socket.h> //socket函数所在头文件
#include <netdb.h> //hostent结构体所在头文件
#include <string.h> //memset等函数所在头文件
#include <unistd.h> //close等函数所在头文件
#include <stdlib.h> //exit等函数所在头文件
#define PORT 3333
int main(int argc,char *args[]){
int sockfd,num; //定义套接字描述符
struct sockaddr_in server;
struct hostent *he;
socklen_t server_len;
char data[100];
if(argc != 2){
printf("Usage:%s <IP Address>\n",args[0]);
exit(0);
}
//创建套接字
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
perror("Socket error!\n");
exit(0);
}
//获取服务器信息
if((he = gethostbyname(args[1])) == NULL){
perror("Gethostbyname error!\n");
exit(0);
}
memset(&server,'\0',sizeof(server));
//配置服务器地址结构
server.sin_family = AF_INET; //配置协议族为IPv4
server.sin_port = htons(PORT); //配置服务器端口
server.sin_addr = *((struct in_addr *)he->h_addr); //服务器地址
server_len = sizeof(server);
//而后可以进行连接
if((connect(sockfd,(struct sockaddr *)&server,sizeof(server))) ==-1){
perror("Connect error!\n");
exit(0);
}
printf("Plz enter your name:");
memset(&data,'\0',sizeof(data)); //每次输入信息前先清空缓存,防止前次数据影响后面输入的数据
scanf("%s",&data);
if(send(sockfd,data,strlen(data),0) ==-1){
//send函数原型send(int sockfd,const char FAR *datafer,int dataferlen,int flags)
//第二个参数为要发送的数据,第三个参数为发送的大小也即是长度,第四个参数通常置0代表其功能与write相同
printf("Send error!\n");
exit(1);
}
//连接之后便可开始数据的发送与接收
while(1){
memset(&data,'\0',sizeof(data));
printf("Now,you can enter something what you want to encode.\n");
scanf("%s",&data);
send(sockfd,data,sizeof(data),0);
if(!strcmp(data,"quit")){
break;
}
else{
memset(&data,'\0',sizeof(data));
if((recv(sockfd,data,100,0) ==-1)){
//recv函数原型recv(int sockfd,char *datafer,int dataferlen,int flags)
//第二个参数为将接收的数据暂存的位置,第三个参数为接收的数据大小,第四个参数通常置0代表其功能与read相同
printf("Recv error!\n");
exit(1);
}
printf("The code message:%s\n",data);
}
}
/*
if((num = recv()) == -1){
perror("Recv error!\n");
exit(0);
}
*/
printf("Bye~\n");
close(sockfd); <a name="#marker" id="marker"></a>
}