电脑开机显示屏黑屏几秒黑屏,显示屏关开一次,电脑工作8秒变黑屏,运行急救箱多次电脑正常了,如何恢复

随笔 - 96&
文章 - 255&
trackbacks - 0
2930123456791113171819202122232425262728293031123456789
长期寻找对战略游戏感兴趣的合作伙伴。
留言簿(20)
SDL相关网站
提供了丰富和基础的SDL入门教程
更新消息,相关下载和学习资料
SDL项目扩展库下载
我的个人网页
欢迎大家来论坛讨论问题
“最终的胜利”及其引擎,UVi小游戏下载。
我更新最勤的博客
我的小游戏
爱情小测试
SDL官方推荐教程(英)
彻底解密C++宽字符系列合集,包括介绍libiconv和gettext
积分与排名
阅读排行榜
评论排行榜
作者:龙飞&&&&&&& 前面一小节,我们已经写出了TcpServer的构造函数。这个函数的实际作用,就是创建了listen socket(监听嵌套字)。这一节,我们来具体分析这个创建的过程。socket和sockaddr的创建是可以相互独立的&&&&&&& 在函数中,我们首先通过socket()系统调用创建了listenSock,然后通过为结构体赋值的方法具体定义了服务器端的sockaddr。(memset()函数的作用是把某个内存段的空间设定为某值,这里是清零。)其他的概念已经在前一小节讲完了。这里需要补充的是说明宏定义INADDR_ANY。这里的意思是使用本机所有可用的IP地址。当然,如果你机器绑定了多个IP地址,你也可以指定使用哪一个。数据流简易模型(SOCK_STREAM)&&&&&&& 我们的例子以电话做的比喻,实际上,socket stream模型不完全类似电话,它至少有以下这些特点:1、一种持续性的连接。这点跟电话是类似的,也可以想象成流动着液体的水管。一旦断开,这种流动就会中断。2、数据包的发送实际上是非连续的。这个世界上有什么事物是真正的线性连续的?呵呵,扯远了,这貌似一个哲学问题。我们仅仅需要知道的是,一个数据包不可能是无限大的,所以,总是一个小数据包一个小数据包这样的发送的。这一点,又有点像邮包的传递。这些数据包到达与否,到达的先后次序本身是无法保证的,即是说,是IP协议无法保证的。但是stream形式的TCP协议,在IP之上,做了一定到达和到达顺序的保证。3、传送管道实际上是非封闭的。要不干嘛叫“网络”-_-!!!。我们之所以能保证数据包的“定点”传送,完全是依靠每个数据包都自带了目的地址信息。&&&&&&& 由此可见,虽然socket和sockaddr可以分别创建,并无依赖关系。但是在实际使用的时候,一个socket至少会绑定一个本机的sockaddr,没有自己的“地址信息”,就不能接受到网络上的数据包(至少在TCP协议里面是这样的)。socket与本机sockaddr的绑定&&&&&&& 有时候绑定是系统的任务,特别是当你不需要知道自己的IP地址和所使用的端口号的时候。但是,我们现在是建立服务器,你必须告诉客户端你的连接信息:IP和Port。所以,我们需要指明IP和Port,然后进行绑定。
int&bind(int&socket,&struct&sockaddr*&localAddress,&unsigned&int&addressLength);
作为C++的程序员,也许你会觉得这个函数很不友好,它似乎更应该写成:
int&bind_cpp_style(int&socket,&const&sockaddr&&localAddress);
我们需要通过函数原型指明两点:1、我们仅仅使用sockaddr结构的数据,但并不会对原有的数据进行修改;2、我们使用的是完整的结构体,而不仅仅是这个结构体的指针。(很显然光用指针是无法说明结构体大小的)幸运的是,在Linux的实现中,这个函数已经被写为:
#include&&sys/socket.h&/*&Give&the&socket&FD&the&local&address&ADDR&(which&is&LEN&bytes&long).&&*/extern&int&bind&(int&__fd,&__CONST_SOCKADDR_ARG&__addr,&socklen_t&__len)&&&&&__THROW;
看到亲切的const,我们就知道这个指针带入是没有“副作用”的。监听:listen()&&&&&&& stream流模型形式上是一种“持续性”的连接,这就是要求信息的流动是“可来可去”的。也就是说,stream流的socket除了绑定本机的sockaddr,还应该拥有对方sockaddr的信息。在listen()中,这“对方的sockaddr”就可以不是某一个特定的sockaddr。实际上,listen socket的目的是准备被动的接受来自“所有”sockaddr的请求。所以,listen()反而就不能指定某个特定的sockaddr。
int&listen(int&socket,&int&queueLimit);
其中第二个参数是等待队列的限制,一般设置在5-20。Linux中实现为:
#include&&sys/socket.h&/*&Prepare&to&accept&connections&on&socket&FD.&&&N&connection&requests&will&be&queued&before&further&requests&are&refused.&&&Returns&0&on&success,&-1&for&errors.&&*/extern&int&listen&(int&__fd,&int&__n)&__THROW;
完成了这一步,回到我们的例子,就像是让你小弟在电话机前做好了接电话的准备工作。需要再次强调的是,这些行为仅仅是改变了socket的状态,实际上我想强调的是,为什么这些函数不会造成block(阻塞)的原因。(block的概念以后再解释)
阅读(4129)
&re: Linux socket 编程入门(一)TCP server 端:5、创建监听嵌套字& 01:54&
你的文章太好了 把我带进了socket大门口,谢谢!&&&&&&
&re: socket 编程入门教程(一)TCP server 端:5、创建监听嵌套字& 21:42&
虽然对初学者的我来说,不是完全懂得,但说的这么清晰,是一个不错的socket入门教材&&&&&&1812人阅读
Linux学习(9)
绝大部分关于socket编程的教程总是从socket的概念开始讲起的。要知道,socket的初衷是个庞大的体系,TCP/IP只是这个庞大体系下一个很小的子集,而我们真正能用上的更是这个子集中的一小部分:运输层(Host-to-Host Transport Layer)的TCP和UDP协议,以及使用这两个协议进行应用层(Application Layer)的开发。即使是socket的核心部分,网络层(Internet Layer)的IP协议,在编程的时候我们也很少会感觉到它的存在&&因为已经被封装好了,我们唯一需要做的事情就是传入一个宏。第一节我想介绍的概念就这么多,当然,既然我们已经说了3个层了,我想最好还是把最后一个层也说出来,即所谓链路层(Network Access Layer),它包括了物理硬件和驱动程序。这四个层从底到高的顺序是:链路层--网络层--运输层--应用层。&&&&&&& 好,说实话我们现在并不清楚所谓TCP到底是什么东东,不过我们知道这东东名气很大。或许你早就知道,另外一个声名狼藉建立在TCP协议基础上的应用程序,它曾经几乎是统治了一个时代,即使是今天,我们依然无法消除他的影响力的&&恩,是的,就是telnet。&&&&&&& 在这个教程中,我使用的环境是Debian GNU/Linux 4.0 etch。传说中的stable -_-!!!,恩,我是很保守的人。如果你不是自己DIY出来的系统,相信默认安装里面就应该有telnet(/usr/bin/telnet,要是没装就自己aptitude install吧)。telnet可以与所有遵循TCP协议的服务器端进行通讯。通常,socket编程总是Client/Server形式的,因为有了telnet,我们可以先不考虑client的程序,我们先写一个支持TCP协议的server端,然后用telnet作为client验证我们的程序就好了。&&&&&&& server端的功能,我们也考虑一种最简单的反馈形式:echo。就如同你在终端输入echo 'Hello World',回车后shell就会给你返回Hello World一样,我们的第一个TCP server就用以实现这个功能。&&&&&&& 什么样的模型适合描述这样的一种server呢?我相信,一个很2的例子会有助于我们记忆TCP server端的基本流程。&&&&&&& 想象你自己是个小大佬,坐办公室(什么样的黑社会做办公室啊?可能是讨债公司吧^^)你很土,只有一个小弟帮你接电话(因为你自己的号码是不敢对外公开的)。一次通讯的流程大概应该是这样的:小弟那里的总机电话响了;小弟接起电话;对方说是你女朋友A妹;小弟转达说,&老大,你马子电话&;你说,接过来;小弟把电话接给你;你和你女朋友聊天半小时;挂电话。&&&&&&& 我们来分析一下整个过程中的元素。先分析成员数据(请注意,这里开始用C++术语了):你小弟(listenSock),你需要他来监听(listen,这是socket编程中的术语)电话;你自己(communicationSock),实际上打电话进行交流的是你自己;你的电话号码(servAddr),否则你女朋友怎么能找到你?你女朋友的电话号码(clntAddr),这个比喻有点牵强,因为事实上你接起电话,不需要知道对方的号码也可以通话(虽然事实上你应该是知道的,你不会取消了来电显示功能吧^^),但是,难道你是只接女朋友电话从来不打过去的牛人吗?这个过程中的行为(成员函数):你小弟接电话并转接给你(isAccept());你自己的通话(handleEcho())(这个行为确实比较土,只会乌鸦学舌的echo,呵呵)。&&&&&&& 简单的说,就是这些了。根据这个模型,我们可以很容易写出实现我们需要的echo功能的TCP server的类:
class&TcpServer{private:&&&&int&listenS&&&&int&communicationS&&&&sockaddr_in&servA&&&&sockaddr_in&clntApublic:&&&&TcpServer(int&listen_port);&&&&bool&isAccept();&&&&void&handleEcho();};
这里面有些简写,比如,sock实际上就是socket,addr就是address。serv和clnt我想你一定能猜到是server和client吧。还有一个socket中的结构体sockaddr_in,实际上就是这个意思:socket address internet(网络嵌套字地址),具体解说,请看下回分解。
二。socket与文件描述符
UNIX中的一切事物都是文件(everything&in Unix is a file!)&&&&&&& 当我在这篇教程中提到UNIX的时候,其意思专指符合UNIX标准的所谓&正统&UNIX的衍生系统(其实我就用来带指那些买了最初UNIX源代码的商业系统)操作系统和类似Linux,BSD这些类UNIX系统。如果某些要点是Linux特有的,或者因为本人孤陋寡闻暂时搞不清楚是Linux特有的还是UNIX通用的,我就会指明是Linux,甚至其发行版(我本人在写这篇教程的时候是以Debian GNU/Linux 4.0 etch为测试平台的)。&&&&&&& 我们学习UNIX的时候,恐怕听到的第一句话就是这句:UNIX中一切都是文件。这是UNIX的基本理念之一,也是一句很好的概括。比如,很多UNIX老鸟会举出个例子来,&你看,/dev/hdc是个文件,它实际上也是我的光盘&&&UNIX中的文件可以是:网络连接(network connection),输入输出(FIFO),管道(a pipe),终端(terminal),硬盘上的实际文件,或者其它任何东东。文件与文件描述符(file & file descriptor)&&&&&&& 你可能对上一章中建模类中的int还记忆犹新。我们用int在描述socket,实际上,所有的文件描述符都是int,没错,用的是一个整数类型。如果你觉得这样让你很难接受,那么恭喜你,你跟我一样,也许是深中C++面向对象思想的毒了^^。因为是int,所以文件描述符不可能是C++概念中的对象,因为int无法发出行为,但是,这并不代表也不能接受一个动作哈。&&&&&&& PASCAL之父在批判面向对象思想教条的时候,曾经生动的举了个例子,&在OOP的概念中,绝对不应该接受a+b这种表达的, OOP对这个问题的表达应该是a.add(b)&。fd(file descriptor)可以作为接受动作的对象,但是本身却无法发出动作,这就如同一个只能做宾语不能做主语的名词,是个不完整的对象。但是,请别忘了Linux和socket本身是C语言的产物,我们必须接受在面向过程时代下的产物,正视历史&&当然,这与我们自己再进行OOP的封装并不矛盾。&&&&&&& 我们应该记住3个已经打开的fd,0:标准输入(STDIN_FILENO);1:标准输出(STDOUT_FILENO);2:标准错误(STDERR_FILENO)。(以上宏定义在&unistd.h&中)一个最简单的使用fd的例子,就是使用&unistd.h&中的函数:write(1, "Hello, World!/n", 20);,在标准输出上显示&Hello, World!&。&&&&&&& 另外一个需要注意的问题是,file和fd并非一定是一一对应的。当一个file被多个程序调用的时候,会生成相互独立的fd。这个概念可以类比于C++中的引用(eg: int& rTmp =)。socket与file descriptor&&&&&&& 文件是应用程序与系统(包括特定硬件设备)之间的桥梁,而文件描述符就是应用程序使用这个&桥梁&的接口。在需要的时候,应用程序会向系统申请一个文件,然后将文件的描述符返回供程序使用。返回socket的文件通常被创建在/tmp或者/usr/tmp中。我们实际上不用关心这些文件,仅仅能够利用返回的socket描述符就可以了。&&&&&& 好了,说了这么多,实际上就解释了一个问题,&为什么socket的类型是int?& -_-!!!
三。sockaddr与sockaddr_in
收件人地址&&&&&&& 一家化妆品公司将一批新产品的样品,准备发给某学校某个班的女生们免费试用。通常情况下,这件邮包的地址上可以这么写:
收件人:全体女生。地址:A省B市C学校,X级Y班。
但是,如果在描述地址的时候这样写呢:
收件人:全体女生。地址:请打电话xxxxxxxx,找他们学校一个叫Lucy的女生,然后把东西送到她的班上。
这种文字是相当的诡异啊-_-!!!,但是并不等于就没有表述清楚邮包的去向和地址。事实上邮局看到这样的地址一定会发飙的,然而对于电脑,如果你的地址描述形式是他可以接受和执行的,他就会老老实实的按你的要求去做&&&&&&&&& 所以,如何描述地址不是问题的关键,关键在于这样的表述是不是能够表述清楚一个地址。一种更加通用的表达形式可能是这样的:
收件人:全体女生。地址:&一种地址描述方式&
&&&&&&& 事实上,在socket的通用address描述结构sockaddr中正是用这样的方式来进行地址描述的:
struct&sockaddr{&&&&unsigned&short&sa_&&&&char&sa_data[14];};
这是一个16字节大小的结构(2+14),sa_family可以认为是socket address family的缩写,也可能被简写成AF(Address Family),他就好像我们例子中那个&收件人:全体女生&一样,虽然事实上有很多AF的种类,但是我们这个教程中只用得上大名鼎鼎的internet家族AF_INET。另外的14字节是用来描述地址的。这是一种通用结构,事实上,当我们指定sa_family=AF_INET之后,sa_data的形式也就被固定了下来:最前端的2字节用于记录16位的端口,紧接着的4字节用于记录32位的IP地址,最后的8字节清空为零。这就是我们实际在构造sockaddr时候用到的结构sockaddr_in(意指socket address internet):
struct&sockaddr_in{&&&&unsigned&short&sin_&&&&unsigned&short&sin_&&&&struct&in_addr&sin_&&&&char&sin_zero[8];};
我想,sin_的意思,就是socket (address) internet吧,只不过把address省略掉了。sin_addr被定义成了一个结构,这个结构实际上就是:
struct&in_addr{&&&&unsigned&long&s_};
in_addr显然是internet address了,s_addr是什么意思呢?说实话我没猜出值得肯定的答案(根据下面网友的评论,其意思为source address,谢谢),也许就是socket address的意思吧,尽管跟更广义的sockaddr结构意思有所重复了。哎,这些都是历史原因,也许我是没有精力去考究了。sockaddr和sockaddr_in在Linux中的实现&&&&&&& 你可能还记得我之前说过,UNIX和Linux上的socket实现都是从BSD的socket实现演变过来的。事实上,socket这个词本来的意思,就是Berkeley Socket interface的简单说法。Linux上的socket与原本的socket的应该是完全兼容的,不过发展到今天,在代码实现上可能有些小的差别。我们就吹毛求疵的来看看这些区别在什么地方。
#include&&bits/socket.h&/*&Structure&describing&a&generic&socket&address.&&*/struct&sockaddr&&{&&&&__SOCKADDR_COMMON&(sa_);&&&&/*&Common&data:&address&family&and&length.&&*/&&&&char&sa_data[14];&&&&&&&&/*&Address&data.&&*/&&};//==============/*&POSIX.1g&specifies&this&type&name&for&the&`sa_family'&member.&&*/typedef&unsigned&short&int&sa_family_t;/*&This&macro&is&used&to&declare&the&initial&common&members&&&of&the&data&types&used&for&socket&addresses,&`struct&sockaddr',&&&`struct&sockaddr_in',&`struct&sockaddr_un',&etc.&&*/#define&&&&__SOCKADDR_COMMON(sa_prefix)&/&&sa_family_t&sa_prefix##family#define&__SOCKADDR_COMMON_SIZE&&&&(sizeof&(unsigned&short&int))
可以看到,转了几次typedef,几次宏定义,实际效果是与标准socket一样的。
#include&&netinet/in.h&/*&Internet&address.&&*/typedef&uint32_t&in_addr_t;struct&in_addr&&{&&&&in_addr_t&s_&&};//=================/*&Structure&describing&an&Internet&socket&address.&&*/struct&sockaddr_in&&{&&&&__SOCKADDR_COMMON&(sin_);&&&&in_port_t&sin_&&&&&&&&&&&&/*&Port&number.&&*/&&&&struct&in_addr&sin_&&&&&&&&/*&Internet&address.&&*/&&&&/*&Pad&to&size&of&`struct&sockaddr'.&&*/&&&&unsigned&char&sin_zero[sizeof&(struct&sockaddr)&-&&&&&&&&&&&&&&&__SOCKADDR_COMMON_SIZE&-&&&&&&&&&&&&&&&sizeof&(in_port_t)&-&&&&&&&&&&&&&&&sizeof&(struct&in_addr)];&&};
同样的,看起来挺复杂,实际上与标准socket的定义是一样的。头文件依赖关系&&&&&&& &bits/socket.h&是包含在&sys/socket.h&中的,&netinet/in.h&是包含在&arpa/inet.h&中的,实际上我们在程序中往往就是:
#include&&sys/socket.h&#include&&arpa/inet.h&
值得知道的是,ARPA是 Advanced research project agency(美国国防部高级研究计划暑)的所写,ARPANET是当今互联网的前身,所以我们就可以想象,为什么inet.h会在arpa目录下了。
四。构造函数涉及的概念
话题回到&黑社会办公室&的例子,讲概念已经扯得比较远了,不过,这一节我们还得讲概念,不过好在有些程序的例子。如果大家不想翻回去看TcpServer类的原型,我这里直接给出这个头文件的完整源代码:
//Filename:&TcpServerClass.hpp#ifndef&TCPSERVERCLASS_HPP_INCLUDED#define&TCPSERVERCLASS_HPP_INCLUDED#include&&unistd.h&#include&&iostream&#include&&sys/socket.h&#include&&arpa/inet.h&class&TcpServer{private:&&&&int&listenS&&&&int&communicationS&&&&sockaddr_in&servA&&&&sockaddr_in&clntApublic:&&&&TcpServer(int&listen_port);&&&&bool&isAccept();&&&&void&handleEcho();};#endif&//&TCPSERVERCLASS_HPP_INCLUDED
我们已经解释了为什么listenSock和communicationSock的类型是int,以及sockaddr_in是什么结构,现在来写这个类的构造函数:
TcpServer::TcpServer(int&listen_port){&&&&if&(&(listenSock&=&socket(PF_INET,&SOCK_STREAM,&IPPROTO_TCP))&&&0&)&{&&&&&&&&throw&"socket()&failed";&&&&}&&&&memset(&servAddr,&0,&sizeof(servAddr));&&&&servAddr.sin_family&=&AF_INET;&&&&servAddr.sin_addr.s_addr&=&htonl(INADDR_ANY);&&&&servAddr.sin_port&=&htons(listen_port);&&&&if&(&bind(listenSock,&(sockaddr*)&servAddr,&sizeof(servAddr))&&&0&)&{&&&&&&&&throw&"bind()&failed";&&&&}&&&&if&(&listen(listenSock,&10)&&&0&)&{&&&&&&&&throw&"listen()&failed";&&&&}}
好,先看看程序培养一下感觉,我们还得说概念。数据封装(Data Encapsutation)&&&&&&& 我们前面说到了网络分层:链路&&网络&&传输&&应用。数据从应用程序里诞生,传送到互联网上每一层都会进行一次封装:Data&&Application&&TCP/UDP&&IP&&OS(Driver, Kernel & Physical Address)我们用socket重点描述的是协议,包括网络协议(IP)和传输协议(TCP/UDP)。sockaddr重点描述的是地址,包括IP地址和TCP/UDP端口。socket()函数&&& 我们从TcpServer::TcpServer()函数可以看到,socket和sockaddr的产生是可以相互独立的。socket()的函数原型是:
int&socket(int&protocolFamily,&int&type,&int&protocol);
在Linux中的实现为:
#include&&sys/socket.h&/*&Create&a&new&socket&of&type&TYPE&in&domain&DOMAIN,&using&&&protocol&PROTOCOL.&&If&PROTOCOL&is&zero,&one&is&chosen&automatically.&&&Returns&a&file&descriptor&for&the&new&socket,&or&-1&for&errors.&&*/extern&int&socket&(int&__domain,&int&__type,&int&__protocol)&__THROW;
第一个参数是协议簇(Linux里面叫作域,意思一样的),还是那句话,我们这篇教程用到的就仅仅是一个PF_INET(protocol family : internet),很多时候你会发现人们也经常在这里赋值为AF_INET,事实上,当前,AF_INET就是PF_INET的一个#define,但是,写成PF_INET从语义上会更加严谨。这也就是TCP/IP协议簇中的IP协议(Internet Protocol),网络层的协议。后面两个参数定义传输层的协议。第二个参数是传输层协议类型,我们教程里用到的宏,只有两个:SOCK_STREAM(数据流格式)和SOCK_DGRAM(数据报格式);(具体是什么我们以后讨论)第三个参数是具体的传输层协议。当赋值为0的时候,系统会根据传输层协议类型自动匹配和选择。事实上,当前,匹配SOCK_STREAM的就是TCP协议;而匹配SOCK_DGRAM就是UDP协议。所以,我们指定了第二个参数,第三个就可以简单的设置为0。不过,为了严谨,我们最好还是把具体协议写出来,比如,我们的例子中的TCP协议的宏名称:IPPROTO_TCP。数据的&地址&&&&&&&& 从数据封装的模型,我们可以看到数据是怎么从应用程序传递到互联网的。我们说过,数据的传送是通过socket进行的。但是socket只描述了协议类型。要让数据正确的传送到某个地方,必须添加那个地方的sockaddr地址;同样,要能接受网络上的数据,必须有自己的sockaddr地址。&&&&&&& 可见,在网络上传送的数据包,是socket和sockaddr共同&染指&的结果。他们共同封装和指定了一个数据包的网络协议(IP)和IP地址,传输协议(TCP/UDP)和端口号。网络字节和本机字节的相互转换&&&&&&& sockaddr结构中的IP地址(sin_addr.s_addr)和端口号(sin_port)将被封装到网络上传送的数据包中,所以,它的结构形式需要保证是网络字节形式。我们这里用到的函数是htons()和htonl(),这些缩写的意思是:h: host,主机(本机)n: network,网络to: to转换s: short,16位(2字节,常用于端口号)l: long, 32位(4字节,常用于IP地址)&反过来&的函数也是存在的ntohs()和ntohl()。动作与持续行为&&&&&&& 本节最后的一个概念可以跟计算机无关。作为动词,有些可以描述动作,有些是描述一重持续的行为状态的(就如同一般动词和be动词一样)。扯到C++来说,我们可以把持续行为封装到函数内部,只留出动作的接口。事实上,构造函数中的bind()和listen()就是这种描述持续状态的行为函数。
五。创建监听套接字
前面一小节,我们已经写出了TcpServer的构造函数。这个函数的实际作用,就是创建了listen socket(监听嵌套字)。这一节,我们来具体分析这个创建的过程。socket和sockaddr的创建是可以相互独立的&&&&&&& 在函数中,我们首先通过socket()系统调用创建了listenSock,然后通过为结构体赋值的方法具体定义了服务器端的sockaddr。(memset()函数的作用是把某个内存段的空间设定为某值,这里是清零。)其他的概念已经在前一小节讲完了。这里需要补充的是说明宏定义INADDR_ANY。这里的意思是使用本机所有可用的IP地址。当然,如果你机器绑定了多个IP地址,你也可以指定使用哪一个。数据流简易模型(SOCK_STREAM)&&&&&&& 我们的例子以电话做的比喻,实际上,socket stream模型不完全类似电话,它至少有以下这些特点:1、一种持续性的连接。这点跟电话是类似的,也可以想象成流动着液体的水管。一旦断开,这种流动就会中断。2、数据包的发送实际上是非连续的。这个世界上有什么事物是真正的线性连续的?呵呵,扯远了,这貌似一个哲学问题。我们仅仅需要知道的是,一个数据包不可能是无限大的,所以,总是一个小数据包一个小数据包这样的发送的。这一点,又有点像邮包的传递。这些数据包到达与否,到达的先后次序本身是无法保证的,即是说,是IP协议无法保证的。但是stream形式的TCP协议,在IP之上,做了一定到达和到达顺序的保证。3、传送管道实际上是非封闭的。要不干嘛叫&网络&-_-!!!。我们之所以能保证数据包的&定点&传送,完全是依靠每个数据包都自带了目的地址信息。&&&&&&& 由此可见,虽然socket和sockaddr可以分别创建,并无依赖关系。但是在实际使用的时候,一个socket至少会绑定一个本机的sockaddr,没有自己的&地址信息&,就不能接受到网络上的数据包(至少在TCP协议里面是这样的)。socket与本机sockaddr的绑定&&&&&&& 有时候绑定是系统的任务,特别是当你不需要知道自己的IP地址和所使用的端口号的时候。但是,我们现在是建立服务器,你必须告诉客户端你的连接信息:IP和Port。所以,我们需要指明IP和Port,然后进行绑定。
int&bind(int&socket,&struct&sockaddr*&localAddress,&unsigned&int&addressLength);
作为C++的程序员,也许你会觉得这个函数很不友好,它似乎更应该写成:
int&bind_cpp_style(int&socket,&const&sockaddr&&localAddress);
我们需要通过函数原型指明两点:1、我们仅仅使用sockaddr结构的数据,但并不会对原有的数据进行修改;2、我们使用的是完整的结构体,而不仅仅是这个结构体的指针。(很显然光用指针是无法说明结构体大小的)幸运的是,在Linux的实现中,这个函数已经被写为:
#include&&sys/socket.h&/*&Give&the&socket&FD&the&local&address&ADDR&(which&is&LEN&bytes&long).&&*/extern&int&bind&(int&__fd,&__CONST_SOCKADDR_ARG&__addr,&socklen_t&__len)&&&&&__THROW;
看到亲切的const,我们就知道这个指针带入是没有&副作用&的。监听:listen()&&&&&&& stream流模型形式上是一种&持续性&的连接,这就是要求信息的流动是&可来可去&的。也就是说,stream流的socket除了绑定本机的sockaddr,还应该拥有对方sockaddr的信息。在listen()中,这&对方的sockaddr&就可以不是某一个特定的sockaddr。实际上,listen socket的目的是准备被动的接受来自&所有&sockaddr的请求。所以,listen()反而就不能指定某个特定的sockaddr。
int&listen(int&socket,&int&queueLimit);
其中第二个参数是等待队列的限制,一般设置在5-20。Linux中实现为:
#include&&sys/socket.h&/*&Prepare&to&accept&connections&on&socket&FD.&&&N&connection&requests&will&be&queued&before&further&requests&are&refused.&&&Returns&0&on&success,&-1&for&errors.&&*/extern&int&listen&(int&__fd,&int&__n)&__THROW;
完成了这一步,回到我们的例子,就像是让你小弟在电话机前做好了接电话的准备工作。需要再次强调的是,这些行为仅仅是改变了socket的状态,实际上我想强调的是,为什么这些函数不会造成block(阻塞)的原因。(block的概念以后再解释)
六。创建&通讯&套接字
这里的&通讯&加上了引号,是因为实际上所有的socket都有通讯的功能,只是在我们的例子中,之前那个socket只负责listen,而这个socket负责接受信息并echo回去。&我们现看看这个函数:
bool&TcpServer::isAccept(){&&&&unsigned&int&clntAddrLen&=&sizeof(clntAddr);&&&&if&(&(communicationSock&=&accept(listenSock,&(sockaddr*)&clntAddr,&&clntAddrLen))&&&0&)&{&&&&&&&&return&false;&&&&}&else&{&&&&&&&&std::cout&&&&"Client(IP:&"&&&&inet_ntoa(clntAddr.sin_addr)&&&&")&connected./n";&&&&&&&&return&true;&&&&}}
用accept()创建新的socket&&&&&&& 在我们的例子中,communicationSock实际上是用函数accept()创建的。
int&accept(int&socket,&struct&sockaddr*&clientAddress,&unsigned&int*&addressLength);
在Linux中的实现为:
/*&Await&a&connection&on&socket&FD.&&&When&a&connection&arrives,&open&a&new&socket&to&communicate&with&it,&&&set&*ADDR&(which&is&*ADDR_LEN&bytes&long)&to&the&address&of&the&connecting&&&peer&and&*ADDR_LEN&to&the&address's&actual&length,&and&return&the&&&new&socket's&descriptor,&or&-1&for&errors.&&&This&function&is&a&cancellation&point&and&therefore&not&marked&with&&&__THROW.&&*/extern&int&accept&(int&__fd,&__SOCKADDR_ARG&__addr,&&&&&&&&&&&socklen_t&*__restrict&__addr_len);
这个函数实际上起着构造socket作用的仅仅只有第一个参数(另外还有一个不在这个函数内表现出来的因素,后面会讨论到),后面两个指针都有副作用,在socket创建后,会将客户端sockaddr的数据以及结构体的大小传回。&&&&&&& 当程序调用accept()的时候,程序有可能就停下来等accept()的结果。这就是我们前一小节说到的block(阻塞)。这如同我们调用std::cin的时候系统会等待输入直到回车一样。accept()是一个有可能引起block的函数。请注意我说的是&有可能&,这是因为accept()的block与否实际上决定与第一个参数socket的属性。这个文件描述符如果是block的,accept()就block,否则就不block。默认情况下,socket的属性是&可读可写&,并且,是阻塞的。所以,我们不修改socket属性的时候,accept()是阻塞的。accept()的另一面connect()&&&&&&& accept()只是在server端被动的等待,它所响应的,是client端connect()函数:
int&connect(int&socket,&struct&sockaddr*&foreignAddress,&unsigned&int&addressLength);
虽然我们这里不打算详细说明这个client端的函数,但是我们可以看出来,这个函数与之前我们介绍的bind()有几分相似,特别在Linux的实现中:
/*&Open&a&connection&on&socket&FD&to&peer&at&ADDR&(which&LEN&bytes&long).&&&For&connectionless&socket&types,&just&set&the&default&address&to&send&to&&&and&the&only&address&from&which&to&accept&transmissions.&&&Return&0&on&success,&-1&for&errors.&&&This&function&is&a&cancellation&point&and&therefore&not&marked&with&&&__THROW.&&*/extern&int&connect&(int&__fd,&__CONST_SOCKADDR_ARG&__addr,&socklen_t&__len);
connect() 也使用了const的sockaddr,只不过是远程电脑上的而非bind()的本机。&&&&&&& accept()在server端表面上是通过listen socket创建了新的socket,实际上,这种行为是在接受对方客户机程序中connect()函数的请求后发生的。综合起看,被创建的新socket实际上包含了listen socket的信息以及客户端connect()请求中所包含的信息&&客户端的sockaddr地址。新socket与sockaddr的关系&&&&&&& accept()创建的新socket(我们例子中的communicationSock,这里我们简单用newSock来带指)首先包含了listen socket的信息,所以,newSock具有本机sockaddr的信息;其次,因为它响应于client端connect()函数的请求,所以,它还包含了clinet端sockaddr的信息。&&&&&&& 我们说过,stream流形式的TCP协议实际上是建立起一个&可来可去&的通道。用于listen的通道,远程机的目标地址是不确定的;但是newSock却是有指定的本机地址和远程机地址,所以,这个socket,才是我们真正用于TCP&通讯&的socket。inet_ntoa()
#include&&arpa/inet.h&/*&Convert&Internet&number&in&IN&to&ASCII&representation.&&The&return&value&&&is&a&pointer&to&an&internal&array&containing&the&string.&&*/extern&char&*inet_ntoa&(struct&in_addr&__in)&__THROW;
&&&&&&& 对于这个函数,我们可以作为一种,将IP地址,由in_addr结构转换为可读的ASCII形式的固定用法。
七。接受与发送
现在,我们通过accept()创建了新的socket,也就是我们类中的数据成员communicationSock,现在,我们就可以通过这个socket进行通讯了。TCP通讯模型&&&&&&& 在介绍函数之前,我们应该了解一些事实。TCP的Server/Client模型类似这样:ServApp&&ServSock&&Internet&&ClntSock&&ClntApp当然,我们这里的socket指的就是用于&通讯&的socket。TCP的server端至少有两个socket,一个用于监听,一个用于通讯;TCP的server端可以只有一个socket,这个socket同时&插&在server的两个socket上。当然,插上listen socket的目的只是为了创建communication socket,创建完备后,listen是可以关闭的。但是,如果这样,其他的client就无法再连接上server了。&&&&&&& 我们这个模型,是client的socket插在server的communication socket上的示意。这两个socket,都拥有完整的本地地址信息以及远程计算机地址信息,所以,这两个socket以及之间的网络实际上形成了一条形式上&封闭&的管道。数据包只要从一端进来,就能知道出去的目的地,反之亦然。这正是TCP协议,数据流形式抽象化以及实现。因为不再需要指明&出处&和&去向&,对这样的socket(实际上是S/C上的socket对)的操作,就如同对本地文件描述符的操作一样。但是,尽管我们可以使用read()和write(),但是,为了完美的控制,我们最好使用recv()和send()。recv()和send()
int&send(int&socket,&const&void*&msg,&unsigned&int&msgLength,&int&flags);int&recv(int&socket,&void*&rcvBuffer,&unsigned&int&bufferLength,&int&flags);
在Linux中的实现为:
#include&&sys/socket.h&/*&Send&N&bytes&of&BUF&to&socket&FD.&&Returns&the&number&sent&or&-1.&&&This&function&is&a&cancellation&point&and&therefore&not&marked&with&&&__THROW.&&*/extern&ssize_t&send&(int&__fd,&__const&void&*__buf,&size_t&__n,&int&__flags);/*&Read&N&bytes&into&BUF&from&socket&FD.&&&Returns&the&number&read&or&-1&for&errors.&&&This&function&is&a&cancellation&point&and&therefore&not&marked&with&&&__THROW.&&*/extern&ssize_t&recv&(int&__fd,&void&*__buf,&size_t&__n,&int&__flags);
这两个函数的第一个参数是用于&通讯&的socket,第二个参数是发送或者接收数据的起始点指针,第三个参数是数据长度,第四个参数是控制符号(默认属性设置为0就可以了)。失败时候传回-1,否则传回实际发送或者接收数据的大小,返回0往往意味着连接断开了。处理echo行为
void&TcpServer::handleEcho(){&&&&const&int&BUFFERSIZE&=&32;&&&&char&buffer[BUFFERSIZE];&&&&int&recvMsgS&&&&bool&goon&=&true;&&&&while&(&goon&==&true&)&{&&&&&&&&if&(&(recvMsgSize&=&recv(communicationSock,&buffer,&BUFFERSIZE,&0))&&&0&)&{&&&&&&&&&&&&throw&"recv()&failed";&&&&&&&&}&else&if&(&recvMsgSize&==&0&)&{&&&&&&&&&&&&goon&=&false;&&&&&&&&}&else&{&&&&&&&&&&&&if&(&send(communicationSock,&buffer,&recvMsgSize,&0)&!=&recvMsgSize&)&{&&&&&&&&&&&&&&&&throw&"send()&failed";&&&&&&&&&&&&}&&&&&&&&}&&&&}&&&&close(communicationSock);}
本小节最后要讲的函数是close(),它包含在&unistd.h&中
#include&&unistd.h&/*&Close&the&file&descriptor&FD.&&&This&function&is&a&cancellation&point&and&therefore&not&marked&with&&&__THROW.&&*/extern&int&close&(int&__fd);
这个函数用于关闭一个文件描述符,自然,也就可以用于关闭socket。下一小节是完整的源代码。默认的监听端口是5000。我们可以通过$telnet 127.0.0.1 5000验证在本机运行的echo server程序。
八。完整源代码
//Filename:&TcpServerClass.hpp#ifndef&TCPSERVERCLASS_HPP_INCLUDED#define&TCPSERVERCLASS_HPP_INCLUDED#include&&unistd.h&#include&&iostream&#include&&sys/socket.h&#include&&arpa/inet.h&class&TcpServer{private:&&&&int&listenS&&&&int&communicationS&&&&sockaddr_in&servA&&&&sockaddr_in&clntApublic:&&&&TcpServer(int&listen_port);&&&&bool&isAccept();&&&&void&handleEcho();};#endif&//&TCPSERVERCLASS_HPP_INCLUDED
//Filename:&TcpServerClass.cpp#include&"TcpServerClass.hpp"TcpServer::TcpServer(int&listen_port){&&&&if&(&(listenSock&=&socket(PF_INET,&SOCK_STREAM,&IPPROTO_TCP))&&&0&)&{&&&&&&&&throw&"socket()&failed";&&&&}&&&&memset(&servAddr,&0,&sizeof(servAddr));&&&&servAddr.sin_family&=&AF_INET;&&&&servAddr.sin_addr.s_addr&=&htonl(INADDR_ANY);&&&&servAddr.sin_port&=&htons(listen_port);&&&&if&(&bind(listenSock,&(sockaddr*)&servAddr,&sizeof(servAddr))&&&0&)&{&&&&&&&&throw&"bind()&failed";&&&&}&&&&if&(&listen(listenSock,&10)&&&0&)&{&&&&&&&&throw&"listen()&failed";&&&&}}bool&TcpServer::isAccept(){&&&&unsigned&int&clntAddrLen&=&sizeof(clntAddr);&&&&if&(&(communicationSock&=&accept(listenSock,&(sockaddr*)&clntAddr,&&clntAddrLen))&&&0&)&{&&&&&&&&return&false;&&&&}&else&{&&&&&&&&std::cout&&&&"Client(IP:&"&&&&inet_ntoa(clntAddr.sin_addr)&&&&")&connected./n";&&&&&&&&return&true;&&&&}}void&TcpServer::handleEcho(){&&&&const&int&BUFFERSIZE&=&32;&&&&char&buffer[BUFFERSIZE];&&&&int&recvMsgS&&&&bool&goon&=&true;&&&&while&(&goon&==&true&)&{&&&&&&&&if&(&(recvMsgSize&=&recv(communicationSock,&buffer,&BUFFERSIZE,&0))&&&0&)&{&&&&&&&&&&&&throw&"recv()&failed";&&&&&&&&}&else&if&(&recvMsgSize&==&0&)&{&&&&&&&&&&&&goon&=&false;&&&&&&&&}&else&{&&&&&&&&&&&&if&(&send(communicationSock,&buffer,&recvMsgSize,&0)&!=&recvMsgSize&)&{&&&&&&&&&&&&&&&&throw&"send()&failed";&&&&&&&&&&&&}&&&&&&&&}&&&&}&&&&close(communicationSock);}
演示程序:
//Filename:&main.cpp//Tcp&Server&C++&style,&single&work#include&&iostream&#include&"TcpServerClass.hpp"int&echo_server(int&argc,&char*&argv[]);int&main(int&argc,&char*&argv[]){&&&&int&mainRtn&=&0;&&&&try&{&&&&&&&&mainRtn&=&echo_server(argc,&argv);&&&&}&&&&catch&(&const&char*&s&)&{&&&&&&&&perror(s);&&&&&&&&exit(EXIT_FAILURE);&&&&}&&&&return&mainR}int&echo_server(int&argc,&char*&argv[]){&&&&int&&&&&if&(&argc&==&2&)&{&&&&&&&&port&=&atoi(argv[1]);&&&&}&else&{&&&&&&&&port&=&5000;&&&&}&&&&TcpServer&myServ(port);&&&&while&(&true&)&{&&&&&&&&if&(&myServ.isAccept()&==&true&)&{&&&&&&&&&&&&myServ.handleEcho();&&&&&&&&}&&&&}&&&&return&0;}
&&相关文章推荐
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:299005次
积分:3687
积分:3687
排名:第7957名
原创:49篇
转载:103篇
评论:69条
(1)(1)(2)(10)(2)(2)(1)(9)(2)(1)(1)(2)(17)(1)(5)(1)(4)(5)(1)(15)(3)(3)(1)(9)(1)(1)(2)(3)(3)(17)(22)(4)}

我要回帖

更多关于 电脑开机显示屏黑屏 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信