socket编程与线程模型.doc_第1页
socket编程与线程模型.doc_第2页
socket编程与线程模型.doc_第3页
socket编程与线程模型.doc_第4页
socket编程与线程模型.doc_第5页
已阅读5页,还剩17页未读 继续免费阅读

下载本文档

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

文档简介

socket编程与线程模型在软件的设计的过程中有些问题是涉及到winsock的问题,为了能够很好的设计线程模型,必须理解清楚socket的内部工作机制。为此,首先从外面开始分析。一、为什么使用多线程1、使用多线程是为了避免应用程序主界面在I/O操作中没有反应,出现假死机现象。Socket是一种特殊的I/O,所以很可能会出现这种现象。例如发送数据,或者连接服务器的时候。2、为了提高cpu利用率(在多cpu环境)和改善应用程序的并发性能。在多cpu环境,几个线程可以同时在不同的cpu上执行,从而提高了应用程序的效率。另外,应用程序有时候需要并发(包括单个cpu环境下的轮流执行)才能使得应用程序的行为比较流畅和连贯。例如收报,发报,报文处理三个工作如果交给一个线程完成,可能会造成报文处理的时候收报或者发报不能继续的结果。二、多线程带来的问题因为socket是I/O,所以,多个线程操作同一个I/O将会引发复杂的同步和互斥问题。如果处理不当,就会出现不可预知的结果。线程切换和管理会造成计算机效率的降低;线程所需的数据结构也是内存开销。 三、socket编程中的要点 1、socket基本结构 Winsock是windows系统上的一个网络通信API编程接口。TCP/IP协议栈只是winsock通信的一个子集,winsock还可以支持除了tcp/ip之外的其它协议栈。BSD socket是unix上tcp/ip协议栈的编程接口,所以winsock和BSD套接字包含的协议栈不一样。所以winsock编程中对于需要榜定的地址必须说明协议族和地址类型等。因为它可以支持很多通信协议。 winsock说明图中紫色的长方形代表数据缓冲区,网卡和协议栈都有缓冲区。数据到达以后,首先在网卡的缓冲区。这个时候,通过网卡驱动数据被拷贝到数据所属的协议栈的缓冲区。最后,应用程序可以从协议栈的缓冲区把数据取走。当应用程序发送数据,数据就会首先被缓存到协议栈的缓冲区,协议栈在适当的时候就会通过网卡驱动把数据拷贝到网卡的缓冲区,最后数据就被网卡驱动发送到物理网络上。但是需要明确,网卡的数据缓冲区比协议栈的小的多。所以,协议栈的缓冲区内容是不断的积累网卡缓冲区内容的结果。2、采用大缓冲区Winsock API可以让程序员设置整个协议栈缓冲区的大小。把这个缓冲区设置的大一点可以接受更多的客户同时发送数据,也可以支持暂时缓存应用程序发送的数据。也就是采用大缓冲区的时候,远端的发送程序不会因为协议栈缓存满而发送失败;本地的应用程序也不会因为缓存满而发送失败。或者在流式套接字的时候是发送被阻塞。3、采用重叠I/O采用重叠I/O可以提高应用程序收发数据的效率。如图所示,采用重叠I/O以后数据就会直接从网卡的数据缓冲区拷贝到应用程序的数据缓冲区,从而减少了协议栈的一个数据缓冲环节,消除了很多内存拷贝操作。从而提高了应用程序的效率。 overlapped IO四、无连接的winsock 1、概述网络中可以用一个三元组全局唯一地标志一个进程,这个三元组的结构是:(协议、本地地址、本地端口号)。这个三元组叫做一个半相关。一个完整的网间进程通信需要由两个进程组成,并且只能使用同一种高层协议(如tcp,udp)。就是说不可能一端用tcp,另一端用udp。因此,一个完整的网间通信需要一个五元组来标识:(协议、本地地址、本地端口好、远地地址、远地端口号)。这样一个五元组叫做全相关。也就是同一个协议的两个半相关才能组成一个全相关,也就是一个连接。 无连接的socketBind()与是否面向连接有关。产生一个socket以后,Bind()把套接字与本地的一个端口相关联。也就是进程在系统中为自己的通信登记一个地址。这个就类似于为一个服务指定一个电话号码,例如114查询服务或者一个客服热线。而创建一个socket的举措类似于建立一个服务,但是没有指定一个电话号码之前(Bind之前),客户无法与之通信。Bind以后,服务方必须让客户知道这项服务的号码,也就是一个半相关(协议、本地地址、本地端口号)。Bind()是显式绑定,客户端一般不一定要显式绑定,例如通过connect()、Sendto()等几个方法可以附带绑定,产生客户端半相关。如果客户端显式绑扎,那么客户端其实和一个服务端在概念上没有区别了。在多点对等通信模型中这一点很重要。首先发送方知道对方的地址,通过sendto()发送数据;接收方通过recvfrom()接收数据,通过这个函数的出口参数可以获知发送方的地址,然后就可以回送数据。所以客户端没有必要显式bind()一个地址。服务器方之所以需要bind()一下,是因为它首先调用recvfrom(),这个接口的参数要求一个已经帮扎了本地地址的socket。如果客户端也显式绑扎一个地址,它就具备了端到端的网络通信的能力了。 无连接的socket二如图所示的无连接socket编程模型,其客户端并没有使用Bind()显式提供半相关。它是通过Sendto和recvfrom进行附带绑扎。那么服务端如何回送数据呢?recvfrom()提供了一个出口参数,用来返回源地址,通过这个源地址,服务端可以回送数据到客户端。 2、socket详解 socket是一种特殊的I/O,所以socket类似于文件指针、文件句柄。通过socket可以写入和读取数据。 socket原理图socket这种I/O的特殊性在于创建了一个socket以后并不能马上进行数据读取或者写入操作。它必须和一定的地址联系起来才可以操作。从无连接的协议看看这个过程。sendto(),首先执行sendto()的一方必须要知道对方的地址,才能sendto(),也就是把数据写入一个socket。 无连接的socket首先是数据的接受方创建一个socket,然后把一个地址SockAddr1与这个特殊的I/O绑定。然后就可以从这个socket进行数据读取:recvfrom()。这个一般是服务器方。然后是客户端方面,它显然必须知道服务器端的SockAddr1这个地址才能写入数据。所以,它创建一个特殊的I/O - socket,然后就进行数据写入:sendto()。这个时候sendto除了向指定的地址写入数据,还要隐式的给这个socket绑定一个本地地址:SockAddr2。服务方通过recvfrom()除了读取到客户提供的数据以外,还可以通过出口参数获得发送者的地址。它使用sendto(),通过任何一个socket(用socket2或者新建一个,新建是浪费资源,就用socket2就可以了)向客户端回写一些数据。上述情况时客户端给新建的socket通过sendto()隐式绑定地址,这个地址是系统随机生成的。客户端也可以在sendto()之前采用bind()显式绑定一个地址,然后sendto()就会采用这个显式绑定的地址作为源地址,但是不鼓励这么做。总结:socket是一种特殊的I/O,通过这种I/O可以读写数据。Socket在使用的时候需要绑定一个地址,这个被绑定的地址就是应用程序用来读取的地址,也就是接收数据的地址。而应用程序写入数据的地址,也就是发送数据的地址并不是和socket需要绑定的,这个地址可以随意变换。所以,在无连接情况下,通过同一个socket可以向多个地址写入数据,但是读取数据的地址只有一个,那就是给这个socket绑定的地址。 无连接多点通信上图所示的例子我们可以认为上方为服务器。服务器创建soctet1,然后与地址SockAddr2显式绑扎。所以,SockAddr2就是服务器从socket1读取数据的地址,只有一个读取地址。然后,3个客户端知道服务器地址SockAddr2,它们向服务器写入数据,sendto()。然后,服务器通过recvfrom()获取数据,并且获知三个客户端的绑扎地址(隐式或者显式都可以)。服务器通过三个客户端的地址,通过同一个socket- socket1,向三个客户端分别写入数据。客户端通过recvfrom()得到服务器数据。对于一个socket,只能进行一次地址绑扎,即一次bind()。假如把上图认为是一个客户端向三个服务器首先进行sendto()写入数据,客户端并没有显式绑扎SockAddr2地址,那么客户端第一次调用sendto()的时候就会进行隐式绑扎,以后就不会。第一次绑扎分配的SockAddr2不会再改变,三个服务器通过recvfrom()获取到的源地址都是SockAddr2。广播:从以上原理,可以知道无连接协议的广播是socket的发送地址的一个特例,而与绑定地址和函数没有关系。只要设置sendto()的目的地址SockAddr为广播类型就可以了。 3、无连接socket与多线程无连接socket很灵活,可以通过同一个socket向很多个地址进行数据写入,从同一个地址进行数据读取。所以这种服务器的组织形式也会很灵活。比如,利用多线程共享同一个服务器端的socket,进行数据读取和写入。但是需要注意,socket是特殊的I/O,既然属于I/O,那么线程同步与互斥是非常重要的。因为它们读写socket的顺序将不能被保证,或者无法预料。理论上一个端口号对应于不同的缓冲区,也就是端口号是tcp/ip协议栈上数据缓冲区的句柄。五、有连接的socket 1、概述有连接的socket,其编程方法与无连接的客户端和服务器端有很大差别。 面向连接的socket需要说明的是,面向连接的socket变成模型中,服务器端创建一个socket,并把一个地址与这个socket显式绑扎。 面向连接的socket二为了详细的了解面向连接的socket,我们从accept()开始。accept()从指定的socket的连接请求等待队列里面取出第一个连接请求,然后它就返回新建的一个socket句柄。这个新建的socket可以完成本次取出的连接请求,并开始为它服务。这个被新建的socket具备和用于监听连接请求的socket一样的属性,包括与之一样的异步选择事件(用 WSAAsyncSelect 或者WSAEventSelect 函数选择的事件)。然后,由新建的socket为accept()本次取出的连接请求服务,而原来的监听请求的socket又可以回到监听状态。 那么面向连接的socket的通信细节和无连接的有何不一样呢?这个需要研究面向连接的socket使用的数据读取和写入接口和其它接口。accept(),接受客户端的连接请求,并生成一个 socket为这个客户服务。accept()的出口参数可以提供客户方的SockAddr,即地址。但是服务方返回一些数据没必要用这个地址,在面向连接的数据写入方法中,只需要一个socket就可以了, send()。而面向连接的数据读取方法recv()也只需要同一个socket就可以完成。所以这个通信过程细节如下:服务端创建一个特殊的I/O - socket,这个socket用来监听客户连接请求。所以,这个socket需要和服务端的本地地址帮扎。从前面知道,bind()地址就是从这个socket上读取数据的地址,不管是显式还是隐式。然后服务器调用listen(socket, num),num是一个表示连接请求队列的最大值的整数。对于这个socket上并发的连接请求(请求连接绑扎地址),服务器不能马上响应的,就会被缓存字这个队列,等待服务器处理。但是队列满了以后,到来的请求就会不能被响应。listen()是非常关键的一步,只有调用了这一步,服务器才能监听客户端请求。listen()以后服务器就调用accept(),提供一个出口参数可以获取请求方的地址。当指定的被accept的socket上的连接请求队列空,accept()会被阻塞。但是accept之前,服务器一直在listen请求。如果这个socket上的连接请求缓存队列有连接请求,那么accept()就会脱离阻塞状态执行。accept()新建一个socket为从队列中取出的当前请求服务,而被accept的socket,或者也就是被listen()的那个socket()继续返回到listen()状态。 面向连接的通信过程如上图,服务器端创建socket1套接字,然后必须把该套接字和一个本地地址sockaddr1进行显式绑扎,这样就可以从socket1上读取数据的,或者说别人可以发送数据到这个地址。随后,服务器在套接字socket1上调用listen(),进行请求的监听。listen()指定了请求缓冲队列的大小。listen之前的客户端连接请求connect()会失败。调用listen()之后,服务器调用accept()从连接请求队列中取出一个连接请求,进行服务。如果队列空,accept被阻塞。accept()从队列中取出一个请求,并创建一个为这个取出的请求服务的套接字newSocket,并从出口参数返回该请求的客户端地址ClientAddr1。newSocket具有两个特点,第一个是具备与socket1一样的属性,这就是说newSocket也是绑扎在地址sockaddr1,这也就是从它读取数据的地址。另外一个是newSocket与ClientAddr1也具备了联系,这个地址是把数据写入newSocket的地方。所以,不需要取出出口参数的ClientAddr1这个客户端地址,仅仅通过新的套接字newSocket服务器端就和某个特定的客户端建立了全相关,就可以读取或者写入数据了。再看客户端。客户端必须首先得到服务器的绑扎地址sockaddr1,客户端创建一个套接字socket2以后,就在该套接字上调用connect函数。connet()把一个本地地址ClientAddr1隐式绑扎到套接字socket2,作为数据接收地址。并且,connect把服务器地址sockaddr1和socket2联系起来。所以,通过socket2,客户端就可以进行读出和写入数据。 面向连接三如上图所示,全相关的建立过程。现在我们有个统一的观点,在一个socket上进行bind()一个本地地址(只能是本地地址才能被绑扎),就是本地程序在这个socket上的数据读取地址;但对通信的另一方来说就是数据的写入地址。服务器端为每一个不同的客户端产生一个newSocket进行服务,它们不同的地方就是这些newSocket具有不同的数据写入地址。但是具有一致的绑扎地址,尽管如此,不同的客户端发送的数据不会混淆,看来读取地址与socket句柄有关,所以不同的newSocket虽然具备同一个读取地址,但是会读到各自的数据。客户端提前知道服务端地址,这是客户端的写地址。通过connect()请求,connect()隐式给客户端绑扎一个本地地址作为读取地址,并且显示绑扎服务端地址作为发送地址。服务器接受请求,并取得客户端地址,作为写地址。这样一来,双方的socket都具备了读写能力,所以建立了一个数据连接通道。 2、socket地址根据前面的分析,我们可以认为soket句柄和本地的绑定地址共同确定了协议栈上的数据接收缓冲区或者read缓冲区。而协议栈上的写缓冲区或者发送是被公用的(但是不同的协议无法公用,例如tcp和udp)。所以,对同一个地址,不同的socket可以收到不同的内容。但是对一个socket上的地址绑扎,无论是显式还是隐式,只能进行一次。3、并发连接如果客户端掉用connect进行连接请求,多个客户端可能存在并发请求。服务器会把不能响应的请求缓存在listen()指定了大小的请求队列。这个时候被缓存了请求的客户端connect()方法会正确返回,并继续执行。但是会在 send和recv方法上被阻塞等待。如果并发数目大于连接请求的缓冲区大小,那么不能被缓存的那些连接connect()方法会返回(暂不清楚有没有返回错误)。但是在这个socket上调用send或者recv方法,就会返回错误结果。所以,如果连接请求的服务过程比较费时间,那么为了不至于被缓存请求的客户端长时间等待和另一些客户端连接失败,一般需要采用多线程方式。因为把服务交给子线程以后,主线程总有机会accept更多的请求。所以,除了把请求队列设置大一些,多线程也是改善服务的方法。4、connect()connect()有一个作用,从面向连接的例子看就是把一个数据发送或者写入地址帮顶到套接字上,从而使得该套接字绑定了两个地址。显式绑扎发送地址和隐式绑扎接收地址。无连接的协议也可以调用connect(),但是这种情况下connect()并不会向服务器进行连接请求。这个时候就是把一个地址显示绑扎到某个套接字,使它具备一个关联的数据发送地址。这样,无连接协议也可以使用send()和recv()在这样一个套接字上写如和读取数据。虽然已经绑扎了一个默认的发送地址,但是通过sendto()又可以把数据发送到非默认的地址。bind()用来显式给一个socket绑扎一个数据读取地址,这是本地地址。客户端不鼓励这种方式,而是采用隐式绑扎。但是如果显式绑扎了也不会错。什么是Socket Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。要学Internet上的TCP/IP网络编程,必须理解Socket接口。 Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话,就很容易了解Socket了。网络的 Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返 回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。常用的Socket类型有两种:流式Socket (SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据 报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。Socket建立为了建立Socket,程序可以调用Socket函数,该函数返回一个类似于文件描述符的句柄。socket函数原型为:int socket(int domain, int type, int protocol); domain指明所使用的协议族,通常为PF_INET,表示互联网协议族(TCP/IP协议族);type参数指定socket的类型: SOCK_STREAM 或SOCK_DGRAM,Socket接口还定义了原始Socket(SOCK_RAW),允许程序使用低层协议;protocol通常赋值0。 Socket()调用返回一个整型socket描述符,你可以在后面的调用使用它。Socket描述符是一个指向内部数据结构的指针,它指向描述符表入口。调用Socket函数时,socket执行体将建立一个Socket,实际上建立一个Socket意味着为一个Socket数据结构分配存储空间。Socket执行体为你管理描述符表。两个网络程序之间的一个网络连接包括五种信息:通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。Socket数据结构中包含这五种信息。Socket配置 通过socket调用返回一个socket描述符后,在使用socket进行网络传输以前,必须配置该socket。面向连接的socket客户端通过 调用Connect函数在socket数据结构中保存本地和远端信息。无连接socket的客户端和服务端以及面向连接socket的服务端通过调用 bind函数来配置本地信息。Bind函数将socket与本机上的一个端口相关联,随后你就可以在该端口监听服务请求。Bind函数原型为: int bind(int sockfd,struct sockaddr *my_addr, int addrlen); Sockfd是调用socket函数返回的socket描述符,my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针;addrlen常被设置为sizeof(struct sockaddr)。 struct sockaddr结构类型是用来保存socket信息的: struct sockaddr unsigned short sa_family; /* 地址族, AF_xxx */char sa_data14; /* 14 字节的协议地址 */; sa_family一般为AF_INET,代表Internet(TCP/IP)地址族;sa_data则包含该socket的IP地址和端口号。 另外还有一种结构类型: struct sockaddr_in short int sin_family; /* 地址族 */ unsigned short int sin_port; /* 端口号 */ struct in_addr sin_addr; /* IP地址 */ unsigned char sin_zero8; /* 填充0 以保持与struct sockaddr同样大小 */ ; 这个结构更方便使用。sin_zero用来将sockaddr_in结构填充到与struct sockaddr同样的长度,可以用bzero()或memset()函数将其置为零。指向sockaddr_in 的指针和指向sockaddr的指针可以相互转换,这意味着如果一个函数所需参数类型是sockaddr时,你可以在函数调用的时候将一个指向 sockaddr_in的指针转换为指向sockaddr的指针;或者相反。使用bind函数时,可以用下面的赋值实现自动获得本机IP地址和随机获取一个没有被占用的端口号: my_addr.sin_port = 0; /* 系统随机选择一个未被使用的端口号 */ my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本机IP地址 */通过将my_addr.sin_port置为0,函数会自动为你选择一个未占用的端口来使用。同样,通过将my_addr.sin_addr.s_addr置为INADDR_ANY,系统会自动填入本机IP地址。注意在使用bind函数是需要将sin_port和sin_addr转换成为网络字节优先顺序;而sin_addr则不需要转换。计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先。Internet上数据以高位字节优先顺序在网络上传输,所以对于在内部是以低位字节优先方式存储数据的机器,在Internet上传输数据时就需要进行转换,否则就会出现数据不一致。 下面是几个字节顺序转换函数:htonl():把32位值从主机字节序转换成网络字节序htons():把16位值从主机字节序转换成网络字节序ntohl():把32位值从网络字节序转换成主机字节序ntohs():把16位值从网络字节序转换成主机字节序 Bind()函数在成功被调用时返回0;出现错误时返回-1并将errno置为相应的错误号。需要注意的是,在调用bind函数时一般不要将端口号置为小于1024的值,因为1到1024是保留端口号,你可以选择大于1024中的任何一个没有被占用的端口号。连接建立面向连接的客户程序使用Connect函数来配置socket并与远端服务器建立一个TCP连接,其函数原型为: int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);Sockfd 是socket函数返回的socket描述符;serv_addr是包含远端主机IP地址和端口号的指针;addrlen是远端地质结构的长度。 Connect函数在出现错误时返回-1,并且设置errno为相应的错误码。进行客户端程序设计无须调用bind(),因为这种情况下只需知道目的机器 的IP地址,而客户通过哪个端口与服务器建立连接并不需要关心,socket执行体为你的程序自动选择一个未被占用的端口,并通知你的程序数据什么时候到 打断口。 Connect函数启动和远端主机的直接连接。只有面向连接的客户程序使用socket时才需要将此socket与远端主机相连。无连接协议从不建立直接连接。面向连接的服务器也从不启动一个连接,它只是被动的在协议端口监听客户的请求。 Listen函数使socket处于被动的监听模式,并为该socket建立一个输入数据队列,将到达的服务请求保存在此队列中,直到程序处理它们。 int listen(int sockfd, int backlog);Sockfd 是Socket系统调用返回的socket 描述符;backlog指定在请求队列中允许的最大请求数,进入的连接请求将在队列中等待accept()它们(参考下文)。Backlog对队列中等待 服务的请求的数目进行了限制,大多数系统缺省值为20。如果一个服务请求到来时,输入队列已满,该socket将拒绝连接请求,客户将收到一个出错信息。当出现错误时listen函数返回-1,并置相应的errno错误码。 accept()函数让服务器接收客户的连接请求。在建立好输入队列后,服务器就调用accept函数,然后睡眠并等待客户的连接请求。 int accept(int sockfd, void *addr, int *addrlen); sockfd是被监听的socket描述符,addr通常是一个指向sockaddr_in变量的指针,该变量用来存放提出连接请求服务的主机的信息(某 台主机从某个端口发出该请求);addrten通常为一个指向值为sizeof(struct sockaddr_in)的整型指针变量。出现错误时accept函数返回-1并置相应的errno值。首先,当accept函数监视的 socket收到连接请求时,socket执行体将建立一个新的socket,执行体将这个新socket和请求连接进程的地址联系起来,收到服务请求的 初始socket仍可以继续在以前的 socket上监听,同时可以在新的socket描述符上进行数据传输操作。数据传输 Send()和recv()这两个函数用于面向连接的socket上进行数据传输。 Send()函数原型为: int send(int sockfd, const void *msg, int len, int flags);Sockfd是你想用来传输数据的socket描述符;msg是一个指向要发送数据的指针;Len是以字节为单位的数据的长度;flags一般情况下置为0(关于该参数的用法可参照man手册)。 Send()函数返回实际上发送出的字节数,可能会少于你希望发送的数据。在程序中应该将send()的返回值与欲发送的字节数进行比较。当send()返回值与len不匹配时,应该对这种情况进行处理。char *msg = Hello!;int len, bytes_sent;len = strlen(msg);bytes_sent = send(sockfd, msg,len,0); recv()函数原型为: int recv(int sockfd,void *buf,int len,unsigned int flags); Sockfd是接受数据的socket描述符;buf 是存放接收数据的缓冲区;len是缓冲的长度。Flags也被置为0。Recv()返回实际上接收的字节数,当出现错误时,返回-1并置相应的errno值。Sendto()和recvfrom()用于在无连接的数据报socket方式下进行数据传输。由于本地socket并没有与远端机器建立连接,所以在发送数据时应指明目的地址。sendto()函数原型为:int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);该函数比send()函数多了两个参数,to表示目地机的IP地址和端口号信息,而tolen常常被赋值为sizeof (struct sockaddr)。Sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。 Recvfrom()函数原型为: int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen); from是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。fromlen常置为sizeof (struct sockaddr)。当recvfrom()返回时,fromlen包含实际存入from中的数据字节数。Recvfrom()函数返回接收到的字节数或 当出现错误时返回-1,并置相应的errno。如果你对数据报socket调用了connect()函数时,你也可以利用send()和recv()进行数据传输,但该socket仍然是数据报socket,并且利用传输层的UDP服务。但在发送或接收数据报时,内核会自动为之加上目地和源地址信息。结束传输当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据操作:close(sockfd);你也可以调用shutdown()函数来关闭该socket。该函数允许你只停止在某个方向上的数据传输,而一个方向上的数据传输继续进行。如你可以关闭某socket的写操作而允许继续在该socket上接受数据,直至读入所有数据。 int shutdown(int sockfd,int how); Sockfd是需要关闭的socket的描述符。参数 how允许为shutdown操作选择以下几种方式: 0-不允许继续接收数据 1-不允许继续发送数据 2-不允许继续发送和接收数据, 均为允许则调用close () shutdown在操作成功时返回0,在出现错误时返回-1并置相应errno。Socket编程实例 代码实例中的服务器通过socket连接向客户端发送字符串Hello, you are connected!。只要在服务器上运行该服务器软件,在客户端运行客户软件,客户端就会收到该字符串。 该服务器软件代码如下:#include #include #include #include #include #include #include #include #define SERVPORT 3333 /*服务器监听端口号 */#define BACKLOG 10 /* 最大同时连接请求数 */main() int sockfd,client_fd; /*sock_fd:监听socket;client_fd:数据传输socket */ struct sockaddr_in my_addr; /* 本机地址信息 */ struct sockaddr_in remote_addr; /* 客户端地址信息 */ if (sockfd = socket(AF_INET, SOCK_STREAM, 0) = -1) perror(socket创建出错!); exit(1); my_addr.sin_family=AF_INET; my_addr.sin_port=htons(SERVPORT); my_addr.sin_addr.s_addr = INADDR_ANY; bzero(&(my_addr.sin_zero),8); if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr) = -1) perror(bind出错!); exit(1); if (listen(sockfd, BACKLOG) = -1) perror(listen出错!); exit(1); while(1) sin_size = sizeof(struct sockaddr_in); if (client_fd = accept(sockfd, (struct sockaddr *)&remote_addr, &sin_size) = -1) perror(accept出错); continue; printf(received a connection from %sn, inet_ntoa(remote_addr.sin_addr); if (!fork() /* 子进程代码段 */ if (send(client_fd, Hello, you are connected!n, 26, 0) = -1) perror(send出错!); close(client_fd); exit(0); close(client_fd); 服务器的工作流程是这样的:首先调用socket函数创建一个Socket,然后调用bind函数将其与本机地址以及一个本地端口号绑定,然后调用 listen在相应的socket上监听,当accpet接收到一个连接服务请求时,将生成一个新的socket。服务器显示该客户机的IP地址,并通过 新的socket向客户端发送字符串Hello,you are connected!。最后关闭该socket。代码实例中的fork()函数生成一个子进程来处理数据传输部分,fork()语句对于子进程返回的值为0。所以包含fork函数的if语句是子进程代码部分,它与if语句后面的父进程代码部分是并发执行的。客户端程序代码如下:#include#include #include #include #include #include #include #include #define SERVPORT 3333#define MAXDATASIZE 100 /*每次最大数据传输量 */main(int argc, char *argv) int sockfd, recvbytes; char bufMAXDATASIZE; struct hostent *host; struct sockaddr_in serv_addr; if (argc h_addr); bzero(&(serv_addr.sin_zero),8); if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr) = -1) perror(connect出错!);exit(1); if (recvbytes=recv(sockfd, buf, MAXDATASIZE, 0) =-1) perror(recv出错!);exit(1); bufrecvbytes = 0; printf(Received: %s,buf); close(sockfd);客户端程序首先通过服务器域名获得服务器的IP地址,然后创建一个socket,调用connect函数与服务器建立连接,连接成功之后接收从服务器发送过来的数据,最后关闭socket。函数gethostbyname()是完成域名转换的。由于IP地址难以记忆和读写,所以为了方便,人们常常用域名来表示主机,这就需要进行域名和IP地址的转换。函数原型为: struct hostent *gethostbyname(const char *name); 函数返回为hosten的结构类型,它的定义如下: struct hostent char *h_name; /* 主机的官方域名 */ char *h_aliases; /* 一个以NULL结尾的主机别名数组 */ int h_addrtype; /* 返回的地址类型,在Internet环境下为AF-INET */ int h_length; /* 地址的字节长度 */ char *h_addr_list; /* 一个以0结尾的数组,包含该主机的所有地址*/ ; #define h_addr h_addr_list0 /*在h-addr-list中的第一个地址*/ 当 gethostname()调用成功时,返回指向struct hosten的指针,当调用失败时返回-1。当调用gethostbyname时,你不能使用perror()函数来输出错误信息,而应该使用herror()函数来输出。无连接的客户/服务器程序的在原理上和连接的客户/服务器是一样的,两者的区别在于无连接的客户/服务器中的客户一般不需要建立连接,而且在发送接收数据时,需要指定远端机的地址。阻塞和非阻塞 阻塞函数在完成其指定的任务以前不允许程序调用另一个函数。例如,程序执行一个读数据的函数调用时,在此函数完成读操作以前将不会执行下一程序语句

温馨提示

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

评论

0/150

提交评论