Linux socket 编程入门.docx

上传人:b****5 文档编号:5970559 上传时间:2023-01-02 格式:DOCX 页数:11 大小:25.45KB
下载 相关 举报
Linux socket 编程入门.docx_第1页
第1页 / 共11页
Linux socket 编程入门.docx_第2页
第2页 / 共11页
Linux socket 编程入门.docx_第3页
第3页 / 共11页
Linux socket 编程入门.docx_第4页
第4页 / 共11页
Linux socket 编程入门.docx_第5页
第5页 / 共11页
点击查看更多>>
下载资源
资源描述

Linux socket 编程入门.docx

《Linux socket 编程入门.docx》由会员分享,可在线阅读,更多相关《Linux socket 编程入门.docx(11页珍藏版)》请在冰豆网上搜索。

Linux socket 编程入门.docx

Linuxsocket编程入门

Linuxsocket-编程入门(TCPserver端)

通常,socket编程总是Client/Server形式的,因为有了telnet,先不考虑client的程序,先写一个支持TCP协议的server端,然后用telnet作为client验证我们的程序。

TCPserver端的基本流程 

       想象你自己是个小大佬,坐办公室(什么样的黑社会做办公室啊?

可能是讨债公司吧^^)你很土,只有一个小弟帮你接电话(因为你自己的号码是不敢对外公开的)。

一次通讯的流程大概应该是这样的:

小弟那里的总机电话响了;小弟接起电话;对方说是你女朋友A妹;小弟转达说,“老大,你马子电话”;你说,接过来;小弟把电话接给你;你和你女朋友聊天半小时;挂电话。

 

       分析一下整个过程中的元素。

你小弟(listenSock),你需要他来监听(listen)电话;你自己(communicationSock),实际上打电话进行交流的是你自己;你的电话号码(servAddr),否则你女朋友怎么能找到你?

你女朋友的电话号码(clntAddr),这个比喻有点牵强,因为事实上你接起电话,不需要知道对方的号码也可以通话(虽然事实上你应该是知道的,你不会取消了来电显示功能吧^^),但是,难道你是只接女朋友电话从来不打过去的牛人吗?

这个过程中的行为(成员函数):

你小弟接电话并转接给你(isAccept());你自己的通话(handleEcho())(这个行为确实比较土,只会乌鸦学舌的echo,呵呵)。

 

UNIX中的一切事物都是文件(everythinginUnixisafile!

  这是UNIX的基本理念之一,也是一句很好的概括。

比如,很多UNIX老鸟会举出个例子来,“你看,/dev/hdc是个文件,它实际上也是我的光盘……”UNIX中的文件可以是:

网络连接(networkconnection),输入输出(FIFO),管道(apipe),终端(terminal),硬盘上的实际文件,或者其它任何东东。

  3个已经打开的fd,0:

标准输入(STDIN_FILENO);1:

标准输出(STDOUT_FILENO);2:

标准错误(STDERR_FILENO)。

(以上宏定义在中)一个最简单的使用fd的例子,就是使用中的函数:

write(1,"Hello,World!

\n",20);,在标准输出上显示“Hello,World!

”。

  file和fd并非一定是一一对应的。

当一个file被多个程序调用的时候,会生成相互独立的fd。

这个概念可以类比于C++中的引用(eg:

int&rTmp=tmp;)。

socket与filedescriptor

       文件是应用程序与系统(包括特定硬件设备)之间的桥梁,而文件描述符就是应用程序使用这个“桥梁”的接口。

在需要的时候,应用程序会向系统申请一个文件,然后将文件的描述符返回供程序使用。

返回socket的文件通常被创建在/tmp或者/usr/tmp中。

我们实际上不用关心这些文件,仅仅能够利用返回的socket描述符就可以了。

 

      

收件人:

全体女生。

地址:

<一种地址描述方式>

       事实上,在socket的通用address描述结构sockaddr中,正是用这样的方式来进行地址描述的:

struct sockaddr 

    unsigned short sa_family; 

    char sa_data[14]; 

};

 

sa_family可以认为是socketaddressfamily的缩写,也可能被简写成AF(AddressFamily),他就好像我们例子中那个“收件人:

全体女生”一样,虽然事实上有很多AF的种类,但是我们这个教程中只用得上大名鼎鼎的internet家族AF_INET。

另外的14字节是用来描述地址的。

这是一种通用结构,事实上,当我们指定sa_family=AF_INET之后,sa_data的形式也就被固定了下来:

最前端的2字节用于记录16位的端口,紧接着的4字节用于记录32位的IP地址,最后的8字节清空为零。

这就是我们实际在构造sockaddr时候用到的结构sockaddr_in(意指socketaddressinternet):

struct sockaddr_in 

    unsigned short sin_family; 

    unsigned short sin_port; 

    struct in_addr sin_addr; 

    char sin_zero[8]; 

};

 

我想,sin_的意思,就是socket(address)internet吧,只不过把address省略掉了。

sin_addr被定义成了一个结构,这个结构实际上就是:

structin_addr

{

   unsignedlongs_addr;

};

in_addr显然是internetaddress了,s_addr是什么意思呢?

说实话我没猜出值得肯定的答案,也许就是socketaddress的意思吧,尽管跟更广义的sockaddr结构意思有所重复了。

哎,这些都是历史原因,也许我是没有精力去考究了。

原文介绍Linux的实现

 

看看源码实现:

Code

//Filename:

 TcpServerClass.hpp 

#ifndef TCPSERVERCLASS_HPP_INCLUDED 

#define TCPSERVERCLASS_HPP_INCLUDED 

#include  

#include  

#include  

#include  

class TcpServer 

private:

 

int listenSock; 

int communicationSock; 

sockaddr_in servAddr; 

sockaddr_in clntAddr; 

public:

 

    TcpServer(int listen_port); 

bool isAccept(); 

void handleEcho(); 

}; 

#endif // TCPSERVERCLASS_HPP_INCLUDED

 

TcpServer:

:

TcpServer(int listen_port)  // 创建了listen socket(监听嵌套字) 

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 recvMsgSize; 

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  

#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 mainRtn; 

int echo_server(int argc, char* argv[]) 

int port; 

if ( argc == 2 ) { 

        port = atoi(argv[1]); 

    } else { 

        port = 5000; 

    } 

    TcpServer myServ(port); 

while ( true ) { 

if ( myServ.isAccept() == true ) { 

            myServ.handleEcho(); 

        } 

    } 

return 0; 

}

 

       我们前面说到了网络分层:

链路——网络——传输——应用。

数据从应用程序里诞生,传送到互联网上每一层都会进行一次封装:

Data>>Application>>TCP/UDP>>IP>>OS(Driver,Kernel&PhysicalAddress)

我们用socket重点描述的是协议,包括网络协议(IP)和传输协议(TCP/UDP)。

sockaddr重点描述的是地址,包括IP地址和TCP/UDP端口。

socket()函数

   我们从TcpServer:

:

TcpServer()函数可以看到,socket和sockaddr的产生是可以相互独立的。

socket()的函数原型是:

int socket(int protocolFamily, int type, int protocol);

在Linux中的实现为:

#include  

/* 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(protocolfamily:

internet),很多时候你会发现人们也经常在这里赋值为AF_INET,事实上,当前,AF_INET就是PF_INET的一个#define,但是,写成PF_INET从语义上会更加严谨。

这也就是TCP/IP协议簇中的IP协议(InternetProtocol),网络层的协议。

后面两个参数定义传输层的协议。

第二个参数是传输层协议类型,我们教程里用到的宏,只有两个:

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()。

 

socket和sockaddr的创建是可以相互独立的

       首先通过socket()系统调用创建了listenSock,然后通过为结构体赋值的方法具体定义了服务器端的sockaddr。

这里需要补充的是说明宏定义INADDR_ANY。

这里的意思是使用本机所有可用的IP地址。

当然,如果你机器绑定了多个IP地址,你也可以指定使用哪一个。

socket与本机sockaddr的绑定

       有时候绑定是系统的任务,特别是当你不需要知道自己的IP地址和所使用的端口号的时候。

但是,我们现在是建立服务器,你必须告诉客户端你的连接信息:

IP和Port。

所以,我们需要指明IP和Port,然后进行绑定。

intbind(intsocket,structsockaddr*localAddress,unsignedintaddressLength);

作为C++的程序员,也许你会觉得这个函数很不友好,它似乎更应该写成:

intbind_cpp_style(intsocket,constsockaddr&localAddress);

我们需要通过函数原型指明两点:

1、我们仅仅使用sockaddr结构的数据,但并不会对原有的数据进行修改;

2、我们使用的是完整的结构体,而不仅仅是这个结构体的指针。

(很显然光用指针是无法说明结构体大小的)

幸运的是,在Linux的实现中,这个函数已经被写为:

#include

/*GivethesocketFDthelocaladdressADDR(whichisLENbyteslong). */

externintbind(int__fd,__CONST_SOCKADDR_ARG__addr,socklen_t__len)

    __THROW;

看到亲切的const,我们就知道这个指针带入是没有“副作用”的。

 

监听:

listen()

       stream流模型形式上是一种“持续性”的连接,这就是要求信息的流动是“可来可去”的。

也就是说,stream流的socket除了绑定本机的sockaddr,还应该拥有对方sockaddr的信息。

在listen()中,这“对方的sockaddr”就可以不是某一个特定的sockaddr。

实际上,listensocket的目的是准备被动的接受来自“所有”sockaddr的请求。

所以,listen()反而就不能指定某个特定的sockaddr。

intlisten(intsocket,intqueueLimit);

其中第二个参数是等待队列的限制,一般设置在5-20。

Linux中实现为:

#include

/*PreparetoacceptconnectionsonsocketFD.

  Nconnectionrequestswillbequeuedbeforefurtherrequestsarerefused.

  Returns0onsuccess,-1forerrors. */

externintlisten(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()创建的。

intaccept(intsocket,structsockaddr*clientAddress,unsignedint*addressLength);

在Linux中的实现为:

/*AwaitaconnectiononsocketFD.

  Whenaconnectionarrives,openanewsockettocommunicatewithit,

  set*ADDR(whichis*ADDR_LENbyteslong)totheaddressoftheconnecting

  peerand*ADDR_LENtotheaddress'sactuallength,andreturnthe

  newsocket'sdescriptor,or-1forerrors.

  Thisfunctionisacancellationpointandthereforenotmarkedwith

  __THROW. */

externintaccept(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()函数:

intconnect(intsocket,structsockaddr*foreignAddress,unsignedintaddressLength);

虽然我们这里不打算详细说明这个client端的函数,但是我们可以看出来,这个函数与之前我们介绍的bind()有几分相似,特别在Linux的实现中:

/*OpenaconnectiononsocketFD

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 人文社科 > 文学研究

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

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