linux网络编程:使用单进程实现多客户端通信

Posted 怪盗dark

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux网络编程:使用单进程实现多客户端通信相关的知识,希望对你有一定的参考价值。

服务端:

//回射服务器
//避免僵尸进程
#include "unistd.h"
#include "sys/types.h"
#include "sys/socket.h"
#include "sys/wait.h"
#include "netinet/in.h"
#include "arpa/inet.h"
#include "stdlib.h"
#include "stdio.h"
#include "errno.h"
#include "string.h"
#include "signal.h"


#define ERR_EXIT(m)           do                          {                             perror(m);                  exit(EXIT_FAILURE);       }while(0)

ssize_t readn(int fd , void *buf, size_t count)
{
  size_t nleft = count; //剩余字节数  //在32位系统中size_t是4字节的,而在64位系统中,size_t是8字节的  增加可移植性
  ssize_t nread;//读到的字节数 //ssize_t:这个数据类型用来表示可以被执行读写操作的数据块的大小,它表示的是sign size_t类型的。
  char *bufp = (char*)buf;

  while (nleft > 0)
  {
    if ((nread = read(fd , bufp ,nleft)) < 0)
    {
      if (errno == EINTR)
        continue;
      return -1;
    }
    else if (nread == 0)   //对等方关闭
      return count - nleft;
    bufp += nread;
    nleft -= nread;
  }
  return count;
}

ssize_t writen(int fd , const void *buf , size_t count)
{
  size_t nleft = count; //剩余字节数
  ssize_t nwritten;
  char *bufp = (char*)buf;

  while (nleft > 0)
  {
    if ((nwritten = write(fd , bufp ,nleft)) < 0)
    {
      if (errno == EINTR)
        continue;
      return -1;
    }
    else if (nwritten == 0)   //对等方关闭
      continue;
    bufp += nwritten;
    nleft -= nwritten;
  }
  return count;
}

ssize_t recv_peek(int sockfd , void *buf , size_t len)
{
  while (1)
  {
    int ret = recv(sockfd , buf , len , MSG_PEEK);
    if (ret == -1 && errno == EINTR)
      continue;
    return ret;
  }
}

ssize_t readline(int sockfd , void *buf , size_t maxline)
{
  int ret;
  int nread;
  char *bufp = buf;
  int nleft = maxline;
  while (1)
  {
    ret = recv_peek(sockfd , bufp , nleft);
    if (ret < 0 )
      return ret;
    else if (ret == 0)
      return ret;
    nread = ret;
    int i;
    for (i=0;i<nread;i++)
    {
      if (bufp[i] == \n)
      {
        ret = readn(sockfd,bufp,i+1); // i+1  包括 \n
        if (ret != i + 1)
          exit(EXIT_FAILURE);
        return ret;
      }
    }
    if (nread > nleft)
      exit(EXIT_FAILURE);
    nleft -= nread;
    ret = readn(sockfd , bufp , nread);
    if (ret != nread)
      exit(EXIT_FAILURE);
    bufp += nread;
  }
  return -1; //运行到这里就出错了
}

void echo_srv(int conn)
{
  char recvbuf[1024];
  int n;
  while(1)
  {
    memset(&recvbuf,0,sizeof(recvbuf));
    int ret = readline(conn,recvbuf,1024);
    //扑捉客户端关闭
    if (ret == -1)
      ERR_EXIT("readline");
    if (ret == 0)
    {
      printf("clientclose\n");
      break;
    }
    fputs(recvbuf,stdout);
    writen(conn,recvbuf,strlen(recvbuf));
  }
}

void handle_sigchld(int sig)
{
  // wait(NULL);
  while (waitpid(-1,NULL,WNOHANG) > 0);//waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。
}

int main(int argc, char const *argv[])
{
 // signal(SIGCHLD,SIG_IGN);//避免僵尸进程
  signal(SIGCHLD,handle_sigchld);//避免僵尸进程
  int listenfd;
  if ((listenfd = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0) //IPv4为PF_INET,IPv6为PF_INET6;TCP传输时为SOCK_STREAM, UDP位SOCK_DGRAM
    ERR_EXIT("socket");
  struct sockaddr_in servaddr;
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET; //设置地址家族   协议族,在socket编程中只能是AF_INET
  servaddr.sin_port = htons(5188); //设置端口
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //设置地址
  // servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  // inet_aton("127.0.0.1",&servaddr.sin_addr);

  int on = 1;
  if (setsockopt(listenfd, SOL_SOCKET , SO_REUSEADDR, &on , sizeof(on)) < 0)//setsockopt()函数,用于任意类型、任意状态套接口的设置选项值。
    ERR_EXIT("setsockopt");

  if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
    ERR_EXIT("bind");
  /*定义函数:int bind(int sockfd, struct sockaddr * my_addr, int addrlen);
    函数说明:bind()用来设置给参数sockfd 的socket 一个名称. 此名称由参数my_addr 指向一sockaddr 结构,对于不同的socket domain 定义了一个通用的数据结构
*/
  if (listen(listenfd,SOMAXCONN) < 0)
    ERR_EXIT("listen");

  struct sockaddr_in peeraddr;
  socklen_t peerlen = sizeof(peeraddr);
  int conn;

  // pid_t pid;
  // while (1)
  // {
  //   if ((conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen)) < 0)
  //     ERR_EXIT("accept");
  //   /*
  //   定义函数:int accept(int s, struct sockaddr * addr, int * addrlen);
  //   函数说明:accept()用来接受参数s 的socket 连线. 参数s 的socket 必需先经bind()、listen()函数处理过,
  //   当有连线进来时accept()会返回一个新的socket 处理代码, 往后的数据传送与读取就是经由新的socket处理,
  //   而原来参数s 的socket 能继续使用accept()来接受新的连线要求. 连线成功时, 参数addr
  //   所指的结构会被系统填入远程主机的地址数据, 参数addrlen 为scokaddr 的结构长度. 关于机构sockaddr 的定义请参考bind().
  //   */
  //   printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr) , ntohs(peeraddr.sin_port));
  //   //inet_ntoa   将网络地址转换成“.”点隔的字符串格式。
  //   //ntohs  本函数将一个16位数由网络字节顺序转换为主机字节顺序。
  //
  //   pid = fork();
  //   if (pid == -1)
  //     ERR_EXIT("fork");
  //   if (pid == 0)
  //   {
  //     close(listenfd);
  //     echo_srv(conn);
  //     exit(EXIT_SUCCESS);
  //   }else
  //     close(conn);
  // }

  int client[FD_SETSIZE];//保存客户端文件描述符
  int maxi = 0;
  int i;
  for(i=0 ; i < FD_SETSIZE;i++)
    client[i] = -1;

  int nready;
  int maxfd = listenfd;
  fd_set rset;
  fd_set allset;
  FD_ZERO(&rset);
  FD_ZERO(&allset);
  FD_SET(listenfd,&allset);
  while (1)
  {
    rset = allset;
    nready = select(maxfd+1,&rset,NULL,NULL,NULL);
    if (nready == -1)
    {
      if (errno == EINTR)
        continue;
      ERR_EXIT("select");
    }
    if (nready == 0)
      continue;
    if (FD_ISSET(listenfd,&rset))
    {
      peerlen = sizeof(peeraddr);
      conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen);
      if (conn == -1)
        ERR_EXIT("accept");
      for (i=0;i<FD_SETSIZE;i++)
      {
        if (client[i] < 0)
        {
          client[i] = conn;
          if (i > maxi)
            maxi = i;
          break;
        }
      }
      if (i == FD_SETSIZE)
      {
        fprintf(stderr,"too many clients\n");
        exit(EXIT_FAILURE);
      }
      printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr) , ntohs(peeraddr.sin_port));
      FD_SET(conn,&allset);
      if (conn > maxfd)
        maxfd = conn;

      if (--nready <= 0)
        continue;
    }

    for(i=0;i<=maxi;i++)
    {
      conn = client[i];
      if (conn == -1)
        continue;
      if (FD_ISSET(conn,&rset))
      {
        char recvbuf[1024] = {0};
        int ret = readline(conn,recvbuf,1024);
        //扑捉客户端关闭
        if (ret == -1)
          ERR_EXIT("readline");
        if (ret == 0)
        {
          printf("clientclose\n");
          FD_CLR(conn,&allset);
          client[i] = -1;
        }
        fputs(recvbuf,stdout);
        writen(conn,recvbuf,strlen(recvbuf));

        if (--nready <= 0)
          break;
      }
    }
  }
  return 0;
}

客户端:

//回射客户端

#include "unistd.h"
#include "sys/types.h"
#include "sys/socket.h"
#include "netinet/in.h"
#include "arpa/inet.h"
#include "stdlib.h"
#include "stdio.h"
#include "errno.h"
#include "string.h"
#include "signal.h"

#define ERR_EXIT(m)           do                          {                             perror(m);                  exit(EXIT_FAILURE);       }while(0)

  ssize_t readn(int fd , void *buf, size_t count)
  {
    size_t nleft = count; //剩余字节数
    ssize_t nread;//读到的字节数
    char *bufp = (char*)buf;

    while (nleft > 0)
    {
      if ((nread = read(fd , bufp ,nleft)) < 0)
      {
        if (errno == EINTR)
          continue;
        return -1;
      }
      else if (nread == 0)   //对等方关闭
        return count - nleft;
      bufp += nread;
      nleft -= nread;
    }
    return count;
  }

  ssize_t writen(int fd , const void *buf , size_t count)
  {
    size_t nleft = count; //剩余字节数
    ssize_t nwritten;
    char *bufp = (char*)buf;

    while (nleft > 0)
    {
      if ((nwritten = write(fd , bufp ,nleft)) < 0)
      {
        if (errno == EINTR)
          continue;
        return -1;
      }
      else if (nwritten == 0)   //对等方关闭
        continue;
      bufp += nwritten;
      nleft -= nwritten;
    }
    return count;
  }

  ssize_t recv_peek(int sockfd , void *buf , size_t len)
  {
    while (1)
    {
      int ret = recv(sockfd , buf , len , MSG_PEEK);
      if (ret == -1 && errno == EINTR)
        continue;
      return ret;
    }
  }

  ssize_t readline(int sockfd , void *buf , size_t maxline)
  {
    int ret;
    int nread;
    char *bufp = buf;
    int nleft = maxline;
    while (1)
    {
      ret = recv_peek(sockfd , bufp , nleft);
      if (ret < 0 )
        return ret;
      else if (ret == 0)
        return ret;
      nread = ret;
      int i;
      for (i=0;i<nread;i++)
      {
        if (bufp[i] == \n)
        {
          ret = readn(sockfd,bufp,i+1); // i+1  包括 \n
          if (ret != i + 1)
            exit(EXIT_FAILURE);
          return ret;
        }
      }
      if (nread > nleft)
        exit(EXIT_FAILURE);
      nleft -= nread;
      ret = readn(sockfd , bufp , nread);
      if (ret != nread)
        exit(EXIT_FAILURE);
      bufp += nread;
    }
    return -1; //运行到这里就出错了
  }

void echo_cli(int sock)
{
  //  char sendbuf[1024] = {0};
  //  char recvbuf[1024] = {0};
  //  while (fgets(sendbuf,sizeof(sendbuf),stdin) != NULL)
  //  {
  //    writen(sock,sendbuf,strlen(sendbuf));  //包头加包体
    //  int ret = readline(sock ,recvbuf,sizeof(recvbuf));
    //  //扑捉客户端关闭
    //  if (ret == -1)
    //    ERR_EXIT("readline");
    //  else if (ret == 0)
    //  {
    //    printf("client close\n");
    //    break;
    //  }
    //  fputs(recvbuf,stdout);
    //  memset(sendbuf , 0 , sizeof(sendbuf));
    //  memset(recvbuf , 0 , sizeof(recvbuf));
  //  }
  //  close(sock);
    fd_set rset;
    FD_ZERO(&rset);

    int nready;
    int maxfd;
    int fd_stdin = fileno(stdin);
    if (fd_stdin > sock)
      maxfd = fd_stdin;
    else
      maxfd = sock;

    char sendbuf[1024] = {0};
    char recvbuf[1024] = {0};
    while (1)
    {
      FD_SET(fd_stdin,&rset);
      FD_SET(sock,&rset);
      nready = select(maxfd+1,&rset,NULL,NULL,NULL);
      if (nready == -1)
        ERR_EXIT("select");
      if (nready == 0)
        continue;
      if (FD_ISSET(sock,&rset))
      {
        int ret = readline(sock ,recvbuf,sizeof(recvbuf));
        //扑捉客户端关闭
        if (ret == -1)
          ERR_EXIT("readline");
        else if (ret == 0)
        {
          printf("server close\n");
          break;
        }
        fputs(recvbuf,stdout);
        memset(recvbuf , 0 , sizeof(recvbuf));
      }

      if (FD_ISSET(fd_stdin,&rset))
      {
        if (fgets(sendbuf,sizeof(sendbuf),stdin) == NULL)
          break;
        writen(sock,sendbuf,strlen(sendbuf));
        memset(sendbuf,0,sizeof(sendbuf));
      }
    }
    close(sock);

}

void handle_sigpipe(int sig)
{
  printf("recv a sig=%d\n",sig);
}

int main(void)
{
  signal(SIGPIPE,handle_sigpipe);
  int sock;
  if ((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0) //IPv4为PF_INET,IPv6为PF_INET6;TCP传输时为SOCK_STREAM, UDP位SOCK_DGRAM
    ERR_EXIT("socket");
  struct sockaddr_in servaddr;
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET; //设置地址家族
  servaddr.sin_port = htons(5188); //设置端口
  servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //设置地址

   if (connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
    ERR_EXIT("connect");

    struct sockaddr_in localaddr;
    socklen_t addrlen = sizeof(localaddr);
    if (getsockname(sock , (struct sockaddr*)&localaddr , &addrlen) < 0 )
      ERR_EXIT("getsockname");
    printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr) , ntohs(localaddr.sin_port));
   echo_cli(sock);
   return 0;
}

 

以上是关于linux网络编程:使用单进程实现多客户端通信的主要内容,如果未能解决你的问题,请参考以下文章

Linux-TCP编程流程-Socket编程-单线程实现TCP客户端和服务端交互-多进程实现TCP客户端和服务端交互

Linux多进程的应用

多线程与多进程的实现

使用 POSIX 消息队列进行进程内通信

Linux----网络编程socket

Python - 并发编程