您的位置:首页 > 理论基础 > 计算机网络

【Linux C/C++】 第08讲 多线程TCP传输文件/select模型

2017-07-23 09:47 239 查看
一、多线程

   pthread.h

   libpthread.so   -lpthread

   1.创建多线程

     1.1 代码

          回调函数

     1.2 线程ID

         pthread_t

     1.3 运行线程

           pthread_create

           int pthread_create(pthread_t* restrict th,

                 const pthread_attr_t * attr,//线程属性 ,为NULL/0,使用进程的默认属性

                 void* (*run) (void*), //线程回调函数

                  void* data //传递给线程回调函数的数据

                 );

              restrict -> 只修饰参数指针,第一次加载之后一直使用寄存器的值

          结论:

                  <1> 进程结束,所有子线程结束

                       int pthread_join(pthread_t thread,

                                        void** re//子线程结束的返回值);

                              等待子线程结束

                   <2> 创建子线程后,主线程继续完成系统分配的时间片

                   <3> 子线程结束就是线程函数返回

                   <4> 子线程和主线程有同等优先级

   2.线程的基本控制

        线程的状态:

              ready -> runny -> deady

                                |

                      sleep/pause

             结束一个线程 

                          内部结束:

                                      pthread_exit(void* value_ptr)

                                      return 只能在线程函数中使用

                          外部结束:

                                      pthread_cancel(pthread_t);

             线程开始

                          

   3.多线程的问题

        数据脏

   4.多线程问题的解决

        互斥锁/互斥量    mutex

        定义互斥量 pthread_mutex_t

        初始化互斥量 1  pthread_mutex_init

        互斥量操作

                置0   pthread_mutex_lock

                                判定互斥量 如果是0 -> 阻塞

                                                     如果是1 -> 置0,返回

                置1   pthread_mutex_unlock

                                 置1,返回

         释放互斥量 pthread_mutex_destroy

         结论:

                互斥量只能保证锁定的代码一个线程执行,

                但不能保证必须执行完

         在lock与unlock之间,调用pthread_exit 

               或者在线程外部调用pthread_cancel 其他线程被永久死锁

         pthread_cleanup_push {

         pthread_cleanup_pop   }

           这对函数作用类似于atexit

           注意:

                 这不是函数,而是宏

                 必须成对使用

二、网络编程
   1.基础(ip)

     1.1  网络工具

          ping

          ping     ip地址

          ping    -b    ip广播地址

          ifconfig   -a

          route

          lsof

          netstat 

      1.2 网络的基本概念

          网络编程采用socket模型,网络通信的本质也是进程之间的IPC(进程间通信),是不同主机之间。

          识别主机:4字节整数 -> IP地址

          识别进程:2字节整数 -> 应用端口

          ip地址的表示:

                   字符串表示“192.168.0.13”

                    整数表示 in_addr_t

                    字结构表示 struct in_addr

        1.3 IP地址的转换

                inet_addr         ->     把字符串转换为整数 (网络字节序)

                inet_aton          ->     把字符串转换为struct in_addr (网络字节序)

                inet_network    ->    把字符串转换为整数(本地字节序)

                inet_ntoa          ->     把结构体转换为字符串

         1.4 计算机系统中的网络配置

                /etc/hosts文件   ->     配置IP,域名,主机名

                                     gethostbyname

                                     gethostbyaddr

                /etc/protocols文件  ->   配置系统支持的协议

                /etc/services文件  ->   配置服务

   2.TCP/UDP编程

       对等模型 :AF_INET    SOCK_DGRAM  默认UDP

                socket

                绑定IP地址bind

                read/recv 

                recvfrom 可以获取发送者的地址

                关闭close

        C/S模型: AF_INET    SOCK_STREAM  默认TCP

                

   3.TCP的服务器编程模型

        TCP的服务端维护多个客户的网络文件描述符

        1. 多进程

         2. IO的异步模型(select模型 poll模型)

         3. 多线程

         4.多进程池

         5.多线程池

   4.综合应用 -- 多进程应用

        1.怎样使用多进程

         2.多进程的缺陷,以及怎么解决

三、多线程TCP传输文件实例

   第04讲 多进程TCP传输文件 的客户端部分不够完善,

       没有处理非阻塞模式下的连接问题

   1.补充一下select模型的概念

    select在socket编程中还是比较重要的

    可是对于初学socket的人来说都不太爱用select写程序

    他们只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程序

    所谓阻塞方式block,顾名思义,就是进程或线程执行到这些函数时必须等待

    某个事件发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回

    可是使用select就可以完成非阻塞,所谓非阻塞方式non-block,

    就是进程或线程执行此函数时不必非要等待事件发生,一旦执行立即返回

    以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,

    若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行

    所以效率较高,它能够监视需要监视的文件描述符的变化情况-读写或异常

    select函数格式:

    int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timsval *timeout);

    先说明两个结构体:

      1> struct fd_set 可以理解为一个集合,这个集合中存放的是文件描述符

          fd_set集合可以通过一些宏由人为来操作,比如:

           FD_ZERO(fd_set*); 清空集合

           FD_SET(int,fd_set*); 将一个文件描述符加入集合中

           FD_CLR(int,fd_set*); 将一个文件描述符从集合中删除

           FD_ISSET(int,fd_set*)检查集合中指定的文件描述符是否可以读写

       2> struct timeval是一个常用的结构体,用来代表时间值,有两个成员

               一个是秒数,另一个是毫秒。

     select的参数具体解释:

       int maxfdp: 是一个整数值,是指集合中所有文件描述符的范围,

             即所有文件描述符的最大值加1

       fd_set *readfds: 需要监视这些文件描述符的读变化,

              根据timeout来判断是否超时,有可读的返回大于0

              没有可读的返回0,错误异常返回负数

       fd_set *writefds: 需要监视这些文件描述符的写变化

       fd_set *errorfds: 需要监视这些文件描述符的错误异常

       struct timeval* timeout: 是select的超时时间,可以使select处于三种状态

           1> 若将NULL以形参传入,select置于阻塞状态

           2> 若将时间设为0秒0毫秒,就变成一个纯粹的非阻塞函数,

                不管文件描述符是否有变化,都立刻返回

           3> timeout的值大于0,select在timeout时间内阻塞,超时之内有事件返回

                超时之后立刻返回

   2.提供一个完善的Linux/Unix/iOS客户端



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

    
   
if(-1 ==
sock)
    {

        return
false;
    }


    struct
sockaddr_in serv_addr;

    
   
memset(&serv_addr,
0, sizeof(serv_addr));
    serv_addr.sin_family =
AF_INET;
    serv_addr.sin_addr.s_addr =
inet_addr("192.168.0.103");
    serv_addr.sin_port =
htons(8888);

    
   
int r = connect(sock, (struct
sockaddr*)&serv_addr,
sizeof(serv_addr));

    
   
if (-1 == r)
     {
        
connected = false;

         printf("network connect fail\n");
        
return false;
     }

    

    //设置为非阻塞模式
   
int flags = fcntl(sock,
F_GETFL, 0);
   
fcntl(sock,
F_SETFL, flags |
O_NONBLOCK);


    struct
fd_set fds,fdsErr;

    struct
timeval timeout={0,0};
//select等待3秒,3秒轮询,要非阻塞就置0
   
int maxfdp = 1;

   
/* 假定已经建立UDP连接,具体过程不写,简单,当然TCP也同理,主机ip和port都已经给定,要写的文件已经打开

     sock=socket(...);

     bind(...);

     fp=fopen(...); */
   
while(1)
    {

        FD_ZERO(&fds);
//每次循环都要清空集合,否则不能检测描述符变化
       
FD_ZERO(&fdsErr);
       
FD_SET(sock,&fds);
//添加描述符
       
FD_SET(sock,&fdsErr);

//        FD_SET(fp,&fds); //同上

//        maxfdp=sock>fp?sock+1:fp+1;    //描述符最大值加1
        maxfdp =
sock+1;
       
switch(select(maxfdp,&fds,NULL,&fdsErr,&timeout))  
//select使用
        {
           
case -1:
exit(-1);break;
//select错误,退出程序
           
case 0:break;
//再次轮询
           
default:
               
if(FD_ISSET(sock,&fds))
//测试sock是否可读,即是否网络上有数据
                {
                   
//read fd
                   
char buffer[1024];
                   
ssize_t len = read(sock,buffer,1024);
                   
if(len > 0)
                    {
                       
//
                    }
                }
               
else if(FD_ISSET(sock,&fdsErr))
                {
                   
//close fd
                   
close(sock);
                   
connected = false;
                }
               
// end if break;

        }// end switch

    }//end while


   服务端的文件传输部分可以直接用,把多进程改成多线程就行

   socket的读写和客户端一样,可以采用select/epoll  实现

   需要把收到read/recv的数据包存入一个全局的队列中

   然后用多线程处理这些数据包

   3. 多线程服务端

static void
create_thread(pthread_t *thread,
void *(*start_routine) (void *),
void *arg) {
   
if (pthread_create(thread,NULL, start_routine, arg)) {

        fprintf(stderr,
"Create thread failed");
       
exit(1);
    }
}

static void *
thread_worker(void *p) {
   
struct message_queue * q =
global_mq;
   
while (!quit) {
       
//取出
        struct message* q = q->pop();
       
if (q == NULL) {
           
//file send
           
...
        }
    }

    return
NULL;
}

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

   
    create_thread(&pid, thread_worker, 0);

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: