您的位置:首页 > 编程语言

winsock编程入门

2007-06-30 08:14 369 查看
一,什么是Socket
接触网络编程当然要了解Socket,Socket(套接字)是一种网络编程接口,Socket提供了很多灵活的函数,帮助程序员写出高效的网络应用。Socket分为BSDUNIX和windows两个版本。
在win32平台上的Winsock编程都要经过下列基本步骤:
定义变量——获得Winsock版本——加载Winsock库——初始化——创建套接字——设置套接字——关闭套接字——卸载Winsock库
使用winsock2API编程,必须包含头文件winsock2.h(链接环境WS2_32.LIB),头文件winsock.h(WSOCK32.LIB)是为了兼容winsock1程序时使用的,另外mswsock.h(MSWSOCK.DLL)是微软的扩展类,用于开发高性能的winsock程序。
准备好后,你就可以着手建立你的第一个网络程序了。

二,Socket编程的基本过程

Socket通信分为面向连接的通信(TCP)和面向无连接的通信(UDP),通信流程如下:

面向连接的通信





无连接的通信



1,Winsock初始化和结束

每一个winsock应用程序必须首先加载相应的winsockdll版本。方法是调用:
[align=left]intWSAStartup([/align]
[align=left]WORDwVersionRequested,库版本,高字节副版本,低字节主版本[/align]
[align=left]LPWSADATAlpWSAData结构指针,函数自动填充该结构。[/align]
);函数调用成功返回0
可以用宏MAKEWORD(x,y)用来指定第一个参数的值

2,建立套接字
套接字是传输提供者的一个句柄。
[align=left]SOCKETsocket([/align]
[align=left]intaf,[/align]
[align=left]inttype,[/align]
[align=left]intprotocolIPPROTO_TCP,IPPROTO_UDP,0(如果不想指定)[/align]
);
第一个参数指定通信协议的协议族,AF_INET(IPv4)或AF_INET6(IPv6)(因为Socket是网络编程接口而不是一个协议,它使用流行的网络协议(TCP/IP,IPX)为应用程序提供的一个编程接口。)
第二个参数指定要创建的套接字的类型。SOCK_STREAM(TCP流套接字),SOCK_DGRAM(UDP数据包套接字),SOCK_RAW(原始套接字)
第三个参数指定应用程序所指定应用程序所使用的通信协议。
函数成功返回套接字描述符,失败返回INVALID_SOCKET

3,配置套接字
当创建一个套接字后,再进行网络通信之前,必须先配置Socket。面向连接的客户端Socket通过调用connect函数在Socket数据结构中保存地址和远端信息。无连接客户端,服务端以及面向连接Socket的服务端,通过调用bind函数来配置本地信息。
[align=left]intbind([/align]
[align=left]SOCKETs,创建的套接字[/align]
[align=left]conststructsockaddrFAR*name,指向地址缓冲区的指针[/align]
[align=left]intnamelen地址缓冲区的大小[/align]
);
成功返回0,失败返回SOCKET_ERROR
当创建一个套接字后,套接字数据结构中有一个默认的IP地址和默认的端口号。一个服务程序必须调用bind函数来给其绑定一个IP地址和一个特定的端口号。
第二个参数指定一个sockaddr结构定义如下:
[align=left]structsockaddr{[/align]
[align=left]u_shortsa_family;[/align]
[align=left]charsa_data[14];[/align]
};
我们通常使用另外一个等价的地址结构:
structsockaddr_in{

shortsin_family;

u_shortsin_port;

structin_addrsin_addr;

charsin_zero[8];

};

其中sin_family是通信协议族,

sin_port
指明端口号,

sin_addr
结构中有一个字段s_addr,表示IP地址,该字段是一个整数,

一般用函数inet_addr把点分字符串形式的IP地址转化成unsignedlong型的整数值。

如果指定为htonl(INADDR_ANY),那么无论哪个网段上的客户机都能与该服务器通信,

否则,只有与指定IP地址处于同一网段上的客户机能与该服务器通信。

sin_zero[8]
为填充,使两个结构大小相同。


有一些细节学要说明:
在计算机把IP地址和端口号指定成多字节时,这个数是按“主机字节”(host-byte)顺序表示的,不同的处理器对数的表示方法有“大头”(big-endian——最有意义的字节到最无意义的字节)和“小头”(little-endian)两种形式。但是如果在网络上指定IP地址和端口号时,必须按照big-endian的形式,一般称之为“网络字节”(network-byte)顺序。
以下几个函数将主机字节顺序转换成网络字节顺序:
[align=left]u_longhtonl(u_longhostlong);[/align]
[align=left][/align]
[align=left]intWSAHtonl([/align]
[align=left]SOCKETs,[/align]
[align=left]u_longhostlong,[/align]
[align=left]u_longFAR*lpnetlong通过指针返回网络字节顺序的4个字节的数[/align]
[align=left]);[/align]
[align=left][/align]
[align=left]u_shorthtons(u_shorthostshort);[/align]
[align=left][/align]
[align=left]intWSAHtons([/align]
[align=left]SOCKETs,[/align]
[align=left]u_shorthostshort,[/align]
[align=left]u_shortFAR*lpnetshort通过指针返回网络字节顺序的2个字节的数[/align]
);
以下几个函数将网络字节顺序转换成主机字节顺序:
[align=left]u_longntohl(u_longnetlong);[/align]
[align=left][/align]
[align=left]intWSANtohl([/align]
[align=left]SOCKETs,[/align]
[align=left]u_longnetlong,[/align]
[align=left]u_longFAR*lphostlong[/align]
[align=left]);[/align]
[align=left][/align]
[align=left]u_shortntohs(u_shortnetshort);[/align]
[align=left][/align]
[align=left]intWSANtohs([/align]
[align=left]SOCKETs,[/align]
[align=left]u_shortnetshort,[/align]
[align=left]u_shortFAR*lphostshort[/align]
);



4,实现功能
(1)服务器端:需要对绑定的端口进行侦听,函数原型如下intlisten(

SOCKET
s
,

int
backlog

);

Backlog
是客户连接请求队列的最大数量,而不是客户机连接的数量限制。


处于侦听的套接字将维护一个客户连接请求队列。

该函数执行成功返回0,失败返回
SOCKET_ERROR
此外,需要从连接请求队列中取出最前面的一个客户请求,需要用到accept()函数:

SOCKETaccept(


SOCKET
s
,

structsockaddrFAR*
addr
,

intFAR*
addrlen

);

该函数创建一个新的套接字来与客户套接字建立通信,如果连接成功,就返回新建的套接字描述符,

以后与客户通信的就是该套接字,而侦听套接字则继续接受新的连接;如果失败就返回
INVALID_SOCKET[/code]
第一个参数是侦听套接字

第二个套接字用来返回新创建的套接字的地址结构

第三个套接字返回地址结构的长度


(2)客户端:

connect函数是客户机建立与远程服务器连接而使用的。

intconnect(


SOCKET
s
,

conststructsockaddrFAR*
name
,

int
namelen

);


5,数据传输

收发数据时网络编程的一切在这里我们只讨论同步函数send和recv,
不讨论异步函数WSASend和WSARecv。
数据发送要用到send函数,原型如下:

intsend(

SOCKETs,

constcharFAR*buf,发送数据缓冲区的地址

intlen,要发送的字节数

intflags一般为0,MSG_DONTROUTE,MSG_OOB(外带数据)

);

成功返回发送字节数,出错返回SOCKET_ERROR

注意send函数把数据从buf复制到socket发送缓冲区后就返回了,

但是这些数据并不时马上就发送到连接的另一端。


在已连接的套接字上接受数据,recv函数是最基本的方式。

intrecv(

SOCKETs,

charFAR*buf,

intlen,准备接收的字节数或buf缓冲区长度

intflags0,MSG_PEEK,MSG_OOB其中MSG_PEEK表示将有用的数据复制到所提供的接收端缓冲区,

但是没有从系统缓冲区中将它删除

);

成功返回接收的数据的字节数量,失败返回SOCKET_ERROR,如果接受数据时网络中断返回0



6,关闭Socket

一旦完成任务,记得将套接字关闭以释放资源:

intcloseSocket(SOCKETs)

三,Socket编程实例

讲了这么多其实还是看实例最为重要了,下面我提供了最简单的面向连接的客户端和服务端程序,

当服务端接受客户端的连接后,先是该客户机地IP地址,并向客户端发送一个回应消息,

最后关闭该连接套接字。这样的服务器当然没什么实际的用途。

设计一个基本的网络服务器有以下几个步骤:

1、初始化WindowsSocket
2、创建一个监听的Socket
3、设置服务器地址信息,并将监听端口绑定到这个地址上
4、开始监听
5、接受客户端连接
6、和客户端通信
7、结束服务并清理WindowsSocket和相关数据,或者返回第4步


#include<winsock2.h>

[align=left]#include<stdio.h>[/align]
[align=left][/align]
[align=left]#defineSERVPORT5050[/align]
[align=left]#pragmacomment(lib,"ws2_32.lib")[/align]
[align=left][/align]
[align=left]voidmain(void)[/align]
[align=left]{[/align]
[align=left]WSADATAwsaData;[/align]
[align=left]SOCKETsListen;//监听socket[/align]
[align=left]SOCKETsClient;//连接socket[/align]
[align=left]SOCKADDR_INserverAddr;//本机地址信息[/align]
[align=left]SOCKADDR_INclientAddr;//客户端地址信息[/align]
[align=left]intclientAddrLen;//地址结构的长度[/align]
[align=left]intnResult;[/align]
[align=left]//初始化WindowsSocket2.2[/align]
[align=left][/align]
[align=left]WSAStartup(MAKEWORD(2,2),&wsaData);[/align]
[align=left][/align]
[align=left]//创建一个新的Socket来响应客户端的连接请求[/align]
[align=left][/align]
[align=left]sListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);[/align]
[align=left][/align]
[align=left]//填写服务器绑定的地址信息[/align]
[align=left]//端口为5150[/align]
[align=left]//IP地址为INADDR_ANY,响应每个网络接口的客户机活动[/align]
[align=left]//注意使用htonl将IP地址转换为网络格式[/align]
[align=left][/align]
[align=left]serverAddr.sin_family=AF_INET;[/align]
[align=left]serverAddr.sin_port=htons(SERVPORT);[/align]
[align=left]serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);[/align]
[align=left]memset(&(serverAddr.sin_zero),0,sizeof(serverAddr.sin_zero));[/align]
[align=left]//绑定监听端口[/align]
[align=left][/align]
[align=left]nResult=bind(sListen,(SOCKADDR*)&serverAddr,sizeof(SOCKADDR));[/align]
[align=left]if(nResult==SOCKET_ERROR)[/align]
[align=left]{[/align]
[align=left]printf("bindfailed!/n");[/align]
[align=left][/align]
[align=left]return;[/align]
[align=left]}[/align]
[align=left][/align]
[align=left]//开始监听,指定最大接受队列长度5,不是连接数的上限[/align]
[align=left][/align]
[align=left]listen(sListen,5);[/align]
[align=left][/align]
[align=left]//接受新的连接[/align]
[align=left]while(1)[/align]
[align=left]{[/align]
[align=left]clientAddrLen=sizeof(SOCKADDR);[/align]
[align=left]sClient=accept(sListen,(SOCKADDR*)&clientAddr,&clientAddrLen);[/align]
[align=left]if(sClient==INVALID_SOCKET)[/align]
[align=left]{[/align]
[align=left]printf("Acceptfailed!");[/align]
[align=left]}[/align]
[align=left]else[/align]
[align=left]{[/align]
[align=left]printf("Acceptedclient:%s:%d/n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));[/align]
[align=left]//向客户端发送信息[/align]
[align=left]nResult=send(sClient,"Connectsuccess!",16,0);[/align]
[align=left]if(nResult==SOCKET_ERROR)[/align]
[align=left]{[/align]
[align=left]printf("sendfailed!");[/align]
[align=left]}[/align]
[align=left]}[/align]
[align=left]//我们直接关闭连接,[/align]
[align=left]closesocket(sClient);[/align]
[align=left]}[/align]
[align=left][/align]
[align=left][/align]
[align=left]//并关闭监听Socket,然后退出应用程序[/align]
[align=left][/align]
[align=left]closesocket(sListen);[/align]
[align=left][/align]
[align=left]//释放WindowsSocketDLL的相关资源[/align]
[align=left][/align]
[align=left]WSACleanup();[/align]
[align=left]}[/align]


客户机程序如下:

#include<winsock2.h>

[align=left]#include<stdio.h>[/align]
[align=left][/align]
[align=left]#defineSERVPORT5050//端口为5150[/align]
[align=left]#defineMAXDATASIZE100[/align]
[align=left]#defineSERVIP"127.0.0.1"//服务器IP地址为"127.0.0.1",注意使用inet_addr将IP地址转换为网络格式[/align]
[align=left]#pragmacomment(lib,"ws2_32.lib")[/align]
[align=left][/align]
[align=left]voidmain(intargc,char*argv[])[/align]
[align=left]{[/align]
[align=left]WSADATAwsaData;[/align]
[align=left]SOCKETsConnect;[/align]
[align=left]SOCKADDR_INserverAddr;[/align]
[align=left]intrecvbytes;[/align]
[align=left]charbuf[MAXDATASIZE];[/align]
[align=left][/align]
[align=left]//初始化WindowsSocket2.2[/align]
[align=left][/align]
[align=left]WSAStartup(MAKEWORD(2,2),&wsaData);[/align]
[align=left][/align]
[align=left]//创建一个新的Socket来连接服务器[/align]
[align=left][/align]
[align=left]sConnect=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);[/align]
[align=left][/align]
[align=left]//填写连接地址信息[/align]
[align=left][/align]
[align=left]serverAddr.sin_family=AF_INET;[/align]
[align=left]serverAddr.sin_port=htons(SERVPORT);[/align]
[align=left]serverAddr.sin_addr.s_addr=inet_addr(SERVIP);[/align]
[align=left][/align]
[align=left]memset(&(serverAddr.sin_zero),0,sizeof(serverAddr.sin_zero));[/align]
[align=left][/align]
[align=left]//向服务器发出连接请求[/align]
[align=left][/align]
[align=left]if(connect(sConnect,(SOCKADDR*)&serverAddr,sizeof(SOCKADDR))==SOCKET_ERROR)[/align]
[align=left]{[/align]
[align=left]printf("connectfailed!/n");[/align]
[align=left][/align]
[align=left]return;[/align]
[align=left]}[/align]
[align=left]//接受服务器的回应消息[/align]
[align=left]recvbytes=recv(sConnect,buf,MAXDATASIZE,0);[/align]
[align=left]if(recvbytes==SOCKET_ERROR)[/align]
[align=left]{[/align]
[align=left]printf("recvfailed!/n");[/align]
[align=left]}[/align]
[align=left]else[/align]
[align=left]{[/align]
[align=left]buf[recvbytes]='/0';[/align]
[align=left]printf("%s/n",buf);[/align]
[align=left]}[/align]
[align=left][/align]
[align=left]closesocket(sConnect);[/align]
[align=left][/align]
[align=left]//释放WindowsSocketDLL的相关资源[/align]
[align=left][/align]
[align=left][/align]
[align=left]WSACleanup();[/align]
[align=left]}[/align]


四,结束语

这里介绍的只不过是winsock最基础最基础的知识,开发网络程序从来都不是一件容易的事情,
尽管只需要遵守很少的一些规则:创建套接字,发起连接,接受连接,
发送和接受数据。真正的困难在于:让你的程序可以适应从单单一个连接到几千个连接,
即开发一个大容量,具可扩展性的Winsock应用程序。
Winscok编程很重要的一部分是IO处理,分别提供了“套接字模式”和“套接字I/O模型”,
可对一个套接字上的I/O行为进行控制。
其中,套接字模式用于决定在随一个套接字调用时,那些Winsock函数的行为,
有阻塞和非阻塞两种模式。
而另一方面,套接字模型描述了一个应用程序如何对套接字上进行的I/O进行管理及处理,
包括包括Select,WSAAsyncSelect,WSAEventSelect,IO重叠模型,完成端口等。
在这里推荐一本好书《windows网络编程技术》,有兴趣研究的同志可以去啃一啃。


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: