IO
第一次作业
使用fgets统计给定文件的行数
#include<myhead.h>
#include<sqlite3.h>
int main(int argc, const char *argv[])
{//以只读形式打开文件FILE *fp = NULL;if((fp = fopen("./03fgets记录行数.c","r")) == NULL){perror("fopen error");return -1;}int num = 0; //记录行数char buf[5]="";char *ptr = NULL;while((ptr = fgets(buf,sizeof(buf),fp)) != NULL) //没读完就一直读,将读取到字符串放入buf中{if(buf[strlen(buf)-1] == '\n')//判断数组中倒数第二个字符是否为换行{num++;}}printf("当前文件的行数为:[%d]\n",num);return 0;
}
使用fputs和fgets完成两个文件的拷贝
#include<myhead.h>
#include<sqlite3.h>
int main(int argc, const char *argv[])
{FILE *fp = NULL,*fq = NULL;//以只读形式打开文件if((fp = fopen("./001.txt","r")) == NULL){perror("fopen error");return -1;}//以只写形式打开文件if((fq = fopen("./002.txt","w")) == NULL){perror("fopen error");return -1;}char buf[5]="";char *ptr = NULL;while((ptr = fgets(buf,sizeof(buf),fp)) != NULL)//从fp中读取数据放入buf中{fputs(buf,fq); //将数据从buf中写入到fq}fclose(fp);fclose(fq);return 0;
}
完成注册登录功能
#include<myhead.h>
#include<sqlite3.h>
//展示界面
void interface()
{printf("0退出\n");printf("1注册\n");printf("2登录\n");
}//注册
int zhuce()
{//打开以追加形式文件FILE *fp = NULL;if((fp = fopen("./passworld.txt","a")) == NULL){perror("fopen error");return -1;}char zczh[128] = "";char zcmm[128] = "";//从终端获取注册的账号和密码printf("请输入注册账号:");fscanf(stdin,"%s",zczh);printf("请输入注册密码:");fscanf(stdin,"%s",zcmm);//将账号和密码格式化输入到文件中fprintf(fp,"%s %s\n",zczh,zcmm);return 0;fclose(fp);
}//登录
int denglu()
{//打开以只读形式文件FILE *fp = NULL;if((fp = fopen("./passworld.txt","r")) == NULL){perror("fopen error");return -1;}char dlzh[128] = "";char dlmm[128] = "";char zh[128] = "";char mm[128] = "";//从终端获取注册的账号和密码printf("请输入登录账号:");fscanf(stdin,"%s",dlzh);printf("请输入登录密码:");fscanf(stdin,"%s",dlmm);//从文件中获取账号和密码int res;while((res = fscanf(fp,"%s %s",zh,mm)) > 0){if(strcmp(dlzh,zh) ==0 && strcmp(dlmm,mm) == 0) //判断登录账号和密码,是否与文件中的一致{printf("登录成功!\n");return 0;}}printf("账号或密码输入错误!\n");fclose(fp);return -1;
}
int main(int argc, const char *argv[])
{while(1){interface();int num;printf("请输入选项:");scanf("%d",&num);switch (num){case 0:exit(0);//结束进程case 1:zhuce();//调用注册函数break;case 2:denglu();//调用登录函数break;default:printf("输入的功能选项错误,请重新输入!\n");break;}while(getchar() != '\n');printf("按任意键继续……\n");getchar();system("clear");}return 0;
}
第二次作业
使用fread和fwrite完成两个文件的拷贝
#include<myhead.h> // 引入自定义头文件
//创建一个结构体用于存储文件标识符
typedef struct fp
{FILE *fp1; // 定义文件指针fp1,用于指向源文件FILE *fp2; // 定义文件指针fp2,用于指向目标文件
}fp,*fpptr; // 定义结构体fp和结构体指针fpptr//打开文件
fp fileopen(int argc, const char *argv[])
{fp fd0; // 定义结构体变量fd0,用于存储打开的文件指针//因为函数有返回值,而返回值是一个结构体,这里创建一个新的结构体变量用于返回出错的了情况fp fderr = {NULL,NULL}; // 定义结构体变量fderr,初始化文件指针为NULL,用于错误处理//判断是否输入两个文件if(argc != 3){printf("输入的文件数量不正确!\n"); // 如果输入的文件数量不为2,打印错误信息return fderr; // 返回错误结构体fderr}//以只读形式打开源文件if((fd0.fp1 = fopen(argv[1],"r")) == NULL){perror("fopen error"); // 如果打开源文件失败,打印错误信息return fderr; // 返回错误结构体fderr}//以只写形式打开目标文件if((fd0.fp2 = fopen(argv[2],"w")) == NULL){perror("fopen error"); // 如果打开目标文件失败,打印错误信息return fderr; // 返回错误结构体fderr}return fd0; // 返回成功打开的文件指针结构体
}//关闭文件
void fileclose(fp fd0)
{fclose(fd0.fp1); // 关闭源文件fclose(fd0.fp2); // 关闭目标文件
}//获取文件长度
int fileline(fp fd)
{int line; // 定义变量line,用于存储文件长度//将光标移到文件末尾,获取文件大小line = fseek(fd.fp1,0,SEEK_END); // 将源文件的读取指针移动到文件末尾//因为不关闭文件,所以要将光标位置复原fseek(fd.fp1,0,SEEK_SET); // 将源文件的读取指针重新定位到文件开头return line; // 返回文件长度
}//拷贝文件
void filecopy(fp fd0)
{//定义一个搬运工char buf[128] = ""; // 定义缓冲区buf,用于临时存储读取的数据int res; // 定义变量res,用于存储每次读取的字节数//循环从源文件读取数据,如果文件没读完并且没读到指定大小则继续循环while(!feof(fd0.fp1)) // 判断文件是否读到末尾{memset(buf,0,sizeof(buf)); // 清空缓冲区bufres = fread(buf,1,sizeof(buf),fd0.fp1); // 从源文件中读取数据到缓冲区buf//将数据写入目标文件fwrite(buf,1,res,fd0.fp2); // 将缓冲区buf的数据写入目标文件}//调用【关闭文件】函数fileclose(fd0); // 关闭文件
}
int main(int argc, const char *argv[])
{fp fd0; // 定义结构体变量fd0,用于存储打开的文件指针//调用【打开文件】函数fd0 = fileopen(argc,argv); // 打开文件,并将文件指针存储到fd0if(fd0.fp1 == NULL){return -1; // 如果源文件打开失败,返回-1}//创建一个变量接收文件长度,调用【获取文件长度】函数int lines = fileline(fd0); // 获取文件长度,并存储到变量lines//调用【拷贝文件】函数filecopy(fd0); // 拷贝文件return 0; // 程序执行成功,返回0
}
使用read、write完成两个文件的拷贝
#include<myhead.h>
//创建一个结构体用于存储文件标识符
typedef struct fd
{int fd1;int fd2;
}fd,*fdptr;//打开文件
fd fileopen(int argc, const char *argv[])
{fd fd0;//因为函数有返回值,而返回值是一个结构体,这里创建一个新的结构体变量用于返回出错的了情况fd fderr = {-1,-1};//判断是否输入两个文件if(argc != 3){printf("输入的文件数量不正确!\n");return fderr;}//以只读形式打开源文件if((fd0.fd1 = open(argv[1],O_RDONLY)) == -1){perror("open error");return fderr;}//以只写形式打开目标文件if((fd0.fd2 = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0664)) == -1){perror("open error");return fderr;}return fd0;
}//关闭文件
void fileclose(fd fd0)
{close(fd0.fd1);close(fd0.fd2);
}//获取文件长度
int fileline(int fd)
{int line;//将光标移到文件末尾,获取文件大小line = lseek(fd,0,SEEK_END);//因为不关闭文件,所以要将光标位置复原lseek(fd,0,SEEK_SET);return line;
}//拷贝文件
void filecopy(fd fd0,int stat,int end)
{//定义一个搬运工char buf[128] = "";//帮助判断循环是否继续执行int res = 1;int now_end = 0; //已经读了多少//从什么地方开始拷贝lseek(fd0.fd1,stat,SEEK_SET);lseek(fd0.fd2,stat,SEEK_SET);//循环从源文件读取数据,如果文件没读完并且没读到指定大小则继续循环while((res > 0) && now_end <= end){memset(buf,0,sizeof(buf));res = read(fd0.fd1,buf,sizeof(buf));//将数据写入目标文件write(fd0.fd2,buf,res);//当前光标所在的位置now_end = lseek(fd0.fd1,0,SEEK_CUR);}//调用【关闭文件】函数fileclose(fd0);
}
int main(int argc, const char *argv[])
{fd fd0;//调用【打开文件】函数fd0 = fileopen(argc,argv);//创建一个变量接收文件长度,调用【获取文件长度】函数int lines = fileline(fd0.fd1);//调用【拷贝文件】函数filecopy(fd0,0,lines);return 0;
}
将时间在文件中跑起来
#include<myhead.h>
#include<sqlite3.h>
//获取文件行号
int filelens()
{//以只读形式打开文件FILE *fp = NULL;if((fp = fopen("./time.txt","r")) == NULL){perror("fopen error");return -1;}int num = 0; //记录行数char buf[5]="";char *ptr = NULL;while((ptr = fgets(buf,sizeof(buf),fp)) != NULL) //没读完就一直读,将读取到字符串放入buf中{if(buf[strlen(buf)-1] == '\n')//判断数组中倒数第二个字符是否为换行{num++;}}fclose(fp);return num;
}//获取系统时间
char *systime()
{static char buf[128] = "";//获取系统时间(秒数)time_t sTime = time(NULL);//将系统时间(秒数)转换成时间结构体struct tm *t = localtime(&sTime);//将时分秒格式化放入字符串数组中,并加上序号snprintf(buf,sizeof(buf),"%d、%02d:%02d:%02d\n",filelens()+1,t->tm_hour,t->tm_min,t->tm_sec);return buf;
}//系统时间写入
int intime()
{FILE *fp = NULL;//以追加的形式打开文件if((fp = fopen ("./time.txt","a")) == NULL){perror("fopen error");return -1;}while(1){//调用【系统时间写入函数】将字符串写入文件fprintf(fp,"%s",systime());fflush(fp);sleep(1);}fclose(fp);return 0;
}
int main(int argc, const char *argv[])
{intime();return 0;
}
第三次作业
使用多进程完成两个文件的拷贝,父进程拷贝前一半,子进程拷贝后一半,父进程回收子进程的资源
#include<myhead.h>
//创建一个结构体用于存储文件标识符
typedef struct fd
{int fd1;int fd2;
}fd,*fdptr;//打开文件
fd fileopen(int argc, const char *argv[])
{fd fd0;//因为函数有返回值,而返回值是一个结构体,这里创建一个新的结构体变量用于返回出错的了情况fd fderr = {-1,-1};//判断是否输入两个文件if(argc != 3){printf("输入的文件数量不正确!\n");return fderr;}//以只读形式打开源文件if((fd0.fd1 = open(argv[1],O_RDONLY)) == -1){perror("open error");return fderr;}//以只写形式打开目标文件if((fd0.fd2 = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0664)) == -1){perror("open error");return fderr;}return fd0;
}//关闭文件
void fileclose(fd fd0)
{close(fd0.fd1);close(fd0.fd2);
}//获取文件长度
int fileline(int fd)
{int line;//将光标移到文件末尾,获取文件大小line = lseek(fd,0,SEEK_END);//因为不关闭文件,所以要将光标位置复原lseek(fd,0,SEEK_SET);return line;
}//拷贝文件
void filecopy(fd fd0,int stat,int end)
{//定义一个搬运工char buf[128] = "";//帮助判断循环是否继续执行int res = 1;int now_end = 0; //已经读了多少//从什么地方开始拷贝lseek(fd0.fd1,stat,SEEK_SET);lseek(fd0.fd2,stat,SEEK_SET);//循环从源文件读取数据,如果文件没读完并且没读到指定大小则继续循环while((res > 0) && now_end <= end){memset(buf,0,sizeof(buf));res = read(fd0.fd1,buf,sizeof(buf));//将数据写入目标文件write(fd0.fd2,buf,res);//当前光标所在的位置now_end = lseek(fd0.fd1,0,SEEK_CUR);}//调用【关闭文件】函数fileclose(fd0);
}
int main(int argc, const char *argv[])
{fd fd0;//调用【打开文件】函数fd0 = fileopen(argc,argv);//创建一个变量接收文件长度,调用【获取文件长度】函数int lines = fileline(fd0.fd1);//printf("%d\n",lines);//创建一个用于接收进程号的变量pid_t pid;//创建子进程if((pid = fork()) < 0){perror("fork error");return -1;}else if(pid > 0){//父进程//调用【拷贝文件】函数和【获取文件长度】函数filecopy(fd0,0,lines/2);//等待回收子进程资源waitpid(pid,NULL,0);}else{//子进程//调用【拷贝文件】函数和【获取文件长度】函数filecopy(fd0,lines/2,lines);//结束进程exit(EXIT_SUCCESS);}return 0;
}
第四次作业
使用多线程完成两个文件的拷贝,第一个线程拷贝前一半,第二个线程拷贝后一半,主线程回收两个线程的资源
#include<myhead.h>
#include<sqlite3.h>
//创建一个结构体用于存储文件标识符
typedef struct fd
{int fd1;int fd2;
}fd,*fdptr;//打开文件
fd fileopen(int argc, const char *argv[])
{fd fd0;//因为函数有返回值,而返回值是一个结构体,这里创建一个新的结构体变量用于返回出错的了情况fd fderr = {-1,-1};//判断是否输入两个文件if(argc != 3){printf("输入的文件数量不正确!\n");return fderr;}//以只读形式打开源文件if((fd0.fd1 = open(argv[1],O_RDONLY)) == -1){perror("open error");return fderr;}//以只写形式打开目标文件if((fd0.fd2 = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0664)) == -1){perror("open error");return fderr;}return fd0;
}//关闭文件
void fileclose(fd fd0)
{close(fd0.fd1);close(fd0.fd2);
}//获取文件长度
int fileline(int fd)
{int line;//将光标移到文件末尾,获取文件大小line = lseek(fd,0,SEEK_END);//因为不关闭文件,所以要将光标位置复原lseek(fd,0,SEEK_SET);return line;
}//拷贝文件
void filecopy(fd fd0,int stat,int end)
{//定义一个搬运工char buf[128] = "";//帮助判断循环是否继续执行int res = 1;int now_end = 0; //已经读了多少//从什么地方开始拷贝lseek(fd0.fd1,stat,SEEK_SET);lseek(fd0.fd2,stat,SEEK_SET);//循环从源文件读取数据,如果文件没读完并且没读到指定大小则继续循环while((res > 0) && now_end <= end){memset(buf,0,sizeof(buf));res = read(fd0.fd1,buf,sizeof(buf));//将数据写入目标文件write(fd0.fd2,buf,res);//当前光标所在的位置now_end = lseek(fd0.fd1,0,SEEK_CUR);}
}//定义一个线程体函数
void *task1(void *arg)
{//解压万能指针fd fd0 = *(fdptr)arg;//调用【获取文件长度】函数int lines = fileline(fd0.fd1);//调用【拷贝文件】函数filecopy(fd0,lines/3,lines*2/3);//退出线程pthread_exit(NULL);
}//定义一个线程体函数
void *task2(void *arg)
{//解压万能指针fd fd0 = *(fdptr)arg;//调用【获取文件长度】函数int lines = fileline(fd0.fd1);//调用【拷贝文件】函数filecopy(fd0,lines*2/3,lines);//退出线程pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{//调用【打开文件】函数fd fd0 = fileopen(argc,argv);//定义一个线程号变量pthread_t tid1,tid2;//创建一个线程if(pthread_create(&tid1,NULL,task1,&fd0)){printf("pthread_create error\n");return -1;}//创建一个线程if(pthread_create(&tid2,NULL,task2,&fd0)){printf("pthread_create error\n");return -1;}//调用【获取文件长度】函数int lines = fileline(fd0.fd1);//调用【拷贝文件】函数filecopy(fd0,0,lines/3);//设置线程分离态pthread_detach(tid1);pthread_detach(tid2);printf("输入空格结束拷贝(请等待几秒):");getchar();//调用【关闭文件】函数fileclose(fd0); return 0;
}
第五次作业
使用有名管道完成两个进程的相互通信
read
#include<myhead.h>
#include<sqlite3.h>
int main(int argc, const char *argv[])
{//创建子进程pid_t pid;if((pid = fork()) < 0){ERR_PRIN("fork");return -1;}else if(pid == 0){//这是子进程int fd;if((fd = open("./write",O_RDONLY)) == -1){ERR_PRIN("open");return -1;}//创建搬运工char buf[128] = "";while(1){//清空数组memset(buf,0,sizeof(buf));//从管道文件中读取数据read(fd,buf,sizeof(buf));if(!strcmp(buf,"quit")){break;}printf("write:%s\n",buf);}//关闭文件close(fd);//退出进程exit(EXIT_SUCCESS);}else{//这是父进程int fd;if((fd = open("./read",O_WRONLY)) == -1){ERR_PRIN("open");return -1;}//创建搬运工char buf[128] = "";while(1){//清空数组memset(buf,0,sizeof(buf));//从终端读取数据fgets(buf,sizeof(buf),stdin);buf[strlen(buf)-1] = 0;//向管道中写入数据write(fd,buf,sizeof(buf));if(!strcmp(buf,"quit")){//杀死杀死子进程kill(pid,SIGKILL);break;}}//关闭文件close(fd);//等待回收子进程资源wait(NULL);}return 0;
}
write
#include<myhead.h>
#include<sqlite3.h>
int main(int argc, const char *argv[])
{//创建子进程pid_t pid;if((pid = fork()) < 0){ERR_PRIN("fork");return -1;}else if(pid == 0){//这是子进程//创建管道文件mkfifo("./read",0664);int fd;if((fd = open("./read",O_RDONLY)) == -1){ERR_PRIN("open");return -1;}//创建搬运工char buf[128] = "";while(1){//清空数组memset(buf,0,sizeof(buf));//从管道文件中读取数据read(fd,buf,sizeof(buf));if(!strcmp(buf,"quit")){//杀死杀死父进程kill(getppid(),SIGKILL);break;}printf("read:%s\n",buf);}//关闭文件close(fd);//退出进程exit(EXIT_SUCCESS);}else{//这是父进程//创建管道文件mkfifo("./write",0664);int fd;if((fd = open("./write",O_WRONLY)) == -1){ERR_PRIN("open");return -1;}//创建搬运工char buf[128] = "";while(1){//清空数组memset(buf,0,sizeof(buf));//从终端读取数据fgets(buf,sizeof(buf),stdin);buf[strlen(buf)-1] = 0;//向管道中写入数据write(fd,buf,sizeof(buf));if(!strcmp(buf,"quit")){//杀死杀死子进程kill(pid,SIGKILL);break;}}//关闭文件close(fd);//等待回收子进程资源wait(NULL);}//删除文件system("rm read");system("rm write");return 0;
}
第六次作业
使用消息队列完成两个进程间相互通信
#include<myhead.h>
#include<sqlite3.h>
//创建消息结构体
typedef struct
{long lei; //消息类型char data[1024]; //正文
}Msg_ds;
//求取正文大小
#define SIZE sizeof(Msg_ds) - sizeof(long)
int main(int argc, const char *argv[])
{//创建键值key_t key = ftok("./",'b');//判断键值是否创建成功if(key == -1){ERR_PERR("ftok error");return -1;}//创建消息队列int msgid;if((msgid = msgget(key,IPC_CREAT|0664)) == -1){ERR_PERR("msgget error");return -1;}//创建子进程pid_t pid;if((pid = fork()) < 0){ERR_PERR("fork error");return -1;}else if(pid == 0){//这是子进程while(1){Msg_ds msg;//接收消息if(msgrcv(msgid,&msg,SIZE,0,0) == -1){ERR_PERR("msgrcv error");return -1;}if(strcmp(msg.data,"quit") == 0){break;}//向终端输出printf("%d = %s\n",getpid(),msg.data);}//退出子进程exit(EXIT_SUCCESS);}else{//这是父进程while(1){Msg_ds msg = {.lei = 100};//从终端获取要发送的消息fgets(msg.data,sizeof(msg.data),stdin);//将换行符转换成0if(msg.data[strlen(msg.data)-1] == '\n'){msg.data[strlen(msg.data)-1] = 0;}//发送消息if(msgsnd(msgid,&msg,SIZE,0) == -1){ERR_PERR("msgsnd error");return -1;}if(strcmp(msg.data,"quit") == 0){break;}}//等待回收子进程资源waitpid(pid,NULL,0);//销毁消息队列if(msgctl(msgid,IPC_RMID,NULL) == -1){ERR_PERR("msgctl error");return -1;}}return 0;
}
网编
第三次作业
使用select实现tcp的服务器端,poll实现tcp的客户端
select
服务器
#include<myhead.h>
#define PORT 8888 //端口号1024~49151
#define IP "192.168.250.100" //本机IP
int main(int argc, char const *argv[])
{//创建socket套接字int sfd = socket(AF_INET,SOCK_STREAM,0);if(sfd < 0){ERR_PERR("socket error");return -1;}//允许端口快速的被复用int reuse = 1;if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0){ERR_PERR("setsockopt");return -1;}//填充信息结构体struct sockaddr_in sin;sin.sin_family = AF_INET; sin.sin_port = htons(PORT); sin.sin_addr.s_addr = inet_addr(IP); //绑定if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin)) < 0){ERR_PERR("bind error");return -1;}//设置监听套接字if(listen(sfd,128) < 0){ERR_PERR("listen error");return -1;}int newfd = -1;struct sockaddr_in cins[1024];// 客户端地址数组char buf[128] = "";fd_set readfds,tempfds; //创建读集合,临时集合FD_ZERO(&readfds); // 清空读集合FD_SET(0,&readfds);FD_SET(sfd,&readfds); //将终端和监听套接字的文件描述符放进读集合中int maxfd = sfd; // 给maxfd赋值while (1){tempfds = readfds; //拷贝一份读集合int res = select(maxfd+1,&tempfds,NULL,NULL,NULL); //阻塞等待集合中的事件发生if (res == -1){ERR_PERR("select");return -1;}else if (res == 0){ERR_PRIN("超出时间");return -1;}for (int i = 0; i <= maxfd; i++){if (!FD_ISSET(i,&tempfds)){continue;}if (i == 0){// 键盘输入事件scanf("%s",buf);printf("键盘输入事件:%s\n",buf);}else if (i == sfd){// 客户端链接事件struct sockaddr_in cin;// 客户端地址socklen_t len = sizeof(cin); //信息结构体大小if ((newfd = accept(i,(struct sockaddr*)&cin,&len)) == -1){ERR_PERR("accept");return -1;}cins[newfd] = cin;// 将新地址放入数组printf("newfd = %d, [%s:%d]链接成功!\n",newfd,inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));FD_SET(newfd,&readfds); // 将客户端文件描述符加入读集合里maxfd = newfd > maxfd ? newfd : maxfd;// 更新最大文件描述符}else{if (FD_ISSET(i,&tempfds)){memset(buf,0,sizeof(buf));int res = -1;if ((res = recv(i,buf,sizeof(buf),0)) == -1) //读取客户端传来的消息{ERR_PERR("recv");return -1;}else if (res == 0){printf("i = %d 客户端下线!\n",i);close(i);maxfd = i < maxfd ? maxfd : maxfd-1;continue;}printf("i = %d [%s:%d]%s\n",i,inet_ntoa(cins[i].sin_addr),ntohs(cins[i].sin_port),buf);strcat(buf,"已阅");send(i,buf,sizeof(buf),0);} } }}close(sfd);return 0;
}
客户端
#include<myhead.h>
#define PORT 8888 //端口号1024~49151
#define IP "192.168.250.100" //本机IP
int main(int argc, char const *argv[])
{int sfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字if (sfd == -1){ERR_PERR("socket");return -1;}//填充信息结构体struct sockaddr_in sin;sin.sin_family = AF_INET; sin.sin_port = htons(PORT); sin.sin_addr.s_addr = inet_addr(IP); if(connect(sfd, (struct sockaddr*)&sin, sizeof(sin))==-1) //链接服务器{perror("connect error");return -1;}fd_set readfds,tempfds;//创建读集合,临时集合FD_ZERO(&readfds); // 清空读集合FD_SET(0,&readfds);FD_SET(sfd,&readfds); //将终端和服务器套接字的文件描述符放进读集合中char buf[128] = "";while (1){tempfds = readfds;int res = select(sfd+1,&tempfds,NULL,NULL,NULL);//阻塞等待集合中的事件发生if (res == -1){ERR_PERR("select");return -1;}else if (res == 0){ERR_PRIN("超出时间");return -1;}if (FD_ISSET(0,&tempfds)) //从终端获取{memset(buf,0,sizeof(buf));scanf("%s",buf);send(sfd,buf,strlen(buf),0);}if (FD_ISSET(sfd,&tempfds)) //接收服务器消息{memset(buf,0,sizeof(buf));int res = recv(sfd,buf,sizeof(buf),0);if (res == 0){break;}printf("%s\n",buf);}}close(sfd);return 0;
}
poll
服务器
#include<myhead.h>
#define PORT 8888
#define IP "192.168.250.100"
int main(int argc, char const *argv[])
{int sfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字if (sfd == -1){ERR_PERR("socket");return -1;}int reuse = 1;if (setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) == -1) //端口快速复用{ ERR_PERR("setsockopt");return -1;}struct sockaddr_in sin; //填充服务器地址信息结构体sin.sin_family = AF_INET;sin.sin_port = htons(PORT);sin.sin_addr.s_addr = inet_addr(IP);if (bind(sfd,(struct sockaddr*)&sin,sizeof(sin)) == -1) //绑定服务器地址信息{ERR_PERR("bind");return -1;}if (listen(sfd,128) == -1) //设置监听套接字{ERR_PERR("listen");return -1;}char buf[128] = "";int maxfd = sfd; //设置最大文件描述符struct pollfd fds[1024]; //创建poll监测集合for (int i = 0; i < 1024; i++){fds[i].fd = -1;}fds[0].fd = 0;fds[0].events = POLLIN;fds[sfd].fd = sfd;fds[sfd].events = POLLIN; //将0和sfd文件描述符设置监测读事件int newfd = -1; //用于接收链接的客户端struct sockaddr_in cins[1024]; //客户端地址结构体数组while (1){int ren;if((ren = poll(fds,1024,-1)) == -1){ERR_PERR("poll");return -1;}else if (ren == 0){ERR_PRIN("超时\n");return -1;}for (int i = 0; i <= maxfd; i++){//链接事件if (fds[i].revents == POLLIN && i == sfd) //如果真的发生了读事件{struct sockaddr_in cin; //客户端地质结构体socklen_t len = sizeof(cin); //结构体大小if ((newfd = accept(sfd,(struct sockaddr*)&cin,&len)) == -1) //链接客户端{ERR_PERR("accept");return -1;}printf("i = %d [%s:%d]链接成功!\n",i,inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));cins[newfd] = cin; //放入数组中fds[newfd].fd = newfd; fds[newfd].events = POLLIN; //设置监测读事件maxfd = newfd > maxfd ? newfd : maxfd; //更新最大描述符printf("newfd = %d maxfd = %d\n",newfd,maxfd);}//键盘事件else if (fds[i].revents == POLLIN && i == 0) //如果真的发生了读事件{scanf("%s",buf);printf("i = %d\n",i);}//交互事件else if (fds[i].revents == POLLIN && i > 3) //如果真的发生了读事件{memset(buf,0,sizeof(buf));int res = -1;if ((res = recv(i,buf,sizeof(buf),0)) == -1) //读取客户端传来的消息{ERR_PERR("recv");return -1;}else if (res == 0){printf("i = %d 客户端下线!\n",i);close(i);fds[i].fd = -1;maxfd = i < maxfd ? maxfd : maxfd-1;continue;}printf("i = %d [%s:%d]%s\n",i,inet_ntoa(cins[i].sin_addr),ntohs(cins[i].sin_port),buf);strcat(buf,"已阅");send(i,buf,sizeof(buf),0);}}}close(sfd);return 0;
}
客户端
#include<myhead.h>
#define IP "192.168.250.100"
#define PORT 8888
int main(int argc, const char *argv[])
{//创建socket套接字int cfd = socket(AF_INET, SOCK_STREAM, 0);if(cfd == -1){perror("socket error");return -1;}//填充信息结构体struct sockaddr_in sin;sin.sin_family = AF_INET; sin.sin_port = htons(PORT); sin.sin_addr.s_addr = inet_addr(IP); if(connect(cfd, (struct sockaddr*)&sin, sizeof(sin))==-1)//链接服务器{perror("connect error");return -1;}char buf[128] = "";while(1){int res = poll(pfd, 2, -1); //阻塞检测集合中是否有事件产生if(res == -1){perror("poll error");return -1;}else if(poll == 0){printf("time out\n");return -1;}if(pfd[0].revents == POLLIN) {//清空数据memset(buf,0,sizeof(buf));fgets(buf, sizeof(buf), stdin); //从终端输入buf[strlen(buf)-1] = 0;send(cfd, buf, strlen(buf), 0);printf("发送成功\n");if(strcmp(buf, "quit") == 0){break;}}if(pfd[1].revents == POLLIN) {//清空数据memset(buf,0,sizeof(buf));recv(cfd, buf, sizeof(buf), 0); printf("收到消息为:%s\n", buf);}}close(cfd);return 0;
}
TCP移动机械臂
#include<myhead.h> // 引入自定义的头文件
#include <ncurses.h> // 引入ncurses库
#define IP "192.168.125.116" // 定义服务器IP地址
#define PORT 8888 // 定义服务器端口号
int main(int argc,const char *argv[]) // 主函数
{//创建套接字int sfd = socket(AF_INET,SOCK_STREAM,0); // 创建TCP套接字if (sfd == -1) // 如果套接字创建失败{ERR_PERR("socket"); // 打印错误信息return -1; // 返回-1}//填充IP地址和端口号struct sockaddr_in cin; // 定义服务器地址结构体cin.sin_family = AF_INET; // 设置地址族为IPv4cin.sin_port = htons(PORT); // 设置端口号cin.sin_addr.s_addr = inet_addr(IP); // 设置IP地址//链接服务器if (connect(sfd,(struct sockaddr *)&cin,sizeof(cin)) == -1) // 连接服务器{ERR_PERR("bind"); // 打印错误信息return -1; // 返回-1}//定义一个标志位int num = 0; // 定义标志位//初始化机械臂位置char r[5] = {0xff,0x02,0x00,0x00,0xff}; // 初始化红色机械臂位置unsigned char b[5] = {0xff,0x02,0x01,0x00,0xff}; // 初始化蓝色机械臂位置sendto(sfd,r,sizeof(r),0,(struct sockaddr*)&cin,sizeof(cin)); // 发送红色机械臂位置信息sleep(1); // 延时1秒,防止沾包sendto(sfd,b,sizeof(b),0,(struct sockaddr*)&cin,sizeof(cin)); // 发送蓝色机械臂位置信息initscr(); // 初始化 ncurses// cbreak(); // 禁用行缓冲// noecho(); // 禁用回显while (1) // 进入循环{char buf = 0; // 定义接收操作的变量buf = getchar(); // 获取用户输入switch (buf) // 开始switch语句{case 'w': // 如果输入为wcase 'W': // 或者大写Wr[3] += 5; // 红色机械臂位置增加5if (r[3] >= 90) // 如果位置超过90度{r[3] = 90; // 将位置设置为90度}else if (r[3] <=-90) // 如果位置小于-90度{r[3] = -90; // 将位置设置为-90度}sendto(sfd,r,sizeof(r),0,(struct sockaddr*)&cin,sizeof(cin)); // 发送红色机械臂位置信息break; // 结束当前casecase 's': // 如果输入为scase 'S': // 或者大写Sr[3] -= 5; // 红色机械臂位置减少5if (r[3] >= 90) // 如果位置超过90度{r[3] = 90; // 将位置设置为90度}else if (r[3] <=-90) // 如果位置小于-90度{r[3] = -90; // 将位置设置为-90度}sendto(sfd,r,sizeof(r),0,(struct sockaddr*)&cin,sizeof(cin)); // 发送红色机械臂位置信息break; // 结束当前casecase 'a': // 如果输入为acase 'A': // 或者大写Ab[3] -= 5; // 蓝色机械臂位置减少5if (b[3] >= 0) // 如果位置大于等于0度{b[3] = 0; // 将位置设置为0度}sendto(sfd,b,sizeof(b),0,(struct sockaddr*)&cin,sizeof(cin)); // 发送蓝色机械臂位置信息break; // 结束当前casecase 'd': // 如果输入为dcase 'D': // 或者大写Db[3] += 5; // 蓝色机械臂位置增加5if (b[3] >= 180) // 如果位置超过180度{b[3] = 180; // 将位置设置为180度}sendto(sfd,b,sizeof(b),0,(struct sockaddr*)&cin,sizeof(cin)); // 发送蓝色机械臂位置信息break; // 结束当前casecase 'q': // 如果输入为qcase 'Q': // 或者大写Qnum = 1; // 设置标志位为1default: // 默认情况continue; // 继续下一次循环break; // 结束当前case}if (num == 1) // 如果标志位为1{break; // 退出循环}}endwin(); // 结束 ncurses//关闭套接字close(sfd); // 关闭套接字return 0; // 返回0
}
上传下载
#include<myhead.h> // 包含自定义头文件myhead.h
#include <ncurses.h> // 引入ncurses库
#define IP "192.168.125.135" // 定义IP地址为"192.168.125.135"
#define PORT 69 // 定义端口号为69
int down(int cfd,struct sockaddr_in sin); // 声明down函数,参数为cfd和sin
int up(int cfd,struct sockaddr_in sin);
void jiemian(void); // 声明jiemian函数,无参数
int main(int argc, char *argv[]) // 主函数,参数为argc和argv
{int cfd = socket(AF_INET,SOCK_DGRAM,0); // 创建UDP套接字cfdif (cfd == -1) // 如果cfd为-1{ERR_PERR("socket"); // 打印错误信息return -1; // 返回-1}struct sockaddr_in sin; // 定义sockaddr_in结构体sinsin.sin_family = AF_INET; // 设置sin的地址族为AF_INETsin.sin_port = htons(PORT); // 设置sin的端口号为PORTsin.sin_addr.s_addr = inet_addr(IP); // 设置sin的IP地址为IP// down(cfd,sin);while (1){system("clear");jiemian(); // 调用jiemian函数printf("请输入选项>>>");char num = getchar();while(getchar() != 10); // 清空输入缓冲区switch (num){case '1':down(cfd,sin); // 调用down函数,参数为cfd和sinbreak;case '2':up(cfd,sin); // 调用up函数,参数为cfd和sinbreak;case '3':close(cfd); // 关闭套接字cfdsystem("clear");return 0; // 返回0break;default:continue;break;}printf("按任意键继续……");getchar();}
}
void jiemian(void) // 定义jiemian函数,无参数
{printf("*************************************\n\
*************************************\n\
*************1、下载*****************\n\
*************2、上传*****************\n\
*************3、退出*****************\n\
*************************************\n\
*************************************\n"); // 打印菜单信息
}
int down(int cfd,struct sockaddr_in sin) // 定义down函数,参数为cfd和sin
{char buf[516] = ""; // 定义长度为516的字符数组bufchar filename[20] = ""; // 定义长度为20的字符数组filenameprintf("请输入文件名:"); // 打印提示信息scanf("%s",filename); // 读取用户输入的文件名while(getchar() != 10); // 清空输入缓冲区int fd = open(filename,O_WRONLY|O_CREAT,0664); // 打开文件,可写且创建,权限为0664if (fd == -1) // 如果打开文件失败{ERR_PERR("open"); // 打印错误信息return -1; // 返回-1}int size = sprintf(buf,"%c%c%s%c%s%c",0,1,filename,0,"octet",0); // 格式化字符串到buf中if (sendto(cfd,buf,size,0,(struct sockaddr*)&sin,sizeof(sin)) < 0) // 发送数据到服务器{ERR_PERR("sendto"); // 打印错误信息return -1; // 返回-1}unsigned short code = 0; // 定义short类型变量num和codewhile (1) // 进入循环{memset(buf,0,sizeof(buf)); // 将buf清零struct sockaddr_in cin;socklen_t len = sizeof(cin); // 定义socklen_t类型变量lenssize_t res = recvfrom(cfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,&len); // 从服务器接收数据if (res == -1) // 如果接收数据失败{ERR_PERR("recvform"); // 打印错误信息return -1; // 返回-1}code = buf[1]; // 获取数据包中的codeunsigned short *p = (unsigned short *)buf; // 定义short指针p,指向bufif (code == 3) // 如果code为3{*p = htons(4); // 转换网络字节序if (sendto(cfd,buf,4,0,(struct sockaddr*)&cin,sizeof(cin)) < 0) // 发送数据到服务器{ERR_PERR("sendto"); // 打印错误信息return -1; // 返回-1}if (res < 516) // 如果接收到的数据小于516{if(write(fd,p+2,strlen(buf+4)) == -1) // 写入文件{ERR_PERR("write");return -1;}break; // 跳出循环}else if (res == 516) // 如果接收到的数据等于516{if(write(fd,p+2,512) == -1) // 写入文件{ERR_PERR("write");return -1;}continue; // 继续循环}}else if (code == 5) // 如果code为5{printf("download error! errno = %d\n",buf[3]); // 打印下载错误信息break; // 跳出循环}}close(fd); // 关闭文件
}
int up(int cfd,struct sockaddr_in sin)
{char buf[516] = ""; // 定义长度为516的字符数组bufchar filename[20] = ""; // 定义长度为20的字符数组filenameprintf("请输入文件名:"); // 打印提示信息scanf("%s",filename); // 读取用户输入的文件名while(getchar() != 10); // 清空输入缓冲区int fd = open(filename,O_RDONLY); // 打开文件,可写且创建,权限为0664if (fd == -1) // 如果打开文件失败{ERR_PERR("open"); // 打印错误信息return -1; // 返回-1}int size = sprintf(buf,"%c%c%s%c%s%c",0,2,filename,0,"octet",0); // 格式化字符串到buf中if (sendto(cfd,buf,size,0,(struct sockaddr*)&sin,sizeof(sin)) < 0) // 发送数据到服务器{ERR_PERR("sendto"); // 打印错误信息return -1; // 返回-1}while (1){memset(buf,0,sizeof(buf)); // 将buf清零struct sockaddr_in cin;socklen_t len = sizeof(cin); // 定义socklen_t类型变量lenssize_t res = recvfrom(cfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,&len); // 从服务器接收数据if (res == -1) // 如果接收数据失败{ERR_PERR("recvform"); // 打印错误信息return -1; // 返回-1}if (buf[1] == 4) // 如果收到的数据包类型为4{unsigned short num = ((buf[2] << 8)|buf[3]) + 1; // 计算下一个块编号memset(buf,0,512); // 将buf清零buf[1] = 3; // 设置数据包类型为3*(unsigned short *)(buf + 2) = htons(num); // 设置块编号ssize_t rec = read(fd,buf+4,512); // 从文件中读取数据到buf中if (rec == 0) // 如果读取到的数据长度为0{break; // 跳出循环}sendto(cfd,buf,rec+4,0,(struct sockaddr *)&cin,sizeof(cin)); // 发送数据包到服务器}}close(fd); // 关闭文件
}
网络聊天室
服务器
fun.h
#ifndef __FUN_H__
#define __FUN_H__
#include<myhead.h> // 引入自定义的头文件
#define PORT 8888 //端口号1024~49151
#define IP "192.168.250.100" //本机IP
//消息结构体
typedef struct data
{char type;//要进行的选项char username[20];//用户名称char data[1024];//消息内容
}data,*dataptr;
//链表节点
typedef struct fun
{struct sockaddr_in cin; //客户端地址char username[20];//用户名称int fd; //文件描述符int len; //记录链表长度struct fun *next; //记录后继节点的地址
}fun,*funptr;
struct cli
{int newfd; // 客户端套接字描述符struct sockaddr_in cin; // 客户端地址信息结构体funptr list;//链表
};
funptr list_create();// 链表的创建
int list_kong(funptr list);// 链表判空
funptr list_new(char *e,struct sockaddr_in cin,int fd);// 申请节点封装数据
int list_head_create(funptr list,char *e,struct sockaddr_in cin,int fd);// 头插
int list_ls(funptr list);// 遍历
funptr list_pos_ptr(funptr list,int pos);// 按位置查找返回节点地址
int list_pos_del(funptr list,int pos);// 任意位置删除
int list_data_ptr(funptr list,char *e);//按值查找返回节点位置
int list_lol(funptr list);// 链表翻转
int list_end_del(funptr list); //尾删
int list_del(funptr *list);// 链表的销毁
void *task(void *arg);// 线程函数
void *task1(void *arg); // 线程函数
#endif
fun.c
#include"fun.h"
// 链表的创建
funptr list_create()
{//从堆区申请空间funptr list_head = (funptr)malloc(sizeof(fun));if(list_head == NULL){ERR_PRIN("list_ptr error");return NULL;}//初始化节点strcpy(list_head->username,"");list_head->len = 0;list_head->fd = -1;list_head->next = NULL;return list_head;
}
// 链表翻转
int list_lol(funptr list)
{if(list_kong(list) || list->next == NULL){ERR_PRIN("链表反转失败!\n");return -1;}funptr L = list->next;list->next = NULL;funptr q;while(L != NULL){q = L;L = L->next;q->next = list->next;list->next = q;}printf("链表翻转成功!\n");return 0;
}
// 申请节点
funptr list_new(char *e,struct sockaddr_in cin,int fd)
{//从堆区申请空间funptr list_head = (funptr)malloc(sizeof(fun));if(list_head == NULL){ERR_PRIN("list_ptr error");return NULL;}//初始化节点list_head->fd = fd;list_head->cin = cin;strcpy(list_head->username,e);list_head->len = 0;list_head->next = NULL;return list_head;
}
// 链表判空
int list_kong(funptr list)
{if(list == NULL){printf("链表不存在!\n");return 0;}if(list->len == 0 && list->next == NULL)return 1;else return 0;
}
// 头插
int list_head_create(funptr list,char *e,struct sockaddr_in cin,int fd)
{if(list == NULL){printf("链表不存在!\n");return -1;}//创建一个中间变量,调用【申请节点】funptr p = list_new(e,cin,fd);p->next = list->next;list->next = p;list->len++;return 0;
}
// 遍历
int list_ls(funptr list)
{if(list_kong(list) || list->next == NULL){ERR_PRIN("遍历失败!\n");return -1;}funptr p = list->next;//循环链表while(p != NULL){printf("%d ",p->fd);p = p->next;}printf("\n");return 0;
}
// 按位置查找返回节点地址
funptr list_pos_ptr(funptr list,int pos)
{if(list_kong(list) || list->next == NULL || pos > list->len){ERR_PRIN("查找失败!\n");return NULL;}funptr p = list;for(int i = 0;i < pos;i++){p = p->next;}return p;
}
// 任意位置删除
int list_pos_del(funptr list,int pos)
{if(list_kong(list) || list->next == NULL || pos > list->len){ERR_PRIN("删除失败!\n");return -1;}//找到前一个结点funptr p = list_pos_ptr(list,pos-1);funptr q = p->next;p->next = q->next;free(q);q = NULL;list->len--;return 0;
}
// 尾删
int list_end_del(funptr list)
{if(list_kong(list) || list->next == NULL){ERR_PRIN("删除失败!\n");return -1;}funptr p = list;//循环找到倒数第二个节点while(p->next->next != NULL){p = p->next;}//释放最后一个节点free(p->next);p->next = NULL;list->len--;printf("删除成功!\n");return 0;
}
// 链表的销毁
int list_del(funptr *list)
{if(*list == NULL){printf("链表不存在!\n");return -1;}while(!list_kong(*list)){list_end_del(*list);}free(*list);*list = NULL;printf("销毁成功!\n");return 0;
}
//按值查找返回位置
int list_data_ptr(funptr list,char *e)
{if(list_kong(list) || list->next == NULL){printf("查找失败!\n");return -1;}funptr p = list->next;int index = 1;while (p != NULL){if (strcmp(p->username,e) == 0){return index;}p = p->next;index++;}return 0;
}
// 线程函数
void *task(void *arg)
{struct cli info = *(struct cli*)arg; // 获取客户端信息ssize_t res = 0; // 接收数据长度data cli_data;//定义消息结构体变量char buf[20] = "";while(1) // 进入循环{//字符串清空memset(buf,0,sizeof(buf));memset(cli_data.data,0,sizeof(cli_data.data)); // 清空缓冲区memset(cli_data.username,0,sizeof(cli_data.username)); // 清空缓冲区//读取消息if((res = recv(info.newfd,&cli_data,sizeof(cli_data),0)) == -1) // 接收消息{ERR_PERR("recv error"); // 打印错误信息return NULL; // 返回空}strcpy(buf,cli_data.username);if (cli_data.type == 'C'){if (strcmp(cli_data.data,"quit") == 0){funptr p = info.list->next; //接收链表while (p != NULL){if (p->fd == info.newfd){printf("%s [%s:%d]客户端下线!\n",p->username,inet_ntoa(info.cin.sin_addr),ntohs(info.cin.sin_port)); // 打印客户端下线信息close(p->fd);}if (p->fd != info.newfd) //判断是否是同一个用户{// printf("p->fd = %d\n",p->fd);cli_data.type = 'L';memset(cli_data.data,0,sizeof(cli_data.data)); //清空消息sprintf(cli_data.data,"-----%s已下线-----",cli_data.username);if(send(p->fd,&cli_data,sizeof(cli_data),0) == -1) // 发送消息{ERR_PERR("send error"); // 打印错误信息return NULL; // 返回空}}p = p->next;}// printf("username = %s\n",cli_data.username);// printf("pos = %d\n",list_data_ptr(info.list,cli_data.username));list_pos_del(info.list,list_data_ptr(info.list,cli_data.username));// list_ls(info.list);break; // 退出循环}funptr p = info.list->next; //接收链表while (p != NULL){if (p->fd != info.newfd) //判断是否是同一个用户{// printf("p->fd = %d\n",p->fd);if(send(p->fd,&cli_data,sizeof(cli_data),0) == -1) // 发送消息{ERR_PERR("send error"); // 打印错误信息return NULL; // 返回空}}p = p->next;}printf("%s [%s:%d] %s\n",cli_data.username,inet_ntoa(info.cin.sin_addr),ntohs(info.cin.sin_port),cli_data.data); // 打印客户端发送的消息}else if(res == 0) // 如果接收到的数据长度为0{funptr p = info.list->next; //接收链表while (p != NULL){if (p->fd == info.newfd){printf("%s [%s:%d]客户端下线!\n",p->username,inet_ntoa(info.cin.sin_addr),ntohs(info.cin.sin_port)); // 打印客户端下线信息}if (p->fd != info.newfd) //判断是否是同一个用户{cli_data.type = 'L';memset(cli_data.data,0,sizeof(cli_data.data));sprintf(cli_data.data,"-----%s已下线-----",buf);if(send(p->fd,&cli_data,sizeof(cli_data),0) == -1) // 发送消息{ERR_PERR("send error"); // 打印错误信息return NULL; // 返回空}close(p->fd);}p = p->next;}list_pos_del(info.list,list_data_ptr(info.list,cli_data.username));break; // 退出循环}}close(info.newfd); // 关闭客户端套接字pthread_exit(NULL); // 退出线程
}
// 线程函数
void *task1(void *arg)
{struct cli info1 = *(struct cli *)arg;data cli_data = {.type = 'C',.data = "",.username = "大王"};while(1){memset(cli_data.data,0,sizeof(cli_data.data)); //清空字符数组scanf("%s",cli_data.data); //从终端获取数据if (strcmp(cli_data.data,"quit") == 0 && info1.list->next == NULL){exit(EXIT_SUCCESS);break;}funptr p = info1.list->next; //接收链表while (p != NULL){if(send(p->fd,&cli_data,sizeof(cli_data),0) == -1) // 发送消息{ERR_PERR("send error"); // 打印错误信息return NULL; // 返回空}p = p->next;}}
}
main.c
#include"fun.h"
int main(int argc, const char *argv[]) // 主函数
{//创建socket套接字int sfd = socket(AF_INET,SOCK_STREAM,0); // 创建套接字if(sfd < 0) // 如果创建套接字失败{ERR_PERR("socket error"); // 打印错误信息return -1; // 返回-1}//允许端口快速的被复用xint reuse = 1; // 设置端口复用if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) // 设置套接字选项{ERR_PERR("setsockopt"); // 打印错误信息return -1; // 返回-1}//填充信息结构体struct sockaddr_in sin; // 服务器地址信息结构体sin.sin_family = AF_INET; //IPv4sin.sin_port = htons(PORT); //端口号转换成网络字节序sin.sin_addr.s_addr = inet_addr(IP); //本机IP转换成网络字节序//绑定if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin)) < 0) // 绑定地址{ERR_PERR("bind error"); // 打印错误信息return -1; // 返回-1}//设置监听套接字if(listen(sfd,128) < 0) // 监听套接字{ERR_PERR("listen error"); // 打印错误信息return -1; // 返回-1}//获取新套接字int newfd = -1; // 新的套接字描述符struct sockaddr_in cin; // 客户端地址信息结构体socklen_t len = sizeof(cin); // 地址信息结构体长度funptr list = list_create();//创建链表data cli_data;//定义消息结构体变量struct cli info1 = {.list = list};pthread_t tid1;pthread_create(&tid1,NULL,task1,&info1); // 创建线程pthread_detach(tid1); // 分离线程while(1) // 进入循环{if((newfd = accept(sfd,(struct sockaddr*)&cin,&len)) < 0) // 接受新的连接{ERR_PERR("accept error"); // 打印错误信息return -1; // 返回-1}if (recv(newfd,&cli_data,sizeof(cli_data),0) == -1) // 读取消息{ERR_PERR("recv");return -1;}if (cli_data.type == 'L'){if (list_head_create(list,cli_data.username,cin,newfd) == -1)// 插入链表{ERR_PRIN("list_head_create");return -1;}printf("%s [%s:%d]客户端连接成功\n",cli_data.username,inet_ntoa(cin.sin_addr),ntohs(cin.sin_port)); // 打印客户端连接成功信息memset(cli_data.data,0,sizeof(cli_data.data)); //清空消息// list_ls(list);funptr p = list->next; //接收链表while (p != NULL){if (p->fd != newfd) //判断是否是同一个用户{// printf("newfd = %d\n",newfd);// printf("p->fd = %d\n",p->fd);sprintf(cli_data.data,"-----%s已上线-----",cli_data.username);if(send(p->fd,&cli_data,sizeof(cli_data),0) == -1) // 发送消息{ERR_PERR("send error"); // 打印错误信息return -1; // 返回空}}p = p->next;}pthread_t tid; // 线程IDstruct cli info = {newfd,cin,list}; // 客户端信息pthread_create(&tid,NULL,task,(void *)&info); // 创建线程pthread_detach(tid); // 分离线程}}list_del(&list);// 销毁链表close(sfd); // 关闭服务器套接字close(newfd); // 关闭客户端套接字return 0; // 返回0
}
客户端
fun.h
#ifndef __FUN_H__
#define __FUN_H__
#include<myhead.h> // 引入自定义的头文件
#define PORT 8888 //端口号1024~49151
#define IP "192.168.250.100" //本机IP
//消息结构体
typedef struct data
{char type;//要进行的选项char username[20];//用户名称char data[1024];//消息内容
}data,*dataptr;
//链表节点
typedef struct fun
{struct sockaddr_in cin; //客户端地址char username[20];//用户名称int fd; //文件描述符int len; //记录链表长度struct fun *next; //记录后继节点的地址
}fun,*funptr;
struct cli
{int newfd; // 客户端套接字描述符struct sockaddr_in cin; // 客户端地址信息结构体funptr list;//链表
};
void *task(void *arg);// 线程函数
#endif
fun.c
#include"fun.h"
// 线程函数
void *task(void *arg)
{int sfd = *(int *)arg;ssize_t res = 0; // 接收数据长度data cli_data;//定义消息结构体变量while(1) // 进入循环{//字符串清空memset(cli_data.data,0,sizeof(cli_data.data)); // 清空缓冲区memset(cli_data.username,0,sizeof(cli_data.username)); // 清空缓冲区//读取消息if((res = recv(sfd,&cli_data,sizeof(cli_data),0)) == -1) // 接收消息{ERR_PERR("recv error"); // 打印错误信息return NULL; // 返回空}if (cli_data.type == 'L'){printf("%s\n",cli_data.data);}if (cli_data.type == 'C'){printf("%s:%s\n",cli_data.username,cli_data.data); // 打印客户端发送的消息}}pthread_exit(NULL); // 退出线程
}
main.c
#include"fun.h"
int main(int argc, const char *argv[])
{//创建socket套接字int sfd = socket(AF_INET,SOCK_STREAM,0);if(sfd < 0){ERR_PERR("socket error");return -1;}//填充信息结构体struct sockaddr_in sin;sin.sin_family = AF_INET; //IPv4sin.sin_port = htons(PORT); //端口号转换成网络sin.sin_addr.s_addr = inet_addr(IP); //本机IP转换成网络//连接服务器if(connect(sfd,(struct sockaddr *)&sin,sizeof(sin)) < 0){ERR_PERR("connect error");return -1;}char buf[20] = ""; //存储用户名称data cli_data = {.type = 'L'};//定义消息结构体变量memset(cli_data.username,0,sizeof(cli_data.username)); // 清空缓冲区printf("请输入名称>>>");scanf("%s",buf);strcpy(cli_data.username,buf);//将用户名称输入结构体//发送消息if(send(sfd,&cli_data,sizeof(cli_data),0) < 0){ERR_PERR("send error");return -1;}ssize_t res = 0;pthread_t tid; // 线程IDpthread_create(&tid,NULL,task,&sfd); // 创建线程pthread_detach(tid); // 分离线程while(1){//字符串清空memset(cli_data.data,0,sizeof(cli_data.data)); // 清空缓冲区//从终端获取消息scanf("%s",cli_data.data);//发送消息cli_data.type = 'C';//切换传输模式if(send(sfd,&cli_data,sizeof(cli_data),0) < 0){ERR_PERR("send error");return -1;}if(!strcmp(cli_data.data,"quit")){if(send(sfd,&cli_data,sizeof(cli_data),0) < 0){ERR_PERR("send error");return -1;}break;}}//关闭套接字close(sfd);return 0;
}