对程序员来说,可以把socket看成一个文件指针,只要向指针所指的文件读写数据,就可以实现双方通信。利用socket进行通信,有两种主要方式。
第一种是面向连接的流方式,两个通信的应用程序之间先要建立一种连接链路,数据才能被正确传送接收。这种方式对应的是TCP协议。特点是:通信可靠,对数据有校验和重发机制,通常用作数据文件的传输,如ftp、telnet。
第二种是无连接的数据报文方式,这时两台计算机像是把数据放在一封信里通过网络寄给对方,信在传输过程中可能会残缺不全,而且后发的信可能会先被收到,对应的是UDP协议。由于取消重发校验机制,能够达到较高的通信速率,可以用作一些对数据可靠性要求不高的通信,如实时语音,图像传送,广播信息。
在ISO的OSI网络七层协议中,WINSOCK主要负责是控制数据的输入输出,也就是传输层和网络层,屏蔽了数据链路层和物理层,给windows下的网络编程带了巨大变化。
l WINSOCK的编程特点
网络通信中,由于网络拥挤或一次发送的数据量过大等原因,经产会发生交换的数据在短时间内不能传送完,收发数据的函数因此不能返回,这种现象叫阻塞。WINSOCK对有可能阻塞的函数提供两种处理方式:
1. 阻塞方式:收发数据的函数在被调用后一直要到传送完毕或者出错才能返回,期间,除了等待网络操作的完成不能进行任何操作。用户可能会因为长时间等待而关闭主窗口。
2. 非阻塞方式:函数被调用后立即返回,当网络操作传送完成后由WINSOCK给应用程序发送一个消息通知操作完成,可以根据发送的消息传出的参数判断操作是否正常。
l WINSOCK基本的API
WSAStartup():初始化
函数原型:
int PASCAL FAR WSAStrartup(WORD wVersionRequest,LPWSADATA lpWSAData);
windows socket由DLL形式提供,为了完成一系列初始化操作,每一个使用windows socket的应用程序必须进行WSAStartup()调用,只有完成成功的调用后才能使用socket。
wVersionRequest :欲使用的windows socket API版本,这是一个WORD类型的整数,它的高位字节定义是次版本号,低位是主版本号。
lpWSAData:指向WSDATA资料的指针。
传回值:0表示成功,失败返回其它相应结果(失败的原因)。
Socket():创建一个Socket
函数原型:
SOCKET Scoket(int af, int type, int proctocol);
该函数功能与文件操作中的fopen类似。
af是address family。一般填写AF_INET 表示是在Internet上的socket;type是socket的类型,当采用流连接方式时写SOCK_STREAM,用数据报文方式写SOCK_DGRAM。proctocol一般为0,表示对两种类型的socket分别采用默认的TCP和UDP传输协议。函数返回值是WINSOCK定义的一种数据类型SOCKET,实际上是个整形数据,创建成功时,返回WINSOCK分配给程序的socket编号,后面调用传输函数,可以把它当作指针一样引用。创建失败返回INVALID_SOCKET。
bind():为创建socket指定通信对象。
函数原型:
int bind(SOCKET s,struct sockaddr_in * name, int namelen);
成功创建socket后,就应该选定通信对象。首先是自己的程序要与网上的哪台计算机通话;其次在多任务时,该台计算机可能会有几个程序在工作,必须指定要与哪个程序通信。前者可通过网络IP来确定,后者通过端口号确定。端口号表示同一台计算机上不同的应用程序,可以在0-65535之间任选,在编制自己通信程序时,应指定端口号大于1024。TCP——21,UDP——69。
s:上一步创建好的套接字。
name:描述通信对象地址信息的结构体的指针,namelen是该结构体的长度。
struct socketadd_in{
short sin_family; //一套地址族,通常设置为AF_INET(表示Internet上的socket)
unsigned short sin_port; //端口号
struct in_addr; //IP地址
char sin_zero[8];
}
//使该结构大小和SOCKADDR结构大小相同(SOCKADDR是一个无符号short型和长度为14的char型数组构成),sockadd_in加入这个数组长度也是16,目的是方便于地址操作。
IP地址in_addr的定义如下:
struct in_addr{
union {
struct{
unsigned char s_b1;
s_b2,s_b3,s_b4;
} S_un_b;
struct{
unsigned short s_w1, s_w2;
} S_un_w;
unsigned long S_addr;
} S_un;
};
对一个IP地址是“10.14.25.90”sockaddr_in结构中的sin_addr可以被这样几种方法赋值:
M1:
sin_addr.S_un.S_un_b.s_b1=10;
sin_addr.S_un.S_un_b.s_b2=14;
sin_addr.S_un.S_un_b.s_b3=25;
sin_addr.S_un.S_un_b.s_b4=90;
M2:
sin_addr.S_un.S_sun_w.s_w1=(14<<8)|10;
sin_addr.S_un.S_sun_w.s_w2=(90<<8)|25;
M3:
sin_addr.S_un.S_addr=(90<<24)|(25<<16)|(14<<8)|10;
listen():设置等待连接状态
函数原型:
int listen(SOCKET s, int backlog);
对于服务器的程序,当申请到socket,并指定通信对象为INADDR_ANY之后,就应该等待一个客户机的程序要求连接,listen()就是把一个socket设置这种状态的函数。backlog表示等待连接队列长度,可取1-5,如果服务器已经和其它客户程序连接,则后来这个连接被放在队列中,等待服务器空闲的时候连接。当队列达到指定长度backlog,再来连接会被拒绝。
accept():接收连接请求。
函数原型:
SOCKET accept(SOCKET s, struct sockaddr_in * addr , int * addrlen);
接收到连接后,会为这个连接建立一个新的socket用来与对方通信,并把它作为返回值,新建的socket与原来的socket有相同的特性,包括端口号,而原来的socket被释放,用于等待其它连接请求。新的SOCKET才是与客户端通信的实际的SOCKET。所以参数中SOCKET称为监听SOCKET,负责连接,而accpet返回的SOCKET成为会话SOCKET。addr和addrlen返回客户机的sockaddr_in结构体。
connect():请求建立连接。适用于客户机。
函数原型:
int connect (SOCKET s,struct sockaddr_in * name, int namlen);
send()/recv():发送接收数据
函数原型:
int send(SOCKET s,char *buf, int len, int flags);
int recv(SOCKET s,char *buf, int len, int flags);
s是连接的socket,buf和len是发送接收的数据包及其长度。flags一般为0。recv()实际上是接收send()的数据包,当读到的数据字节少于规定接收的数目时,就把数据全部接收,返回实际字节数。当多于规定值时,流方式下,剩余字节由下一个recv()读出;在数据报文方式下,多余数据将被丢弃,这两个函数出错返回SOCKET_ERROR。
数据报文方式通信的socket,由于事先不建立连接,所以跳过了connect()而直接调用以下方式:
int recvfrom (SOCKET s,char *buf, int len, int flags,struct sockaddr_infrom,int *fromlen);
int sendto (SOCKET s, char *buf, int len, int flags, struct sockaddr_into, int *tolen);
from,fromlen,to,tolen分别表示接收发送数据对象。
closesocket():关闭socket
函数原型:
closesocket(SOCKET s);
通信结束,关闭socket。