网络编程(二)——TCP并发

一、 tcp服务器(并发)

  1. 循环服务器

  2. 关心客户端的ip和端口

  3. 设置地址重用(端口没有真正被某一进程 占用)

  4. 拓展: 条件编译和 宏函数的调试

    条件编译

      // 宏函数实现  开关打印 调试
      //#define QDEBUG     //这个QDEBUG 宏存在 则 开启  否则 关闭 
      
      #ifdef  QDEBUG
          #define pri(fmt,...);   printf("%s\n",fmt); 
      #else 
          #define pri(fmt, ...);  
      #endif 
      
  1. setsockopt函数:
      setsockopt()函数,用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。
          #include <sys/types.h>
          #include <sys/socket.h>
         
          int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
          
      sockfd:标识一个套接口的描述字。
      level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
      optname:需设置的选项。
      optval:指针,指向存放选项待设置的新值的缓冲区。
      optlen:optval缓冲区长度。
          
       optname:需设置的选项:  
          SO_BINDTODEV char * 将套接字绑定到指定端口。
          SO_BROADCAST BOOL 允许套接口传送广播信息。
          SO_DEBUG BOOL 记录调试信息。
          SO_DONTLINER BOOL 不要因为数据未发送就阻塞关闭操作。设置本选项相当于将SO_LINGER的l_onoff元素置为零。
          SO_DONTROUTE BOOL 禁止选径;直接传送。
          SO_KEEPALIVE BOOL 发送“保持活动”包。
          SO_LINGER struct linger FAR* 如关闭时有未发送数据,则逗留。
          SO_OOBINLINE BOOL 在常规数据流中接收带外数据。
          SO_RCVBUF int 为接收确定缓冲区大小。
          SO_REUSEADDR BOOL 允许套接口和一个已在使用中的地址捆绑(参见bind())。
          SO_SNDBUF int 指定发送缓冲区大小。
          TCP_NODELAY BOOL 禁止发送合并的Nagle算法。
          setsockopt()不支持的BSD选项有:
          选项名 类型 意义
          SO_ACCEPTCONN BOOL 套接口在监听。
          SO_ERROR int 获取错误状态并清除。
          SO_RCVLOWAT int 接收低级水印。
          SO_RCVTIMEO int 接收超时。
          SO_SNDLOWAT int 发送低级水印。
          SO_SNDTIMEO int 发送超时。
          SO_TYPE int 套接口类型。
          IP_OPTIONS 在IP头中设置选项。
           
          
              
           重点:
              SO_REUSEADDR BOOL 允许套接口和一个已在使用中的地址捆绑(参见bind())。
           	SO_BROADCAST BOOL 允许套接口传送广播信息。
              SO_RCVTIMEO int 接收超时。
              SO_SNDTIMEO int 发送超时。
           返回值:   
                    若无错误发生,setsockopt()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。
              错误代码:
              WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。
              WSAENETDOWN:套接口实现检测到网络子系统失效。
              WSAEFAULT:optval不是进程地址空间中的一个有效部分。
              WSAEINPROGRESS:一个阻塞的套接口调用正在运行中。
              WSAEINVAL:level值非法,或optval中的信息非法。
              WSAENETRESET:当SO_KEEPALIVE设置后连接超时。
              WSAENOPROTOOPT:未知或不支持选项。其中,SOCK_STREAM类型的套接口不支持SO_BROADCAST选项,SOCK_DGRAM类型的套接口不支持SO_DONTLINGER 、SO_KEEPALIVE、SO_LINGER和SO_OOBINLINE选项。
              WSAENOTCONN:当设置SO_KEEPALIVE后连接被复位。
              WSAENOTSOCK:描述字不是一个套接口。
     

二.TCP 客户端实现

  1. 搭建流程:

    1. socket
      1. 准备 要连接的服务器ip 和端口等
      2. connect 连接服务器
      3. 通信
      4. 关闭套接字
  2. connect 函数

       功能:connect - connect a socket
       
       头文件:SYNOPSIS
              #include <sys/socket.h>
       函数原型:
              int connect(int socket, const struct sockaddr *address,
                     socklen_t address_len);
       参数说明:
       	    
       返回值:
       

三、实现并发

1.多进程的服务器

  1. 进程的相关函数

    1. fork()
    2. 回收:
      1. wait()
      2. waitpid( -1 ,NULL , WNOHANG)
    3. signal()
   //让父进程 负责监听  子进程 负责通信 
   signal( SIGCHLD , signal_handerl);   //注册信号 
   
   socket ()

   bind 
   
   listen
   
   while(1)
   {
       connfd = accept()
       {
           fork(); 
           pid > 0 
           {
               close(connfd); 
   			//break;   //退出循环 等待监听 
           }
           pid == 0
           {
               close(listenfd); 
               //通信
           }
   
       }
       
    }
   
   
   //子进程回收机制
   int signal_handerl(int sig)
   {
   	waitpid(); 
   }

多进程服务器代码

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include  <sys/types.h>           /*See NOTES */
#include <sys/socket.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/socket.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>


//宏函数实现条件编译
#ifndef QDEBUG
    #define pri(fmt,...); printf("%s\n",fmt);
#else
    #define pri(frt,...);
#endif

#define SERV_IP "0.0.0.0"
#define SERV_PORT  8888
void signal_recycle(int sig);//子进程回收函数

int serv_init();//监听初始化

void do_communication(int connfd);//通信

int main(int argc, char *argv[])
{
    //1.创建监听套接字


    //2.绑定服务器ip和端口


    //3.监听到连接请求建立连接


    //4.while循环中创建进程,主进程实现监听功能,每一个连接创建一个子进程实现通信
    
    
    //注册SIGCHLD信号,回收子进程
    signal(SIGCHLD,signal_recycle);

    int listenfd ;
    int connfd ;

    listenfd = serv_init();//监听
    if( -1 == listenfd )
    {
        perror("serv_init fail");
        return -1;
    }

    while(1)
    {
        //建立连接
        connfd = accept(listenfd,NULL,NULL);
        if( -1 == connfd )
        {
            if( errno == EINTR )//被信号打断则不结束,再来一次
            {
                continue;
            }
            else
            {
                exit(0);
            }
        }

        //创建子进程
        pid_t pid ; 
flag:   pid = fork();
        if( -1 == pid )//进程创建失败
        {
            goto flag;//重新创建
        }
        else if( 0 == pid )//子进程进行通信
        {
            close(listenfd);//子进程关闭监听,只进行通信
            do_communication( connfd);
        }
        else
        {
            //父进程只监听,不通信
            close(connfd);
            continue;
        }

    }


    return 0;
} 


void signal_recycle(int sig)//子进程回收函数
{
    while(waitpid(-1,NULL,WNOHANG)>0);
}

int serv_init()//监听初始化
{
    int listenfd ;
    int ret = -1 ;
    listenfd = socket(AF_INET,SOCK_STREAM,0);
    if( -1 == listenfd)
    {
        perror("socket fail");
        exit(-1);
    }
    pri("socket listenfd OK");

    //准备ip和端口
    struct sockaddr_in ser_addr ={
        .sin_family = AF_INET,
        .sin_port   = htons(SERV_PORT),
        .sin_addr.s_addr = inet_addr(SERV_IP)
    };


    //设置地址重用
    int opt = 1;
    setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//SO_REUSEADDR 允许在bind()过程中本地地址可重复使用

    //绑定
    ret = bind(listenfd,(struct sockaddr *)&ser_addr,sizeof(ser_addr));
    if( -1 == ret )
    {
        perror("bind ");
        exit(-1);
    }
    pri("bind OK");

    //监听
    ret = listen(listenfd,1024);
    if( -1 == ret )
    {
        perror("listen fail");
        exit(-1);
    }
    pri("listen OK");

    return listenfd ;
}


void do_communication(int connfd)
{
    char buf[BUFSIZ];

    while(1)
    {
        memset(buf ,0 ,BUFSIZ);
        
        int ret = read(connfd,buf,BUFSIZ);
        if( 0 == ret )
        {
            puts(" quit ");
            close(connfd);//关闭连接
            exit(0);
        }
        else if( -1 == ret )
        {
            perror("read fail");
            close(connfd);
            exit(-1);
        }
        else
        {
            puts(buf);
        }

    }
}

2.多线程服务器

  1. 线程函数

    1. pthread_create(&tid, NULL, thread_fun, (void *)arg);
         int arg = 4; 
         一、
         pthread_create(&tid, NULL, thread_fun,  (void *)arg);      
         
         void *thread_fun(void  arg)
         {
             int connfd = (int ) arg; 
         }
         二、
         pthread_create(&tid, NULL, thread_fun,  (void *)&arg);      
         
         void *thread_fun(void  *arg)
         {
             int connfd = *(int *) arg; 
         }
         
  1. pthread_exit()

  2. pthread_join(); //阻塞

  3. pthread_detach(); // 线程分离

  4. 框架:

      //主线程 实现监听
      //子线程 实现通信 
      
      server_init()
      while(1)
      {
      	connfd = accept();
      	pthread_create();  //监听
          pthread_create() ; //通信 
      }
      
      void * thread_fun(void *arg)
      {
          int  connfd  =  (int )arg;
   
          
      }

多线程代码

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include  <sys/types.h>           /*See NOTES */
#include <sys/socket.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/socket.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>

//宏函数实现条件编译
#ifndef QDEBUG
    #define pri(fmt,...); printf("%s\n",fmt);
#else
    #define pri(frt,...);
#endif

#define SERV_IP "0.0.0.0"
#define SERV_PORT  8888

int serv_init();//监听初始化

void* do_communication(void *fd);//通信

int main(int argc, char *argv[])
{
    //1.创建监听套接字


    //2.绑定服务器ip和端口


    //3.监听到连接请求建立连接


    //4.while循环中创建线程,主线程实现监听功能,每一个连接创建一个子线程实现通信
    
    

    int listenfd ;
    int fd ;

    listenfd = serv_init();//监听
    if( -1 == listenfd )
    {
        perror("serv_init fail");
        return -1;
    }

    while(1)
    {
        //建立连接
        fd = accept(listenfd,NULL,NULL);
        if( -1 == fd )
        {
            if( errno == EINTR )//被信号打断则不结束,再来一次
            {
                continue;
            }
            else
            {
                exit(0);
            }
        }

        //创建子线程
        pthread_t  tid ; 
        pthread_create(&tid,NULL,do_communication,&fd);//子线程实现通信号
        pthread_detach(tid);//线程分离
    }
    return 0;
} 


int serv_init()//监听初始化
{
    int listenfd ;
    int ret = -1 ;
    listenfd = socket(AF_INET,SOCK_STREAM,0);
    if( -1 == listenfd)
    {
        perror("socket fail");
        exit(-1);
    }
    pri("socket listenfd OK");

    //准备ip和端口
    struct sockaddr_in ser_addr ={
        .sin_family = AF_INET,
        .sin_port   = htons(SERV_PORT),
        .sin_addr.s_addr = inet_addr(SERV_IP)
    };


    //设置地址重用
    int opt = 1;
    setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//SO_REUSEADDR 允许在bind()过程中本地地址可重复使用

    //绑定
    ret = bind(listenfd,(struct sockaddr *)&ser_addr,sizeof(ser_addr));
    if( -1 == ret )
    {
        perror("bind ");
        exit(-1);
    }
    pri("bind OK");

    //监听
    ret = listen(listenfd,1024);
    if( -1 == ret )
    {
        perror("listen fail");
        exit(-1);
    }
    pri("listen OK");

    return listenfd ;
}


void * do_communication(void * fd)
{
    char buf[BUFSIZ];
    int connfd = *(int*)fd;

    while(1)
    {
        memset(buf ,0 ,BUFSIZ);
        
        int ret = read(connfd,buf,BUFSIZ);
        if( 0 == ret )
        {
            puts(" quit ");
            close(connfd);//关闭连接
            return NULL;
        }
        else if( -1 == ret )
        {
            perror("read fail");
            close(connfd);
            return NULL ;
        }
        else
        {
            puts(buf);
        }

    }
}

4.多进程和多线程的服务器区别:

1、都是并发、用文件描述符标识 客户端的通信(3-1023)
2、进程是申请资源的最小单位
3、线程是执行的最小单位
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值