ImageVerifierCode 换一换
格式:DOCX , 页数:11 ,大小:25.45KB ,
资源ID:5970559      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/5970559.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(Linux socket 编程入门.docx)为本站会员(b****5)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

Linux socket 编程入门.docx

1、Linux socket 编程入门Linux socket-编程入门(TCP server 端)通常,socket编程总是Client/Server形式的,因为有了telnet,先不考虑client的程序,先写一个支持TCP协议的server端,然后用telnet作为client验证我们的程序。TCP server端的基本流程 想象你自己是个小大佬,坐办公室(什么样的黑社会做办公室啊?可能是讨债公司吧)你很土,只有一个小弟帮你接电话(因为你自己的号码是不敢对外公开的)。一次通讯的流程大概应该是这样的:小弟那里的总机电话响了;小弟接起电话;对方说是你女朋友A妹;小弟转达说,“老大,你马子电话”;

2、你说,接过来;小弟把电话接给你;你和你女朋友聊天半小时;挂电话。分析一下整个过程中的元素。你小弟(listenSock),你需要他来监听(listen)电话;你自己(communicationSock),实际上打电话进行交流的是你自己;你的电话号码(servAddr),否则你女朋友怎么能找到你?你女朋友的电话号码(clntAddr),这个比喻有点牵强,因为事实上你接起电话,不需要知道对方的号码也可以通话(虽然事实上你应该是知道的,你不会取消了来电显示功能吧),但是,难道你是只接女朋友电话从来不打过去的牛人吗?这个过程中的行为(成员函数):你小弟接电话并转接给你(isAccept());你自己的

3、通话(handleEcho())(这个行为确实比较土,只会乌鸦学舌的echo,呵呵)。UNIX中的一切事物都是文件(everything in Unix is a file!)这是UNIX的基本理念之一,也是一句很好的概括。比如,很多UNIX老鸟会举出个例子来,“你看,/dev/hdc是个文件,它实际上也是我的光盘”UNIX中的文件可以是:网络连接(network connection),输入输出(FIFO),管道(a pipe),终端(terminal),硬盘上的实际文件,或者其它任何东东。3个已经打开的fd,0:标准输入(STDIN_FILENO);1:标准输出(STDOUT_FILENO

4、);2:标准错误(STDERR_FILENO)。(以上宏定义在中)一个最简单的使用fd的例子,就是使用中的函数:write(1, Hello, World!n, 20);,在标准输出上显示“Hello, World!”。file和fd并非一定是一一对应的。当一个file被多个程序调用的时候,会生成相互独立的fd。这个概念可以类比于C+中的引用(eg: int& rTmp = tmp;)。socket与file descriptor 文件是应用程序与系统(包括特定硬件设备)之间的桥梁,而文件描述符就是应用程序使用这个“桥梁”的接口。在需要的时候,应用程序会向系统申请一个文件,然后将文件的描述符返

5、回供程序使用。返回socket的文件通常被创建在/tmp或者/usr/tmp中。我们实际上不用关心这些文件,仅仅能够利用返回的socket描述符就可以了。收件人:全体女生。 地址: 事实上,在socket的通用address描述结构sockaddr中, 正是用这样的方式来进行地址描述的:structsockaddrunsignedshortsa_family;charsa_data14;sa_family可以认为是socket address family的缩写,也可能被简写成AF(Address Family),他就好像我们例子中那个“收件人:全体女生”一样,虽然事实上有很多AF的种类,但是

6、我们这个教程中只用得上大名鼎鼎的internet家族AF_INET。另外的14字节是用来描述地址的。这是一种通用结构,事实上,当我们指定sa_family=AF_INET之后,sa_data的形式也就被固定了下来:最前端的2字节用于记录16位的端口,紧接着的4字节用于记录32位的IP地址,最后的8字节清空为零。这就是我们实际在构造sockaddr时候用到的结构sockaddr_in(意指socket address internet):structsockaddr_inunsignedshortsin_family;unsignedshortsin_port;structin_addrsin_

7、addr;charsin_zero8;我想,sin_的意思,就是socket (address) internet吧,只不过把address省略掉了。sin_addr被定义成了一个结构,这个结构实际上就是:struct in_addr unsigned long s_addr; ;in_addr显然是internet address了,s_addr是什么意思呢?说实话我没猜出值得肯定的答案,也许就是socket address的意思吧,尽管跟更广义的sockaddr结构意思有所重复了。哎,这些都是历史原因,也许我是没有精力去考究了。原文介绍Linux的实现看看源码实现:Code/Filenam

8、e:TcpServerClass.hpp#ifndefTCPSERVERCLASS_HPP_INCLUDED#defineTCPSERVERCLASS_HPP_INCLUDED#include#include#include#includeclassTcpServerprivate:intlistenSock;intcommunicationSock;sockaddr_inservAddr;sockaddr_inclntAddr;public:TcpServer(intlisten_port);boolisAccept();voidhandleEcho();#endif/TCPSERVERCL

9、ASS_HPP_INCLUDEDTcpServer:TcpServer(intlisten_port)/创建了listensocket(监听嵌套字)if(listenSock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)0)throwsocket()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

10、(listenSock,(sockaddr*)&servAddr,sizeof(servAddr)0)throwbind()failed;if(listen(listenSock,10)0)throwlisten()failed;boolTcpServer:isAccept()unsignedintclntAddrLen=sizeof(clntAddr);if(communicationSock=accept(listenSock,(sockaddr*)&clntAddr,&clntAddrLen)0)returnfalse;elsestd:coutClient(IP:inet_ntoa(cl

11、ntAddr.sin_addr)connected.n;returntrue;voidTcpServer:handleEcho()constintBUFFERSIZE=32;charbufferBUFFERSIZE;intrecvMsgSize;boolgoon=true;while(goon=true)if(recvMsgSize=recv(communicationSock,buffer,BUFFERSIZE,0)0)throwrecv()failed;elseif(recvMsgSize=0)goon=false;elseif(send(communicationSock,buffer,

12、recvMsgSize,0)!=recvMsgSize)throwsend()failed;close(communicationSock);/Filename:main.cpp/TcpServerC+style,singlework#include#includeTcpServerClass.hppintecho_server(intargc,char*argv);intmain(intargc,char*argv)intmainRtn=0;trymainRtn=echo_server(argc,argv);catch(constchar*s)perror(s);exit(EXIT_FAIL

13、URE);returnmainRtn;intecho_server(intargc,char*argv)intport;if(argc=2)port=atoi(argv1);elseport=5000;TcpServermyServ(port);while(true)if(myServ.isAccept()=true)myServ.handleEcho();return0; 我们前面说到了网络分层:链路网络传输应用。数据从应用程序里诞生,传送到互联网上每一层都会进行一次封装: DataApplicationTCP/UDPIPOS(Driver, Kernel & Physical Addres

14、s) 我们用socket重点描述的是协议,包括网络协议(IP)和传输协议(TCP/UDP)。 sockaddr重点描述的是地址,包括IP地址和TCP/UDP端口。socket()函数 我们从TcpServer:TcpServer()函数可以看到,socket和sockaddr的产生是可以相互独立的。socket()的函数原型是:intsocket(intprotocolFamily,inttype,intprotocol);在Linux中的实现为:#include/*CreateanewsocketoftypeTYPEindomainDOMAIN,usingprotocolPROTOCOL.I

15、fPROTOCOLiszero,oneischosenautomatically.Returnsafiledescriptorforthenewsocket,or-1forerrors.*/externintsocket(int_domain,int_type,int_protocol)_THROW;第一个参数是协议簇(Linux里面叫作域,意思一样的),还是那句话,我们这篇教程用到的就仅仅是一个PF_INET(protocol family : internet),很多时候你会发现人们也经常在这里赋值为AF_INET,事实上,当前,AF_INET就是PF_INET的一个#define,但是,

16、写成PF_INET从语义上会更加严谨。这也就是TCP/IP协议簇中的IP协议(Internet Protocol),网络层的协议。 后面两个参数定义传输层的协议。 第二个参数是传输层协议类型,我们教程里用到的宏,只有两个:SOCK_STREAM(数据流格式)和SOCK_DGRAM(数据报格式);(具体是什么我们以后讨论) 第三个参数是具体的传输层协议。当赋值为0的时候,系统会根据传输层协议类型自动匹配和选择。事实上,当前,匹配SOCK_STREAM的就是TCP协议;而匹配SOCK_DGRAM就是UDP协议。所以,我们指定了第二个参数,第三个就可以简单的设置为0。不过,为了严谨,我们最好还是把具

17、体协议写出来,比如,我们的例子中的TCP协议的宏名称:IPPROTO_TCP。数据的“地址” 从数据封装的模型,我们可以看到数据是怎么从应用程序传递到互联网的。我们说过,数据的传送是通过socket进行的。但是socket只描述了协议类型。要让数据正确的传送到某个地方,必须添加那个地方的sockaddr地址;同样,要能接受网络上的数据,必须有自己的sockaddr地址。 可见,在网络上传送的数据包,是socket和sockaddr共同“染指”的结果。他们共同封装和指定了一个数据包的网络协议(IP)和IP地址,传输协议(TCP/UDP)和端口号。网络字节和本机字节的相互转换 sockaddr结构

18、中的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()。socket和sockaddr的创建是可以相互独立的 首先通过socket()系统调用创建了listenSock,然后通过为结构体赋值的方法具体定义了服务器端的soc

19、kaddr。这里需要补充的是说明宏定义INADDR_ANY。这里的意思是使用本机所有可用的IP地址。当然,如果你机器绑定了多个IP地址,你也可以指定使用哪一个。socket与本机sockaddr的绑定 有时候绑定是系统的任务,特别是当你不需要知道自己的IP地址和所使用的端口号的时候。但是,我们现在是建立服务器,你必须告诉客户端你的连接信息:IP和Port。所以,我们需要指明IP和Port,然后进行绑定。int bind(int socket, struct sockaddr* localAddress, unsigned int addressLength);作为C+的程序员,也许你会觉得这个

20、函数很不友好,它似乎更应该写成:int bind_cpp_style(int socket, const sockaddr& localAddress);我们需要通过函数原型指明两点: 1、我们仅仅使用sockaddr结构的数据,但并不会对原有的数据进行修改; 2、我们使用的是完整的结构体,而不仅仅是这个结构体的指针。(很显然光用指针是无法说明结构体大小的) 幸运的是,在Linux的实现中,这个函数已经被写为:#include /* Give the socket FD the local address ADDR (which is LEN bytes long). */ extern in

21、t bind (int _fd, _CONST_SOCKADDR_ARG _addr, socklen_t _len) _THROW;看到亲切的const,我们就知道这个指针带入是没有“副作用”的。监听:listen() stream流模型形式上是一种“持续性”的连接,这就是要求信息的流动是“可来可去”的。也就是说,stream流的socket除了绑定本机的sockaddr,还应该拥有对方sockaddr的信息。在listen()中,这“对方的sockaddr”就可以不是某一个特定的sockaddr。实际上,listen socket的目的是准备被动的接受来自“所有”sockaddr的请求。所

22、以,listen()反而就不能指定某个特定的sockaddr。int listen(int socket, int queueLimit);其中第二个参数是等待队列的限制,一般设置在5-20。Linux中实现为:#include /* 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 _

23、fd, int _n) _THROW;完成了这一步,回到我们的例子,就像是让你小弟在电话机前做好了接电话的准备工作。需要再次强调的是,这些行为仅仅是改变了socket的状态,实际上我想强调的是,为什么这些函数不会造成block(阻塞)的原因。(block的概念以后再解释)创建“通讯 ”嵌套字 这里的“通讯”加上了引号,是因为实际上所有的socket都有通讯的功能,只是在我们的例子中,之前那个socket只负责listen,而这个socket负责接受信息并echo回去。 我们现看看这个函数:boolTcpServer:isAccept()unsignedintclntAddrLen=sizeof

24、(clntAddr);if(communicationSock=accept(listenSock,(sockaddr*)&clntAddr,&clntAddrLen)0)returnfalse;elsestd:coutClient(IP:inet_ntoa(clntAddr.sin_addr)connected.n;returntrue;用accept()创建新的socket 在我们的例子中,communicationSock实际上是用函数accept()创建的。int accept(int socket, struct sockaddr* clientAddress, unsigned i

25、nt* 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 addresss actual length, and return the new sockets descriptor,

26、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(

27、)的时候,程序有可能就停下来等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

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1