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)); // 清空缓冲区buf
res = 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); // 打开文件,并将文件指针存储到fd0
if(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);
//将换行符转换成0
if(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; // 设置地址族为IPv4
cin.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': // 如果输入为w
case 'W': // 或者大写W
r[3] += 5; // 红色机械臂位置增加5
if (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; // 结束当前case
case 's': // 如果输入为s
case 'S': // 或者大写S
r[3] -= 5; // 红色机械臂位置减少5
if (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; // 结束当前case
case 'a': // 如果输入为a
case 'A': // 或者大写A
b[3] -= 5; // 蓝色机械臂位置减少5
if (b[3] >= 0) // 如果位置大于等于0度
{
b[3] = 0; // 将位置设置为0度
}
sendto(sfd,b,sizeof(b),0,(struct sockaddr*)&cin,sizeof(cin)); // 发送蓝色机械臂位置信息
break; // 结束当前case
case 'd': // 如果输入为d
case 'D': // 或者大写D
b[3] += 5; // 蓝色机械臂位置增加5
if (b[3] >= 180) // 如果位置超过180度
{
b[3] = 180; // 将位置设置为180度
}
sendto(sfd,b,sizeof(b),0,(struct sockaddr*)&cin,sizeof(cin)); // 发送蓝色机械臂位置信息
break; // 结束当前case
case 'q': // 如果输入为q
case 'Q': // 或者大写Q
num = 1; // 设置标志位为1
default: // 默认情况
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套接字cfd
if (cfd == -1) // 如果cfd为-1
{
ERR_PERR("socket"); // 打印错误信息
return -1; // 返回-1
}
struct sockaddr_in sin; // 定义sockaddr_in结构体sin
sin.sin_family = AF_INET; // 设置sin的地址族为AF_INET
sin.sin_port = htons(PORT); // 设置sin的端口号为PORT
sin.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和sin
break;
case '2':
up(cfd,sin); // 调用up函数,参数为cfd和sin
break;
case '3':
close(cfd); // 关闭套接字cfd
system("clear");
return 0; // 返回0
break;
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的字符数组buf
char filename[20] = ""; // 定义长度为20的字符数组filename
printf("请输入文件名:"); // 打印提示信息
scanf("%s",filename); // 读取用户输入的文件名
while(getchar() != 10); // 清空输入缓冲区
int fd = open(filename,O_WRONLY|O_CREAT,0664); // 打开文件,可写且创建,权限为0664
if (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和code
while (1) // 进入循环
{
memset(buf,0,sizeof(buf)); // 将buf清零
struct sockaddr_in cin;
socklen_t len = sizeof(cin); // 定义socklen_t类型变量len
ssize_t res = recvfrom(cfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,&len); // 从服务器接收数据
if (res == -1) // 如果接收数据失败
{
ERR_PERR("recvform"); // 打印错误信息
return -1; // 返回-1
}
code = buf[1]; // 获取数据包中的code
unsigned short *p = (unsigned short *)buf; // 定义short指针p,指向buf
if (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的字符数组buf
char filename[20] = ""; // 定义长度为20的字符数组filename
printf("请输入文件名:"); // 打印提示信息
scanf("%s",filename); // 读取用户输入的文件名
while(getchar() != 10); // 清空输入缓冲区
int fd = open(filename,O_RDONLY); // 打开文件,可写且创建,权限为0664
if (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类型变量len
ssize_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
}
//允许端口快速的被复用x
int 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; //IPv4
sin.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; // 线程ID
struct 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; //IPv4
sin.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; // 线程ID
pthread_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;
}