并发回应服务器与客户端的设计

linux中的socket

Socket是Linux跨进程通信(IPC,Inter Process Communication,详情参考:Linux进程间通信方式总结)方式的一种。相比于其他IPC方式,Socket更牛的地方在于,它不仅仅可以做到同一台主机内跨进程通信,它还可以做到不同主机间的跨进程通信。根据通信域的不同可以划分成2种:Unix domain socket 和 Internet domain socket。

Socket工作示意图和原理图:

 

图 1 数据处理原理图

 

图 2 工作流程示意图

 

 Linux中的信号

信号(IPC)最初是UNIX系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动。简单来说信号是操作系统(内核)响应某些条件而产生的一个事件(给进程)。进程之间无法通信,可以使用信号来解决。

信号时由于某些错误条件而生成的,如内存段冲突,浮点处理器错误或非法指令等。他们由shell和终端处理器生成来引起中断,他们还可以作为在进程中传递消息或修改行为的一种方式,明确地由一个进程发送给另外一个进程。

linux中常用信号:

 

设计方案

1.客户端

用户在终端运行客户端程序,命令格式如下:

$>./client <server IP address> [< data.txt]

以上命令格式中服务器IP地址不可省略(一般测试时为127.0.0.1);方括号中的重定向可以没有,当没有重定向文件时,用户键盘输入为客户端发送给服务器的数据来源,每当用户按下回车键时,此行输入会被发送至服务端,并在客户端回显(来自服务端的回应),当用户在键盘上按下Ctrl D时,客户端程序结束;当有重定向文件时,客户端将文件中数据逐行发送至服务端,发送完毕且回显完毕结束程序,全过程用户无需键盘输入。

2.服务端

服务器程序在终端中以如下格式后台运行:

$>./server &

主进程做好套接字准备后会阻塞在accept上等待哭护短请求到达;当客户端请求到达时,服务器主进程需要创建子进程为客户端服务;结束时子进程退出,主进程通过信号处理调用waitpid回收子进程;服务器可以同时处理多个客户端的请求。

具体实现

1.客户端与服务器的套接字初始化配置

图 4客户端socket配置

图 5服务器socket配置

 

 

3.客户端数据传输

无重定向文件时,因为控制终端(/dev/tty)如果当前进程有控制终端(Controlling Terminal)的话,那么/dev/tty就是当前进程的控制终端的设备特殊文件。所以能够在当前终端直接进行输入,不满足参数为3个所以不进行重定向,通过read读取终端输入的数据读到buf里面,buf相当于中间变量,然后在将buf里的数据write写到套接字socket_fd里面,然后清空一下buf,然后下边一个if判断,观察的是服务器有没有回传数据,如果有的话就在终端上打印出来,(向服务器写着,客户端读者)

 

                         图 11 无重定向文件测试

有重定向文件时,argc == 3,所以进行重定向,现在开始指向终端的文件描述符指向data_fd指向的地方,及重定向文件,所以图()的fd指的是data_fd,将重定向文件一行一行读出来放到buf进行传输,传输完成之后,回显到客户端终端

 

             

                                         图 14 重定向文件的测试

4.创建多个进程和回收进程

只要有一个连接就会fork一个子进程,然后这样就能实现多个客户端共同与服务器进行通信的目的,里面对数据的处理都是相同的,pid == 0时就是在子进程里,pid > 0,就是在父进程里面,在父进程里面当接收到SIGCHLD信号时,调用catch_child函数(内核调用),在函数里通过waitpid回收子进程,因为每次都只能回收一个子进程,所有要使用while循环,回收之后打印一下回收的子进程号。

 图 15 服务器创建进程和处理数据核心代码

 图 16 回收子进程的函数(内核调用)

 图 17 服务器的终端显示

五、测试

开始时一个server进程(最下边)

 图 18 开启服务器,客户端不开启

通过ps –elf我们看到数据传输结束还是只有一个server进程,证明我们的子进程回收程序正常运行了。

图 19 开启服务器和客户端

 

附录(源代码)

客户端代码:

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <unistd.h>

#include <netinet/in.h>

#include <string.h>

#include <fcntl.h>

#include <errno.h>

#include <arpa/inet.h>

int main(int argc,char *argv[])

{

       int port = 52347;

       int socket_fd= socket(AF_INET,SOCK_STREAM,0);

       if(socket_fd == -1)

       {

              printf("socket_fd failed:%d\n",errno);

       }

      

       struct sockaddr_in dest_addr;                            // 2.目标地址信息,连接

       bzero(&dest_addr,sizeof(struct sockaddr_in));   //初始化

       dest_addr.sin_family = AF_INET;                             //协议

           dest_addr.sin_port = htons((unsigned short)port);               //目标地址端口号

           dest_addr.sin_addr.s_addr = inet_addr(argv[1]); //服务器IP

       connect(socket_fd,(struct sockaddr*)&dest_addr,sizeof(dest_addr));

       //操作

       char S_buf[256] = "";

       char R_buf[256] = "";

       int fd = open("/dev/tty",O_RDWR);

       if(fd == -1)

       {

              printf("fd failed:%d\n",errno);

       }

       int data_fd;

       if(argc == 3)

       {

              data_fd = open(argv[2], O_RDWR);

              dup2(data_fd, fd); //后-->前

       }    

       int num;

       while(1)

       {

              if((num = read(fd, S_buf, 256)) > 0)

              {

                     write(socket_fd, S_buf, num);

                     memset(S_buf, 0, 256);

                     if(read(socket_fd,R_buf,256) > 0)

                     {

                            printf("%s\n",R_buf);

                            memset(R_buf, 0, 256);

                     }           

              }

              else

              {

                     return 0;

              }    

       }

       close(fd);

       close(data_fd);

       //close(socket_fd);

       return 0;

}

服务器代码:

#include<stdio.h>

#include<stdlib.h>

#include <unistd.h>

#include<string.h>

#include <fcntl.h>

#include <sys/types.h>          /* See NOTES */

#include <sys/socket.h>

#include <errno.h>

#include <arpa/inet.h>

#include <signal.h>

#include <sys/wait.h>

#include <pthread.h>

void catch_child(int signo)

{

       pid_t wpid;

       while ((wpid = waitpid(-1, NULL, WNOHANG)) > 0)

              printf("child %d exit with \n", wpid);   

}

int main()

{

       int port = 52347;

       int socket_fd= socket(AF_INET,SOCK_STREAM,0);              /* 1.建立socket */

       if(socket_fd == -1)

       {

              printf("socket_fd failed:%d",errno);

       }

      struct sockaddr_in server_addr;                                // 2.目标地址信息,连接

       bzero(&server_addr,sizeof(struct sockaddr_in));              //初始化

       server_addr.sin_family = AF_INET;                          //协议

   server_addr.sin_port = htons((unsigned short)port);             //目标地址端口号

   server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");         //服务器IP   

       int ret = bind(socket_fd,(struct     sockaddr*)&server_addr,sizeof(server_addr));

       if(ret == -1)                                              //绑定地址结构体和socket

       {

           printf("bind error");

           return -1;

           }

       listen(socket_fd,5);

       //操作ser

       int connect_fd;

       char buf[256] = "";

       int num;

       while(1)

       {

              connect_fd = accept(socket_fd, NULL, NULL);

              if(connect_fd==-1){

                  printf("accept error");

                     exit(1);

              }

             pid_t pid = fork();

              if(pid == 0)

              {

                     while(1)

                     {

                            if((num = read(connect_fd, buf, 256)) > 0)

                            {

                                   write(connect_fd, buf, num);

                                   memset(buf, 0, 256);

                            }

                            else

                            {

                                   printf("回显完成!\n");

                                   return 1;

                            }    

                     }

              }

              else if(pid > 0)

              {

                     signal(SIGCHLD,catch_child);

              }

       }

       return 0;

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值