Winsock

Winsock

  • 编译采用了WINSOCK.H的应用程序时,需要链接到WS2_32.LIB库
  • 使用WINSOCK.H时,需要链接到WSOCK32.LIB
  • 使用MSWSOCK.H时,需要链接到MSWCOCK.DLL
初始化、反初始化

1. 加载Winsock库
示例:

	WSADATA wsaData;
	WSAStartup( MAKEWORD(2, 2), &wsaData);

原型:

int
WSAAPI
WSAStartup(
    __in WORD wVersionRequested,	//用于指定准备加载的Winsock库的版本,高位字节是次版本,低字节是主版本。通常使用MAKEWORD(x,y)来指定【x是高位字节,y是低位字节】
    __out LPWSADATA lpWSAData	//用与其加载的库版本有关的信息填充这个结构
    );
    
//WSADATA
typedef struct WSAData {
        WORD                    wVersion;	//设置为将要使用的Winsock版本
        WORD                    wHighVersion;	//包含了现有的Winsock库的最高版本
#ifdef _WIN64
        unsigned short          iMaxSockets;
        unsigned short          iMaxUdpDg;
        char FAR *              lpVendorInfo;
        char                    szDescription[WSADESCRIPTION_LEN+1];
        char                    szSystemStatus[WSASYS_STATUS_LEN+1];
#else
        char                    szDescription[WSADESCRIPTION_LEN+1];
        char                    szSystemStatus[WSASYS_STATUS_LEN+1];
        unsigned short          iMaxSockets;	//可以同时打开的最大套接字数量,不固定,尽量不要用这个值
        unsigned short          iMaxUdpDg;	//数据报的最大长度,不固定,尽量不要用这个值
        char FAR *              lpVendorInfo;
#endif
} WSADATA, FAR * LPWSADATA;

在这里插入图片描述
2. 释放
使用后需要调用WSACleanup函数,来使Winsock释放所有由Winsock分配的资源,并取消这个应用程序挂起的Winsock调用。
原型:
int WSAAPI WSACleanup( void );

错误检查和处理

出现错误时(比如SOCKET_ERROR),可以调用WSAGetLastError函数来获得一段代码,这段代码专用来说明错误
原型:

#if INCL_WINSOCK_API_PROTOTYPES
WINSOCK_API_LINKAGE
int
WSAAPI
WSAGetLastError(
    void
    );
#endif /* INCL_WINSOCK_API_PROTOTYPES */
  • 如果想要手动设置WSAGetLastError获取的错误代码,可以调用WSASetLastError。
  • 注意:WSAStartup函数的调用不能使用WSAGetLastError来确定导致故障的特定错误(因为Winsock没有加载),用WSAStartup的返回状态判断(0是成功)

使用IP协议创建基本的Winsock调用来建立通信

  • Winsock是一种独立于协议的接口
  • IP是一种无连接协议,它不能确保数据传输的成功
  • TCP和UDP通过IP进行面向连接和无连接的数据通信
IPv4寻址

程序通过sockaddr_in结构来指定IP地址和服务端口信息
示例:

	sockaddr_in sockAddr;
	memset(&sockAddr, 0, sizeof(sockAddr));
	sockAddr.sin_family = PF_INET;		//#define PF_INET         AF_INET,该字段设为AF_INET,以告知Winsock此时正在使用IP地址族。
	sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	sockAddr.sin_port = htons(1234);	//端口号在选择的时候,需要特别注意,某些端口是“已知的”服务保留的

sockaddr_in结构:

struct sockaddr_in {
        short   sin_family;		//协议族
        u_short sin_port;		//端口号
        struct  in_addr sin_addr;	//ip
        char    sin_zero[8];		//填充项,以使SOCKADDR_IN结构和SOCKADDR结构的长度一样
};

inet_addr函数可以将一个点分的IP转换成一个32位的无符号长整数。

字节排序

网络字节(network-byte)顺序:从最有意义到最无意义的字节排序(big-endian)
将一个数从主机字节顺序转换成网络字节顺序API(具体用法用到的时候再补充吧):

htol
WSAHtol
htons
WSAHtons
创建套接字
SOCKET servSock = socket(AF_INET, SOCK_STREAM, 0);

函数原型:

#if INCL_WINSOCK_API_PROTOTYPES
WINSOCK_API_LINKAGE
__checkReturn
SOCKET
WSAAPI
socket(
    __in int af,			//协议的地址族
    __in int type,		//协议的套接字类型。TCP/IP对应SOCK_STREAM;UDP/IP对应SOCK_DGRAM
    __in int protocol	//用于在给定地址族和套接字类型具有多重入口时,对具体的传送作限定。TCP--IPPROTO_TCP;UDP--IPPROTO_UDP
    );
#endif /* INCL_WINSOCK_API_PROTOTYPES */

面向连接的通信

  • 在IP中,面向连接的通信是通过TCP/IP协议完成的。
  • TCP提供两个计算机间可靠无误的数据传输。
在Winsock中,建立通信的步骤:
服务器:

1. 用Socket或WSASocker将给定的协议的套接字绑定到它已知的名称上

bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));

函数原型:

#if INCL_WINSOCK_API_PROTOTYPES
WINSOCK_API_LINKAGE
int
WSAAPI
bind(
    __in SOCKET s,		//等待客户机连接的套接字
    __in_bcount(namelen) const struct sockaddr FAR * name,	//缓冲区,根据使用的协议,将实际地址填充到一个地址缓冲区,并在调用bind的时候将其转换为一个struct sockaddr
    __in int namelen	//要传递的、由协议决定的地址结构的长度
    );
#endif /* INCL_WINSOCK_API_PROTOTYPES */
  • 一旦出错,bind会返回SOCKET_ERROR。
  • 对于bind而言,最常见的错误是WSAEADDINUSE。
  • 如果使用的是TCP/IP,WSAEADDINUSE表示的是另一个进程已经同本地的IP接口及端口号绑定到了一起,或者那个IP和端口号处于TIME_WAIT状态。
  • 假如对一个已被绑定的套接字调用bind,返回的会是WSAEFAULT错误。

2. 将套接字置为监听模式
listen(servSock, 20);
函数原型:

#if INCL_WINSOCK_API_PROTOTYPES
WINSOCK_API_LINKAGE
int
WSAAPI
listen(
    __in SOCKET s,	//被绑定的套接字
    __in int backlog		//指定了被搁置的连接的最大队列长度。
    );
#endif /* INCL_WINSOCK_API_PROTOTYPES */
  • 因为在同一时间,可能会有多个服务器的连接请求,如果请求的数量过backlog,那么前面的backlog个请求被放在一个“挂起”队列中,以便应用程序(服务器)依次为他们提供服务。而backlog之后的连接请求都会失败,错误为WSAECONNREFUSED。
  • 一旦服务器接收了一个连接,那个连接请求就会从队列中删去,以便别人可以继续发出请求。
  • WSAEINVAL通常意味着,在调用listen之前没有调用bind。

3. 若一台客户机试图创立连接,服务器必须通过accept或WSAAccept调用来接受连接

	SOCKADDR clntAddr;
	int nSize = sizeof(SOCKADDR);
	SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);

函数原型:

#if INCL_WINSOCK_API_PROTOTYPES
WINSOCK_API_LINKAGE
__checkReturn
SOCKET		//返回值:一个新的套接字,用于已经被接受的那个客户机的连接。(后续的消息发送什么的应该用这个套接字)
WSAAPI
accept(
    __in SOCKET s,	//监听的套接字
    __out_bcount_opt(*addrlen) struct sockaddr FAR * addr,	//新连接的地址信息(输出)
    __inout_opt int FAR * addrlen	//连接结构体的大小
    );
#endif /* INCL_WINSOCK_API_PROTOTYPES */
  • 对于连接成功的客户机,在后续的操作中应该使用accept返回的新的套接字
  • 原本的监听套接字仍然用于接受其他客户机的连接,并且仍处于监听模式
  • WSAAccept可以根据指定条件接受一个连接
客户机:

客户机的创建步骤:

  1. 创建一个套接字
  2. 建立一个SOCKADDR地址结构,结构名称为准备连接到的服务器名。(对于TCP/IP,这是客户机应用程序锁监听的服务器的IP地址和端口号)
  3. 用connect或WSAConnect初始化客户机与服务器的连接
套接字状态

连接:

  • 对于每个套接字来说,它的初始状态都是CLOSED。
  • 若客户机初始化了一个连接,就会向服务器发送一个SYN包,同时将客户机套接字的状态置为SYN_SENT。
  • 服务器收到SYN包后,会发出一个SYN-ACK包,客户机需要用一个ACK包对它做出响应。此时,客户机的套接字将处于ESTABLISHED状态。
  • 如果服务器一直不发送SYN-ACK包,客户机就会超时,并返回CLOSED状态。
  • 服务器的套接字同本地接口及端口绑定起来,并在它上面进行监听,那么套接字的状态便是LISTEN。
  • 客户机试图与服务器连接时,服务器就会收到一个SYN包,并用一个SYN-ACK包作出回应。此时,服务器套接字的状态就编程SYN_RCVD。
  • 最后,客户机发出一个ACK包,它将使服务器套接字的状态变成ESTABLISHED。

关闭连接:
一旦应用程序处于ESTABLISHED状态,就可以通过两种方法来关闭它。
主动关闭:

  • 应用程序会发出一个FIN包(应用程序调用closesocket或shutdown),并且套接字编程FIN_WAIT_1。
  • 通信的对方会用一个ACK包作为回应,套接字的状态随之编程FIN_WAIT_2
在PB中使用WINSOCK.OCX做双向通信的简单例子----PowerBuilder 一、在窗口中添加WINSOCK控件:   在应用中新开一个窗口,在窗口画板中点击controls-->OLE菜单项,弹出 Insert object窗口,单击Insert control标签,从列表框中双击选定 Microsoft Winsock control,将winsock的图标贴在窗口上。   在程序中该控件名称定为winsock_a(甲方)和winsock_b(乙方)。   二、设置信息输入输出文本框:   在窗口中增加一个按钮cb_1,两个单行文本框sle_1,sle_2,分别用于输入 要发送的字符串和接受对方发送的字符串。   三、设置通讯协议:   WINSOCK控件允许用户以UDP和TCP两种协议中任选一种进行通讯。   1.UDP协议设置:UDP协议是一种无连接的通讯协议,在通讯之前,需要绑 定remotehost和remoteport属性,如果需要双向通讯,还要设置localport属性 。   在甲方(本机地址为:134.1.1.1)窗口的Open事件中加入如下语句: winsock_a.object.protocol=1 //winsock通讯协议设为UDP协议 winsock_a.object.remotehost="134.1.1.2" //对方的ip地址 winsock_a.object.remoteport=6000 //对方的winsock通讯端口号 winsock_a.object.localport=6001 //本机的winsock通讯端口号 winsock_a.object.bind //绑定通讯协议   在乙方(本机地址为:134.1.1.2)窗口的Open事件中加入如下语句: winsock_b.object.protocol=1 //winsock通讯协议设为UDP协议 winsock_b.object.remotehost="134.1.1.1" //对方的ip地址 winsock_b.object.remoteport=6001 //对方的winsock通讯端口号 winsock_b.object.localport=6000 //本机的winsock通讯端口号 winsock_b.object.bin //绑定通讯协议   2.TCP协议设置:TCP协议在通讯前需要进行连接。   在甲方(作为服务器端)窗口的Open事件中加入如下语句: winsock_a.object.protocol=0 //winsock通讯协议设为TCP协议 winsock_a.object.localport=6001 //本机的winsock通讯端口号 winsock_a.listen() //启动监听   在甲方winsock_a控件的Connectionrequest事件中加入如下语句: //接受到对方的连接请求后 if winsock_a.object.state0 then winsock_a.close() end if winsock_a.accept(requestID) //建立直接连接 //requestID是Connectionrequest事件自己的参数   在乙方(作为客户端)窗口的Open事件中加入如下语句: winsock_b.object.protocol=0 //winsock通讯协议设为TCP协议 winsock_b.object.remotehost="134.1.1.2" //对方的ip地址 winsock_b.object.remoteport=6000 //对方的winsock通讯端口号 winsock_b.connect() //发出连接请求   3.无论采用哪种协议,都要在窗口的Close事件中加入如下语句: if winsock_a/*或winsock_b*/.object.state0 then winsock_a.close() end if   否则可能第二次使用时发生异常问题   四、开始通讯   在按钮cb_1(caption属性设为‘发送’)的click事件中加入如下语句: winsock_a/*或winsock_b*/.object.send (sle_1.text)   在winsock_a/*或winsock_b*/控件的dataarrival事件中加入如下语句: //接受到对方数据后 string datastr1 winsock_a/*或winsock_b*/.object.getdata (def datastr1) sle_2.text=datastr1 //将数据字符串显示在文本框中   以上程序实际上体现了聊天器的底层工作原理,稍加修改扩充就可以做成
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值