资源预览内容
第1页 / 共141页
第2页 / 共141页
第3页 / 共141页
第4页 / 共141页
第5页 / 共141页
第6页 / 共141页
第7页 / 共141页
第8页 / 共141页
第9页 / 共141页
第10页 / 共141页
亲,该文档总共141页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述
协议分层TCP/IP协议栈与OSI网络模型TCP/IP网络中的套接字对(源IP, 源Port, 目的IP, 目的Port)C/S编程模型并发式服务器(通常基于TCP)循环式服务器(通常基于UDP)第1章3传输层(TCP, UDP)网络层(IP)数据链路层(Ethernet, )物理层应用层(HTTP, FTP, SMTP, )Linux kernelLinux kernelUser spaceUser spaceTCP/IP网络协议栈TCP/IP网络协议栈5循环应答服务请求循环式服务器6众所周知端口监听通信请求,并分配临时端口用于建立通信连接并发式服务器第2章主要内容:Linux下的C编程环境中的重要知识。进程进程创建、子进程派生资源拷贝、子进程资源的回收。线程线程创建、共享资源、线程同步信号信号掩码、可重入函数时间与定时器设置定时器文件操作打开、关闭、文件属性8初始进程fork初始进程继续执行新进程返回新进程pid返回0pid = fork();switch(pid) case -1 : /* Error */ case 0 : /* 子进程 */ default : /* 父进程 */ 进程标识及进程创建pid_t pid = getpid();pid_t ppid = getppid();进程执行进程执行exit进程的进程的main函数执行函数执行return进程收到了无法处理的信号进程收到了无法处理的信号$ kill -s SIGKILL 283939进程终止僵死进程的产生及其危害UNIX为了让父进程随时了解子进程的状态,设计了:在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号theprocessID,退出状态theterminationstatusoftheprocess,运行时间theamountofCPUtimetakenbytheprocess等)。此时它将变成一个僵尸进程(Zombie),直到父进程通过wait/waitpid来取时才释放。危害:如果进程不调用wait/waitpid的话,则保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程。11子进程退出的异步善后处理父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起。如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后,父进程会收到该信号,可以在handler中调用wait回收。如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN)通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号。还有一些技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还要自己做。12pid_t wait(int *stat_loc);pid_t waitpid(pid_t pid, int *stat_loc, int options);参数pid:0,等待进程号=pid的子进程参数options=WNOHANG,则若无已结束之子进程,则返回0返回值: 已结束运行的子进程id代表成功,-1代表失败void sigchld_handler(int signo) /* 等待已退出的所有子进程 */ do pid = waitpid (-1, &status, WNOHANG); while (pid 0); 子进程退出的异步善后处理父进程先于子进程结束,能否产生僵死进程?每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程,看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init来接管他,成为他的父进程。故:子程序退出时,由Init回收,不会产生僵死进程每个线程有一个标识,类型为每个线程有一个标识,类型为pthread_t,只在所属的进程中有效,不具备全局性只在所属的进程中有效,不具备全局性 14task_structthread 1thread 1thread nthread nint pthread_equal(pthread_t tid1, pthread_t tid2);/*0表示不等*/pthread_t pthread_self(void);线程标识15int pthread_create (pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void), void *restrict arg); thread,新创建线程 attr,线程属性,默认可用NULL start_routine,新创建线程的线程入口函数 arg,向线程函数提供的运行参数,可以为一个结构体指针,以便传递多个参数创建线程一个进程中任何一个线程调用一个进程中任何一个线程调用exit,_Exit或或_exit,都将导致整个进程终止,都将导致整个进程终止当线程收到一个默认为终止进程的信号时也将当线程收到一个默认为终止进程的信号时也将导致整个进程的终止导致整个进程的终止一个线程可以通过下面三个方式退出,而不会一个线程可以通过下面三个方式退出,而不会造成整个进程的终止造成整个进程的终止 线程在线程函数中执行线程在线程函数中执行return 线程被同一进程的其他线程执行线程被同一进程的其他线程执行pthread_cancel线程执行线程执行pthread_exit 16终止线程17#include int pthread_cancel (pthread_t thread);int pthread_join (pthread_t thread, void *ptr); void pthread_exit (void *ptr);pthread_cancel函数用来取消一个线程的执行。当执行取消操作后,线程的善后处理程序将被调用。pthread_join函数用来等待线程的终止,因此除非被指定的线程已经终止了,否则调用该函数的线程将被阻塞,直到指定的线程结束运行ptr为一void指针,与传给启动线程的参数类似,进程中的其他线程可以通过调用pthread_join函数访问该指针若对线程返回码不感兴趣,可设为NULL终止线程为了解决多个线程同时访问临界资源的问题,为了解决多个线程同时访问临界资源的问题,Linux提供了互斥量提供了互斥量mutex对临界资源进行保护,确保同一对临界资源进行保护,确保同一时间只有一个线程访问数据时间只有一个线程访问数据 18pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 静态初始化互斥量互斥量19#include int pthread_mutex_lock (pthread_mutex_t *mutex);int pthread_mutex_unlock (pthread_mutex_t *mutex);int pthread_mutex_trylock (pthread_mutex_t *mutex);pthread_mutex_lock用来对互斥量进行加锁,若互斥量已经加锁,则调用线程将被阻塞pthread_mutex_unlock用来对互斥量进行解锁,当线程对临界资源操作完毕,则调用该函数释放互斥量锁pthread_mutex_trylock用来尝试对互斥量进行加锁,当加锁失败后,调用线程并不阻塞互斥量加锁20#include int pthread_cond_wait (pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);int pthread_cond_signal (pthread_cond_t *cond);pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_cond_wait函数使用参数mutex对条件变量进行保护,调用线程被放到等待条件变为真的线程队列上,然后对mutex进行自动解锁这两步操作属于原子操作 pthread_cond_signal函数通知系统条件已变为真,于是等待线程被唤醒并自动加锁mutex条件变量-2该内存只能由cond指针来访问。21 /* 线程1 */void *thread1_run(void *arg) for (;) pthread_mutex_lock (&mqlock); /* 加锁互斥量 */ list_add_tail (pkt, &pkt_queue) /* 分组入队列尾 */ pthread_mutex_unlock (&mqlock); /* 解锁互斥量 */ /* 通知线程2条件已变为真 */ pthread_cond_signal (&mqlock_ready); /* 定义临界资源互斥变量 */ pthread_mutex_t mqlock = PTHREAD_MUTEX_INITIALIZER; /* 定义条件变量 */pthread_cond_t mqlock_ready = PTHREAD_COND_INITIALIZER;条件变量-322/* 线程2 */void *thread2_run(void *arg) for (;) pthread_mutex_lock (&mqlock); /* 加锁互斥量 */ while (list_empty(&pkt_queue) pthread_cond_wait (&mqlock_ready, &mqlock); pkt = getnextpkt (&pkt_queue); /* 取下一分组 */ pthread_mutex_unlock (&mqlock); /* 必须释放互斥锁 */ handle_packet (pkt); /* 处理此分组 */ 条件变量-4条件检查1.空:休眠,自动解锁2.不空(满足条件):返回并自动上锁。23Linux常常见见信信号号信信 号号信号信号值含含 义默默认处理理SIGABRT6进程中止依赖具体实现SIGALRM14闹钟到时异常终止SIGBUS7访问内存对象未定义部分依赖具体实现SIGCHLD17子进程退出、停止或者继续执行忽略SIGCONT18使暂停进程继续继续SIGFPE8算术操作错误,例如被0除依赖具体实现SIGHUP1控制终端挂起异常终止SIGILL4非法硬件指令依赖具体实现SIGINT3终端中断,例如Ctrl+C异常终止SIGIO29异步I/O忽略SIGKILL9终止(不可捕获或忽略)异常终止SIGPIPE13向无读进程管道写异常终止SIGQUIT2终端退出(例如Ctrl-|)异常终止SIGSEGV11非法内存引用终止SIGSTOP19停止(不可捕获或忽略)停止SIGTERM15终止异常终止SIGTSTP20终端停止停止SIGTTIN21后台读控制tty停止SIGTTOU22后台写至控制tty停止SIGURG23紧急数据忽略SIGUSR110用户定义信号1异常终止SIGUSR212用户定义信号2异常终止24信号如何产生用用户户在在终终端端键键入入CTRL+C,产产生生SIGINT中断信中断信号号硬件异常硬件异常产产生生相相应信号信号。如如SIGSEGV信号信号产产生于非法内存地址引用生于非法内存地址引用kill函数允函数允许许一个一个进进程向其他程向其他进进程程发发送信号送信号控制台控制台kill命令允命令允许许Linux用用户户向向进进程程发发送消送消息,例如息,例如杀杀死某个死某个进进程程25kill l ,列出当前linux支持的信号种类kill -s signal_name pid,向进程pid发送信号kill -signal_name pidkill -signal_number pid #include int kill (pid_t pid, int sig);调用成功返回0,错误返回-1,并设置errno:EINVAL,信号非法或不被支持EPERM,调用者没有权限发送此信号ESRCH,信号接收方不存在(1)Kill 命令(2)Kill 函数KILL命令和KILL函数26对信号的处理方式忽略信号忽略信号收到信号然后直接收到信号然后直接丢丢弃。弃。进进程可以忽略大多数程可以忽略大多数信号,但是信号,但是SIGKILL和和SIGSTOP信号除外信号除外捕捕获获信号信号定定义义信号信号处处理程序,当信号理程序,当信号发发生生时执时执行行该该信号信号处处理程序,理程序,SIGKILL和和 SIGSTOP 除外除外缺省缺省处处理理每个信号都有一个默每个信号都有一个默认认的的处处理方式理方式,通常是,通常是结束接收信号的束接收信号的进程。程。27信号集常用的操作函数#includeintsigemptyset(sigset_t*set);intsigfillset(sigset_t*set);intsigaddset(sigset_t*set,intsigno);intsigdelset(sigset_t*set,intsigno);intsigismember(constsigset_t*set,intsigno);28#include int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset); oset,若其非空,则当前进程的信号掩码将通过oset返回 set,用于设置的新信号集合 how,指明将怎样根据非空的set参数来修改当前进程的信号掩码,取值分为3种情况: SIG_BLOCK- 将信号集合set添加到当前被阻塞信号集中 SIG_UNBLOCK- 从当前被阻塞信号集中删除信号集合set SIG_SETMASK- 设置当前被阻塞信号集合为指定的setSIG_BLOCK-SIG_BLOCK-并集并集setsetsetsetSIG_UNBLOCK-SIG_UNBLOCK-交集交集sigprocmask函数_检查或修改信号掩码注意:sigprocmask函数只能用于单线程。29信号处理函数sigaction#includeintsigaction(intsignum,conststructsigaction*restrictact,structsigaction*restrictoldact);signum,所要捕获或者忽略的信号act,代表新设置的信号处理结构体oldact,代表之前设置的信号处理结构体structsigactionvoid(*sa_handler)(int);/*信号处理函数*/sigset_tsa_mask;/*信号掩码*/intsa_flags;/*信号处理选项,如SA_RESTART*/void(*sa_sigaction)(int,siginfo_t*,void*);SIG_DFLSIG_DFL:defaultdefaultSIG_IGNSIG_IGN:ignoreignore30struct sigaction act;int main(int argc, char *argv) act.sa_handler = catchctrlc; act.sa_flags = 0; / 清空信号掩码,设置SIGINT的信号处理函数 if (sigemptyset(&act.sa_mask) = -1) | (sigaction(SIGINT, &act, NULL) = -1) perror(Failed to set SIGINT to handle Ctrl-C); for(;) ; return 0;void catchctrlc(int signo) /* 恢复SIGINT信号处理函数 */ act.sa_handler = SIG_DFL; if(sigaction (SIGINT, &act, NULL) = -1) 捕捉信号-CATCHCTRLC.C31 struct sigaction act; memset (&act, 0, sizeof(act); act.sa_flags = 0; act.sa_handler = SIG_IGN; /* 忽略SIGTSTP信号 */ if (sigaction (SIGTSTP, &act, NULL) = -1) perror(Failed to set handler for SIGTSTP); for(; ;) ; /* 此时CTRL+Z将不被响应 */ return 0; 通过忽略信号,进程可以避免被不希望的异步事件打断忽略信号-IGNORESIG.C可重入函数与不可重入函数可重入函数:可以被多个同时运行的任务共同执行而不会造成临界资源混乱的函数。不可重入函数:不可以被多个同时运行的任务所调用,除非采用互斥保护措施(如信号量或暂时禁止中断)以确保临界资源丢失或混乱。可重入函数的特点不在连续的多次调用中使用静态数据来保存状态不返回一个指向静态数据的指针,函数所需所有数据都由调用者提供使用局部数据,或者使用全局数据的一个本地拷贝不调用任何不可重入函数对临界区代码的保护在临界区代码前执行用信号掩码来暂时阻塞某些信号,临界区代码执行完毕后,取消阻塞sigset_t newmask, oldmask, zeromask;struct sigaction act;/*注册act*/if(sigaction(SIGALARM, &act, NULL) = -1) perror(“Failed to set handler for SIGALRM”);/*初始化信号集*/sigemtyset(&newmask);sigemtyset(&zeromask);sigaddset(&newmask, SIGALRM);/*阻塞SIGALRM,并备份当前信号掩码到oldmask*/sigprocmask(SIG_BLOCK, &newmask, &oldmask);/临界区代码sigprocmask(SIG_SETMASK, &oldmask, NULL);对临界区代码的保护36#includetypedef long time_t; 表达时间的最简单数据类型是time_t time_t表示从epoch以来所经过的秒数因为32比特的long类型目前最多能表示2,147,483,647秒,因此到2038年时将会产生溢出时间表示-137struct timeval /* #include */ time_t tv_sec; /* 秒 */ suseconds_t tv_usec; /* 微秒*/; struct timespec /* #include */ time_t tv_sec; /* 秒 */ long tv_nsec; /* 纳秒 */;时间表示-238struct tm /* #include */ int tm_sec; /* 秒 */ int tm_min; /* 分 */ int tm_hour; /* 时 */ int tm_mday; /* 日期 */ int tm_mon; /* 月份 */ int tm_year; /* 年 */ int tm_wday; /* 星期,06 */ int tm_yday; /* 一年中的第几天,1365 */ int tm_isdst; /* 是否采用夏令时 */ #ifdef _BSD_SOURCE long tm_gmtoff; /* GMT时区偏移*/ const char *tm_zone; /* 时区缩写*/#endif /* _BSD_SOURCE */; 时间表示-339#includetime_t time (time_t *t); 返回当前时间,其值为从epoch以来所经过的秒数 #includeint gettimeofday (struct timeval *tv, struct timezone *tz); 返回当前时间在tv,其值为从epoch以来所经过的秒数和微秒数 tz已过时不再使用 获得时间40#includeunsigned int sleep (unsigned int seconds); int usleep (unsigned long usec);int nanosleep (const struct timespec *req, struct timespec *rem); 对sleep函数,用于指定休眠的“秒”数。当被中断后,返回值可以为剩下未休眠的“秒”数 对usleep函数,用于指定休眠的“微秒”数 对nanosleep函数,用于指定休眠的“纳秒”数,参数rem用来存放剩下未休眠的时间休眠时间41#include int getitimer (int which, struct itimerval *value);int setitimer (int which, const struct itimerval *value, struct itimerval *ovalue); ITIMER_REAL ,以实际时间为单位进行计时。当给定的时间到时后,内核向进程发送SIGALRM信号 ITIMER_VIRTUAL ,以进程在用户态下的运行时间进行计时,当给定的时间到时后,内核向进程发送SIGVTALRM信号 ITIMER_PROF ,无论一个进程工作于用户态下,还是正处于系统调用过程,都将进行计时。当给定的时间到时后,内核向进程发送SIGPROF信号 间隔定时器-142#include struct itimerval /* 下次倒计时值 */ struct timeval it_interval; /* 当前倒计时值 */ struct timeval it_value; ; it_interval it_interval的值为的值为0 0,定时器超时后不再重新自动启动,定时器超时后不再重新自动启动 it_value it_value值为值为0 0,定时器将停止工作并且不重新自动启动,定时器将停止工作并且不重新自动启动 ovalue ovalue 不为空,则不为空,则setitimersetitimer返回设置前的定时器的时间返回设置前的定时器的时间间隔定时器-243#include FILE *fopen (const char *path, const char *mode); r,打开只读文件,该文件必须存在 r+,打开可读写的文件,该文件必须存在 w,打开只写文件,若文件存在则文件长度清为0,若文件 不存在则创建该文件 w+,打开可读写文件,若文件存在则文件长度清为0,若文件不存在则创建该文件 a,以附加的方式打开只写文件。若文件不存在,则创建该文件,反之写入的数据会被追加到文件尾 a+,以附加的方式打开可读写文件。若文件不存在,则创 建该文件,反之写入的数据会被追加到文件尾 2.5文件44#include #include #include #include #include #include #include #include int int fclosefclose (FILE *fp); (FILE *fp); int int fflushfflush (FILE *stream); (FILE *stream);int int statstat (const char *path, struct stat *buf); (const char *path, struct stat *buf); struct stat dev_t st_dev;/* 包含文件的设备ID */ ino_t st_ino;/* inode数 */ mode_t st_mode;/* 文件保护模式 */ nlink_t st_nlink;/* 硬链接数 */ uid_t st_uid;/* 文件属主的用户ID */ gid_t st_gid;/* 文件属主的组ID */ dev_t st_rdev;/* 设备ID(特殊文件) */ off_t st_size;/* 文件总大小字节数 */ blksize_t st_blksize; /* 文件I/O操作的块大小 */ blkcnt_t st_blocks; /* 分配的文件块数 */ time_t st_atime; /* 上次文件访问时间 */ time_t st_mtime; /* 上次文件修改时间 */ time_t st_ctime; /* 上次文件状态改变时间 */ ;第三章网络程序中常遇到的典型知识结构体大小的计算数据存储与字节序Linux链表和散列链表函数指针校验用户态下的多定时器的实现结构体大小的计算sizeof()注意:编译器的优化操作带来的对结构体进行的字节填充协议解析出错解决:在协议结构体定义时,明确要求编译器不对结构体进行字节填充在协议结构体定义时,引入某些特殊字段进行人为填充47禁止优化对齐的方法#pragmapack(1)/*禁止对齐*/structScharc;inti;structX;#pragmapack()/*结束禁止对齐*/structScharc;inti;_attribute_(packed);/告诉gcc取消优化对齐数据存储与字节序主机字节序不同的CPU有不同的字节序类型(整数在内存中保存的顺序)常见的字节序Littleendian:将低序字节存储在起始地址(低地址),80x86采用,高高低低原则。Bigendian:将高序字节存储在起始地址问题:存在大小端两种不同的字节序的机器(字节存储与位存储顺序不同)在不同类型机器间传递数据时容易发生错乱解决:在不同类型机器间进行数据传输时,采用统一的网络字节序传输方式。网络字节序网络数据在传输时的顺序。TCP/IP规定采用Big-Endian字节序49TCP包包头中的位域字中的位域字节序序0123456789101112131415+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|C|E|U|A|P|R|S|F|HeaderLength|Reserved|W|C|R|C|S|S|Y|I|(doff)|(res)|R|E|G|K|H|T|N|N|+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+TCP协议头部第13-14字节50TCP包包头中的位域字中的位域字节序序structS1/*小端机*/u_int16_tres:4,doff:4,fin:1,syn:1,rst:1,psh:1,ack:1,urg:1,ece:1,cwr:1;Linux链表和散列链表521. Linux内核通用内核通用链表表链表宿主链表宿主链表宿主structlist_headstructlist_head*next,*prev;structlist_userstructlist_headlist;intdata;53创建建链表表内核定义:(list.h)#defineLIST_HEAD(name)structlist_headname=LIST_HEAD_INIT(name)#defineLIST_HEAD_INIT(name)&(name),&(name)LIST_HEAD(example_list);54添加添加链表元素表元素list_add_tail(&example_struct.list,&example_list);nextprevexample_listnextprevexample_struct55链表指表指针和宿主指和宿主指针的的转换-1由链表结构地址获得链表宿主数据结构地址由链表结构地址获得链表宿主数据结构地址structlist_head*ptr;ptr=example_struct.next;/*已知的指向链表结构体的指针*/structexample_struct*pnode=list_entry(ptr,example_struct,list);p56链表指表指针和宿主指和宿主指针的的转换-2由链表结构地址获得链表宿主数据结构地址由链表结构地址获得链表宿主数据结构地址在list.h中:#definelist_entry(ptr,type,member)(type*)(char*)(ptr)-(unsignedlong)(&(type*)0)-member)structlist_headstructlist_head*next,*prev;structexample_structstructlist_headlist;intdata;57链表指表指针和宿主指和宿主指针的的转换-3由链表结构地址获得链表宿主数据结构地址由链表结构地址获得链表宿主数据结构地址#definelist_entry(ptr,type,member)(type*)(char*)(ptr)-(unsignedlong)(&(type*)0)-member)1.(type*)0)将0转型为type类型指针2.(type*)0)-member访问结构中的数据成员3.&(type*)0)-member)取出数据成员member的地址4.(unsignedlong)(&(type*)0)-member)结果转换类型.也就是member所在结构实例的偏移地址,是一个unsignedlong型的数5.(char*)(ptr)将宿主中list的地址转换为char*型,便于按字节进行计算6.ptr中的地址-list所在该宿主中的偏移=该宿主的首地址的数值7、对6步的结果进行(type*)转型,获得宿主首地址58Linux内核通用哈希内核通用哈希链表表hashcode = 0key1key1key1hashcode = 2key2key2hashcode = 4key3structhlist_headstructhlist_node59哈希哈希链表的定表的定义structhlist_head/*哈希表头*/structhlist_node*first;structhlist_node/*哈希表节点*/structhlist_node*next,*pprev;60创建哈希建哈希链表表structhlist_headhtable64;structhlist_userstruthlist_nodehlist;intdata;structhlist_headhtable64630firstfirst61初始化哈希初始化哈希链表表#defineINIT_HLIST_HEAD(ptr)(ptr)-first=NULL)INIT_HLIST_HEAD(&htable0);/*哈希链表头初始化*/*哈希链表元素节点初始化*/staticinlinevoidINIT_HLIST_NODE(structhlist_node*h)h-next=NULL;h-pprev=NULL;62哈希哈希链表操作表操作举例例/*hash()为所使用的散列函数*/intindex=hash(key);structhlist_head*head=&htableindex;hlist_for_each_entry(tpos,pos,head,member);63遍遍历哈希哈希链表表#definehlist_for_each_entry(tpos,pos,head,member)for(pos=(head)-first;pos&(;1;)&(tpos=hlist_entry(pos,typeof(*tpos),member);1;);pos=pos-next)tpos,指向宿主节点的指针,注意不是hlist_node类型结构体指针pos,指向hlist_node节点的指针,注意不是哈希链表宿主结构体指针head,哈希链表结构体的链表的头指针,注意不是宿主结构体指针member,宿主结构体中作为哈希链表节点而嵌入的成员名hlist_entry,原理同list_entry函数指针通过函数指针,可在调用接口不变的情况下,动态使用不同的函数实现。可提高程序的灵活性校验IP头中有一个字的校验和,为该IP头所有其它字的带进位加,并将进位也加进来,后求反。66校校验和和计算示例算示例-1Type(0x8)Code(0x0)Checksum()Id(0x0400)Sequence(0x1400)0x610x620x630x648bits8bits16bits67校校验和和计算示例算示例-208000400140061626364E4C61B3968校校验和和计算程序算程序u16checksum(u8*buf,intlen)u32sum=0;u16*cbuf;cbuf=(u16*)buf;/将待计算的字节序列转型为u16while(len1)sum+=*cbuf+;/累加16bit数len-=2;/剩余尚未累加的16比特的个数if(len)/若len的长度不是偶数sum+=*(u8*)cbuf;/用第1个字节补齐/判断是否有16比特加法溢出,若有则将进位位继续累加直到无进位while(sum16)sum=(sum16)+(sum&0xffff);/进位+余数returnsum;/返回检验和用用户态下多定下多定时器的器的实现用户态下一个进程只能拥有一个间隔定时器如果需要多个定时器,则可以通过软件来实现6970软件件实现多个定多个定时器器定时器1定时器2定时器nstructsnmp_alarm间隔定时器应用进程thecallbackthecallbackthecallbackthealarms第四章套接字是Linux为用户态应用提供的网络编程接口。套接字分类:流式套接字面向连接、可靠传输针对TCP数据报套接字非连接、不保证可靠针对UDP流式套接字程序(SOCK_STREAM)服务器程序创建监听套接字(socket)将套接字绑定到指定的地址和端口(bind)开始监听(listen)收到客户端的connect请求后,accept,派生一个子进程进行read和writeshutdown/close处理僵死子进程客户端程序创建套接字(socket,无需绑定到固定端口)与服务器的某个端口发出连接请求connect打开会话read/write关闭套接字shutdown/close数据报套接字程序(SOCK_DGRAM)服务器程序创建监听套接字(socket)绑定到指定地址和端口(bind)recvfrom等待客户端发来的数据客户端程序创建套接字sendto发送数据报1. 创建套接字建套接字74#include #include int socket(int domain, int type, int protocol);参数:Domain:套接字的域名,用于指明所使用的地址族(协议族)types:套接字的类型,SOCK_STREAM、SOCK_DGRAM、SOCK_RAWProtocol:使用的协议,一般情况下该参数为0,表示由系统在当前设定的domain下,自动选择适合的协议类型返回:Socket描述符(socketdescriptor)2. 域和地址族域和地址族75PF_UNIX, PF_LOCAL用于本地通信PF_INETIPv4,Internet协议PF_INET6IPv6,Internet协议PF_IPXNovell 网络协议PF_X25ITU-T X.25 / ISO-8208 协议.AF:AddressFamilyPF:ProtocolFamily其定义在socket.h中#defineAF_INET2#definePF_INETAF_INET目前多用PF_*3.套接字地址不是每个套接字均需要地址无地址的套接字无名套接字通常用于本地的不同进程间通信(如socketpair会生成一对互相连接的无名套接字)远程通信的套接字需要地址通用套接字地址通用套接字地址结构构sockaddr77#include typedef unsigned short sa_family_t;struct sockaddr sa_family_t sa_family; char sa_data14; AF_*,Internet(IPv4)套接字地址套接字地址结构构sockaddr_in78#include struct sockaddr_in sa_family_t sa_family; uint16_t sin_port; struct in_addr sin_addr; unsigned char sin_zero8; struct in_addr uint32_t s_addr;sin_port与sin_addr必须是网络字节序的形式,可使用htonl()等进行转换使用套接字使用套接字801)创建套接字建套接字80#include #include int socket(int domain, int type, int protocol);AF_INETSOCK_DGRAMSOCK_STREAMSOCK_SEQPACKETSOCK_RAW0成功则返回 file descriptor;失败则返回-1,并设errno选0表示由系统在当前domain下自动选择适合的协议类型812)流式套接字)流式套接字连接接发起起81#include #include int connect (int sockfd, const struct sockaddr_in *serveraddr, int serveraddrlen);将sockfd所指的socket与serveraddr所指的socket相连接,addrlen指serv_addr的大小。若sockfd所指socket是SOCK_DGRAM类型,则serv_addr就是要发向的数据报目的地址,只有此地址可接收数据报。若是SOCK_STREAM或SOCK_SEQPACKET类型,则将与serveraddr所指的目标建立连接。若成功:返回0,失败:返回-1,并设errno822)流式套接字)流式套接字连接接发起起82int connect (int sockfd, const struct sockaddr_in *serveraddr, int serveraddrlen);常见errno:ETIMEDOUT,若客户端TCP在发出首个SYN分段后没有收到任何应答,则大约在调用connect()函数75秒后将返回该错误ECONNREFUSED,当服务器进程并未启动,此时客户端TCP将向客户端应用返回该错误EHOSTUNREACH或ENETUNREACH,若客户端TCP发出的SYN分段收到了途经的中间路由器的“目标不可达”ICMP报文,则客户端TCP会重发SYN分段直到超过75秒,此时客户端TCP向客户端应用返回该错误EALREADY,Thesocketisnon-blockingandapreviousconnectionattempthasnotyetbeencompleted.833)绑定套接字定套接字83#include #include int bind (int sockfd, const struct sockaddr_in *myaddr, int addrlen);将套接字sockfd绑定到本地地址myaddr,myaddr长addrlen字节addrlen可以设置为sizeof(structsockaddr)。不同地址族,其绑定的规则不同对于TCP套接字的地址绑定:若ip地址为INADDR_ANY,则将绑定到所有本地IP接口若端口地址设为0,则有内核为其所及选择一个未用的端口若TCP套接字不进行绑定,就执行connect或listen,由内核为其随机设置一个未使用的端口,并将其ip地址设为INADDR_ANY一般服务器要明确进行绑定,客户端不显式绑定端口843)绑定套接字定套接字84#include #include int bind (int sockfd, const struct sockaddr_in *myaddr, int addrlen);IP地址地址端口端口含含义INADDR_ANY0内核内核选择IP地址和端口地址和端口INADDR_ANY非非0内核内核选择IP,应用确定端口用确定端口本地本地IP0应用确定用确定IP,内核,内核选择临时端口端口本地本地IP非非0应用用选择IP和端口和端口myaddr指定的特殊地址的含义:854)流式套接字)流式套接字连接接监听听85#include #include int listen (int sockfd, int backlog );通常通过socket创建的一个套接字若自愿可以接受他人的连接都可以通过listen设置连接队列上限,然后就可以用accept来接受连接。Listen()只能用于SOCK_STREAMorSOCK_SEQPACKET类型Backlog参数指明已建立连接并等待accept的套接字队列长度。若成功:返回0,失败:返回-1,并设errno。常用errno:EADDRINUSE已有一个socket在监听此端口EBADFsockfd无效ENOTSOCKsockfd不是一个socket.EOPNOTSUPP此socket的类型不适合使用listen操作864)流式套接字)流式套接字连接接监听听86#include #include int listen (int sockfd, int backlog );对于AF_INET地址族,backlog最大值默认为SOMAXCONN(=128)875)流式套接字)流式套接字连接接收接接收87#include #include int accept (int sockfd, const struct sockaddr_in *clientaddr, int * clientaddrlen);用于面向连接的服务,使用accept从backlog队列中接受下一个连接,对于阻塞socket,若backlog队列为空,则阻塞,进入休眠状态。对于非阻塞socket,则失败,errno设为EAGAINclientaddr不是作为函数的传入参数,而是函数的返回参数若成功:返回一个新的套接字描述符,用于与客户端进行通信,称为连接套接字,原监听套接字继续监听。若失败:返回-1,并设errno6)套接字I/O操作流式套接字可以像文件一样读写和关闭readwritecloseshutdown数据报套接字读写sendtorecvfrom89读流式套接字流式套接字89#include ssize_t read(int sockfd, void *buf, size_t count);从sockfd读入最多count字节到buf所指的缓冲区若count 为0,则read() returns zero and has no other results. 若 count 大于SSIZE_MAX,则结果不确定。 buf,读入数据的缓冲区,count为其长度,代表本次读的最大数据长度 函数返回值,实际所读取的数据字节长度,0表示读到文件尾,1表示错误90写流式套接字写流式套接字90#include ssize_t write(int sockfd, const void *buf, size_t count); 向sockfd所指socket写最多count字节,内容为由buf所指的数据缓冲区。 函数返回值为实际所写的数据字节长度,0表示什么也没写,1表示出错,并相应设置errno91关关闭流式套接字流式套接字91#include int close (int sockfd);int shutdown (int sockfd, int how);close()完全关闭套接字,此sockfd不再使用,相关资源释放。注意:一定要确保没有线程阻塞在recv状态,才能使用close,负责将永远阻塞。通常要采用shutdown关闭所有连接后,再close若成功:返回0,失败:返回-1,并设errno。92关关闭流式套接字流式套接字92#include int close (int sockfd);int shutdown (int sockfd, int how);值宏宏说明明0SHUT_RD不允不允许本地本地socket进行行读操作操作1SHUT_WR不允不允许本地本地socket进行写操作行写操作2SHUT_RDWR不允不允许本地本地socket进行行读和写操作和写操作(等于(等于close)how的取值及含义 93TCP流式套接字通信模式流式套接字通信模式93子进程子进程94读数据数据报套接字套接字94#include #include int sendto(int sockfd, /* 发送方套接字 */ const void *msg, /* 发送方数据报缓存指针 */ int len, /* 待发送数据报大小*/ unsigned flags, /* 发送选项,通常为0 */ const struct sockaddr *to, /* 接收方地址*/ int tolen); /* 接收方地址长度*/注意并不能保证这些发送成功的数据一定会被远端数据报套接字正确接收95写数据写数据报套接字套接字95#include #include int recvfrom(int sockfd, /* 接收方套接字 */ void *buf, /* 接收数据报缓存指针 */ int len, /* 接收数据报缓存的大小 */ unsigned flags, /* 接收选项,通常为0 */ struct sockaddr *from, /* 接收方地址 */ int fromlen); /* 接收方地址长度 */函数返回-1则表示出错,并由errno指出错误原因,否则返回buf中实际收到的字节数。96UDP数据数据报套接字通信模式套接字通信模式96套接字套接字编程常用函数与数据程常用函数与数据类型型981)字)字节顺序序转换函数函数98#include unsigned long htonl(unsigned long host_long);unsigned short htons(unsigned short host_short);unsigned long ntohl(unsigned long net_long);unsigned short ntohs(unsigned short net_short);注意,这里短整型表示16位,长整型表示32位, 和C语言中的数据类型不一样h:主机字节序:hostbyteordern:网络字节序:networkbyteorder字字节顺序序转换函数函数举例例port16进制表示进制表示内存映像内存映像90000x23280x28-0x23102750x28230x23-2899struct sockaddr_in adr_inet; .unsigned char *p;adr_inet.sin_family=AF_INET;adr_inet.sin_port=htons(9000); /* 9000=0x2328 */p=(char *)&adr_inet.sin_port;printf(“htons(9000)=%d , *p=0x%x, *(p+1)=0x%xn”, adr_inet.sin_port, *p, *(p+1);htons1002)地址)地址转换函数函数100#include #include #include int inet_aton(const char *string, struct in_addr *addr);char *inet_ntoa(struct in_addr addr);unsigned long inet_network(const char * addr);unsigned long inet_lnaof(struct in_addr addr);unsigned long inet_netof(struct in_addr addr);struct in_addr uint32_t s_addr;aton-address-networkbyteorderlnaof将以网络字节序表示的ip地址转换为以主机字节序表示的没有网络位的主机ID:thelocalhostaddresspartoftheInternetaddressaddr函数说明1、int inet_aton(const char *string, struct in_addr *addr);将ip字符串形式的地址string转换为网络字节序32位整型ip地址addr2、 char *inet_ntoa(struct in_addr addr);将32位整型网络字节序ip地址转换为字符串型3、unsigned long inet_network(const char * addr);将ip字符串形式的地址转换为主机序32位整型ip地址4、unsigned long inet_lnaof(struct in_addr addr);将以网络字节序表示的ip地址转换为以主机字节序表示的没有网络位的主机ID5、unsigned long inet_netof(struct in_addr addr);将以网络字节序表示的ip地址转换为以主机字节序表示的没有主机位的网络ID102地址地址转换函数函数举例例102struct sockaddr_in adr_inet;const char *ip=“”; unsigned long net_addr; unsigned long host_id;unsigned long net_id;inet_aton(ip, &adr_inet.sin_addr); /* 网络序表示 */printf(“adr_inet.sin_addr=0x%xn”, adr_inet.sin_addr);net_addr=inet_network(ip); /* 主机序表示 */host_id=inet_lnaof(adr_inet.sin_addr); /* 主机序表示 */net_id=inet_netof(adr_inet.sin_addr); /* 主机序表示 */printf(“host_id=%x, net_id=%xn”, host_id, net_id);net_addr=0xc0a80901adr_inet.sin_addr=0x109a8c0host_id=0x01net_id=0xc0a8091033)远程主机信息函数程主机信息函数103#include struct hostent *gethostbyname(const char *name);struct hostent /host entry char *h_name; /* 主机的正式名称 */ char *h_aliases; /* 主机别名列表 */ int h_addrtype; /* 主机地址类型 */ int h_length; /* 地址长度 */ char *h_addr_list; /* 地址列表 */;/* 保持后向兼容 */#define h_addr h_addr_list0以以b(byte)开)开头的函数:的函数: #includevoid bzero(void *s,int n); void bcopy ( const void *src,void *dest ,int n); int bcmp ( const void *s1,const void * s2,int n);以以mem开开头函数:函数: #includevoid * memset (void *s ,int c, size_t n); void * memcpy(void *dest, const void * src, size_t n) int memcmp (const void *s1,const void *s2,size_t n); 4)字处理函数bzero()会将参数s所指的内存区域前n个字节,全部设为零值。相当于调用memset(void*)s,0,size_tn);bcopy()与memcpy()一样都是用来拷贝src所指的内存内容前n个字节到dest所指的地址,不过参数src与dest在传给函数时是相反的位置。bcmp()用来比较s1和s2所指的内存区间前n个字节,若参数n为0,则返回0。返回值:若参数s1和s2所指的内存内容都完全相同则返回0值,否则返回非零值。memcmp()功能与其相同。其字符串大小的比较是以ASCII码表上的顺序来决定,次顺序亦为字符的值。memcmp()首先将s1第一个字符值减去s2第一个字符的值,若差为0则再继续比较下个字符,若差值不为0则返回。比较结果相同,返回0,否则非零。函数说明第五章本章介绍了Linux网路套接字的有关高级特性及其使用方法。socketoption函数SO_RESUSEADDRSO_KEEPALIVEI/O多路复用信号驱动I/Oepoll原始套接字getsockopt获得套接字得套接字选项当前当前值107#includeintgetsockopt(intsockfd,/套接字intlevel,/选项协议层intoptname,/选项名void*optval,/选项接收缓存指针socklen_t*optlen/选项长度指针);函数返回-1则表示出错,并由errno指出错误原因,否则返回0表示成功。setsockopt设置套接字置套接字选项108#includeintsetsockopt(intsockfd,/套接字intlevel,/选项协议层intoptname,/需设置的选项名constvoid*optval,/指向新的选项值socklen_toptlen/选项值长度);函数返回-1则表示出错,并由errno指出错误原因,否则返回0表示成功。选项协议层参数参数levelLeveloptnamemeaningSOL_SOCKETSO_RESUSEADDR允许本地地址被重用SO_KEEPALIVE周期地测试连接是否存活SO_LINGER允许TCP连接保持一段时间直到数据发送完毕SO_SNDBUF套接字发送缓冲大小SO_RCVBUF套接字接收缓冲大小SO_TYPE获得套接字类型SO_ERROR获得套接字已发生的错误并清除 IPPROTO_IPIP_TTL存活时间IP_HDRINCLIP头部包括的数据IPPROTO_TCPTCP_NODELAY禁止TCP Nagle算法TCP_MAXSEGTCP最大分段值TCP_KEEPALIVE控测对方是否存活前连接闲置秒数109I/O编程模式I/O多路复用(select模式)功能:使TCP服务器在只有一个进程的情况下并行处理多个网络套接字,也可同时处理其它需要监控的文件描述符。方法:通过select函数遍历所有要监控的套接字;服务器需设为非阻塞方式问题:当连接数较大时,服务器效率信号驱动I/O(SIGIO模式):一般用于UDP事件I/O(epoll)方法:epoll_create、epoll_ctl、epoll_wait优点:无需遍历所有套接字,等待即可。连接数多时,效率原始套接字:更灵活地处理各种协议Linux的I/O模式1.同步阻塞I/O:用户进程进行I/O操作,一直阻塞到I/O操作完成为止。不能进行多I/O高效处理2.同步非阻塞I/O:用户程序可以通过设置文件描述符的属性O_NONBLOCK,I/O操作可以立即返回,但是并不保证I/O操作成功。查询方式3.异步事件阻塞I/O:用户进程可以对I/O事件进行阻塞,但是I/O操作并不阻塞。通过select/poll/epoll等函数调用来达到此目的。4.异步时间非阻塞I/O:也叫做异步I/O(AIO),用户程序可以通过向内核发出I/O请求命令,不用等带I/O事件真正发生,可以继续做另外的事情,等I/O操作完成,内核会通过函数回调或者信号机制通知用户进程。这样很大程度提高了系统吞吐量。同步阻塞同步阻塞I/O112应用被阻塞用户进程进行I/O操作,一直阻塞到I/O操作完成使用同步阻塞I/O实现多个I/O处理单线程:效率。如:下页介绍的情况多线程模型:每个连结对应一个线程。如:ftpd多进程模型:每个连结对应一个进程。如:inetd适合于处理偶发性I/O,运行完即释放,节省系统资源I/O多路复用模型:多路复用模型:select模型模型 114应用进程在执行select时被阻塞,等待任何文件描述符准备好后,才启动读/写。select函数函数 115#include#includeintselect(intmaxfd+1,/*文件描述符最大取值再加1*/fd_set*readset,/*读数据文件描述符集合*/fd_set*writeset,/*写数据文件描述符集合*/fd_set*exceptset,/*异常文件描述符集合*/conststructtimeval*timeout);/*超时值设置*/structtimevallongtv_sec;/*秒*/longtv_usec;/*微秒*/;指定需要监视的文件描述符select函数的功能函数的功能 116u进程通过调用select,监视多个文件描述符,此时为阻塞状态。当发生可读、可写、异常或超时时,跳出阻塞状态。u不同timeout,不同的阻塞状态u若timeout为NULL,select函数一直阻塞直到其中一个文件描述符的数据就绪u若timeout为指定时间值,select函数一直阻塞到有任何一个文件描述符数据就绪,但不能超过timeout规定的时间u若timeout结构体的tv_sec和tv_usec为0,select函数检查完所设定的文件描述符后,立刻返回,而不阻塞select函数的返回函数的返回值 1170,返回值为当前发生了事件的所有文件描述符的个数N,并且各个文件描述符集均被修改以表明哪个文件描述符状态已变(对应位置位)=0,超时未发生事件,超时未发生事件-1,出错,相应置,出错,相应置errnoEBADF无效文件无效文件EINTR收到了信号收到了信号EINVAL无效参数无效参数ENOMEM无空间无空间文件描述符集合的相关操作文件描述符集合的相关操作118voidFD_ZERO(fd_set*fdset);/*清空fdset:所有bit位清0*/voidFD_SET(intfd,fd_set*fdset);/*添加fd到fdset中:fdset中所对应位置1*/voidFD_CLR(intfd,fd_set*fdset);/*从fdset中清除fd:fdset中对应位清0*/intFD_ISSET(intfd,fd_set*fdset);/*检测fdset中fd对应位是否被置1*/fdset1=fdset2/*将一个文件描述符集合赋值给另一个集合*/fd_setrset;FD_ZERO(&rset);/*initializetheset:allbitsoff*/FD_SET(1,&rset);/*turnonbitforfd1*/FD_SET(4,&rset);/*turnonbitforfd4*/FD_SET(5,&rset);/*turnonbitforfd5*/非阻塞非阻塞I/O119无数据可读无数据可读对应 connect调用,返回错误代码为EINPROGRESSfcntl设置非阻塞模式置非阻塞模式120操作操作cmdarg设置套接字置套接字为非阻塞模式非阻塞模式F_SETFLO_NONBLOCK设置套接字置套接字为信号信号驱动模式模式F_SETFLO_ASYNC设置套接字所有者置套接字所有者(接收接收SIGIO和和SIGURG)F_SETOWN正:正:pid,负:组id。一般。一般getpid()获得套接字所有者得套接字所有者F_GETOWN无无#include #include int fcntl (int fd, int cmd);int fcntl(int fd, int cmd, long arg);F_SETFL设置file flags此时arg只能是O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, O_NONBLOCK F_SETOWN设置file 所有者fcntl设置非阻塞模式置非阻塞模式121intflags;/*设置套接字为非阻塞模式*/if(flags=fcntl(fd,F_GETFL,0)0)bail(f_cntl(F_GETFL);flags|=O_NONBLOCK;if(fcntl(fd,F_SETFL,flags)0)bail(f_cntl(F_SETFL);if(fcntl(fd,F_SETFL,O_NONBLOCK)0)Signal-Driven I/O Model122设置信号置信号驱动I/O工作模式的三个步工作模式的三个步骤1231.注册SIGIO信号处理程序sigaction(SIGIO,&action,NULL) 2.为socket设置所有者fcntl(s,F_SETOWN,getpid()3.在socket上设置允许信号驱动I/O和非阻塞模式flags|=O_ASYNC|O_NONBLOCKfcntl(s,F_SETFL,flags)SIGIO 模式一般用于模式一般用于UDP Sockets124socketsocketreceivereceivebufferbufferserverserverlooploopSIGIOSIGIOHandlerHandlerserverserverlooploopsocketsocketreceivereceivebufferbufferqueuequeue(1 1)non-SIGIOnon-SIGIO(2 2)SIGIOSIGIO通常SIGIO模式用于UDP服务器,而不用于TCP服务器,因为对于TCP模式,有很多事件都会导致SIGIO产生,因此程序无法分辨epoll相关系相关系统调用用Epoll通过三个系统调用来完成高效的I/O模型epoll_create初始化epoll上下文环境epoll_ctl向epoll上下文中添加或去除需要系统监视的文件描述符epoll_wait等待文件描述符上发生事件125epoll_create126#includeintepoll_create(intsize)size:表示应用预计需要内核监视的文件描述符个数返回值:文件描述符表示成功,-1表示错误,errno记录错误号intepfd;epfd=epoll_create(100);if(epfd0)perror(epoll_create);epoll_ctl127#include/*向epoll环境添加,删除,改变要监视的文件描述符*/intepoll_ctl(intepfd,intop,intfd,structepoll_event*event);返回值:0表示成功,-1表示错误,errno记录错误号调用参数:epfd,epoll_create创建的epoll环境句柄op,规定对fd的操作方式event,要监视的事件EPOLL_CTL_ADDEPOLL_CTL_DELEPOLL_CTL_MODstruct epoll_event128#includestructepoll_event/*要epoll监视的事件,多个事件使用按位或运算进行组合*/_u32events;unionvoid*ptr;intfd;/*回传产生事件的文件描述符*/_u32u32;_u64u64;data;epoll_events结构体中的构体中的events129指出要监视的事件,可为下列各事件进行指出要监视的事件,可为下列各事件进行按位或按位或。EPOLLIN:文件描述符可读EPOLLOUT:文件描述符可写EPOLLRDHUP(从内核开始)流式套接字的对端执行了close,或shutdownwritinghalfEPOLLPRI:收到紧急数据EPOLLERR:被监视文件描述符上发生了错误,无论是否指定该事件总是被监视EPOLLHUP:文件描述符挂起,无论是否指定该事件总是被监视EPOLLET:允许对监视文件描述符使用边缘触发模式(ET)EPOLLONESHOT:文件描述符发生了事件并进行了相关操作后,该文件描述符将不再被epoll监视注册文件描述符到注册文件描述符到epoll130structepoll_eventevent;intret;/*将来epoll会返回此fd给应用*/event.data.fd=fd;/*监视此fd上的可读和可写事件*/event.events=EPOLLIN|EPOLLOUT;ret=epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&event);if(ret)perror(epoll_ctl);修改修改epoll监视的事件的事件131structepoll_eventevent;intret;/*将来epoll会返回此fd给应用*/event.data.fd=fd;/*监视此fd上的可读事件*/event.events=EPOLLIN;ret=epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&event);if(ret)perror(epoll_ctl);取消取消epoll监视的的fd132structepoll_eventevent;intret;ret=epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&event);if(ret)perror(epoll_ctl);epoll_wait133#includeintepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout);返回值:发生事件的文件描述符个数表示成功,-1表示错误,errno记录错误号限定本次epoll_wait能返回的文件描述符的最大数目使用使用epoll_wait134/*设置一次epoll_wait能返回的最多事件*/#defineMAX_EVENTS64structepoll_event*events;intnr_events,i,epfd;events=malloc(sizeof(structepoll_event)*MAX_EVENTS);if(!events)perror(malloc);return1;nr_events=epoll_wait(epfd,events,MAX_EVENTS,-1);if(nr_events0)perror(epoll_wait);free(events);return1;/* 对epoll返回的nr_events事件依次遍历,进行处理 */for(i=0;inr_events;i+)printf(event=%ldonfd=%dn,eventsi.events,eventsi.data.fd);/*处理eventsi.data.fd文件描述符.*/free(events);epoll和和select的的对比比 - epoll135nev=epoll_wait(epfd,events,MAX_EVENTS,-1);if(nev0)free(events);bail(epoll_wait);for(inti=0;ifd=eventsi.data.fd)p_so-do_task(p_so);Epoll和和select的的对比比 - select136n=select(maxfd+1,&rfds,NULL,NULL,NULL);/*依次处理发生的n个事件*/for(;n0;n-)/*在文件描述符选项链表中进行迭代*/list_for_each_entry(p_so,&fd_queue,list)/*检测此套接字文件描述符是否被置位*/if(FD_ISSET(p_so-fd,&rfds)p_so-do_task(p_so);/*执行对应的处理*/边沿触沿触发和水平触和水平触发: :ET触触发和和LT触触发137100bytes50bytesepoll_wait(epfd,events,MAX_EVENTS,-1);epoll_wait(epfd,events,MAX_EVENTS,-1);前次调用再次调用边沿触沿触发和水平触和水平触发: : ET触触发和和LT触触发%D%T%Y %A138epoll_wait(epfd,events,MAX_EVENTS,-1);epoll_wait(epfd,events,MAX_EVENTS,-1);read创建原始套接字建原始套接字139#include#includeintsockfd;sockfd=socket(PF_INET,SOCK_RAW,protocol);enumIPPROTO_ICMP=1,IPPROTO_IGMP=2,IPPROTO_TCP=6,IPPROTO_UDP=17,原始套接字的写原始套接字的写140数据发送仍然是调用sendto或sendmsg未设置IP_HDRINCL:由内核负责创建IP报文头部设置了IP_HDRINCL:由应用负责创建IP报文的头部,发送的内容包括头部和数据。IP头部ID字段可以设置为0,此时由内核负责设置此值内核负责计算和设置IP头部的校验和字段可以包含或不包含IP选项部分内核负责对超过了发送端口MTU规定长度的套接字数据进行分段原始套接字的原始套接字的读141使用recvfrom接收内核不会将已收到的UDP和TCP分组再交给原始套接字进行处理内核处理完收到的ICMP消息后,会将大多数ICMP分组交给原始套接字进行处理当内核不能识别已收到IP报文头部的协议字段时,将把此分组交给原始套接字
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号