国嵌Linux视频课件整理(2)

来源:互联网 发布:抢车票软件 编辑:程序博客网 时间:2024/05/19 19:34

进程通信

信号通信

信号(signal)机制是Unix系统中最为古老的进程间通信机制,很多条件可以产生一个信号:

1、当用户按某些按键时,产生信号。

2、硬件异常产生信号:除数为0、无效的存储访问等等。这些情况通常由硬件检测到,将其通知内核,然后内核产生适当的信号通知进程,例如,内核对正访问一个无效存储区的进程产生一个SIGSEGV信号。

3、进程用kill函数将信号发送给另一个进程。

4、用户可用kill命令将信号发送给其他进程。

信号类型

1) SIGHUP 2) SIGINT3) SIGQUIT4) SIGILL5) SIGTRAP 6) SIGIOT 7) SIGBUS

8) SIGFPE9) SIGKILL10) SIGUSR1 11) SIGSEGV 12) SIGUSR213) SIGPIPE

14) SIGALRM 15)SIGTERM17) SIGCHLD 18) SIGCONT 19) SIGSTOP20) SIGTSTP 21) SIGTTIN 22)SIGTTOU23) SIGURG 24) SIGXCPU 25) SIGXFSZ26) SIGVTALRM 27) SIGPROF 28)SIGWINCH29) SIGIO 30) SIGPWR

下面是几种常见的信号:

§ SIGHUP: 从终端上发出的结束信号

§ SIGINT: 来自键盘的中断信号(Ctrl-C)

§ SIGKILL:该信号结束接收信号的进程

§ SIGTERM:kill 命令发出的信号

§ SIGCHLD:标识子进程停止或结束的信号

§ SIGSTOP:来自键盘(Ctrl-Z)或调试程序的停止执行信号

信号处理

当某信号出现时,将按照下列三种方式中的一种进行处理:

1、忽略此信号

大多数信号都按照这种方式进行处理,但有两种信号却决不能被忽略。它们是:

SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供了一

种终止或停止进程的方法。

2、执行用户希望的动作

通知内核在某种信号发生时,调用一个用户函数。在用户函数中,执行用户希望的处理。

3、执行系统默认动作

对大多数信号的系统默认动作是终止该进程。

信号发送

发送信号的主要函数有 kill和raise。

区别:

Kill既可以向自身发送信号,也可以向其他进程发送信号。与kill

函数不同的是,raise函数是向进程自身发送信号。

#include<sys/types.h>

#include<signal.h>

int kill(pid_t pid,int signo)

int raise(int signo)

kill的pid参数有四种不同的情况:

1、pid>0

将信号发送给进程ID为pid的进程。

2、pid == 0

将信号发送给同组的进程。

3、pid < 0

将信号发送给其进程组ID等于pid绝对值的进程。

4、pid ==-1

将信号发送给所有进程。

Alarm

使用alarm函数可以设置一个时间值(闹钟时间),当所设置的时间到了时,产生SIGALRM信

号。如果不捕捉此信号,则默认动作是终止该进程。

#include<unistd.h>

unsigned intalarm(unsigned int seconds)

Seconds:经过了指定的seconds秒后会产生信号SIGALRM。

每个进程只能有一个闹钟时间。如果在调用alarm时,以前已为该进程设置过闹钟时间,而

且它还没有超时,以前登记的闹钟时间则被新值代换。

如果有以前登记的尚未超过的闹钟时间,而这次seconds值是0,则表示取消以前的闹钟。

Pause

pause函数使调用进程挂起直至捕捉到一个信号。

#include<unistd.h>

int pause(void)

只有执行了一个信号处理函数后,挂起才结束。

信号的处理

当系统捕捉到某个信号时,可以忽略该信号或是使用指定的处理函数来处理该信号,或者使用系统默认的方式。

信号处理的主要方法有两种,一种是使用简单的signal函数,另一种是使用信号集函数组。

signal

#include<signal.h>

void (*signal (intsigno, void (*func)(int)))(int)

typedef void(*sighandler_t)(int)

sighandler_tsignal(int signum, sighandler_t handler))

Func可能的值是:

1、SIG_IGN:忽略此信号

2、SIG_DFL: 按系统默认方式处理

3、信号处理函数名:使用该函数处理

共享内存

共享内存是被多个进程共享的一部分物理内存。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。

共享内存实现分为两个步骤:

一、创建共享内存,使用shmget函数。

二、映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。

int shmget( key_t key, int size, int shmflg )

key标识共享内存的键值: 0/IPC_PRIVATE。 当key的取值为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;如果key的取值为0,而参数shmflg中又设置IPC_PRIVATE这个标志,则同样会创建一块新的共享内存。

返回值:如果成功,返回共享内存标识符;如果失败,返回-1。

int shmat( int shmid, char *shmaddr, int flag)

参数:

shmid:shmget函数返回的共享存储标识符

flag:决定以什么方式来确定映射的地址(通常为0)

返回值:如果成功,则返回共享内存映射到进程中的地址;如果失败,则返回- 1。

当一个进程不再需要共享内存时,需要把它从进程地址空间中脱离。

int shmdt ( char*shmaddr )

消息队列

unix早期通信机制之一的信号能够传送的信息量有限,管道则只能传送无格式的字节流,这无疑会给应用程序开发带来不便。消息队列(也叫做报文队列)则克服了这些缺点。

消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式。进程可以向中按照一定的规则添加新消息;另一些进程则可以从消息队列中读走消息。

目前主要有两种类型的消息队列:POSIX消息队列以及系统V消息队列,系统V消息队列目前被大量使用。

持续性

系统V消息队列是随内核持续的,只有在内核重起或者人工删除时,该消息队列才会被删除。

键值

消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,必须提供该消息队列的键值。

#include<sys/types.h>

#include<sys/ipc.h>

key_t ftok(char*pathname, char proj)

功能:

返回文件名对应的键值。

pathname:文件名

proj:项目名(不为0即可)

打开/创建

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/msg.h>

int msgget(key_tkey, int msgflg)

返回值:与健值key相对应的消息队列描述字

key:键值,由ftok获得。

msgflg:标志位。

IPC_CREAT 创建新的消息队列

IPC_EXCL 与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。

IPC_NOWAIT 读写消息队列要求无法得到满足时,不阻塞。

在以下两种情况下,将创建一个新的消息队列:

如果没有与健值key相对应的消息队列,并且msgflg中包含了IPC_CREAT标志位。

key参数为IPC_PRIVATE。

发送消息

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/msg.h>

int msgsnd(intmsqid,struct msgbuf*msgp,int msgsz,int msgflg)

功能:向消息队列中发送一条消息。

msqid 已打开的消息队列id

msgp 存放消息的结构

msgsz 消息数据长度

msgflg 发送标志,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。

消息格式

struct msgbuf

{

long mtype; /* 消息类型 > 0 */

char mtext[1]; /* 消息数据的首地址 */

};

接收消息

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/msg.h>

int msgrcv(intmsqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg)

功能:从msqid代表的消息队列中读取一个msgtyp类型的消息,并把消息存储在msgp指向

的msgbuf结构中。在成功地读取了一条消息以后,队列中的这条消息将被删除。

信号量

信号量(又名:信号灯)与其他进程间通信方式不大相同,主要用途是保护临界资源。进程可以根据它判定是否能够访问某些共享资源。除了用于访问控制外,还可用于进程同步。

分类

二值信号灯:信号灯的值只能取0或1,类似于互斥锁。 但两者有不同:信号灯强调共享资源,

只要共享资源可用,其他进程同样可以修改信号灯的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。

计数信号灯:信号灯的值可以取任意非负值。

创建/打开

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/sem.h>

int semget(key_tkey, int nsems, int semflg)

key:键值,由ftok获得

nsems:指定打开或者新创建的信号灯集中将包含信号灯的数目

semflg:标识,同消息队列

操作

int semop(int semid,struct sembuf *sops, unsigned nsops)

功能:对信号量进行控制。

semid:信号量集的ID

sops:是一个操作数组,表明要进行什么操作

nsops:sops所指向的数组的元素个数。

struct sembuf {

unsigned shortsem_num; /* semaphore index in array */

short sem_op; /*semaphore operation */

short sem_flg; /*operation flags */

};

sem_num:要操作的信号量在信号量集中的编号,第一个信号的编号是0。

sem_op:如果其值为正数,该值会加到现有的信号量值中,通常用于释放信号量;如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值,通常用于获取信号量;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。

Sem_flg:信号操作标志,可能的选择有两种:

IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。

IPC_UNDO:程序结束时(不论正常或不正常)释放信号量,这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。

 多线程

线程理论基础

线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中

期,solaris是这方面的佼佼者。传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程。现在,多线程技术已经被许多操作系统所支持,包括Windows/NT、Linux。

为什么有了进程,还要引入线程呢?使用多线程到底有哪些好处?

使用多线程的理由之一是:

和进程相比,它是一种非常“节俭”的多任务操作方式。在Linux系统下,启动一个新的进程

必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这

是一种"昂贵"的多任务工作方式。运行于一个进程中的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,一个进程的开销大约是一个线程开销的30倍左右。

使用多线程的理由之二是:

线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。

除了以上所说的优点外,多线程程序作为一种多任务、并发的工作方式,有如下优点:

使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。

改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。

多线程程序设计

创建线程

#include<pthread.h>

intpthread_create(pthread_t * tidp,const pthread_attr_t *attr,

void*(*start_rtn)(void),void *arg)

tidp:线程id

attr:线程属性(通常为空)

start_rtn:线程要执行的函数

arg:start_rtn的参数

编译

因为pthread的库不是linux系统的库,所以在进行编译的时候要加上

-lpthread

# gcc filename-lpthread

终止线程

如果进程中任何一个线程中调用exit或_exit,那么整个进程都会终止。线程的正常退出方式有:

(1) 线程从启动例程中返回

(2) 线程可以被另一个进程终止

(3) 线程自己调用pthread_exit函数

#include<pthread.h>

voidpthread_exit(void * rval_ptr)

功能:终止调用线程

Rval_ptr:线程退出返回值的指针。

线程等待

#include<pthread.h>

intpthread_join(pthread_t tid,void **rval_ptr)

功能:阻塞调用线程,直到指定的线程终止。

Tid :等待退出的线程id

Rval_ptr:线程退出的返回值的指针

线程标识

#include<pthread.h>

pthread_tpthread_self(void)

功能:

获取调用线程的 thread identifier

清除

线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其他线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。

不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,如何保证线程终止时能顺利的释放掉自己所占用的资源,是一个必须考虑解决的问题。

从pthread_cleanup_push的调用点到pthread_cleanup_pop之间的程序段中的终

止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push()所指定的清理函数。

#include<pthread.h>

voidpthread_cleanup_push(void (*rtn)(void *),void *arg)

功能:

将清除函数压入清除栈

Rtn:清除函数

Arg:清除函数的参数

#include<pthread.h>

voidpthread_cleanup_pop(int execute)

功能:

将清除函数弹出清除栈

参数:

Execute执行到pthread_cleanup_pop()时是否在弹出清

理函数的同时执行该函数,非0:执行; 0:不执行

线程同步

进行多线程编程,因为无法知道哪个线程会在哪个时候对共享资源进行操作,因此让如何保护共享资源变得复杂,通过下面这些技术的使用,可以解决线程之间对资源的竞争:

1 互斥量Mutex

2 信号灯Semaphore

3 条件变量Conditions

互斥量

为什么需要互斥量:

Item * p=queue_list;

Queue_list=queue_list->next;

process_job(p);

free(p);

当线程1处理完Item *p=queue_list后,系统停止线程1的运行,改而运行线程2。线程2照样取出头节点,然后进行处理,最后释放了该节点。过了段时间,线程1重新得到运行。而这个时候,p所指向的节点已经被线程2释放掉,而线程1对此毫无知晓。他会接着运行

process_job(p)。而这将导致无法预料的后果!

对于这种情况,系统给我们提供了互斥量。线程在取出头节点前必须要等待互斥量,如果此时有其他线程已经获得该互斥量,那么该线程将会阻塞在这里。只有等到其他线程释放掉该互斥量后,该线程才有可能得到该互斥量。互斥量从本质上说就是一把锁, 提供对共享资源的保护访问。

创建

在Linux中, 互斥量使用类型pthread_mutex_t表示。在使用前, 要对它进行初始化:

对于静态分配的互斥量, 可以把它设置为默认的mutex对象PTHREAD_MUTEX_INITIALIZER

对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy。

#include<pthread.h>

int pthread_mutex_init(pthread_mutex_t*mutex,

constpthread_mutexattr_t *attr)

intpthread_mutex_destroy(pthread_mutex_t *mutex)

加锁

对共享资源的访问, 要使用互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁。

intpthread_mutex_lock(pthread_mutex_t *mutex)

intpthread_mutex_trylock(pthread_mutex_t *mutex)

返回值: 成功则返回0, 出错则返回错误编号。

trylock是非阻塞调用模式, 如果互斥量没被锁住, trylock函数将对互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了,trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态。

解锁

在操作完成后,必须给互斥量解锁,也就是前面所说的释放。这样其他等待该锁的线程才有机会获得该锁,否则其他线程将会永远阻塞。

intpthread_mutex_unlock(pthread_mutex_t *mutex)

互斥量PK信号量

Mutex是一把钥匙,一个人拿了就可进入一个房间,出来的时候把钥匙交给队列的第一个。

Semaphore是一件可以容纳N人的房间,如果人不满就可以进去,如果人满了,就要等待有人出来。

对于N=1的情况,称为binary semaphore。

Binary semaphore与Mutex的差异:

1. mutex要由获得锁的线程来释放(谁获得,谁释放)。而semaphore可以由其它线程释放

2. 初始状态可能不一样:mutex的初始值是1 ,而semaphore的初始值可能是0(或者为1)。

1.2.2 解决默认壁纸是动态壁纸的问题

终于找到默认壁纸无法改变的原因了:

在/vendor/samsung/smdkv210/overlay里对一些系统参数进行的重载配置,

故在原来的地方改默认壁纸是无效的。

 网络编程

TCP/IP协议

TCP/IP协议族

TCP/IP 实际上一个协同工作的通信家族,为网络数据通信提供通路。为讨论方便可TCP/IP 协议组大体上分为三部分:

• Internet 协议(IP)

• 传输控制协议(TCP)和用户数据报协议(UDP)

• 处于 TCP 和 UDP 之上的一组应用协议。它们包括:TELNET,文件传送协议(FTP),域名服务(DNS)和简单的邮件传送程序(SMTP)等。

网络层

第一部分称为网络层。主要包括Internet 协议(IP)、网际控制报文协议(ICMP)和地址解析协议(ARP):

• Internet 协议(IP)

该协议被设计成互联分组交换通信网,以形成一个网际通信环境。它负责在源主机和目的地主机之间传输来自其较高层软件的称为数据报文的数据块,它在源和目的地之间提供非连接型传递服务。

• 网际控制报文协议(ICMP)

它实际上不是IP层部分,但直接同IP层一起工作,报告网络上的某些出错情况。允许网际路由器传输差错信息或测试报文。

• 地址解析协议(ARP)

ARP 实际上不是网络层部分,它处于IP和数据链路层之间,它是在32位IP地址和48位物理地址之间执行翻译的协议。

传输层协议

第二部分是传输层协议,包括传输控制协议和用户数据报文协议。

•传输控制协议(TCP):

该协议对建立网络上用户进程之间的对话负责,它确保进程之间的可靠通信,所提供的功能如下:

1. 监听输入对话建立请求

2. 请求另一网络站点对话

3. 可靠的发送和接收数据

4.适度的关闭对话

用户数据报文协议(UDP):

UDP 提供不可靠的非连接型传输层服务,它允许在源和目的地之间传送数据,而不必在传送数据之前建立对话。它主要用于那些非连接型的应用程序,如:视频点播。

应用协议

这部分主要包括Telnet,文件传送协议(FTP 和TFTP),简单文件传送协议(SMTP)和域名服务(DNS)等协议。

IP协议

IP主要有以下四个主要功能:

• 数据传送

• 寻址

• 路由选择

• 数据报文的分段

IP的主要目的是为数据输入/输出网络提供基本算法,为高层协议提供无连接的传送服务。这意味着在IP将数据递交给接收站点以前不在传输站点和接收站点之间建立对话。它只是封装和传递数据,但不向发送者或接收者报告包的状态,不处理所遇到的故障。

IP包由IP协议头与协议数据两部分构成。

TCP协议

TCP是重要的传输层协议,目的是允许数据同网络上的其他节点进行可靠的交换。它能提供端口编号的译码,以识别主机的应用程序,而且完成数据的可靠传输。

•TCP 协议具有严格的内装差错检验算法确保数据的完整性。

•TCP 是面向字节的顺序协议,这意味着包内的每个字节被分配一个顺序编号,并分配给每包一个顺序编号。

UDP协议

UDP也是传输层协议,它是无连接的,不可靠的传输服务。当接收数据时它不向发送方提供确认信息,它不提供输入包的顺序,如果出现丢失包或重份包的情况,也不会向发送方发出差错报文。由于它执行功能时具有较低的开销,因而执行速度比TCP快。

linux socket编程

Socket

Linux中的网络编程通过Socket(套接字)接口实现,Socket是一种文件描述符。

套接字socket有三种类型:

• 流式套接字(SOCK_STREAM)可以提供可靠的、面向连接的通讯流。它使用了TCP协议。TCP保证了数据传输的正确性和顺序性。

• 数据报套接字(SOCK_DGRAM)字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错,它使用数据报协议UDP。

• 原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议的测试等。

地址结构

struct sockaddr

{

u_short sa_family;

char sa_data[14];

}

Sa_family:地址族,采用“AF_xxx”的形式,如:AF_INET。

Sa_data:14字节的特定协议地址。

struct sockaddr_in

{

short intsin_family; /* Internet地址族 */

unsigned short intsin_port; /* 端口号 */

struct in_addrsin_addr; /* IP地址 */

unsigned charsin_zero[8]; /* 填0 */

}

编程中一般并不直接针对sockaddr数据结构操作,而是使用与sockaddr等价的sockaddr_in数据结构

struct in_addr

{

unsigned longs_addr;

}

S_addr: 32位的地址。

地址转换

IP地址通常由数字加点(192.168.0.1)的形式表示,而在struct in_addr中使用的是IP地址是由32位的整数表示的,为了转换我们可以使用下面两个函数:

int inet_aton(constchar *cp,struct in_addr *inp)

char*inet_ntoa(struct in_addr in)

函数里面 a 代表 ascii n 代表network.第一个函数表示将a.b.c.d形式的IP转换为32位的IP,存储在 inp指针里面。第二个是将32位IP转换为a.b.c.d的格式。

字节序转换

为什么要进行字节序转换?

例:

INTEL的CPU使用的小端字节序MOTOROLA 68k系列CPU使用的是大端字节序 MOTOROLA发一个16位数据0X1234给INTEL, 传到INTEL时 ,就被INTEL解释为0X3412 。

不同类型的 CPU 对变量的字节存储顺序可能不同:有的系统是高位在前,低位在后,而有的系统是低位在前,高位在后,而网络传输的数据顺序是一定要统一的。所以当内部字节存储顺序和网络字节顺序不同时,就一定要进行转换。

网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统

等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用

big endian排序方式。

htons把unsigned short类型从主机序转换到网络序

htonl把unsigned long类型从主机序转换到网络序

ntohs把unsigned short类型从网络序转换到主机序

ntohl把unsigned long类型从网络序转换到主机序

IP与主机名

在网络上标识一台机器可以用IP,也可以使用主机名。

struct hostent*gethostbyname(const char *hostname)

struct hostent

{

char *h_name; /* 主机的正式名称 */

char *h_aliases; /* 主机的别名 */

int h_addrtype; /* 主机的地址类型 AF_INET*/

int h_length; /* 主机的地址长度 */

char **h_addr_list;/* 主机的IP地址列表 */

}

#define h_addrh_addr_list[0] /* 主机的第一个IP地址*/

函数

进行Socket编程的常用函数有:

• socket 创建一个socket。

• bind 用于绑定IP地址和端口号到socket。

• connect 该函数用于绑定之后的client端,与服务器建立连接。

• listen 设置能处理的最大连接要求,Listen()并未开始接收连线,只是设置socket为listen模式。

• accept 用来接受socket连接。

• send 发送数据

• recv 接收数据

基于TCP-服务器

1. 创建一个socket,用函数socket()

2. 绑定IP地址、端口等信息到socket上,用函数bind()

3. 设置允许的最大连接数,用函数listen()

4. 接收客户端上来的连接,用函数accept()

5. 收发数据,用函数send()和recv(),或者read()和write()

6. 关闭网络连接

基于TCP-客户端

1. 创建一个socket,用函数socket()

2. 设置要连接的对方的IP地址和端口等属性

3. 连接服务器,用函数connect()

4. 收发数据,用函数send()和recv(),或者

read()和write()

5. 关闭网络连接

基于UDP-服务器

1. 创建一个socket,用函数socket()

2. 绑定IP地址、端口等信息到socket上,用函数bind()

3. 循环接收数据,用函数recvfrom()

4. 关闭网络连接

基于UDP-客户端

1. 创建一个socket,用函数socket()

2. 绑定IP地址、端口等信息到socket上,用函数bind()

3. 设置对方的IP地址和端口等属性

4. 发送数据,用函数sendto()

5. 关闭网络连接

服务器模型

在网络程序里面,一般来说都是许多客户对应一个服务器,为了处理客户的请求, 对服务端的程序就提出了特殊的要求。目前最常用的服务器模型有:

•循环服务器:服务器在同一个时刻只可以响应一个客户端的请求

•并发服务器:服务器在同一个时刻可以响应多个客户端的请求

UDP循环服务器

UDP循环服务器的实现方法:UDP服务器每次从套接字上读取一个客户端的请求->处理->然后将结果返回给客户机。

socket(...);

bind(...);

while(1)

{

recvfrom(...);

process(...);

sendto(...);

}

因为UDP是非面向连接的,没有一个客户端可以老是占住服务端, 服务器对于每一个客户机的请求总是能够满足。

TCP循环服务器

TCP服务器接受一个客户端的连接,然后处理,完成了这个

客户的所有请求后,断开连接。算法如下:

socket(...);

bind(...);

listen(...);

while(1)

{

accept(...);

process(...);

close(...);

}

TCP循环服务器一次只能处理一个客户端的请求。只有在这个客户的所有请求都满足后, 服务器才可以继续后面的请求。这样如果有一个客户端占住服务器不放时,其它的客户机都不能工作了,因此,TCP服务器一般很少用循环服务器模型的。

TCP并发服务器

并发服务器的思想是每一个客户机的请求并不由服务器直接处

理,而是由服务器创建一个 子进程来处理。算法如下:

socket(...);

bind(...);

listen(...);

while(1) {

accept(...);

if(fork(..)==0) {

process(...);

close(...);

exit(...);

}

close(...);

}

TCP并发服务器可以解决TCP循环服务器客户机独占服务器的情况。但同时也带来了问题:为了响应客户的请求,服务器要创建子进程来处理,而创建子进程是一种非常消耗资源的操作。

多路复用I/O

阻塞函数在完成其指定的任务以前不允许程序继续向下执行。例如:当服务器运行到accept语句时,而没有客户请求连接,服务器就会停止在accept语句上等待连接请求的到来。这种情况称为阻塞(blocking),而非阻塞操作则可以立即完成。例如,如果你希望服务器仅仅检查是否有客户在等待连接,有就接受连接,否则就继续做其他事情,则可以通过使用select系统调用来实现。除此之外,select还可以同时监视多个套接字 。

int select(intmaxfd, fd_set *readfds, fd_set *writefds, fe_set

*exceptfds, conststruct timeval *timeout)

Maxfd: 文件描述符的范围,比待检的最大文件描述符大1

Readfds:被读监控的文件描述符集

Writefds:被写监控的文件描述符集

Exceptfds:被异常监控的文件描述符集

Timeout:定时器

Timeout取不同的值,该调用有不同的表现:

Timeout值为0,不管是否有文件满足要求,都立刻返回,无文件满足要求返回0,有文件满足要求返回一个正值。

Timeout为NULL,select将阻塞进程,直到某个文件满足要求

Timeout值为正整数,就是等待的最长时间,即select在timeout时间内阻塞进程。

Select调用返回时,返回值有如下情况:

1. 正常情况下返回满足要求的文件描述符个数;

2. 经过了timeout等待后仍无文件满足要求,返回值为0;

3. 如果select被某个信号中断,它将返回-1并设置errno为EINTR。

4. 如果出错,返回-1并设置相应的errno。

Select使用步骤

1. 设置要监控的文件

2. 调用Select开始监控

3. 判断文件是否发生变化

统提供了4个宏对描述符集进行操作:

#include<sys/select.h>

void FD_SET(int fd,fd_set *fdset) 宏FD_SET将文件描述符fd添加到文件描述符集fdset中;

void FD_CLR(int fd,fd_set *fdset) 宏FD_CLR从文件描述符集fdset中清除文件描述符fd;

void FD_ZERO(fd_set*fdset) 宏FD_ZERO清空文件描述符集fdset;

void FD_ISSET(intfd, fd_set *fdset) 在调用select后使用FD_ISSET来检测文件描述符集fdset中的文件fd发生了变化。

FD_ZERO(&fds);//清空集合

sock1 =socket(......);

sock2 =socket(......);

bind(sock1,...);

bind(sock2,...);

listen(sock1,...);

listen(sock1,...);

FD_SET(sock1,&fds);//设置描述符

FD_SET(sock2,&fds);//设置描述符

maxfdp=(sock1>sock2?sock1:sock2)+ 1;

switch(select(maxfdp,&fds,NULL,NULL,&timeout))

case -1:exit(-1);break; //select错误,退出程序

case 0:break;

default:

if(FD_ISSET(sock1,&fds))//测试sock1是否可读

accpet(sock1,...)

...............................................