实现简单的TCP网络(多进程、多线程版本)

之前实现了单进程版本的TCP网络通信,但实际上单进程版本的网络通信使用较少,更常用的便是多进程与多线程的版本。

多进程方式:

服务器创建子进程,子进程再创建孙子进程,此时子进程便成为二代子进程,孙子进程为一代子进程,让子进程退出后,这样就可以避免僵尸进程的形成。

//server.c,服务器端实现
int Start(char* ip,int port)
{
    //使用socket函数建立套接字,打开网卡文件,返回文件描述符
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)//打开失败
    {
        printf("sock error\n");
        exit(2);
    }
    //建立sockaddr_in类型的结构体,存放套接字的相关信息
    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_addr.s_addr=inet_addr(ip);
    local.sin_port=htons(port);

    //绑定IP地址和端口,才能被客户端程序正确找到
    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        printf("bind error\n");
        exit(3);
    }
    //使服务器端处于监听状态,最多允许5个客户端处于连接等待的状态,多于5个的连接请求直接忽略
    if(listen(sock,5)<0)
    {
        printf("listen error\n");
        exit(4);
    }

    return sock;//返回套接字文件的文件描述符
}
void service(int sock,char* ip,int port)
{
    char buf[64];//开辟缓冲区buf
    while(1)
    {
        buf[0]=0;
        size_t s=read(sock,&buf,sizeof(buf));//存放从客户端读到的请求到buf中
        if(s>0)//实际读到的字节数大于0时,输出请求的内容到屏幕上
        {
            buf[s]=0;
            printf("%s:%d say# %s\n",ip,port,buf);
            write(sock,buf,strlen(buf));
        }
        else if(s==0)//实际读到0字节时,代表客户端已不再发送请求了,输出提示信息
        {
            printf("client %s quit!\n",ip);
            break;
        }
        else
        {
            printf("read error!\n");
            break;
        }
    }

}
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        printf("Usage:%s:ip port\n",argv[0]);
        exit(1);
    }
    int listen_sock=Start(argv[1],atoi(argv[2]));//创建套接字文件,存放套接字(IP+port)
    struct sockaddr_in peer;
    char ipbuf[64];
    while(1)
    {
        ipbuf[0]=0;
        socklen_t len=sizeof(peer);
        int new_sock=accept(listen_sock,(struct sockaddr*)&peer,&len);
        //当客户端调用coonect函数时,服务器端要调用accept函数接收连接请求
        if(new_sock<0)
        {
            printf("accept error\n");
            continue;
        }
        //将整型转为“点分十进制”的字符串(该函数适于多进程下的服务器端)
        inet_ntop(AF_INET,(const void* )&peer.sin_addr,ipbuf,sizeof(ipbuf));
        int p=ntohs(peer.sin_port);
        printf("get a new connection:[%s:%d]\n",ipbuf,p);

        pid_t id=fork();//创建子进程
        if(id==0)//子进程不用监听socket,所以关闭该套接字文件
        {
            close(listen_sock);
            if(fork()<0)//在子进程中创建孙子进程
            {
                exit(0);
            }
            service(new_sock,ipbuf,p);//进行与客户端间的通信
            close(new_sock);
            exit(0);
        }
        else if(id>0)//父进程等到子进程的终止
        {
            waitpid(id,NULL,0);
        }
        else
        {
            printf("fork error!\n");
            continue;
        }

    }
    return 0;
}

多进程版本优点:

(1)可以处理多用户的请求

(2)进程具有独立性,所以多进程服务器端的稳定性更好

缺点:

(1)服务器端在接受连接后,才创建子进程,创建子进程需要浪费一定时间,影响性能‘

(2)多进程服务器随着进程数量的增多,CPU的压力会变大,CPU调度的时间增长,也会造成性能的影响

多线程版本

#include <stdio.h>
#include <error.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//1.调用socket函数,创建套接字文件,
//2.调用bind函数,绑定Ip地址 和 端口号
//3.调用listen函数,监听客户端的请求
//开始进行事件循环
//  4、当客户端调用connect函数时,服务器端接收客户端的连接请求
//  5. 处理客户端发来的请求
//   a)这里简单的是将从客户端发来的请求读取输出后,再将请求发送给客户端
//   b)将客户端的请求显示在标准输出上
//   c)再将请求发回给客户端
 


//多线程服务器可以同时处理多个用户的请求和响应
//需要注意以下几点
//1.线程空间的开辟应该是在堆上开辟的,使用malloc函数开辟,不能是栈上或者全局的
//2.线程资源的回收
    //应该采用线程分离,使内核自动回收线程资源,而不能使用线程等待函数(阻塞式等待,不能同时处理 
     // 多个用户的请求和响应)
//3.属于同一个进程的所有线程,是共享其所属的进程的资源的,因此关闭一个线程就可以了

typedef struct  Info//定义结构体存放套接字结构体,文件描述符 
{
  struct sockaddr_in _peer;
  int fd;
}Info;


void * Enter(void * arg)
{

  Info * info =(Info *)arg;
  sockaddr_in peer =info->_peer;
  int new_socket=info->fd; 
  printf("Entry:%s:%d\n",inet_ntoa(info->_peer.sin_addr),info->fd);

  char buf[1024]={0};
//用来处理一次连接。需要循环的处理

  while(1)
  {
    //7.接收来自客户端的请求
    ssize_t read_size=read(new_socket,buf,sizeof(buf)-1);

    if(read_size<0)
    {
      continue;
    }
    if(read_size==0)
    {
      printf("client discount!\n");
      //用来标识用户已经关闭连接
      close(new_socket);
      return NULL;
    }

    buf[read_size]='\0';

    //8.将客户端的请求显示再标准输出上 
    printf("peer %s:%d  say %s",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buf);

    //9.将请求发送给客户端

    write(new_socket,buf,read_size);
    printf("写会客户端\n");
  }

  return NULL;
}


void ProcessConnect(int new_socket,sockaddr_in peer)//处理请求
{

  printf("参数:%s :%d\n",inet_ntoa(peer.sin_addr),new_socket);
  pthread_t pthread;

  
  Info  *info=(Info *)malloc(sizeof(Info));//为线程开辟内存空间
  info->fd=new_socket;
  info->_peer=peer;
  printf("准备给线程传参:%s:%d\n",inet_ntoa(info->_peer.sin_addr),info->fd);

  pthread_create(&pthread,0,Enter,info);//创建线程
  
  //int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,(void*)(*start_rtn)(void*) ,void *arg);
 
 //第一个参数为指向线程标识符的指针。
 //第二个参数用来设置线程属性。
 //第三个参数是线程运行函数的起始地址。
 //第四个参数是运行函数的参数。
 //pthread_create() 在调用成功完成之后返回零。其他任何返回值都表示出现了错误。
  //回收线程的资源
  pthread_detach(pthread);
  return;

}


int main(int argc,char * argv[])
{
  //1.检查命令行参数
  if(argc!=3)
  {
    printf("Usage: ./service [IP] [Port]\n");
    return 1;
  }

  //2.调用socket函数创建套接字文件,返回文件描述符fd 
  int fd=socket(AF_INET,SOCK_STREAM,0);
  if(fd<0)
  {
    perror("socket");
    return 1;
  }

  //3.将套接字的相关信息存储在sockaddr_in 类型的结构体addr中

  sockaddr_in addr;
  addr.sin_family=AF_INET;
  socklen_t addr_len=sizeof(addr);
  //将点分十进制的IP地址 转化 为32位数字
  addr.sin_addr.s_addr=inet_addr(argv[1]);
  //将端口号转为网络字节序
  addr.sin_port=htons(atoi(argv[2]));


  //4.绑定套接字文件,绑定ip地址与端口号
  int bind_ret=bind(fd,(sockaddr *)&addr,addr_len);
  if(bind_ret<0)
  {
    perror("bind");
    return 1;
  }

  //5.服务器处于监听状态
  int listen_ret=listen(fd,5);
  if(listen_ret<0)
  {
    perror("listen");
    return 1;
  }

  //开始事件循环,不断接收来自客户端的连接请求,处理请求

  while(1)
  {
    //6.接受客户端的连接请求

    int new_socket=accept(fd,(sockaddr *)&addr,&addr_len);
    //这里的返回值是一个新的 socket 将内核态建立的连接放到用户态执行
    //后续信息传输建立在这个socket
    if(new_socket<0)
    {
      perror("accept");
      continue;
    }
    //7.处理一次连接
    ProcessConnect(new_socket,addr);
  }
  close(fd);
  return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40170007/article/details/87776479