当前位置:   article > 正文

socket搭建Linux服务器客户端,实现消息发送和文件传输,思路简单,适合新手。_linux服务端发送消息给客户端

linux服务端发送消息给客户端

第一步 当然是创建socket,绑定bind,监听listen

  1. ret_socket = socket(AF_INET,SOCK_STREAM,0);
  2. if(ret_socket == -1)
  3. {
  4. perror("socket");
  5. exit(EXIT_FAILURE);
  6. }
  1. struct sockaddr_in t0; //在前面定义结构体变量
  2. t0.sin_family = AF_INET;
  3. t0.sin_port = htons(atoi(argv[2]));
  4. inet_aton(argv[1],&t0.sin_addr);
  5. //通过输入的方式传参 int main(int argc ,char**argv )
  6. //也可以先将 atoi(argv[2]) 替换为数字,将 argv[1] 替换为ip地址
  7. //自己搞清楚htons atoi inet_aton这三个函数
  8. if( bind(ret_socket, (struct sockaddr*)&t0,sizeof(struct sockaddr_in) ) ==-1 )
  9. {
  10. perror("bind");
  11. exit(EXIT_FAILURE);
  12. }
  1. if( listen(ret_socket,10) ==-1)
  2. {
  3. perror("listen");
  4. exit(EXIT_FAILURE);
  5. }

 第二步 等待客户端接入accept(),先进入一个循环while(1),如果有客户端接入就fork(),即父进程不断接入新的客户端,子进程去处理客户端的请求

  1. while(1)
  2. {
  3. ac = accept(ret_socket, (struct sockaddr*)&ac_addr, &addrlen);
  4. if( ac == -1)
  5. {
  6. perror("accept");
  7. exit(EXIT_FAILURE);
  8. }
  9. //父进程一直等待客户端连接,如果有客户端接入,fork一个子程序处理该客户端的连接
  10. fk = fork();
  11. if(fk == 0)
  12. {
  13. ...

 第三步 实现服务器和客户端的聊天(收发消息)功能

为了让接收消息和发送消息互不干扰,我在子进程里面再次fork(), 子父 进程负责发send()消息, 子子进程负责收recv()消息,既然要不断地收发消息,那肯定是各自进入一个循环里面

下面是服务器的代码,客户端也要不断的收发消息,思路是一样的

  1. if(fk == 0)
  2. {
  3. fk2 = fork();
  4. if(fk2==0)//这里是第二次fork后的子进程,即子子进程
  5. {
  6. while(1)
  7. {
  8. memset(r_buf,0,sizeof(r_buf));
  9. recv(ac,r_buf,sizeof(r_buf),0);//不断接收客户端的消息
  10. if(strlen(r_buf)!=0)
  11. {
  12. printf("Client Message:%s\n",r_buf);
  13. }
  14. }
  15. }
  16. else if(fk2>0)//这里是第二次fork后的父进程 即子父进程
  17. {
  18. while(1)
  19. {
  20. memset(w_buf,0,sizeof(w_buf));
  21. scanf("%s",w_buf);
  22. getchar();
  23. send(ac,w_buf,strlen(w_buf),0);//不断向客户端发消息
  24. }
  25. }
  26. else //这是第二次fork失败的判断
  27. {
  28. perror("fork 2");
  29. exit(EXIT_FAILURE);
  30. }
  31. }
  32. else if(fk>0)//这里是第一次fork父进程
  33. {
  34. printf("client connection,ip=%s\n",inet_ntoa(ac_addr.sin_addr));
  35. }
  36. else //这是第一次fork失败的判断
  37. {
  38. perror("fork 1");
  39. exit(EXIT_FAILURE);
  40. }
  41. }

第四步 轻松写出客户端的代码

客户端首先创建socket,连接connect

  1. int client_socket = socket(AF_INET,SOCK_STREAM,0);//第一步创建socket
  2. if(client_socket == -1)
  3. {
  4. perror("socket");
  5. exit(EXIT_FAILURE);
  6. }
  7. struct sockaddr_in client_addr;
  8. memset(&client_addr,0, sizeof(client_addr) );
  9. client_addr.sin_family = AF_INET;
  10. client_addr.sin_port = htons(atoi(argv[2]));
  11. //这里atoi(argv[2])和argv[1] 传参,和服务器一样,不清楚就替换成数字
  12. //搞清楚htons atoi 和main函数传参
  13. inet_aton(argv[1],&client_addr.sin_addr);
  14. int client_connect = //第二步 连接服务器 connect()函数
  15. connect(client_socket, (struct sockaddr*)&client_addr,sizeof(struct sockaddr_in));
  16. if(client_connect == -1)
  17. {
  18. perror("connect");
  19. exit(EXIT_FAILURE);
  20. }

 然后fork,子进程负责向服务器发消息,父进程负责接收服务器消息,基本上就是把服务器代码复制一遍

  1. pid_t fk=fork();
  2. if(fk==0) //这里是客户端的子进程,不停的发消息
  3. {
  4. while(1)
  5. {
  6. memset(client_send_str,0,sizeof(client_send_str));
  7. //client_send_str是自定义的一个数组,存放用户输入的字符,client_send_str[1024]={0};
  8. scanf("%s",client_send_str);
  9. getchar();
  10. client_send = send(client_socket,client_send_str,sizeof(client_send_str),0);
  11. if(client_send == -1)
  12. {
  13. perror("send");
  14. exit(EXIT_FAILURE);
  15. }
  16. }
  17. else if(fk>0)//这里是客户端的父进程,不停的收消息
  18. {
  19. while(1)
  20. {
  21. memset(client_buf,0,sizeof(client_buf));
  22. //client_buf定义的一个数组 client_buf[1024]={0};
  23. client_recv = recv(client_socket,client_buf,sizeof(client_buf),0);
  24. if(client_recv == -1)
  25. {
  26. perror("recv");
  27. exit(EXIT_FAILURE);
  28. }
  29. if(strlen(client_buf)!=0)
  30. {
  31. printf("receive data from server:%s\n",client_buf);
  32. }
  33. }
  34. else
  35. {
  36. perror("fork");
  37. }

第五步 获取服务器文件列表,获取客户端文件列表,

1、客户端连上服务器后,想下载服务里的文件,首先我们来实现第一步:看看服务器的文件有哪些。

具体的思路是:约定指令,我向服务器发送"sfile",服务器收到"sfile"后,把他的文件列表发给客户端;这里使用popen函数;

将下面这段代码加在服务器第二次fork后的子子进程里面

  1. //首先应当搞清楚strcmp字符串比较函数,两个字符串相等返回0,判断"sfile"
  2. //popen函数把shell命令 ls -l 的结果保存起来
  3. //fp应在之前声明 即FILE*fp;
  4. //fread将ls -l 的结果读到f_buf这个数组中,f_buf也应在之前声明 f_buf[1024]={0};
  5. //ac 是accept函数返回的标识符,用send函数将数据发给客户端
  6. if( strcmp(r_buf,"sfile") ==0)
  7. {
  8. memset(f_buf,0,sizeof(f_buf));
  9. fp = popen("ls -l","r");
  10. fread(f_buf,1,sizeof(f_buf),fp);
  11. send(ac,f_buf,sizeof(f_buf),0);
  12. pclose(fp);
  13. }

2、作为客户端,我连上服务器后,我又想上传本地文件到服务器,思路:判断用户输入,用户输入的字符会保存到client_send_str这个数组,如果数组里面的字符是   "cfile",就把客户端文件列表打印出来;这里也使用popen函数;

将下面第一段代码加在客户端代码fork后的子进程里面

  1. //和获取服务器文件列表基本相同,客户端更简单一些
  2. //cf应在之前声明即FILE*cf
  3. //arr 也应在前面声明 char arr[1024]={0};
  4. if(strcmp(client_send_str,"cfile") ==0)
  5. {
  6. memset(arr,0,sizeof(arr));
  7. cf=popen("ls -l","r");
  8. fread(arr,1,sizeof(arr),cf);
  9. printf("########################client-files###########################\n");
  10. printf("%s\n",arr);
  11. pclose(cf);
  12. }
  13. //下面这些打印都是为了美观,可有可无
  14. if(strcmp(client_send_str,"sfile") ==0)
  15. {
  16. printf("########################server-files###########################\n");
  17. }
  18. //下面这个也是为了美观,与服务器建立连接后,先打印出来,调用background_print()可有可无
  19. void background_print()
  20. {
  21. char buf[64]={0};
  22. memset(buf,0,64);
  23. FILE*fp;
  24. fp=popen("date","r");
  25. fread(buf,1,64,fp);
  26. printf("####################################################\n");
  27. printf(" > welcome to connect server!\n");
  28. printf(" > sign time: %s",buf);
  29. printf(" > you can send or receive message,transfer files\n");
  30. printf(" > Enter 'sfile' to look server files\n");
  31. printf(" > Enter 'cfile' to look client files\n");
  32. printf(" > Enter 'download' to get server file\n");
  33. printf(" > Enter 'quit' to disconnect\n");
  34. printf("####################################################\n");

 第六步 控制客户端断开连接

客户端想断开和服务器的连接,向服务器发送"quit",服务器收到"quit"后,

把他的 子父进程 和 子子进程 杀死。服务器这边的代码:

首先要获取这两个进程的pid,

再用sprintf函数构造"kill -9 pid1  pid2"字符串,

然后用system函数执行构造的字符串命令,

最后用waitpid函数回收僵尸进程,而父进程继续等待下一个客户端的连接。

客户端这边也要杀死父子两个进程,比服务器代码简单很多。

下面是服务器代码,和第五步类似,但是多了waitpid函数

  1. //这段代码也是加在第二次fork后的子子进程
  2. //在前面声明char arr[100]={0};
  3. if( strcmp(r_buf,"quit") ==0)
  4. {
  5. printf("client quit,ip=%s\n",inet_ntoa(ac_addr.sin_addr));
  6. memset(arr,0,sizeof(arr));
  7. sprintf(arr,"kill -9 %d %d",getpid(),getppid() ); //首先搞清楚sprintf函数
  8. //这里用getppid()获取子父进程的pid,也是为什么要在子子进程进行收取消息的原因
  9. //printf("%s\n",arr);
  10. system(arr);
  11. close(ac);
  12. }

你可以先用ps -aux|grep ser (将ser替换成你的可执行文件名)shell命令对比查看quit前后,服务器代码的运行结果

上面的代码杀死了服务器的两个进程,还有一个问题:即父程序一直在运行,等待下一个客户端的接入,他的子进程被杀死后,该子进程会变成僵尸进程,需要在父进程里(服务器第一次fork的父进程)加入waitpid函数来清理

我们首先调用signal函数捕获子进程终止时的退出信号,再使用waitpid以非阻塞的方式清理僵尸进程,如果是以阻塞方式,那父进程就不能接入新的客户端

  1. else if(fk>0)
  2. {
  3. printf("client connection,ip=%s\n",inet_ntoa(ac_addr.sin_addr));
  4. signal(SIGCHLD, handler);
  5. //SIGCHLD信号:SIGCHLD是第17号信号.
  6. //子进程在终止时会向父进程发SIGCHLD信号,该信号的默认动作是忽略,
  7. //父进程便可以自定义SIGCHLD信号的处理函数,这样它只需专心处理自己的工作,
  8. //而不必关⼼子进程了, 子进程终止时会通知父进程,父进程在信号处理函数中调用waitpid清理子进程即可。
  9. }
  10. //应声明在main之前
  11. void handler(int sig)
  12. {
  13. //这段代码的作用是在循环中等待所有子进程的状态改变,当所有子进程的状态都不可用时,循环结束。
  14. //因为子进程可能不止一个,在同一时间可能有多个子进程结束运行所以使用while
  15. pid_t fw;
  16. while( (fw=waitpid(-1,NULL,WNOHANG)) >0 ) //先搞清楚waitpid函数,WNOHANG即非阻塞
  17. {
  18. // printf("waitpid=%d\n",fw);
  19. }

第七步 下载服务器文件到客户端

我们在第五步用"sfile"查看了服务器的文件列表,现在我们下载想要的文件,先来理一下思路:

首先我们故技重施,当客户端发送"download"指令到服务器后,服务器用strcmp先对字符串做判断;

如果字符正确,就进入一个条件语句中,服务器要求客户端发送想要下载的文件名(在第五步已经将文件列表发给客户端,在这里也可以再调用一次popen将ls的结果发给客户端,你添加代码);

服务器收到客户端发过来的文件名后要做出判断,有没有该文件?如果有,就打开该文件,将文件内容读到一个数组里,再将该数组的内容发给客户端;如果没有,就告诉客户端文件名输入错误。下面代码虽然有点啰嗦,其实很简单,首先用popen()函数得到find 命令的运行结果,即文件路径(如果find命令成功会返回文件路径,失败返回空,你需要先搞清楚shell命令find ./ -name file),再通过结构体的方式保存文件路径和文件内容,发给客户端;

  1. //下面代码有点长,你可能受不了
  2. else if(strcmp(r_buf,"download") ==0)
  3. {
  4. //char p_buf[64]="Enter Filename to Download"; 使用前先声明
  5. send(ac,p_buf,sizeof(p_buf),0);
  6. //char l_buf[32]={0};
  7. memset(l_buf,0,sizeof(l_buf));
  8. recv(ac,l_buf,sizeof(l_buf),0);//接收客户端发的信息:即客户端想要下载的文件名
  9. //printf("l_buf=%s\n",l_buf);
  10. //char h_buf[64]={0};
  11. memset(h_buf,0,sizeof(h_buf));
  12. sprintf(h_buf,"find ./ -name %s",l_buf);
  13. //用客户端发过来的文件名组成一个find命令的字符串,保存到h_buf
  14. //printf("h_buf =%s\n",h_buf);
  15. fy=popen(h_buf,"r");//执行find ./ -name xxxx
  16. memset(l_buf,0,sizeof(l_buf));
  17. fread(l_buf,1,sizeof(l_buf),fy);//将find命令执行后的信息,读取到l_buf
  18. //printf("l_buf=%s\n",l_buf);
  19. pclose(fy);
  20. /*在前面声明了一个结构体,这里需要注意,客户端那边定义的结构体和要和这个一样
  21. typedef struct file
  22. {
  23. char name[128];
  24. char cont[4096];
  25. }F;
  26. F f1; //定义结构体变量f1
  27. */
  28. if(strlen(l_buf)!=0)
  29. {
  30. //printf("find success\n");
  31. memset(f1.name,0,sizeof(f1.name));
  32. //这里用strncpy把后面的换行符删掉
  33. strncpy(f1.name,l_buf,strlen(l_buf)-1);
  34. printf("f1.name=%s\n",f1.name);
  35. //FILE*fu;
  36. fu = fopen(f1.name,"r+");
  37. if(fu==NULL)
  38. {
  39. perror("fopen failed");
  40. exit -1;
  41. }
  42. fseek(fu,0,SEEK_SET);
  43. memset(f1.cont,0,sizeof(f1.cont));
  44. fread(f1.cont,1,sizeof(f1.cont),fu);
  45. fclose(fu);
  46. printf("cont=%s\n",f1.cont);
  47. memcpy(r_buf,&f1,sizeof(f1));//这里先搞清楚memcpy函数
  48. send(ac,r_buf,sizeof(r_buf),0);
  49. }
  50. else
  51. {
  52. //char b_buf[64]="Filename is inexistent, Enter'download' try again";
  53. send(ac,b_buf,sizeof(b_buf),0);
  54. }
  55. }

服务器这边发送数据的工作在上面已经完成,接下来客户端应该接收数据,客户端那边也应使用相同的结构体保存数据,首先判断服务器发过来的数据,然后创建一个文件,文件名保存在结构体第一个元素中, 再将文件内容写入到创建的文件中,文件内容保存在结构体第二个元素中。

  1. //下面代码加在客户端的父程序中,在前面声明char client_buf[5120]={0};
  2. if(strlen(client_buf)!=0)
  3. {
  4. /* typedef struct file
  5. {
  6. char name[128];
  7. char cont[4096];
  8. }D;
  9. D d1;
  10. */
  11. memset(&d1,0,sizeof(d1));
  12. memcpy(&d1,client_buf,sizeof(d1));
  13. //使用strstr函数,请先搞清楚strstr函数
  14. p = strstr(d1.name,"./");//将文件名保存到p中,在前面声明char*p;
  15. if(p) //如果d1的第一个元素中找到"./"字符
  16. {
  17. printf("p = %s\n",p);
  18. //FILE* fl;
  19. fl=fopen(p,"w+");//创建文件
  20. fwrite(d1.cont,1,strlen(d1.cont),fl);
  21. //将接收到的文件内容写入刚才创建的文件
  22. fseek(fl,0,SEEK_SET);
  23. fclose(fl);
  24. }
  25. else
  26. {
  27. // printf("not found\n");
  28. printf("receive data from server:%s\n",client_buf);
  29. }
  30. }

如果在客户端输入"download"后,输入的文件名正确,这时客户端已经成功下载一个文件。

第八步 将客户端文件上传到服务器

基本是把第七步再写一遍:客户端打开该文件输入"upload"后,构造find命令,使用popen将find命令运行后的结果(文件路径),保存到一个数组中,用open函数打开该文件,将文件路径保存到结构体的第一个元素,将文件内容读到第二个元素,使用memcpy函数,将结构体数据发给服务器,服务器接收数据,并创建文件,将数据写入创建的文件。读者自己写一下吧。完。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/622883
推荐阅读
相关标签
  

闽ICP备14008679号