socket编程指南文档格式.docx
《socket编程指南文档格式.docx》由会员分享,可在线阅读,更多相关《socket编程指南文档格式.docx(21页珍藏版)》请在冰豆网上搜索。
3再说两句
网上很多文章对于Socket的来龙去脉有如教科书一般的精准。
但是涉及具体编程技术就往往被VC等集成开发环境所毒害了,把WindowsSDK、MFC、Socket、多线程、DLL以及编译链接等等技术搅合在一起煮成一锅夹生饭。
既然要学习Socket,就应该用最简单直白的方式把Socket的几个使用要点讲出来。
我认为程序员最关心的有以下几点,按照优先级排列如下:
1.Socket的机制是什么?
2.用C/C++写Socket需要什么头文件、库文件、DLL,它们可以由谁提供,安装后一般处于系统的哪个文件夹内?
3.编写Socket程序需要的编程基础是什么?
4.Socket库内最重要的几个函数和数据类型是什么?
5.两个最简单的例子程序;
6.一个贴近应用的稍微复杂的Socket应用程序。
我将一一讲述这些要点,并给出从简到繁,从朴素到花哨的所有源代码以及编译链接的命令。
4Socket的机制是什么?
我们可以简单的把Socket理解为一个可以连通网络上不同计算机程序之间的管道,把一堆数据从管道的A端扔进去,则会从管道的B端(也许同时还可以从C、D、E、F……端冒出来)。
管道的端口由两个因素来唯一确认,即机器的IP地址和程序所使用的端口号。
IP地址的含义所有人都知道,所谓端口号就是程序员指定的一个数字,许多著名的木马程序成天在网络上扫描不同的端口号就是为了获取一个可以连通的端口从而进行破坏。
比较著名的端口号有http的80端口和ftp的21端口(我记错了么?
)。
当然,建议大家自己写程序不要使用太小的端口号,它们一般被系统占用了,也不要使用一些著名的端口,一般来说使用1000~5000之内的端口比较好。
Socket可以支持数据的发送和接收,它会定义一种称为套接字的变量,发送数据时首先创建套接字,然后使用该套接字的sendto等方法对准某个IP/端口进行数据发送;
接收端也首先创建套接字,然后将该套接字绑定到一个IP/端口上,所有发向此端口的数据会被该套接字的recv等函数读出。
如同读出文件中的数据一样。
5所需的头文件、库文件和DLL
对于目前使用最广泛的WindowsSocket2.0版本,所需的一些文件如下(以安装了VC6为例说明其物理位置):
●头文件winsock2.h,通常处于C:
"
ProgramFiles"
MicrosoftVisualStudio"
VC98"
INCLUDE;
查看该头文件可知其中又包含了windows.h和pshpack4.h头文件,因此在windows中的一些常用API都可以使用;
●库文件Ws2_32.lib,通常处于C:
Lib;
●DLL文件Ws2_32.dll,通常处于C:
WINDOWS"
system32,这个是可以猜到的。
6编写Socket程序需要的编程基础
在开始编写Socket程序之前,需要以下编程基础:
●C++语法;
●一点点windowsSDK的基础,了解一些SDK的数据类型与API的调用方式;
●一点点编译、链接和执行的技术;
知道cl和link的最常用用法即可。
7UDP
用最通俗的话讲,所谓UDP,就是发送出去就不管的一种网络协议。
因此UDP编程的发送端只管发送就可以了,不用检查网络连接状态。
下面用例子来说明怎样编写UDP,并会详细解释每个API和数据类型。
7.1UDP广播发送程序
下面是一个用UDP发送广播报文的例子。
#include<
winsock2.h>
iostream.h>
voidmain()
{
SOCKETsock;
//socket套接字
charszMsg[]="
thisisaUDPtestpackage"
;
//被发送的字段
//1.启动SOCKET库,版本为2.0
WORDwVersionRequested;
WSADATAwsaData;
interr;
wVersionRequested=MAKEWORD(2,0);
err=WSAStartup(wVersionRequested,&
wsaData);
if(0!
=err)//检查Socket初始化是否成功
cout<
<
Socket2.0初始化失败,Exit!
return;
}
//检查Socket库的版本是否为2.0
if(LOBYTE(wsaData.wVersion)!
=2||HIBYTE(wsaData.wVersion)!
=0)
WSACleanup();
//2.创建socket,
sock=socket(
AF_INET,//internetwork:
UDP,TCP,etc
SOCK_DGRAM,//SOCK_DGRAM说明是UDP类型
0//protocol
);
if(INVALID_SOCKET==sock){
Socket创建失败,Exit!
//3.设置该套接字为广播类型,
boolopt=true;
setsockopt(sock,SOL_SOCKET,SO_BROADCAST,reinterpret_cast<
charFAR*>
(&
opt),sizeof(opt));
//4.设置发往的地址
sockaddr_inaddrto;
//发往的地址
memset(&
addrto,0,sizeof(addrto));
addrto.sin_family=AF_INET;
//地址类型为internetwork
addrto.sin_addr.s_addr=INADDR_BROADCAST;
//设置ip为广播地址
addrto.sin_port=htons(7861);
//端口号为7861
intnlen=sizeof(addrto);
unsignedintuIndex=1;
while(true)
Sleep(1000);
//程序休眠一秒
//向广播地址发送消息
if(sendto(sock,szMsg,strlen(szMsg),0,(sockaddr*)&
addrto,nlen)
==SOCKET_ERROR)
WSAGetLastError()<
endl;
else
uIndex++<
:
anUDPpackageissended."
if(!
closesocket(sock))//关闭套接字
WSAGetLastError();
WSACleanup())//关闭Socket库
}
编译命令:
CL/cUDP_Send_Broadcast.cpp
链接命令(注意如果找不到该库,则要在后面的/LIBPATH参数后加上库的路径):
linkUDP_Send_Broadcast.objws2_32.lib
执行命令:
D:
Code"
成品代码"
Socket"
socket_src>
UDP_Send_Broadcast.exe
1:
anUDPpackageissended.
2:
3:
4:
^C
下面一一解释代码中出现的数据类型与API函数。
有耐心的可以仔细看看,没耐心的依葫芦画瓢也可以写程序了。
7.2SOCKET类型
SOCKET是socket套接字类型,在WINSOCK2.H中有如下定义:
typedefunsignedintu_int;
typedefu_intSOCKET;
可知套接字实际上就是一个无符号整型,它将被Socket环境管理和使用。
套接字将被创建、设置、用来发送和接收数据,最后会被关闭。
7.3WORD类型、MAKEWORD、LOBYTE和HIBYTE宏
WORD类型是一个16位的无符号整型,在WTYPES.H中被定义为:
typedefunsignedshortWORD;
其目的是提供两个字节的存储,在Socket中这两个字节可以表示主版本号和副版本号。
使用MAKEWORD宏可以给一个WORD类型赋值。
例如要表示主版本号2,副版本号0,可以使用以下代码:
注意低位内存存储主版本号2,高位内存存储副版本号0,其值为0x0002。
使用宏LOBYTE可以读取WORD的低位字节,HIBYTE可以读取高位字节。
7.4WSADATA类型和LPWSADATA类型
WSADATA类型是一个结构,描述了Socket库的一些相关信息,其结构定义如下:
typedefstructWSAData{
WORDwVersion;
WORDwHighVersion;
charszDescription[WSADESCRIPTION_LEN+1];
charszSystemStatus[WSASYS_STATUS_LEN+1];
unsignedshortiMaxSockets;
unsignedshortiMaxUdpDg;
charFAR*lpVendorInfo;
}WSADATA;
typedefWSADATAFAR*LPWSADATA;
值得注意的就是wVersion字段,存储了Socket的版本类型。
LPWSADATA是WSADATA的指针类型。
它们不用程序员手动填写,而是通过Socket的初始化函数WSAStartup读取出来。
7.5WSAStartup函数
WSAStartup函数被用来初始化Socket环境,它的定义如下:
intPASCALFARWSAStartup(WORDwVersionRequired,LPWSADATAlpWSAData);
其返回值为整型,调用方式为PASCAL(即标准类型,PASCAL等于__stdcall),参数有两个,第一个参数为WORD类型,指明了Socket的版本号,第二个参数为WSADATA类型的指针。
若返回值为0,则初始化成功,若不为0则失败。
7.6WSACleanup函数
这是Socket环境的退出函数。
返回值为0表示成功,SOCKET_ERROR表示失败。
7.7socket函数
socket的创建函数,其定义为:
SOCKETPASCALFARsocket(intaf,inttype,intprotocol);
第一个参数为intaf,代表网络地址族,目前只有一种取值是有效的,即AF_INET,代表internet地址族;
第二个参数为inttype,代表网络协议类型,SOCK_DGRAM代表UDP协议,SOCK_STREAM代表TCP协议;
第三个参数为intprotocol,指定网络地址族的特殊协议,目前无用,赋值0即可。
返回值为SOCKET,若返回INVALID_SOCKET则失败。
7.8setsockopt函数
这个函数用来设置Socket的属性,若不能正确设置socket属性,则数据的发送和接收会失败。
定义如下:
intPASCALFARsetsockopt(SOCKETs,intlevel,intoptname,
constcharFAR*optval,intoptlen);
其返回值为int类型,0代表成功,SOCKET_ERROR代表有错误发生。
第一个参数SOCKETs,代表要设置的套接字;
第二个参数intlevel,代表要设置的属性所处的层次,层次包含以下取值:
SOL_SOCKET代表套接字层次;
IPPROTO_TCP代表TCP协议层次,IPPROTO_IP代表IP协议层次(后面两个我都没有用过);
第三个参数intoptname,代表设置参数的名称,SO_BROADCAST代表允许发送广播数据的属性,其它属性可参考MSDN;
第四个参数constcharFAR*optval,代表指向存储参数数值的指针,注意这里可能要使用reinterpret_cast类型转换;
第五个参数intoptlen,代表存储参数数值变量的长度。
7.9sockaddr_in、in_addr类型,inet_addr、inet_ntoa函数
sockaddr_in定义了socket发送和接收数据包的地址,定义:
structsockaddr_in{
shortsin_family;
u_shortsin_port;
structin_addrsin_addr;
charsin_zero[8];
};
其中in_addr的定义如下:
structin_addr{
union{
struct{u_chars_b1,s_b2,s_b3,s_b4;
}S_un_b;
struct{u_shorts_w1,s_w2;
}S_un_w;
u_longS_addr;
}S_un;
首先阐述in_addr的含义,很显然它是一个存储ip地址的联合体(忘记union含义的请看c++书),有三种表达方式:
第一种用四个字节来表示IP地址的四个数字;
第二种用两个双字节来表示IP地址;
第三种用一个长整型来表示IP地址。
给in_addr赋值的一种最简单方法是使用inet_addr函数,它可以把一个代表IP地址的字符串赋值转换为in_addr类型,如
addrto.sin_addr.s_addr=inet_addr("
192.168.0.2"
本例子中由于是广播地址,所以没有使用这个函数。
其反函数是inet_ntoa,可以把一个in_addr类型转换为一个字符串。
sockaddr_in的含义比in_addr的含义要广泛,其各个字段的含义和取值如下:
第一个字段shortsin_family,代表网络地址族,如前所述,只能取值AF_INET;
第二个字段u_shortsin_port,代表IP地址端口,由程序员指定;
第三个字段structin_addrsin_addr,代表IP地址;
第四个字段charsin_zero[8],很搞笑,是为了保证sockaddr_in与SOCKADDR类型的长度相等而填充进来的字段。
以下代表指明了广播地址,端口号为7861的一个地址:
7.10sockaddr类型
sockaddr类型是用来表示Socket地址的类型,同上面的sockaddr_in类型相比,sockaddr的适用范围更广,因为sockaddr_in只适用于TCP/IP地址。
Sockaddr的定义如下:
structsockaddr{
u_shortsa_family;
charsa_data[14];
可知sockaddr有16个字节,而sockaddr_in也有16个字节,所以sockaddr_in是可以强制类型转换为sockaddr的。
事实上也往往使用这种方法。
7.11Sleep函数
线程挂起函数,表示线程挂起一段时间。
Sleep(1000)表示挂起一秒。
定义于WINBASE.H头文件中。
WINBASE.H又被包含于WINDOWS.H中,然后WINDOWS.H被WINSOCK2.H包含。
所以在本例中使用Sleep函数不需要包含其它头文件。
7.12sendto函数
在Socket中有两套发送和接收函数,一是sendto和recvfrom;
二是send和recv。
前一套在函数参数中要指明地址;
而后一套需要先将套接字和一个地址绑定,然后直接发送和接收,不需绑定地址。
sendto的定义如下:
intPASCALFARsendto(SOCKETs,constcharFAR*buf,intlen,intflags,conststructsockaddrFAR*to,inttolen);
第一个参数就是套接字;
第二个参数是要传送的数据指针;
第三个参数是要传送的数据长度(字节数);
第四个参数是传送方式的标识,如果不需要特殊要求则可以设置为0,其它值请参考MSDN;
第五个参数是目标地址,注意这里使用的是sockaddr的指针;
第六个参数是地址的长度;
返回值为整型,如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR。
7.13WSAGetLastError函数
该函数用来在Socket相关API失败后读取错误码,根据这些错误码可以对照查出错误原因。
7.14closesocket
关闭套接字,其参数为SOCKET类型。
成功返回0,失败返回SOCKET_ERROR。
7.15小结
总结以上内容,写一个UDP发送程序的步骤如下:
1.用WSAStartup函数初始化Socket环境;
2.用socket函数创建一个套接字;
3.用setsockopt函数设置套接字的属性,例如设置为广播类型;
很多时候该步骤可以省略;
4.创建一个sockaddr_in,并指定其IP地址和端口号;
5.用sendto函数向指定地址发送数据,这里的目标地址就是广播地址;
注意这里不需要绑定,即使绑定了,其地址也会被sendto中的参数覆盖;
若使用send函数则会出错,因为send是面向连接的,而UDP是非连接的,只能使用sendto发送数据;
6.用closesocket函数关闭套接字;
7.用WSACleanup函数关闭Socket环境。
那么,与之类似,一个UDP接收程序的步骤如下,注意接收方一定要bind套接字:
5.用bind函数将套接字与接收的地址绑定起来,然后调用recvfrom函数或者recv接收数据;
注意这里一定要绑定,因为接收报文的套接字必须在网络上有一个绑定的名称才能保证正确接收数据;
广播接收程序见源程序代码UDP_Recv_Broadcast.cpp。
编译、链接、执行与UDP_Send_Broadcast类似。
7.16UDP点对点发送接收程序
广播发送和接收使用并不广泛,一般来说指定发送和接收的IP比较常用。
点对点方式的UDP发送和接收与上面的例子非常类似,不同的就是需要指定一个具体的IP地址。
并且不需要调用setsockopt设置socket的广播属性。
其具体源代码见UDP_Send_P2P.cpp和UDP_Recv_P2P.cpp。
注意在使用这两个程序时要设为自己所需的IP。
8TCP
TCP与UDP最大的不同之处在于TCP是一个面向连接的协议,在进行数据收发之前TCP必须进行连接,并且在收发的时候必须保持该连接。
发送方的步骤如下(省略了Socket环境的初始化、关闭等内容):
1.用socket函数创建一个套接字sock;
2.用bind将sock绑定到本地地址;
3.用listen侦听sock套接字;
4.用accept函数接收客户方的连接,返回客户方套接字clientSocket;
5.在客户方套接字clientSocke