Linux-网络通信编程_第1页
Linux-网络通信编程_第2页
Linux-网络通信编程_第3页
Linux-网络通信编程_第4页
Linux-网络通信编程_第5页
已阅读5页,还剩78页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Linux

操作系统与程序设计软件工程系何海涛内容1.概念:协议套接字通信2.TCPUDPFTP通信3.多线程多进程服务器SchoolofComputerSicence几个概念进程通信:单机:进程之间交换信息(通过pipe,signal等)网络:不同计算机(软/硬件)之间的信息交换--最终,仍是进程通信需要解决的问题进程标识协议差错控制、流量控制、报文顺序、连接管理......解决方案TCP/IP协议+Socket编程机制SchoolofComputerSicence服务和端口服务器客户上网电子邮件文件传输一个IP65536个端口SchoolofComputerSicence编写网络通信程序TCP/IP协议制定了通信双方通信的细节:如数据包的格式,建立连接的形式等协议的实现在每个系统上是不一样的,而且协议很复杂,具体实现用到了成百上千个函数和无数的结构体,即使程序员知道了协议细则,要直接调用协议中各种函数完成一个网络通信程序是很困难的如何简化:把协议“封装”的简单一点,如同打电话SchoolofComputerSicenceSocket接口socket:套接字,是一组接口,使得编写网络通信程序,如同打电话般简单Socket的英文原义是“孔”或“插座”:一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电,有的提供110伏交流电,有的则提供有线电视节目。客户软件将插头插到不同编号的插座,就可以得到不同的服务。SchoolofComputerSicencesocket编程和打电话IP地址和端口三次握手或UDPsendreceive电话用哪个运营商的电话打给谁拨号和接听交谈挂断socket用什么协议通信和谁通信请求和接受收发信息关闭socket步骤对比SchoolofComputerSicencesocket实例网络通信涉及到2台电脑如果是C/S模式:一台作为“服务器”,一台为“客户机”也可以对等P2P:地位平等服务器:提供服务接受客户端的连接响应客户端的要求给客户端发消息客户端:向服务器发送请求从服务器收取消息SchoolofComputerSicencesocket实例服务器程序先运行,等待客户端请求/消息到来客户端向服务器发送一个字符串hello服务器在终端输出客户端发来的字符串SchoolofComputerSicence最简单的UDP服务器udps.c#include<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>intmain(intargc,char**argv){intsockfd;structsockaddr_inservaddr;charbuff[1024];intn,sinsize;

sockfd=socket(AF_INET,SOCK_DGRAM,0);memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=INADDR_ANY;servaddr.sin_port=htons(9091);bind(sockfd,(structsockaddr*)&servaddr,sizeof(servaddr));sinsize=sizeof(servaddr);n=recvfrom(sockfd,buff,1024,0,NULL,&sinsize);buff[n]='\0';printf("recvmsgfromclient:%s\n",buff);close(sockfd);}SchoolofComputerSicence最简单的UDP客户端udpc.c#include…...intmain(intargc,char**argv){intsockfd,n;charsendline[1024];structsockaddr_inservaddr;sockfd=socket(AF_INET,SOCK_DGRAM,0);memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(9091);servaddr.sin_addr.s_addr=inet_aton("");n=sizeof(structsockaddr);sendto(sockfd,"hello",5,0,(structsockaddr*)&servaddr,n);close(sockfd);exit(0);}SchoolofComputerSicence编译运行同一台电脑,打开2个终端分别编译运行服务器程序和客户端程序注意:不能不指定输出的可执行文件名,必须使用-o参数,否则,两个程序默认的可执行文件都是a.out,会冲突不同的计算机之间需要服务器的IP地址客户端程序中修改IP地址使用命令ifconfig查看本机的IP地址SchoolofComputerSicence基本socketAPIsocket()创建一个套接字,#include<sys/socket.h>函数原型:intsocket(intdomain,inttype,intprotocol);参数说明domain:通信协议族,即地址族,通常是AF_INET(TCP/IP(V4))type:套接字类型SOCK_STREAM:TCP协议SOCK_DGRAM:UDP协议SOCK_RAW:原始套接字protocol:通信协议,设置为0,由内核根据指定的类型和协议族使用默认的协议返回值:成功时,返回一个大于等于0的文件描述符:可以用文件读写函数来操作socket失败时,返回一个小于0的值关闭:close(socketfd)类似于关闭文件,回收资源intsockfd;sockfd=socket(AF_INET,SOCK_DGRAM,0);SchoolofComputerSicence地址的表示---sockaddr_in结构体//定义结构体变量servadd

structsockaddr_inservaddr;//初始化变量

memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=INADDR_ANY;

servaddr.sin_port=htons(9091);程序中,需要用一个数据结构保存地址信息,包括:端口号和IP地址等原本地址表示用的结构体是:sockaddr,但因为这个结构体使用不方便,人们又重新创建了个sockaddr_in结构体来代替sockaddrSchoolofComputerSicencesockaddr_in结构体structsockaddr_in{

shortintsin_family;/*地址族*/

unsignedshortintsin_port;/*端口号*/

structin_addrsin_addr;/*IP地址*/

unsignedcharsin_zero[8];/*凑数,为了使sockaddr_in和sockaddr长度相同*/};in_addr结构体:存放IP地址structin_addr{unsignedlongs_addr;//32-bit无符号长整形};即:ip地址本身是一个数,但平时使用的是字符串,如"6",把字符串赋值给需要转换,使用inet_aton函数(internetasctonum)如:inet_aton("")本机IP地址的赋值操作为:

结构体变量.sin_addr.s_addr=inet_aton("");或

sin_addr=INADDR_ANY;表示填入本机IP地址SchoolofComputerSicence初始化结构体memset:清零当sin_addr=INADDR_ANY时,填入本机IP地址端口号:除了系统保留的(1~1024),可以自己指定端口(1025~65535),但需要转换字节顺序(用htons函数)//定义结构体变量servadd

structsockaddr_inservaddr;//初始化变量

memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=INADDR_ANY;

servaddr.sin_port=htons(9091);SchoolofComputerSicence字节顺序不同的CPU有不同的字节顺序类型,这些字节顺序类型指的是整数在内存中保存的顺序,即主机字节顺序(HBO,HostByteOrder)常见的有两种:大端模式(big-endian):地址的高位存储值的低位,如部分MIPS,POWERPC机器小端模式(little-endian):地址的低位存储值的低位,如Intelx86机器以unsigned

int

value

=

0x12345678为例其值的低位和高位分别是?低位高位x86电脑上:addraddr+1addr+2addr+378563412大端电脑上:addraddr+1addr+2addr+312345678网络上有各种各样的机器,为保证解析正确性和可移植性,要统一顺序。host-to-network:hton():把主机顺序转换为网络顺序(大端顺序)network-to-host:ntoh():收到数据把时把网络字节转换为主机根据转换的数类型:short,long,共4个函数:htons,htonl,ntohs,ntohlSchoolofComputerSicence指定服务器端口号(1).HTTP协议代理服务器常用端口号:80/8080/3128/8081/9080(2).SOCKS代理协议服务器常用端口号:1080(3).FTP(文件传输)协议代理服务器常用端口号:21(4).Telnet(远程登录)协议代理服务器常用端口:23(5).SMTP/POP3:25/110小于256的端口作为保留端口通常自己指定的端口号可以大于1024structsockaddr_in

servaddr;memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=inet_aton("");

servaddr.sin_port=htons(9091);SchoolofComputerSicence绑定bindLinux下一切皆文件发送和接受网络数据,也是通过读写文件完成这里的文件,指的是"socket"。为了实现网络通信的目标,还需要把socket文件和IP地址,端口号等绑定(关联)起来。WriteSOCKET文件主机端口端口端口网络SchoolofComputerSicencebind()函数intbind(intsockfd,structsockaddr*my_addr,socklen_taddrlen);参数说明sockfd:调用socket返回的文件描述符my_addr:保存地址信息(IP地址和端口)addrlen:设置为sizeof(structsockaddr)返回值成功时,返回0失败时,返回-1(如端口被占用)intsockfd;

structsockaddr_inservaddr;sockfd=socket(AF_INET,SOCK_DGRAM,0);memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=inet_aton("");servaddr.sin_port=htons(9091);

bind(sockfd,(structsockaddr*)&servaddr,sizeof(servaddr));servaddr的类型是sockaddr_in,不是sockaddr*类型,做一个强制类型转换SchoolofComputerSicence接收网络信息一切就绪,等待从网络来的消息(读socket文件)intrecvfrom(intsockfd,void*buf,intlen,unsignedintflags,structsockaddr*from,int*fromlen);sockfd:将要从其接收数据的套接字buf:存放消息接收后的缓冲区len:buf所指缓冲区的容量flags:接收数据的一些参数,为0则为默认from:保存数据来源(ip,端口),如不需保存可以设为NULLfromlen:from的长度地址(注意是指针)成功执行时,返回接收到的字节数。另一端已关闭则返回0。失败返回-1。recvfrom默认是阻塞型structsockaddr_inservaddr;charbuff[1024];sinsize=sizeof(servaddr);

n=recvfrom(sockfd,buff,1024,0,NULL,&sinsize);SchoolofComputerSicenceRecvfrom应用举例执行到recvfrom,程序暂停,等待从端口过来消息。有信息到来,把消息存放到缓冲区,并解析包,把源地址和端口存放到相应变量中,然后继续往下执行如果一次到来的消息缓冲区放不下,则丢弃多余的包charbuff[4096];structsockaddr_inservaddr;//服务器地址信息structsockaddr_inclientaddr;//客户端地址,用来保存从哪发过来的size=sizeof(sockaddr);n=recvfrom(sockfd,buff,4096,0,(structsockaddr*)&clientaddr,&size);buff[n]='\0';printf("recvmsgfromclient:%s\n",buff);printf("消息来自于IP:%s\n",inet_ntoa(clientaddr.sin_addr));

……在64位系统上,要用#include<arpa/inet.h>,inet_ntoa才能正常运行SchoolofComputerSicenceIP地址转换将点分十进制字符串转换成长整型数inet_addr("")inet_aton("")intinet_pton(intaf,constchar*src,void*dst);inet_pton(AF_INET,ip,&servaddr.sin_addr);第一个参数af是地址族,转换后存在dst中将长整型IP地址转换成点分字符串char*inet_ntoa(structin_addraddr)如inet_ntoa(clientaddr.sin_addr)constchar*inet_ntop(intaf,constvoid*src,char*dst,socklen_tlen);把src指向的in_addr数字地址转换为字符串,len:dst的长度推荐使用inet_pton和inet_ntop,其他函数为不可重入函数SchoolofComputerSicence完善UDP服务器自己动手完善服务器1.输出客户端信息2.给recvfrom加上循环,使服务器能一直接受客户端连接好习惯是不要省略函数返回值的判断,否则出错时不容易定位......#include<arpa/inet.h>intmain(intargc,char**argv){intsockfd;structsockaddr_inservaddr,clientAddr;charbuff[1024];intn,sinsize;sockfd=socket(AF_INET,SOCK_DGRAM,0);

//判断socket是否创建成功memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=inet_aton("");servaddr.sin_port=htons(9091);bind(sockfd,(structsockaddr*)&servaddr,sizeof(servaddr));

//判断bind是否绑定成功sinsize=sizeof(servaddr);

//修改recvfrom,填入客户端地址

//n=recvfrom(sockfd,buff,1024,0,NULL,&sinsize);buff[n]='\0';

//输出客户端IP地址,和端口号(clientAddr.sin_port)SchoolofComputerSicenceUDP客户端发送数据的步骤建立socket发送数据sendto:给出服务器/目标端的IP地址和端口号,以及要发送数据的首地址说明:不需要绑定bind,在发送数据时系统自动随机选择一个端口发送数据SchoolofComputerSicencesendto发送数据函数函数原型intsendto(intsockfd,constvoid*msg,intlen,unsignedintflags,conststructsockaddr*to,inttolen);参数解释sockfd:同recvfrom,获得的socket文件句柄msg:要发送数据的指针len:数据长度flags:一些参数to:发送的目的地tolen:to结构体的长度返回值成功时,返回实际发送的数据的字节数失败时,返回-1SchoolofComputerSicence完善UDP客户端修改程序:1.从键盘输入字符串发送2.给sendto加上循环,可以循环发送(*)给main函数带参数,可以给不同IP的服务器发送#include…...intmain(intargc,char**argv){intsockfd,n;charsendline[1024];structsockaddr_inservaddr;sockfd=socket(AF_INET,SOCK_DGRAM,0);

//判断socket是否创建成功memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(9091);servaddr.sin_addr.s_addr=inet_addr("");n=sizeof(structsockaddr);

//从键盘输入一个字符串发送

//sendto(sockfd,.....,...,0,(structsockaddr*)&servaddr,n);

close(sockfd);exit(0);}SchoolofComputerSicence几点注意事项字符串定义和输入charstr[80]输入使用fgets(str,80,stdin),在linux下用gets会有警告,因gets不安全main函数参数使用argc:参数个数,命令本身算一个参数

servaddr.sin_addr.s_addr=inet_addr(argv[1]);argv[1]代表第一个参数,如./a.out

其中的“”就是argv[1]SchoolofComputerSicenceUDP实现一个“小”文件传输文件传输和消息发送原理一样服务器端:收到信息,写入文件FILE*fout=fopen("test_rec","wb");n=recvfrom(.......);fwrite(buff,1,n,fout)//把n个字节的buff写入到fout中....fclose(fout);//关闭文件客户端:从小文件中读取数据---abc.txt要少于1024字节FILE*fin=fopen("abc.txt","rb");//打开文件abc.txtt=fread(buff,1,1024,fin);//从文件中读数据到buff中sendto(....,buff,t....);//发送t个字节到服务器fclose(fin)SchoolofComputerSicence传输大文件循环发送:while((t=fread(buff,1,1024,fin))>0)当文件没结束(读的字节数>0)时,循环发送循环接收:当客户端结束传送时,服务器端应结束接收。以下服务器端结束的条件可行吗?自行验证,若不可行,请提出办法while(n>0){n=recvfrom(.......);fwrite(buff,1,n,fout)}........SchoolofComputerSicence思考UDP协议传输文件有何缺点?SchoolofComputerSicence练习:写一个聊天程序即可以收,又可以发一个程序,不分客户端和服务器端,都是一样的。运行在2台电脑上,可以聊天,如编译的程序名为talk:./talk5表示和某个IP的电脑聊天如果是同一台电脑上的两个终端,无法用一个程序完成(因为不能都绑定同一个端口),只需要修改下端口号重新编译运行即可思路:用多进程----一个进程用于发送,一个进程用于接收多线程也类似SchoolofComputerSicence*sendfile#include<sys/sendfile.h>ssize_tsendfile(intout_fd,intin_fd,off_t*offset,size_tcount);将一个本地文件通过socket发送出去通常的做法是:打开文件fd和一个socket,然后循环地从文件fd中read数据,并将读取的数据send到socket中。这样,每次读写我们都需要两次系统调用,并且数据会被从内核拷贝到用户空间(read),再从用户空间拷贝到内核(send)sendfile就将整个发送过程封装在一个系统调用中,避免了多次系统调用,避免了数据在内核空间和用户空间之间的大量拷贝(零拷贝机制)。可以通过sendfile提高效率SchoolofComputerSicence聊天程序结构初始化同前UDP程序和udps,udpc一样,但不绑定端口(是为了让接收和发送用不同的端口)fork子进程:发送,同前可以有bind,也可以没有父进程:接收注意:把bind放到父进程中来调试运行udps.c和udpc.c,在可以循环接收和循环发送的基础上,对其中某个程序进行修改(添加fork)即可,注意pid=fork()的位置,对structsockaddr_in变量赋值的代码父子进程可以共用,不用再写一遍intpid;.....pid=fork();if(pid==0){while(1){

input...

sendto....

}}else{bind....while(1){

recvfrom.....printf

}}SchoolofComputerSicenceUDPUserDatagramProtocol,用户数据包协议,提供面向事务的简单不可靠信息传送服务UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的;同样,服务器也无法知道客户端的状态在网络质量较差的环境下,UDP协议数据包丢失会比较严重。但是由于UDP的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多SchoolofComputerSicenceTCP通信使用UDP协议发送数据,程序中直接用sendto发送,没有确认服务器已经就绪TCP:在通信之前需要建立连接(三次握手)服务器流程:1.socket:使用SOCKET_STREAM,其他同UDPsocket(AF_INET,SOCKET_STREAM,0)2.bind:同UDP3.listen:监听,等待客户端发送连接请求4.accept:如果条件允许,接受请求,向客户端发送响应5.recv:接受数据(和recvfrom类似)6.处理收到的信息SchoolofComputerSicenceTCP服务器的listen函数函数原型intlisten(intsockfd,intbacklog);参数说明sockfd:调用socket返回的文件描述符backlog:accept应答之前,允许在进入队列中等待的连接数目,出错时返回-1(此数目=未完成连接客户端数+已完成连接的客户端数),如果两个队列都是满的,tcp就忽略客户端的同步SYN信号(但不发送RST信号,否则会导致客户端connect出错。忽略,客户端超时会重发)返回值成功时,返回0失败时,返回-1说明服务器使用listen后,套接字从CLOSED状态变为LISTEN状态,可以接受连接在使用listen()之前,需要调用bind()绑定到需要的端口三次握手是内核负责完成的(由客户端发起)SchoolofComputerSicenceTCP的accept函数建立套接字连接,从建立的连接队列中取一个处理,默认是阻塞型的(如果没有已完成三次握手的队列,则等待)函数原型intaccept(intsockfd,structvoid*addr,socklen_t*addrlen);参数说明sockfd:正在监听端口的套接字文件描述符(调用socket()函数生成的)addr:指向本地数据结构sockaddr_in的指针,当连接成功时,会填入连入的远程主机(客户端)地址信息addrlen:设置为sizeof(structsockaddr_in)的变量的地址返回值:成功:返回已连接的socket描述字失败:返回-1SchoolofComputerSicence服务器的最大连接数是否是由listen(intsockfd,intbacklog)中的backlog决定?比如backlog为5,是否表示最多5个客户端连接到服务器?SchoolofComputerSicenceaccept的两个socket文件描述符一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。intmain(){intsockfd1,sockfd2;structsockaddr_inraddr;/*客户端地址信息*/

sockfd1=socket(AF_INET,SOCK_STREAM,0); ……s=sizeof(structsockaddr_in);

while(1){

sockfd2=accept(sockfd,(structsockaddr*)&raddr,&s); ……recv(sockfd2….)使用accept返回的socket//处理完成后关闭sockfd2}

SchoolofComputerSicencerecv函数功能通过socket接收数据函数原型ssize_trecv(intsockfd,void*buf,size_tlen,intflags);参数说明sockfd:要读的连接SOCKET描述符(accept函数的返回值)buf:要读的信息的缓冲区len:缓冲的最大长度flags:一般设置为0返回值成功时,返回实际接收到的数据的字节数失败时,返回-1。如果正常关闭了连接,返回为0和recvfrom类似,由于是TCP,已经建立了连接信息,所以不必再写客户端的IP地址和端口号SchoolofComputerSicence简单的TCP服务器tcps.c#main(){intlistenfd,connfd;structsockaddr_inservaddr,clientaddr;charbuff[4096];intn;intsinsize;

.......close(connfd);close(listenfd);}listenfd=socket(AF_INET,SOCK_STREAM,0);memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=htonl(INADDR_ANY);servaddr.sin_port=8888;bind(listenfd,(structsockaddr*)&servaddr,sizeof(servaddr));listen(listenfd,10);sinsize=sizeof(clientaddr);connfd=accept(listenfd,(structsockaddr*)&clientaddr,&sinsize);n=recv(connfd,buff,4096,0);buff[n]='\0';printf("recvmsgfromclient:%s\n",buff);SchoolofComputerSicenceTCP客户端普通流程1.建立socket,同服务器2.connect和服务器连接3.send发送消息(或接受消息)4.关闭SchoolofComputerSicenceTCP客户端connect函数功能建立套接字连接#include<sys/socket.h>函数原型intconnect(intsockfd,conststructsockaddr*serv_addr,socklen_taddrlen);参数说明sockfd:调用socket返回的文件描述符serv_addr:远程主机IP地址和端口addrlen:设置为sizeof(structsockaddr)返回值成功时,返回0。因为是阻塞模式,可能超时,具体时间由内核设置决定。失败时,返回负数,具体的错误代码存放在errorno中SchoolofComputerSicenceTCP客户端send函数通过socket发送数据函数原型ssize_tsend(intsockfd,constvoid*buf,size_tlen,intflags);参数说明sockfd:发送数据的套接字描述符msg:指向发送数据的指针len:数据长度flags:一般设置为0返回值成功时,返回实际发送的数据的字节数失败时,返回-1。返回SOCKET_ERROR表示网络断开了。SchoolofComputerSicencesend函数send先比较要发送数据的长度nbytes和套接字sockfd的发送缓冲区的长度buf,如果nbytes>buf该函数返回SOCKET_ERROR系统提供的socket缓冲区大小为8K,可以设置为大一些,尤其在传输实时视频时注意:并不是send把套接字的发送缓冲区中的数据传到连接的另一端的,而是协议传送的,send仅仅是把buf中的数据copy到套接字sockfd的发送缓冲区的剩余空间里send函数把buff中的数据成功copy到sockfd的改善缓冲区的剩余空间后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个socket函数就会返回SOCKET_ERROR。每一个除send的socket函数在执行的最开始总要先等待套接字的发送缓冲区中的数据被协议传递完毕才能继续,如果在等待时出现网络错误那么该socket函数就返回SOCKET_ERRORSchoolofComputerSicence简单的tcp客户端intmain(intargc,char**argv){intsockfd,n;structsockaddr_inservaddr;sockfd=socket(AF_INET,SOCK_STREAM,0);memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=8888;servaddr.sin_addr.s_addr=inet_addr("");connect(sockfd,(structsockaddr*)&servaddr,sizeof(servaddr));send(sockfd,"hello",5,0);close(sockfd);exit(0);}SchoolofComputerSicence练习完善TCP服务器端和客户端程序服务器端和客户端都改为循环模式注意循环位置思考UDP和TCP的效率,用途,区别和联系

socket(...);

bind(...);

listen(...);

while(1){

accept(...);

{

recv(...);

process(...);

}

close(...);

}SchoolofComputerSicence综合实例:UDP实现收发消息程序功能:客户端可以向服务器发送命令如发送:TIME时,服务器返回服务器的时间给客户端,客户端显示如发送:DATA时,服务器返回字符串“HELLO”给客户端显示如发送:END时,服务器结束服务器循环接收命令客户端循环发送命令使用UDP实现SchoolofComputerSicence程序框架-服务器初始化socket,地址,端口等绑定套接字while(字符串buff!=“END”){缓冲区buff清零recvfrom接收数据如果收到数据>0如果数据是TIME给字符串赋值为当前时间:sprintf(buff,"%s",....)发送给客户端sendto如果数据是DATA给字符串赋值为HELLO发送给客户端关闭套接字,结束SchoolofComputerSicence程序框架-客户端初始化socket,地址,端口等while(字符串buff!=“END”){提示输入字符串,并读入发送给服务器sendto清空接受缓冲区(字符串)接收数据recvfrom如果接收的数据>0打印收到的数据关闭套接字,退出SchoolofComputerSicence改为TCP协议实现-服务器端如果用TCP实现收发,过程和UDP类似:初始化socket,地址,端口等,绑定套接字监听本地端口listenwhile(字符串buff!=“END”){接收客户端的连接accept(....),得到连接套接字socketConrecv接收数据如果收到数据>0如果数据是TIME给字符串赋值为当前时间:sprintf(buff,"%s",....)发送给客户端sendto关闭连接套接字socketCon

关闭套接字,结束注意:accept在循环体内每来一个客户端,产生一个新的socket处理完客户连接后,关闭socket再处理下一个客户连接SchoolofComputerSicence改为TCP协议实现-客户端初始化socket,地址,端口等while(字符串buff!=“END”){提示输入字符串,并读入发送给服务器send清空接受缓冲区(字符串)接收数据recv如果接收的数据>0打印收到的数据关闭套接字,退出连接服务器connect(....)??根据服务器的处理情况服务器如果关闭,则重连SchoolofComputerSicence扩展练习:编写“聊天”程序:即可以接收,也可以发送如:使用UDP,则程序是对等的,不分客户端服务器端编写一个ftp程序能够传输文件和传递消息一样,从文件中读数据,然后发送,直到文件结束用TCP还是UDP?SchoolofComputerSicence一个简单的Web服务器Web服务器原理客户端是“浏览器”,当在浏览器输入网址如":8080/index.html",浏览器会向指定IP的服务器的8080端口发送请求,具体请求类似于GET/index.htmlHTTP/1.1Host::8848User-Agent:Mozilla/5.0(X11;U;Linuxi686;zh-CN;rv:)Gecko/20060313Fedora/-9Firefox/pango-textAccept:text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5Accept-Language:zh-cn,zh;q=0.5Accept-Encoding:gzip,deflateAccept-Charset:gb2312,utf-8;q=0.7,*;q=0.7Keep-Alive:300Connection:keep-aliveSchoolofComputerSicenceWeb服务器原理服务器收到请求,分析请求,根据客户端的要求响应,即向浏览器发送一些信息,包括:状态头,响应头,实体等,如HTTP/1.1200OKCache-Control:privateContent-Type:text/html;charset=UTF-8Content-Encoding:gzipServer:GWS/2.1Content-Length:1851Date:Sat,14Oct200611:33:39GMT<html><head>.....浏览器收到响应信息,根据信息进行解析,将html显示出来SchoolofComputerSicenceweb服务器模拟的简化1、忽略浏览器的具体请求当浏览器请求时,给浏览器返回一个指定的html文件,如/var/www/index.html2、响应头简化可以只要状态和类型,如sprintf(buf,"HTTP/1.0200OK\r\n");sprintf(buf,"%sContent-type:%s\r\n\r\n",buf,"text/html");3、单进程单线程完成SchoolofComputerSicenceweb服务器框架总体思路基于TCP协议的服务器1.初始化服务器:绑定端口,监听2.无限等待,并响应while(1)接受连接接收数据读index.html文件把响应头+index.html文件发送给客户端关闭连接套接字SchoolofComputerSicence服务器模型总体可分为C/S和P2PC/S:客户端软件向服务器发出请求,服务器然后对客户端请求做出响应,在这种情况下,如果客户端越多,此时服务器的压力就越大P2P技术实现的每台计算机既是客户端,也是服务器,他们的功能都是对等的(BT、电驴、迅雷、QQ、MSN和PPlive等都是基于P2P方式实现的软件)用户之间传输多,网络负担将加重主机之间很难发现(配备发现服务器或索引服务器)SchoolofComputerSicence服务器基本框架I/O处理单元等待接受客户连接,可以是一个专门的接入服务器,实现负载均衡逻辑处理单元进程或线程,分析处理数据,然后发给IO或客户端网络存储单元(可选)如果需要,可以是独立的数据库,缓存或文件服务器请求队列(*)各个逻辑单元的抽象,如IO处理,通知某逻辑处理单元;多个逻辑处理单元访问存储,需要同步SchoolofComputerSicence简单的TCP并发服务器模型--多进程进程缓冲池:预先开一些进程来处理可能到来的连接//main函数

s=socket(...);

bind(s,...);

listen(s,...);

//处理客户端的连接

for(i=0;i<预指定进程数;i++){

pid[i]=fork(); if(pid[i]==0) 处理连接:handle(s)}close(s)//处理连接函数voidhandle(ints){

while(1){news=accept(s,.....);.....接收recv(news,....)处理....close(news);}客户端1accept()recv()处理数据客户端2accept()recv()处理数据服务器子进程服务器子进程accept()recv()处理数据服务器子进程SchoolofComputerSicence另一种并发模型统一accept当客户端连接请求到来时,临时fork子进程处理将连接请求和业务处理分离//main函数

s=socket(...);

bind(s,...);

listen(s,...);

//处理客户端的连接

while(1){

s_c=accept(s,.....); if(fork()==0) 处理连接:handle(s_c)elseclose(s_c);}close(s)//处理函数voidhandle(ints_c){.....接收recv(s_c,....)处理....close(s);}这种模型很容易改为“多线程”模型创建线程pthread_create(..handle...)

线程函数SchoolofComputerSicence多线程并发服务器-线程池模型和多进程一样,但因为线程是共享资源(socket文件句柄),为防止多个线程竞争,必须互斥使用互斥区只需要保护accept//main函数

s=socket(...);

bind(s,...);

listen(s,...);

//处理客户端的连接

for(i=0;i<预指定线程数;i++)

创建线程//线程函数void*handle(void*arg){

while(1){pthread_mutex_lock(&MU);s_c=accept(s,.....);

pthread_mutex_unlock(&MU);.....接收recv(s_c,....)处理....close(s_c);}SchoolofComputerSicenceTCP并发服务器--多线程示例线程比进程更节省资源功能描述客户端多线程:向服务器发送从标准输入得到的字符在另一个线程中将从服务器端返回的字符显示到标准输出服务器端多线程将客户端发来的数据原样返回给客户端,每一个客户在服务器上对应一个线程SchoolofComputerSicenceTCP多线程-客户端static intsockfd;//线程:负责键盘输入和发送void*cRecv(void*arg){intrs=0;charstr[80];while(1){scanf("%s",str);send(sockfd,str,80,0);}}intmain(intargc,char**argv){intn,rs;charstr[80],buf[4096];pthread_ttid;sockfd=socket(AF_INET,SOCK_STREAM,0);memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(8888);servaddr.sin_addr.s_addr=inet_addr("");connect(sockfd,(structsockaddr*)&servaddr,sizeof(servaddr));pthread_create(&tid,NULL,cRecv,NULL);while(1){rs=recv(sockfd,buff,4096,0);if(rs>0){buff[rs]='\0';printf("recvmsgfromserver:%s\n",buff);}}}tcpcMulthread.cSchoolofComputerSicenceTCP多线程-服务器端线程:循环接收来自客户端的信息并原样发回去void*handle(void*arg){intsockClient=*((int*)arg);intn=0;charbuff[4096];while(1){n=recv(sockClient,buff,4096,0);if(n>0){buff[n]='\0';printf("recvmsgfromclient:%s\n",buff);send(sockClient,buff,4096,0);}}}tcpsMulthread.cSchoolofComputerSicenceTCP多线程-服务器端统一的accept函数,来了新的客户端则开启新线程intmain(){intn,sinsize,listenfd,connfd;structsockaddr_inservaddr,clientaddr;charbuff[4096];pthread_tpid;listenfd=socket(AF_INET,SOCK_STREAM,0);memset(&servaddr,0,sizeof(servaddr));servaddr.......bind(listenfd,(structsockaddr*)&servaddr,sizeof(servaddr));listen(listenfd,50);while(1){connfd=accept(listenfd,(structsockaddr*)&clientaddr,&sinsize);if(connfd>0){pthread_create(&pid,NULL,handle,(void*)&connfd);}}}SchoolofComputerSicence综合练习写一个简单的网络猜数游戏客户端:运行程序,输入服务器IP地址,连接服务器,接收并显示服务器的信息:服务器会提示让用户输入一个数,并根据用户输入数的大小提示猜的数字大了还是小了,直到猜对为止服务器:当客户端连接时,产生一个随机数,并根据用户的输入发送适当的信息几点问题:UDP和TCP你选哪种协议来完成?随机数的问题:多个用户连接,是用同一个随机数,还是不同的?若不同的,当新的用户连接时,不要把前面产生的随机数给覆盖了为增加游戏的趣味性,服务器应保存客户的哪些信息?如何保存?SchoolofComputerSicence小结前面TCP/UDP中使用send/recv系列函数(I/O函数)来发、收信息,因为socket是文件,因此,可以改为使用write/read函数像文件一样进行操作,如read(fd,buff,1024)connect(),accept(),send(),recv()等阻塞情况读/recv:缓冲区没有数据,线程就一直睡眠直到数据来(数据到来由内核通知:数据先到内核,然后用读copy到应用层来)写/send:socket缓冲区没足够的空间accept:没有连接请求connect:服务器没应答之前(至少等待到服务器的一次往返时间)阻塞模式套接字简单,易于实现,适用于并发量小(客户端数目少),连续传输大数据量的情况下,如FTP;缺点但是当同时处理大量套接字时,采用多线程,系统开销大SchoolofComputerSicence阻塞模式的工作方式请求服务器客户1新线程线程:和客户1联系recv...send..请求客户2因为阻塞,线程不能马上完成;线程在处理完客户端请求后结束新线程......当很多个线程时,系统负担很大(Linux中1个线程栈默认是8M)SchoolofComputerSicence非阻塞模式的工作方式请求服务器客户1处理请求recv...send..请求客户2因为非阻塞,没收到数据不等待,继续往下执行......客户n轮询polling:如果把每个请求处理看成一个线程的话,这个线程很快就结束了SchoolofComputerSicence多路复用模式如何实现非阻塞IO模式sockfd=socket(AF_INET,SOCK_STREAM,0);fcntl(sockfd,

F_SETFL,

O_NONBLOCK);

将socket设置为非阻塞模式真正的轮询是不实际的(why?)轮询:当有事件发生时通知内核依次检测所有连接的socket(包括读,写),如果某个socket可以操作了(可以读了,或可以写了),通知用户linux提供select函数int

select(int

nfds,fd_set

*readfds,fd_set

*writefds,

fd_set

*except

fds,struct

timeval

*timeout)

函数参数:要读的socket集合,要写的socket集合,....,超时时间调用select函数时,进程会一直阻塞直到以下的一种情况发生.

1

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论