网络编程基础知识


物理试题 2019-10-04 00:15:43 物理试题
[摘要]网络编程基础知识篇(一):网络编程基础知识网络理论介绍OSI模式  Open Systems interconnection应用层,表示层,会话层,传输层,网络层,数据链路层,物理层  1        2       3       4       5          6        7li

【www.shanpow.com--物理试题】

网络编程基础知识篇(一):网络编程基础知识


网络理论介绍OSI模式  Open Systems interconnection应用层,表示层,会话层,传输层,网络层,数据链路层,物理层  1        2       3       4       5          6        7
linux模型 应用层,传输层,网际层,网络接口123       4        5       67
网络接口层把数据链路层和物理层合并在了一起,提供访问物理设备的驱动程序,对应的网络协议主要有以太网协议。网际层协议管理离散计算机的数据传输,如IP协议为用户和远程计算机提供了信号包得传输方法,确保信息包能正确到达目的机器。重要的网络层协议包括ARP(地址解析协议),ICMP(Internet 控制消息协议)和IP协议(网际协议)等。传输层的功能包括:格式化信息流,提供可靠的传输。传输层包括TCP(Transmisson Control Protocol,传输控制协议)和UDP(User Datagram Protocol,用户数据报协议),它们是传输层中最主要的协议。应用层位于协议栈的顶端,它的主要任务是服务于应用,如利用FTP协议传输文件。常用的应用层协议有:HTTP,FTP,Telnet等,应用层是linux网络设定很关键的一层,linux服务器的配置主要针对应用层中的协议。

网络编程基础知识篇(二):网络编程基础


网络编程基础作者:ILSY经常会看到许多人问几乎相同的基础问题,这里根据自己的习惯做一些描述,这个帖子会慢慢根据大家的问题更新(希望可以坚持住^_^),希望对初学者有所帮助。一、关于语言、编译器及系统我们知道,在计算机里面进行程序设计至少需要掌握一种程序设计语言。常见的程序设计语言包括C/C++、ASM、BASIC、Delphi、Perl等, 你随便根据兴趣去学习一种语言都可以实现程序设计这个目的,这些语言并没有谁比谁更好的问题(这是一个理解问题,实际上一些语言应该说比一些语言更先进, 比如可以说C++比C更先进,但不能说谁比谁好,每一种语言都是优秀思想的结晶,我这样理解),只是它们的侧重点不同。比如,ASM、C比其他语言更接近 系统的底层,使得它们适合做系统方面的程序设计、而用BASIC可以更快的设计应用程序等。作为网络程序设计我更喜欢C/C++语言,它有目标程序较小、 运行速度快等优点,还包括习惯问题(所以,下面的描述中如果不特别说明,都以C/C++为例),当然选择什么语言进行程序设计是见仁见智的问题(你决定选 择C/C++了吗?如果决定了,下面的的描述会更有针对性)。我们选择好一种语言后,并编写好了程序代码,这么样才能让系统运行我们的程序呢?这就需要编译器了,编译器的作用就是把类似下面的源代码编译成系统可以识别并执行的代码:#include <stdlib.h>int main(){printf(“hello word.\r\n”);return 0;}所有的语言都有自己的编译器,C语言的编译器有TC、BC等,C++语言的编译器有Visual C++、C++ Builder等。C和C++语言的关系是很密切的,C++语言是C语言的超集,而因为向下的兼容性,所以,如果你写的C源代码符合ANSI C标准,那么其在C和C++的编译器里面都可以编译,相反,C++的源代码只能在C++的编译器中编译。那么编译器除了可以把源代码编译成可执行代码还有其他的作用吗?答案是有,一般的情况下,编译器都提供一个集成的环境给设计这编写、编译程序,还包括一系 列的函数库,比如上面例子中的prinrf就是stdlib.h提供的一个库函数。在涉及到网络程序设计中,常听到Winsock、Winapi等说法, 而这些也是编译器提供的一套库函数,而这些库函数有一些编译器是不提供的,如TC、BC等,所以这些编译器不可以你作为网络程序设计的编译器,当然,这些 编译器不适合作为网络程序设计的编译器还有其他重要的原因。常看见下面这样的问题:这个程序我用TC怎么编译不了?可以不可以用TC编写网络程序?…现在告诉你,不可以,除了上面的原因,更重要的原因是我们现在程序所运行的平台是Windows(当然有其他的,但情况是类似的),而Winsock、 Winapi本身是由系统(Windows)提供给我们的接口,编译器是帮助我们使用这些接口的桥梁,而TC等编译器本身没有这个功能,所以不能使用它们 来帮助我们编写网络程序。还有,TC等编译器是为DOS等16位操作系统设计的,已经不能够适用我们现在32位的操作系统环境了。所以,我推荐 Visual C++、C++ Builder或其他32位C++编译器。Visual C++、C++ Builder或其他的32位C++编译器谁更好呢?这也是见仁见智的问题,常见到有些报道说现在的C++ Builder比Visual C++要好,这两个编译器不断的在竞争、攀比。我选择Visual C++,并不是说它比C++ Builder好,而是因为它编写的可执行代码(目标文件)比较小,这也不是说它比C++ Builder好,而是因为它是Microsoft出品的,而Windows也是Microsoft出品的,在Windows的发行版本中本身就包含了很 多运行库,这样Visual C++的目标代码比较小。 二、关于代理一般做坏事的时候都害怕对方发现自己的真实IP,怎么办?用代理是简单的办法。下面描述如何编程使用各种代理。1、HTTP代理HTTP代理可以把我们的HTTP请求通过HTTP代理服务器转发到我们要访问的HTTP服务器,再把结果返回给我们,以达到代理的目的。但其功能单一,只能实现HTTP的代理,具体可以查看RFC 2068、2616等相关RFC文档。正常情况下,我们请求HTTP服务是这样的:首先和目的服务器的HTTP服务端口建立TCP连接,然后做类似“GET /index.html HTTP/1.0”的请求,HTTP服务器返回结果。当通过HTTP代理的时候是这样工作的:首先和HTTP代理服务器的服务端口建立TCP连接,然后做 类似“GET http://目标服务器地址/index.htm HTTP/1.0”的请求,代理服务器对你的目标服务器做请求后返回结果给你。相关的代码在网上很容易可以找到,这里就不列举了。2、socks代理socks是一个简单灵活的协议框架,包括4和5两个版本,sock5是由IETF核准的基于TCP/IP协议的基本应用程序代理协议,socks由两个 部分组成,服务端和客户端。具体信息可以查看RFC 1928相关文档,在网上也可以搜索到许多基于socks5的开源项目,对照RFC文档,你可以了解这个协议的使用。sock5代理客户端的工作程序是: 1.客户端向代理方服务器发出请求信息。 2.代理方服务器应答 3.客户端接到应答后发送向代理方服务器发送目的ip和端口 4.代理方服务器与目的连接 5.代理方服务器将客户端发出的信息传到目的方,将目的方发出的信息传到客户端。代理完成。由于网上的信息传输基本上都是运用tcp或udp进行的,所以使用socks5代理可以办到网上所能办到的一切,而且不用担心目的方会查到你的ip,既安全又方便。如何用代理TCP协议:1.向服务器的1080端口建立tcp连接。 2.向服务器发送 05 01 00 (此为16进制码,以下同) 3.如果接到 05 00 则是可以代理 4.发送 05 01 00 01 + 目的地址(4字节) + 目的端口(2字节),目的地址和端口都是16进制码(不是字符串)。 例202.103.190.27 - 7201 则发送的信息为:05 01 00 01 CA 67 BE 1B 1C 21 (CA=202 67=103 BE=190 1B=27 1C21=7201) 5.接受服务器返回的自身地址和端口,连接完成 6.以后操作和直接与目的方进行TCP连接相同。 如何用代理UDP连接 1.向服务器的1080端口建立udp连接 2.向服务器发送 05 01 00 3.如果接到 05 00 则是可以代理 4.发送 05 03 00 01 00 00 00 00 + 本地UDP端口(2字节) 5.服务器返回 05 00 00 01 +服务器地址+端口 6.需要申请方发送 00 00 00 01 +目的地址IP(4字节)+目的端口 +所要发送的信息 7.当有数据报返回时 向需要代理方发出00 00 00 01 +来源地址IP(4字节)+来源端口 +接受的信息 注:此为不需要密码的代理协议,只是socks5的一部分,完整协议请看RFC1928下面为一个实例程序:在网络程序设计过程中,我们经常要与各种类型的代理服务器打交道,比如在企业内部网通过代理去访问Internet网上的服务器等等,一般代理服务器支持 几种常见的代理协议标准,如Socks4,Socks5,Http代理,其中Socks5需要用户验证,代理相对复杂。我在查阅RFC文档和相关资料后, 特总结一些TCP协议穿透代理服务器的程序片断,希望对大家有所帮助。//使用到的结构struct sock4req1{char VN;char CD;unsigned short Port;unsigned long IPAddr;char other[1];};struct sock4ans1{char VN;char CD;};struct sock5req1{char Ver;char nMethods;char Methods[255];};struct sock5ans1{char Ver;char Method;};struct sock5req2{char Ver;char Cmd;char Rsv;char Atyp;char other[1];};struct sock5ans2{char Ver;char Rep;char Rsv;char Atyp;char other[1];};struct authreq{char Ver;char Ulen;char Name[255];char PLen;char Pass[255];};struct authans{char Ver;char Status;};//通过Socks4方式代理if( !ClientSock.Connect( g_ProxyInfo.m_strProxyIP,g_ProxyInfo.m_nProxyPort) ){m_sError = _T("不能连接到代理服务器!");ClientSock.Close();return FALSE;}char buff[100];memset(buff,0,100);struct sock4req1 *m_proxyreq;m_proxyreq = (struct sock4req1 *)buff;m_proxyreq->VN = 4;m_proxyreq->CD = 1;m_proxyreq->Port = ntohs(GetPort());m_proxyreq->IPAddr = inet_addr(GetServerHostName());ClientSock.Send(buff,9);struct sock4ans1 *m_proxyans;m_proxyans = (struct sock4ans1 *)buff;memset(buff,0,100);ClientSock.Receive(buff,100);if(m_proxyans->VN != 0 || m_proxyans->CD != 90){m_sError = _T("通过代理连接主站不成功!");ClientSock.Close();return FALSE;}//通过Socks5方式代理if( !ClientSock.Connect( g_ProxyInfo.m_strProxyIP,g_ProxyInfo.m_nProxyPort) ){m_sError = _T("不能连接到代理服务器!");ClientSock.Close();return FALSE;}char buff[600];struct sock5req1 *m_proxyreq1;m_proxyreq1 = (struct sock5req1 *)buff;m_proxyreq1->Ver = 5;m_proxyreq1->nMethods = 2;m_proxyreq1->Methods[0] = 0;m_proxyreq1->Methods[1] = 2;ClientSock.Send(buff,4);struct sock5ans1 *m_proxyans1;m_proxyans1 = (struct sock5ans1 *)buff;memset(buff,0,600);ClientSock.Receive(buff,600);if(m_proxyans1->Ver != 5 || (m_proxyans1->Method!=0 && m_proxyans1->Method!=2)){m_sError = _T("通过代理连接主站不成功!");ClientSock.Close();return FALSE;}if(m_proxyans1->Method == 2){int nUserLen = strlen(g_ProxyInfo.m_strProxyUser);int nPassLen = strlen(g_ProxyInfo.m_strProxyPass);struct authreq *m_authreq;m_authreq = (struct authreq *)buff;m_authreq->Ver = 1;m_authreq->Ulen = nUserLen;strcpy(m_authreq->Name,g_ProxyInfo.m_strProxyUser);m_authreq->PLen = nPassLen;strcpy(m_authreq->Pass,g_ProxyInfo.m_strProxyPass);ClientSock.Send(buff,513);struct authans *m_authans;m_authans = (struct authans *)buff;memset(buff,0,600);ClientSock.Receive(buff,600);if(m_authans->Ver != 1 || m_authans->Status != 0){m_sError = _T("代理服务器用户验证不成功!");ClientSock.Close();return FALSE;}}struct sock5req2 *m_proxyreq2;m_proxyreq2 = (struct sock5req2 *)buff;m_proxyreq2->Ver = 5;m_proxyreq2->Cmd = 1;m_proxyreq2->Rsv = 0;m_proxyreq2->Atyp = 1;unsigned long tmpLong = inet_addr(GetServerHostName());unsigned short port = ntohs(GetPort());memcpy(m_proxyreq2->other,&tmpLong,4);memcpy(m_proxyreq2->other+4,&port,2);ClientSock.Send(buff,sizeof(struct sock5req2)+5);struct sock5ans2 *m_proxyans2;memset(buff,0,600);m_proxyans2 = (struct sock5ans2 *)buff;ClientSock.Receive(buff,600);if(m_proxyans2->Ver != 5 || m_proxyans2->Rep != 0){m_sError = _T("通过代理连接主站不成功!");ClientSock.Close();return FALSE;}//通过HTTP方式代理if( !ClientSock.Connect( g_ProxyInfo.m_strProxyIP,g_ProxyInfo.m_nProxyPort) ){m_sError = _T("不能连接到代理服务器!");ClientSock.Close();return FALSE;}char buff[600];sprintf( buff, "%s%s:%d%s","CONNECT ",GetServerHostName(),GetPort()," HTTP/1.1\r\nUser-Agent: MyApp/0.1\r\n\r\n");ClientSock.Send(buff,strlen(buff)); //发送请求memset(buff,0,600);ClientSock.Receive(buff,600);if(strstr(buff, "HTTP/1.0 200 Connection established") == NULL) //连接不成功{m_sError = _T("通过代理连接主站不成功!");ClientSock.Close(); return FALSE;} 我们一般先与代理服务器连通,然后向代理服务器发送代理验证的用户名和密码(如果需要,如Socks5代理),验证成功后,再向代理服务器发送需要连接的 目的地址和端口。以上代码仅用于TCP连接,如果在内部网侦听或通过UDP协议发送信息,可查阅RFC1928等文档资料。 3、加密代理这个吗啥都可以代理,而且是加密的,安全的,常用openssl来架设加密代理服务器,你可以去http://www.openssl.org(这是一个开源的项目)去了解详细信息,就不要自己编写了,工程太大,用现成的就好了。三、关于编译拿到源代码以后如何编译?出现错误如何处理?在Windows下,如果你拿到的源码包解压后(一般要带目录解压)首先看有没有readme文件,如果这些源码是一个VC的项目,那么readme文件 中会包含一个文件列表及文件的功能及一些说明性的文字(这个文件是由VC自动建立的),那么你应该查找有没有一个dsw后缀的文件,这个文件是VC的工作 区文件,双击这个文件,VC会自动启动并打开这个文件,一般情况下是可以直接编译通过的;如果没有这个文件,那么看是不是有一个dsp后缀的文件,这个文 件是VC的项目文件,同dsw文件的打开方式,在VC下编译的时候会问你是不是建立工作区,选择是后也是可以正确编译的;如果这些源码不是由VC建立的项 目,那么这个readme文件中会包含这些源码的编译方式及编译环境,看好,是不是可以在windows下编译,怎么编译。一般情况下,如果我们找到这些 说明文件,那么源码是可以很好的编译的;如果没有找到任何说明性的文件,那就比较麻烦了,是不是非编译不可?如果不是那就算了,找个好编译的。 ^_^在windows下,源码包一般是zip、rar后缀的,如果是gz、tar后缀的,一般情况下不是windows下的源码,大多数情况下,一个完整的源码包是可以很简单的编译成功的。经常有人问exploit如何编译,比如xfocus就有很多exploit,有独立的也有在文章里面贴出来的,一般就是一段代码,遇到这样的情况先把代 码复制下来,存成c后缀的文件(这个很重要,编译器在编译的时候会安装源码文件的后缀区分你是用什么语言编写的而编译,C++源码文件的后缀是cpp,C ++的检查项目要比c严谨的多,而exploit大多是用c编写的,如果后缀不对,有可能编译不了)。然后打开这个c文件,看看开始的说明有没有编译方 式,一般好的作者会写上编译的环境及其方法的,比如在vc的编译环境下用cl example.c编译或者在linux下用等gcc example example.c(gcc是linux下的C/C++编译器)等,那么我们运行vc目录下bin目录的VCVARS32.BAT文件(vc控制台环境设 置文件,vc安装的时候会设置这些环境,并在系统启动的时候生效,如果你安装的时候没有选择,那么才需要运行这个文件),然后照说明里面的方法编译就可以 了;如果没有说明如何编译,那么我们先区分一下这个代码是在什么环境下编译的,如果包括windows.h或者winsock.h这些头文件,那肯定在 windows下是可以编译的,如果不包括,那么就没准是linux下的了。:)如果是windows下可以编译的代码,那么用vc打开这个c文件,直接 编译好了,当然有可能出错误,如果出现编译(Compiling)错误,那就是代码的写法上面有问题,要具体情况具体分析,根据知识自己改,但一般编译 exploit的时候很少出现编译错误,经常会出现连接(Linking)错误,比如下面这样:error LNK2001: unresolved external symbol __imp__recv@8~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~连接错误号 错误原因 函数名 是说编译器不能连接recv这个函数,不知道从那个lib文件中获得函数的详细信息,解决的办法是在msdn中查找recv这个函数,函数的说明文件会有下面的文字:Requirements Windows NT/2000/XP: Included in Windows NT 3.1 and later.Windows 95/98/Me: Included in Windows 95 and later.Header: Declared in Winsock2.h.Library: Use Ws2_32.lib.~~~~~~~~~~~~~~~~~~~~~~~注意这句话查到后,只要在代码的开始加如下面语句:#pragma comment(lib,"ws2_32")就可以了,也可以在vc中选择Project->Settings->Link-> Object/library Modules中添加这个lib文件也可以。当然我只是举recv这个函数,别的函数可以同样处理。如果遇到别的连接错误,可以在msdn中搜索这个错误信息(搜索关键字不要包含函数名称)。比如上面那个错误可以搜索“error LNK2001: unresolved external symbol”,可以在msdn中找到错误的原因及其解决办法。如果是编译错误,呵呵,只能靠你自己了。四、关于TCP/IP数据包的截取和分析TCP/IP数据包的截取是一个简单的工作,在Windows 2000/xp下,下面方法可以完成TCP/IP数据包的截获:1、通过建立rawsocket来完成对TCP/IP数据包的截获。从Windows 2000开始,Winsock 2开始支持原始socket,可以 截获所有经过本机的TCP/IP数据包,支持拨号网络,但对本机向外发送的TCP/IP数据包截获有缺陷。可以从http: //www.codeguru.com/Cpp/I-N/network/tcpip/article.php/c5413得到更加详细的信息及演示代 码,现在很多基于rawsocket的代码都是参照这篇文章完成的。2、通过WinPcap Developer‘s pack来完成对TCP/IP数据包的截获。这个开发包可以很好的在windows 9x/nt/2000/xp下工作,不支持拨号网络。可以从http://winpcap.polito.it/获得更详细的信息和用户手册,在本版的精 华版也包括一个介绍这个开发包的文章可以参照。Windows下对TCP/IP数据包的截获大多通过这两个方法来完成。如果你需要编写一个轻量级的TCP/IP数据包的截取和分析工具,而且只在 Windows 2000/xp下工作,你可以选择第一种方法,比较简单,而且不需要另外的驱动;否则,你需要选择第二种方法或其他方法。在类UNIX系统(比如linux)下,通常使用libpcap(http://www.tcpdump.org/)完成TCP/IP数据包的截取工作,它可以工作在多种操作系统下。可以从互联网上面搜索到很多Windows下的TCP/IP数据包截取的演示代码,还可以搜索到很多linux下的开源项目,甚至功能完备复杂的大型TCP/IP数据包截取项目。TCP/IP数据包的分析就比较困难和麻烦了。首先这种分析是基于对TCP/IP协议族的理解之上的,如果你还不了解,《TCP-IP详解卷1:协议》是首先需要翻阅的书籍,如果懒的看,那也不要提什么分析了,呵呵。截取了一个TCP/IP数据包后,首先分离出IP协议(IP“Internet Protocol”协议是TCP/IP协议族中最为核心的协议,所有的TCP、UDP、ICMP和IGMP数据等都是以IP数据报格式传输的)的头部分, 从IP协议头中可以得到很多关键的数据,如IP头的长度、源IP、目的IP、TCP/IP协议类型等,下面演示代码实现这个功能:typedef struct _IP_HEADER //定义IP首部{unsigned char h_lenver; //4位首部长度+4位IP版本号unsigned char tos; //8位服务类型TOSunsigned short total_len; //16位总长度(字节)unsigned short ident; //16位标识unsigned short frag_and_flags; //3位标志位+13位片偏移unsigned char ttl; //8位生存时间 TTLunsigned char proto; //8位协议 (TCP, UDP 或其他)unsigned short checksum; //16位IP首部校验和unsigned int sourceIP; //32位源IP地址unsigned int destIP; //32位目的IP地址} IP_HEADER;typedef struct _TCP_HEADER //定义TCP首部{USHORT th_sport; //16位源端口USHORT th_dport; //16位目的端口UINT th_seq; //32位序列号UINT th_ack; //32位确认号UCHAR th_lenres; //4位首部长度/6位保留字UCHAR th_flag; //6位标志位USHORT th_win; //16位窗口大小USHORT th_sum; //16位校验和USHORT th_urp; //16位紧急数据偏移量} TCP_HEADER; unsigned short DecodeIPHeader(char *buf) //IP头解码函数 (得到IP头的长度,其他内容得到方法类似){IP_HEADER * ipheader;unsigned short ipheaderlen;ipheader = (IP_HEADER *)buf;ipheaderlen = sizeof(unsigned long) * (ipheader->h_lenver & 0xf); return ipheaderlen;}好,现在我们获得了IP头的长度,用类似的方法也可以获得了协议类型等其他数据。假设协议类型是TCP,那么接着就可以从数据包中分离出TCP协议 (TCP协议是可靠的端到端协议)头的数据,从TCP协议头中得到很多关键的数据,如源端口、目的端口、数据偏移、控制位等。从目的端口中我们可以简单的 (但不一定准确)判断一下跟着的数据是什么协议,如端口是80那么是HTTP协议、端口是21是FTP等(HTTP、FTP等协议都是基于TCP协议实现 的)。unsigned short DecodeIPHeader(char *buf) //TCP头解码函数 (得到目的端口,其他内容得到方法类似){TCP_HEADER * tcpheader;unsigned short ipheaderlen;ipheaderlen = DecodeIPHeader(buf);tcpheader = (TCP_HEADER *)(buf + ipheaderlen);return tcpheader->th_dport;}通过类似的方法,得到所有需要的数据。一个正常的TCP连接总是由三次握手开始的,也就是说开始的三个数据包它的数据偏移是0,通过控制位可以知道三次握 手的完成状态。接着就是分析接下来的数据了,接下来的数据就有很多变化了,一般情况下针对每一种协议都要写解码函数,可以通过端口和数据的一些标志判断数 据包是什么协议的数据包,比如SMB协议开始会有0xff加SMB的标志,当然,这首先一个条件是协议是已知并且公开的,如果一个协议是不公开的,那就需 要对使用这个协议的网络应用程序进行分析以确定协议的结构并写出相应的解码函数,这就是更复杂和麻烦的工作了,需要的知识也就更多,就不是本文要解决的问 题了。关于各种协议的解码函数在Windows下我是没有找到公开的源码,但如果有时间和耐心,可以参考下面linux下的开源项目,对编写自己的协议解码还是可能有所帮助。如:http://www.cpan.org/authors/id/T/TI/TIMPOTTER/NetPacket-0.03.tar.gz用于基本的IP/TCP/UDP等包解码的模块,剥除各种协议头,抽取各个字段。http://www.ethereal.com/download.htmlEthereal是一个开源项目,功能强大的数据截取分析工具。

网络编程基础知识篇(三):socket网络编程的一些基础知识


http://blog.csdn.net/zhuichao001/article/details/5599562
2012
目录: 1) 什么是套接字? 2) Internet 套接字的两种类型 3) 网络理论 4) 结构体 5) 本机转换 6) IP 地址和如何处理它们 7) socket()函数 8) bind()函数 9) connect()函数 10) listen()函数 11) accept()函数 12) send()和recv()函数 13) sendto()和recvfrom()函数 14) close()和shutdown()函数 15) getpeername()函数 16) gethostname()函数 17) 域名服务(DNS) 18) 客户-服务器背景知识 19) 简单的服务器 20) 简单的客户端 21) 数据报套接字Socket 22) 阻塞 23) select()--多路同步I/O 24) 参考资料
-------------------------------------------------------------------------------- 什么是 socket? 你经常听到人们谈论着 “socket”,或许你还不知道它的确切含义。现在让我告诉你:它是使用 标准Unix 文件描述符 (file descriptor) 和其它程序通讯的方式。 什么? 你 也许听到一些Unix高手(hacker)这样说过:“呀,Unix中的一切就是文件!”那个家伙也许正在说到一个事实:Unix 程序在执行任何形式的 I/O 的时候,程序是在读或者写一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数。但是(注意后面的话),这个文件可能是一个网络连接, FIFO,管道,终端,磁盘上的文件或者什么其它的东西。Unix 中所有的东西就是文件!所以,你想和Internet上别的程序通讯的时候,你将要使用到文件描述符。你必须理解刚才的话。现在你脑海中或许冒出这样的念 头:“那么我从哪里得到网络通讯的文件描述符呢?”,这个问题无论如何我都要回答:你利用系统调用 socket(),它返回套接字描述符 (socket descriptor),然后你再通过它来进行send() 和 recv()调用。 “但是...”,你可能有很大的疑惑,“如果它是个文件描述符,那么为什 么不用一般调用read()和write()来进行套接字通讯?”简单的答案是:“你可以使用!”。详细的答案是:“你可以,但是使用send()和recv()让你更好的控制数据传输。” 存 在这样一个情况:在我们的世界上,有很多种套接字。有DARPA Internet 地址 (Internet 套接字),本地节点的路径名 (Unix套接字),CCITT X.25地址 (你可以将X.25 套接字完全忽略)。也许在你的Unix 机器上还有其它的。我们在这里只讲第一种:Internet 套接字。 -------------------------------------------------------------------------------- Internet 套接字的两种类型 什么意思?有两种类型的Internet 套接字?是的。不,我在撒谎。其实还有很多,但是我可不想吓着你。我们这里只讲两种。除了这些, 我打算另外介绍的 "Raw Sockets" 也是非常强大的,很值得查阅。 那 么这两种类型是什么呢?一种是"Stream Sockets"(流格式),另外一种是"Datagram Sockets"(数据包格式)。我们以后谈到它们的时候也会用到 "SOCK_STREAM" 和 "SOCK_DGRAM"。数据报套接字有时也叫“无连接套接字”(如果你确实要连接的时候可以用connect()。) 流式套接字是可靠的双向通讯的数据流。如果你向套接字按顺序输出“1,2”,那么它们将按顺序“1,2”到达另一边。它们是无错误的传递的,有自己的错误 控制,在此不讨论。 有什么在使用流式套接字?你可能听说过 telnet,不是吗?它就使用流式套接字。你需要你所输入的字符按顺序到达,不是吗?同样,WWW浏览器使用的 HTTP 协议也使用它们来下载页面。实际上,当你通过端口80 telnet 到一个 WWW 站点,然后输入 “GET pagename” 的时候,你也可以得到 HTML 的内容。为什么流式套接字可以达到高质量的数据传输?这是因为它使用了“传输控制协议 (The Transmission Control Protocol)”,也叫 “TCP” (请参考 RFC-793 获得详细资料。)TCP 控制你的数据按顺序到达并且没有错 误。你也许听到 “TCP” 是因为听到过 “TCP/IP”。这里的 IP 是指“Internet 协议”(请参考 RFC-791。) IP 只是处理 Internet 路由而已。 那 么数据报套接字呢?为什么它叫无连接呢?为什么它是不可靠的呢?有这样的一些事实:如果你发送一个数据报,它可能会到达,它可能次序颠倒了。如果它到达, 那么在这个包的内部是无错误的。数据报也使用 IP 作路由,但是它不使用 TCP。它使用“用户数据报协议 (User Datagram Protocol)”,也叫 “UDP” (请参考 RFC-768。) 为什么它们是无连接的呢?主要是因为它并不象流式套接字那样维持一个连接。你只要建立一个包,构造一个有目标信息的IP 头,然后发出去。无需连接。它们通常使用于传输包-包信息。简单的应用程序有:tftp, bootp等等。 你 也许会想:“假如数据丢失了这些程序如何正常工作?”我的朋友,每个程序在 UDP 上有自己的协议。例如,tftp 协议每发出的一个被接受到包,收到者必须发回一个包来说“我收到了!” (一个“命令正确应答”也叫“ACK” 包)。如果在一定时间内(例如5秒),发送方没有收到应答,它将重新发送,直到得到 ACK。这一ACK过程在实现 SOCK_DGRAM 应用程序的时候非常重要。 -------------------------------------------------------------------------------- 网络理论 既然我刚才提到了协议层,那么现在是讨论网络究竟如何工作和一些 关于 SOCK_DGRAM 包是如何建立的例子。当然,你也可以跳过这一段, 如果你认为已经熟悉的话。 现 在是学习数据封装 (Data Encapsulation) 的时候了!它非常非常重 要。它重要性重要到你在网络课程学(图1:数据封装)习中无论如何也得也得掌握它。主要 的内容是:一个包,先是被第一个协议(在这里是TFTP )在它的报头(也许是报尾)包装(“封装”),然后,整个数据(包括 TFTP 头)被另外一个协议 (在这里是 UDP )封装,然后下一个( IP ),一直重复下去,直到硬件(物理) 层( 这里是以太网 )。 当另外一台机器接收到包,硬件先剥去以太网头,内核剥去IP和UDP 头,TFTP程序再剥去TFTP头,最后得到数据。 现 在我们终于讲到声名狼藉的网络分层模型 (Layered Network Model)。这种网络模型在描述网络系统上相对其它模型有很多优点。例如, 你可以写一个套接字程序而不用关心数据的物理传输(串行口,以太网,连接单元接口 (AUI) 还是其它介质),因为底层的程序会为你处理它们。实际 的网络硬件和拓扑对于程序员来说是透明的。 不说其它废话了,我现在列出整个层次模型。如果你要参加网络考试, 可一定要记住: 应用层 (Application) 表示层 (Presentation) 会话层 (Session) 传输层(Transport) 网络层(Network) 数据链路层(Data Link) 物理层(Physical) 物理层是硬件(串口,以太网等等)。应用层是和硬件层相隔最远的--它 是用户和网络交互的地方。 这个模型如此通用,如果你想,你可以把它作为修车指南。把它对应 到 Unix,结果是: 应用层(Application Layer) (telnet, ftp,等等) 传输层(Host-to-Host Transport Layer) (TCP, UDP) Internet层(Internet Layer) (IP和路由) 网络访问层 (Network Access Layer) (网络层,数据链路层和物理层) 现在,你可能看到这些层次如何协调来封装原始的数据了。 看 看建立一个简单的数据包有多少工作?哎呀,你将不得不使用 "cat" 来建立数据包头!这仅仅是个玩笑。对于流式套接字你要作的是 send() 发送数据。对于数据报式套接字,你按照你选择的方式封装数据然后使用 sendto()。内核将为你建立传输层和 Internet 层,硬件完成网络访问层。 这就是现代科技。 现在结束我们的网络理论速成班。哦,忘记告诉你关于路由的事情了。 但是我不准备谈它,如果你真的关心,那么参考 IP RFC。 -------------------------------------------------------------------------------- 结构体 终于谈到编程了。在这章,我将谈到被套接字用到的各种数据类型。 因为它们中的一些内容很重要了。 首先是简单的一个:socket描述符。它是下面的类型: int 仅仅是一个常见的 int。 从 现在起,事情变得不可思议了,而你所需做的就是继续看下去。注 意这样的事实:有两种字节排列顺序:重要的字节 (有时叫 "octet",即八位位组) 在前面,或者不重要的字节在前面。前一种叫“网络字节顺序 (Network Byte Order)”。有些机器在内部是按照这个顺序储存数据,而另外 一些则不然。当我说某数据必须按照 NBO 顺序,那么你要调用函数(例如 htons() )来将它从本机字节顺序 (Host Byte Order) 转换过来。如果我没有 提到 NBO, 那么就让它保持本机字节顺序。 我的第一个结构(在这个技术手册TM中)--struct sockaddr.。这个结构 为许多类型的套接字储存套接字地址信息: struct sockaddr { unsigned short sa_family; /* 地址家族, AF_xxx */ char sa_data[14]; /*14字节协议地址*/ }; sa_family 能够是各种各样的类型,但是在这篇文章中都是 "AF_INET"。 sa_data包含套接字中的目标地址和端口信息。这好像有点 不明智。 为了处理struct sockaddr,程序员创造了一个并列的结构: struct sockaddr_in ("in" 代表 "Internet"。) struct sockaddr_in { short int sin_family; /* 通信类型 */ unsigned short int sin_port; /* 端口 */ struct in_addr sin_addr; /* Internet 地址 */ unsigned char sin_zero[8]; /* 与sockaddr结构的长度相同*/ }; 用 这个数据结构可以轻松处理套接字地址的基本元素。注意 sin_zero (它被加入到这个结构,并且长度和 struct sockaddr 一样) 应该使用函数 bzero() 或 memset() 来全部置零。 同时,这一重要的字节,一个指向 sockaddr_in结构体的指针也可以被指向结构体sockaddr并且代替它。这 样的话即使 socket() 想要的是 struct sockaddr *,你仍然可以使用 struct sockaddr_in,并且在最后转换。同时,注意 sin_family 和 struct sockaddr 中的 sa_family 一致并能够设置为 "AF_INET"。最后,sin_port和 sin_addr 必须是网络字节顺序 (Network Byte Order)! 你也许会反对道:"但是,怎么让整个数据结构 struct in_addr sin_addr 按照网络字节顺序呢?" 要知道这个问题的答案,我们就要仔细的看一看这 个数据结构: struct in_addr, 有这样一个联合 (unions): /* Internet 地址 (一个与历史有关的结构) */ struct in_addr { unsigned long s_addr; }; 它 曾经是个最坏的联合,但是现在那些日子过去了。如果你声明 "ina" 是数据结构 struct sockaddr_in 的实例,那么 "ina.sin_addr.s_addr" 就储 存4字节的 IP 地址(使用网络字节顺序)。如果你不幸的系统使用的还是恐 怖的联合 struct in_addr ,你还是可以放心4字节的 IP 地址并且和上面 我说的一样(这是因为使用了“#define”。) -------------------------------------------------------------------------------- 本机转换 我们现在到了新的章节。我们曾经讲了很多网络到本机字节顺序的转 换,现在可以实践了! 你 能够转换两种类型: short (两个字节)和 long (四个字节)。这个函 数对于变量类型 unsigned 也适用。假设你想将 short 从本机字节顺序转 换为网络字节顺序。用 "h" 表示 "本机 (host)",接着是 "to",然后用 "n" 表 示 "网络 (network)",最后用 "s" 表示 "short": h-to-n-s, 或者 htons() ("Host to Network Short")。 太简单了... 如果不是太傻的话,你一定想到了由"n","h","s",和 "l"形成的正确 组合,例如这里肯定没有stolh() ("Short to Long Host") 函数,不仅在这里 没有,所有场合都没有。但是这里有: htons()--"Host to Network Short" htonl()--"Host to Network Long" ntohs()--"Network to Host Short" ntohl()--"Network to Host Long" 现 在,你可能想你已经知道它们了。你也可能想:“如果我想改变 char 的顺序要怎么办呢?” 但是你也许马上就想到,“用不着考虑的”。你也许会想到:我的 68000 机器已经使用了网络字节顺序,我没有必要去调用 htonl() 转换 IP 地址。你可能是对的,但是当你移植你的程序到别的机器 上的时候,你的程序将失败。可移植性!这里是 Unix 世界!记住:在你将数据放到网络上的时候,确信它们是网络字节顺序的。 最后一点:为什么在数据结构 struct sockaddr_in 中, sin_addr 和 sin_port 需要转换为网络字节顺序,而sin_family 需不需要呢? 答案是: sin_addr 和 sin_port 分别封装在包的 IP 和 UDP 层。因此,它们必须要 是网络字节顺序。但是 sin_family 域只是被内核 (kernel) 使用来决定在数 据结构中包含什么类型的地址,所以它必须是本机字节顺序。同时, sin_family 没有发送到网络上,它们可以是本机字节顺序。 -------------------------------------------------------------------------------- IP 地址和如何处理它们 现 在我们很幸运,因为我们有很多的函数来方便地操作 IP 地址。没有必要用手工计算它们,也没有必要用"<<"操作来储存成长整字型。首先,假设你已经有了一个sockaddr_in结构体ina, 你有一个IP地址"132.241.5.10"要储存在其中,你就要用到函数inet_addr(),将IP地址从 点数格式转换成无符号长整型。使用方法如下: ina.sin_addr.s_addr = inet_addr("132.241.5.10"); 注意,inet_addr()返回的地址已经是网络字节格式,所以你无需再调用 函数htonl()。 我 们现在发现上面的代码片断不是十分完整的,因为它没有错误检查。 显而易见,当inet_addr()发生错误时返回-1。记住这些二进制数字?(无符号数)-1仅仅和IP地址255.255.255.255相符合!这 可是广播地址!大错特 错!记住要先进行错误检查。 好了,现在你可以将IP地址转换成长整型了。有没有其相反的方法呢? 它可以将一个in_addr结构体输出成点数格式?这样的话,你就要用到函数 inet_ntoa()("ntoa"的含义是"network to ascii"),就像这样: printf("%s",inet_ntoa(ina.sin_addr)); 它 将输出IP地址。需要注意的是inet_ntoa()将结构体in-addr作为一个参数,不是长整形。同样需要注意的是它返回的是一个指向一个字符的 指针。它是一个由inet_ntoa()控制的静态的固定的指针,所以每次调用 inet_ntoa(),它就将覆盖上次调用时所得的IP地址。例如: char *a1, *a2; . . a1 = inet_ntoa(ina1.sin_addr); /* 这是198.92.129.1 */ a2 = inet_ntoa(ina2.sin_addr); /* 这是132.241.5.10 */ printf("address 1: %sn",a1); printf("address 2: %sn",a2); 输出如下: address 1: 132.241.5.10 address 2: 132.241.5.10 假如你需要保存这个IP地址,使用strcopy()函数来指向你自己的字符 指针。 上面就是关于这个主题的介绍。稍后,你将学习将一个类 似"wintehouse.gov"的字符串转换成它所对应的IP地址(查阅域名服务,稍 后)。 -------------------------------------------------------------------------------- socket()函数 我想我不能再不提这个了-下面我将讨论一下socket()系统调用。 下面是详细介绍: #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); 但 是它们的参数是什么? 首先,domain 应该设置成 "AF_INET",就 象上面的数据结构struct sockaddr_in 中一样。然后,参数 type 告诉内核 是 SOCK_STREAM 类型还是 SOCK_DGRAM 类型。最后,把 protocol 设置为 "0"。(注意:有很多种 domain、type,我不可能一一列出了,请看 socket() 的 man帮助。当然,还有一个"更好"的方式去得到 protocol。同 时请查阅 getprotobyname() 的 man 帮助。) socket() 只是返回你以后在系统调用种可能用到的 socket 描述符,或 者在错误的时候返回-1。全局变量 errno 中将储存返回的错误值。(请参考 perror() 的 man 帮助。) -------------------------------------------------------------------------------- bind()函数 一旦你有一个套接字,你可能要将套接字和机器上的一定的端口关联起来。(如果你想用listen()来侦听一定端口的数据,这是必要一步--MUD 告 诉你说用命令 "telnet x.y.z 6969"。)如果你只想用 connect(),那么这个步 骤没有必要。但是无论如何,请继续读下去。 这里是系统调用 bind() 的大概: #include <sys/types.h> #include <sys/socket.h> int bind(int sockfd, struct sockaddr *my_addr, int addrlen); sockfd 是调用 socket 返回的文件描述符。my_addr 是指向数据结构 struct sockaddr 的指针,它保存你的地址(即端口和 IP 地址) 信息。 addrlen 设置为 sizeof(struct sockaddr)。 简单得很不是吗? 再看看例子: #include <string.h> #include <sys/types.h> #include <sys/socket.h> #define MYPORT 3490 main() { int sockfd; struct sockaddr_in my_addr; sockfd = socket(AF_INET, SOCK_STREAM, 0); /*需要错误检查 */ my_addr.sin_family = AF_INET; /* host byte order */ my_addr.sin_port = htons(MYPORT); /* short, network byte order */ my_addr.sin_addr.s_addr = inet_addr("132.241.5.10"); bzero(&(my_addr.sin_zero),; /* zero the rest of the struct */ /* don"t forget your error checking for bind(): */ bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)); . . . 这里也有要注意的几件事情。my_addr.sin_port 是网络字节顺序, my_addr.sin_addr.s_addr 也是的。另外要注意到的事情是因系统的不同, 包含的头文件也不尽相同,请查阅本地的 man 帮助文件。 在 bind() 主题中最后要说的话是,在处理自己的 IP 地址和/或端口的 时候,有些工作是可以自动处理的。 my_addr.sin_port = 0; /* 随机选择一个没有使用的端口 */ my_addr.sin_addr.s_addr = INADDR_ANY; /* 使用自己的IP地址 */ 通过将0赋给 my_addr.sin_port,你告诉 bind() 自己选择合适的端 口。同样,将 my_addr.sin_addr.s_addr 设置为 INADDR_ANY,你告诉 它自动填上它所运行的机器的 IP 地址。 如 果你一向小心谨慎,那么你可能注意到我没有将 INADDR_ANY 转 换为网络字节顺序!这是因为我知道内部的东西:INADDR_ANY 实际上就 是 0!即使你改变字节的顺序,0依然是0。但是完美主义者说应该处处一致,INADDR_ANY或许是12呢?你的代码就不能工作了,那么就看下面 的代码: my_addr.sin_port = htons(0); /* 随机选择一个没有使用的端口 */ my_addr.sin_addr.s_addr = htonl(INADDR_ANY);/* 使用自己的IP地址 */ 你或许不相信,上面的代码将可以随便移植。我只是想指出,既然你 所遇到的程序不会都运行使用htonl的INADDR_ANY。 bind() 在错误的时候依然是返回-1,并且设置全局错误变量errno。 在你调用 bind() 的时候,你要小心的另一件事情是:不要采用小于 1024的端口号。所有小于1024的端口号都被系统保留!你可以选择从1024 到65535的端口(如果它们没有被别的程序使用的话)。 你 要注意的另外一件小事是:有时候你根本不需要调用它。如果你使 用 connect() 来和远程机器进行通讯,你不需要关心你的本地端口号(就象你在使用 telnet 的时候),你只要简单的调用 connect() 就可以了,它会检查套接字是否绑定端口,如果没有,它会自己绑定一个没有使用的本地端 口。 -------------------------------------------------------------------------------- connect()程序 现在我们假设你是个 telnet 程序。你的用户命令你得到套接字的文件 描述符。你听从命令调用了socket()。下一步,你的用户告诉你通过端口 23(标准 telnet 端口)连接到"132.241.5.10"。你该怎么做呢? 幸运的是,你正在阅读 connect()--如何连接到远程主机这一章。你可不想让你的用户失望。 connect() 系统调用是这样的: #include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); sockfd 是系统调用 socket() 返回的套接字文件描述符。serv_addr 是 保存着目的地端口和 IP 地址的数据结构 struct sockaddr。addrlen 设置 为 sizeof(struct sockaddr)。 想知道得更多吗?让我们来看个例子: #include <string.h> #include <sys/types.h> #include <sys/socket.h> #define DEST_IP "132.241.5.10" #define DEST_PORT 23 main() { int sockfd; struct sockaddr_in dest_addr; /* 目的地址*/ sockfd = socket(AF_INET, SOCK_STREAM, 0); /* 错误检查 */ dest_addr.sin_family = AF_INET; /* host byte order */ dest_addr.sin_port = htons(DEST_PORT); /* short, network byte order */ dest_addr.sin_addr.s_addr = inet_addr(DEST_IP); bzero(&(dest_addr.sin_zero),; /* zero the rest of the struct */ /* don"t forget to error check the connect()! */ connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr)); . . . 再一次,你应该检查 connect() 的返回值--它在错误的时候返回-1,并 设置全局错误变量 errno。 同时,你可能看到,我没有调用 bind()。因为我不在乎本地的端口号。 我只关心我要去那。内核将为我选择一个合适的端口号,而我们所连接的 地方也自动地获得这些信息。一切都不用担心。 -------------------------------------------------------------------------------- listen()函数 是换换内容得时候了。假如你不希望与远程的一个地址相连,或者说, 仅仅是将它踢开,那你就需要等待接入请求并且用各种方法处理它们。处 理过程分两步:首先,你听--listen(),然后,你接受--accept() (请看下面的 内容)。 除了要一点解释外,系统调用 listen 也相当简单。 int listen(int sockfd, int backlog); sockfd 是调用 socket() 返回的套接字文件描述符。backlog 是在进入 队列中允许的连接数目。什么意思呢? 进入的连接是在队列中一直等待直到你接受 (accept() 请看下面的文章)连接。它们的数目限制于队列的允许。 大多数系统的允许数目是20,你也可以设置为5到10。 和别的函数一样,在发生错误的时候返回-1,并设置全局错误变量 errno。 你可能想象到了,在你调用 listen() 前你或者要调用 bind() 或者让内 核随便选择一个端口。如果你想侦听进入的连接,那么系统调用的顺序可 能是这样的: socket(); bind(); listen(); /* accept() 应该在这 */ 因为它相当的明了,我将在这里不给出例子了。(在 accept() 那一章的 代码将更加完全。)真正麻烦的部分在 accept()。 -------------------------------------------------------------------------------- accept()函数 准备好了,系统调用 accept() 会有点古怪的地方的!你可以想象发生 这样的事情:有人从很远的地方通过一个你在侦听 (listen()) 的端口连接 (connect()) 到你的机器。它的连接将加入到等待接受 (accept()) 的队列 中。你调用 accept() 告诉它你有空闲的连接。它将返回一个新的套接字文 件描述符!这样你就有两个套接字了,原来的一个还在侦听你的那个端口, 新的在准备发送 (send()) 和接收 ( recv()) 数据。这就是这个过程! 函数是这样定义的: #include <sys/socket.h> int accept(int sockfd, void *addr, int *addrlen); sockfd 相当简单,是和 listen() 中一样的套接字描述符。addr 是个指 向局部的数据结构 sockaddr_in 的指针。这是要求接入的信息所要去的地 方(你可以测定那个地址在那个端口呼叫你)。在它的地址传递给 accept 之 前,addrlen 是个局部的整形变量,设置为 sizeof(struct sockaddr_in)。 accept 将不会将多余的字节给 addr。如果你放入的少些,那么它会通过改 变 addrlen 的值反映出来。 同样,在错误时返回-1,并设置全局错误变量 errno。 现在是你应该熟悉的代码片段。 #include <string.h> #include <sys/socket.h> #include <sys/types.h> #define MYPORT 3490 /*用户接入端口*/ #define BACKLOG 10 /* 多少等待连接控制*/ main() { int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */ struct sockaddr_in my_addr; /* 地址信息 */ struct sockaddr_in their_addr; /* connector"s address information */ int sin_size; sockfd = socket(AF_INET, SOCK_STREAM, 0); /* 错误检查*/ my_addr.sin_family = AF_INET; /* host byte order */ my_addr.sin_port = htons(MYPORT); /* short, network byte order */ my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */ bzero(&(my_addr.sin_zero),; /* zero the rest of the struct */ /* don"t forget your error checking for these calls: */ bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)); listen(sockfd, BACKLOG); sin_size = sizeof(struct sockaddr_in); new_fd = accept(sockfd, &their_addr, &sin_size); . . . 注意,在系统调用 send() 和 recv() 中你应该使用新的套接字描述符 new_fd。如果你只想让一个连接进来,那么你可以使用 close() 去关闭原 来的文件描述符 sockfd 来避免同一个端口更多的连接。 -------------------------------------------------------------------------------- send() and recv()函数 这两个函数用于流式套接字或者数据报套接字的通讯。如果你喜欢使 用无连接的数据报套接字,你应该看一看下面关于sendto() 和 recvfrom() 的章节。 send() 是这样的: int send(int sockfd, const void *msg, int len, int flags); sockfd 是你想发送数据的套接字描述符(或者是调用 socket() 或者是 accept() 返回的。)msg 是指向你想发送的数据的指针。len 是数据的长度。 把 flags 设置为 0 就可以了。(详细的资料请看 send() 的 man page)。 这里是一些可能的例子: char *msg = "Beej was here!"; int len, bytes_sent; . . len = strlen(msg); bytes_sent = send(sockfd, msg, len, 0); . . . send() 返回实际发送的数据的字节数--它可能小于你要求发送的数 目! 注意,有时候你告诉它要发送一堆数据可是它不能处理成功。它只是发送它可能发送的数据,然后希望你能够发送其它的数据。记住,如果 send() 返回的数据和 len 不匹配,你就应该发送其它的数据。但是这里也有个好消息:如果你要发送的包很小(小于大约 1K),它可能处理让数据一 次发送完。最后要说得就是,它在错误的时候返回-1,并设置 errno。 recv() 函数很相似: int recv(int sockfd, void *buf, int len, unsigned int flags); sockfd 是要读的套接字描述符。buf 是要读的信息的缓冲。len 是缓 冲的最大长度。flags 可以设置为0。(请参考recv() 的 man page。) recv() 返回实际读入缓冲的数据的字节数。或者在错误的时候返回-1, 同时设置 errno。 很简单,不是吗? 你现在可以在流式套接字上发送数据和接收数据了。 你现在是 Unix 网络程序员了! -------------------------------------------------------------------------------- sendto() 和 recvfrom()函数 “这很不错啊”,你说,“但是你还没有讲无连接数据报套接字呢?” 没问题,现在我们开始这个内容。 既然数据报套接字不是连接到远程主机的,那么在我们发送一个包之 前需要什么信息呢? 不错,是目标地址!看看下面的: int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen); 你 已经看到了,除了另外的两个信息外,其余的和函数 send() 是一样 的。 to 是个指向数据结构 struct sockaddr 的指针,它包含了目的地的 IP 地址和端口信息。tolen 可以简单地设置为 sizeof(struct sockaddr)。 和函数 send() 类似,sendto() 返回实际发送的字节数(它也可能小于 你想要发送的字节数!),或者在错误的时候返回 -1。 相似的还有函数 recv() 和 recvfrom()。recvfrom() 的定义是这样的: int recvfrom(int sockfd, void *buf, int len, unsigned int flags,  struct sockaddr *from, int *fromlen); 又 一次,除了两个增加的参数外,这个函数和 recv() 也是一样的。from 是一个指向局部数据结构 struct sockaddr 的指针,它的内容是源机器的 IP 地址和端口信息。fromlen 是个 int 型的局部指针,它的初始值为 sizeof(struct sockaddr)。函数调用返回后,fromlen 保存着实际储存在 from 中的地址的长度。 recvfrom() 返回收到的字节长度,或者在发生错误后返回 -1。 记住,如果你用 connect() 连接一个数据报套接字,你可以简单的调 用 send() 和 recv() 来满足你的要求。这个时候依然是数据报套接字,依 然使用 UDP,系统套接字接口会为你自动加上了目标和源的信息。 -------------------------------------------------------------------------------- close()和shutdown()函数 你已经整天都在发送 (send()) 和接收 (recv()) 数据了,现在你准备关 闭你的套接字描述符了。这很简单,你可以使用一般的 Unix 文件描述符 的 close() 函数: close(sockfd); 它将防止套接字上更多的数据的读写。任何在另一端读写套接字的企 图都将返回错误信息。 如果你想在如何关闭套接字上有多一点的控制,你可以使用函数 shutdown()。它允许你将一定方向上的通讯或者双向的通讯(就象close()一 样)关闭,你可以使用: int shutdown(int sockfd, int how); sockfd 是你想要关闭的套接字文件描述复。how 的值是下面的其中之 一: 0 – 不允许接受 1 – 不允许发送 2 – 不允许发送和接受(和 close() 一样) shutdown() 成功时返回 0,失败时返回 -1(同时设置 errno。) 如果在无连接的数据报套接字中使用shutdown(),那么只不过是让 send() 和 recv() 不能使用(记住你在数据报套接字中使用了 connect 后 是可以使用它们的)。 -------------------------------------------------------------------------------- getpeername()函数 这个函数太简单了。 它太简单了,以至我都不想单列一章。但是我还是这样做了。 函数 getpeername() 告诉你在连接的流式套接字上谁在另外一边。函 数是这样的: #include <sys/socket.h> int getpeername(int sockfd, struct sockaddr *addr, int *addrlen); sockfd 是连接的流式套接字的描述符。addr 是一个指向结构 struct sockaddr (或者是 struct sockaddr_in) 的指针,它保存着连接的另一边的 信息。addrlen 是一个 int 型的指针,它初始化为 sizeof(struct sockaddr)。函数在错误的时候返回 -1,设置相应的 errno。 一旦你获得它们的地址,你可以使用 inet_ntoa() 或者 gethostbyaddr() 来打印或者获得更多的信息。但是你不能得到它的帐号。(如果它运行着愚蠢的守护进程,这是可能的,但是它的讨论已经超出了本文的范围,请参 考 RFC-1413 以获得更多的信息。) -------------------------------------------------------------------------------- gethostname()函数 甚至比 getpeername() 还简单的函数是 gethostname()。它返回你程 序所运行的机器的主机名字。然后你可以使用 gethostbyname() 以获得你 的机器的 IP 地址。 下面是定义: #include <unistd.h> int gethostname(char *hostname, size_t size); 参数很简单:hostname 是一个字符数组指针,它将在函数返回时保存 主机名。size是hostname 数组的字节长度。 函数调用成功时返回 0,失败时返回 -1,并设置 errno。 

本文来源:https://www.shanpow.com/xx/477366/

《网络编程基础知识.doc》
将本文的Word文档下载到电脑,方便收藏和打印
推荐度:
点击下载文档

文档为doc格式

相关阅读
  • 初中物理试题范文(精选5篇) 初中物理试题范文(精选5篇)
  • 初中物理试题及答案范文四篇 初中物理试题及答案范文四篇
  • 八年级下册物理试题范文(通用7篇) 八年级下册物理试题范文(通用7篇)
  • 湖北省鄂东南2023年高三秋季期中联考物理试题及答案(合集2篇) 湖北省鄂东南2023年高三秋季期中联考物理试题及答案(合集2篇)
  • 2023年江苏宿迁中考物理试题及答案 2023年江苏宿迁中考物理试题及答案
  • 2023年江苏泰州中考物理试题及答案 2023年江苏泰州中考物理试题及答案
  • 2023年重庆普通高中学业水平选择性考试物理试题及答案 2023年重庆普通高中学业水平选择性考试物理试题及答案
  • 2023年重庆高考物理试题及答案【精选】 2023年重庆高考物理试题及答案【精选】
为您推荐