Winsock编程详解

一.TCP 服务器程序和客户程序的创建过程

服务器端创建监听套接字,并为它关联一个本地地址(指定IP 地址和端口号),
然后进入监听状态准备接受客户的连接请求。
为了接受客户端的连接请求,服务器端必须调用accept 函数。
客户端创建套接字后即可调用 connect 函数去试图连接服务器监听套接字。当服务器端
的accept 函数返回后,connect 函数也返回。此时客户端使用socket 函数创建的套接字,服务
器端使用accept 函数创建的套接字,双方就可以通信了。

 

 

 

 1.套接字的创建和关闭

使用套接字之前,必须调用socket函数创建一个套接字对象,此函数调用成功将返回套接字句柄。

函数失败返回INVALID_SOCKET   即(-1),可通过调用WSAGetLastError()取地具体错误代码

SOCKET socket(
int af,           // 用来指定套接示使用的地址格式,WinSock 中只支持AF_INET
int type,       // 用来指定套接字的类型
int protocol // 配合type 参数使用,用来指定使用的协议类型。可以是IPPROTO_TCP 等
);

type 参数用来指定套接字的类型。套接字有流套接字、数据报套接字和原始套接字等,
下面是常见的几种套接字类型定义。
􀁺 SOCK_STREAM 流套接字,使用TCP 提供有连接的可靠的传输
􀁺 SOCK_DGRAM 数据报套接字,使用UDP 提供无连接的不可靠的传输
􀁺 SOCK_RAW 原始套接字,Winsock 接口并不使用某种特定的协议去封装它,而是由程序
自行处理数据报以及协议首部
当 type 参数指定为SOCK_STREAM 和 SOCK_DGRAM 时,系统已经明确使用TCP 和
UDP 来工作,所以protocol 参数可以指定为0。

关闭套接字

int closesocket(SOCKET s); // 函数唯一的参数就是要关闭的套接字的句柄

 

2.绑定套接字到指定的IP 地址和端口号
为套接字关联本地地址的函数是 bind,用法如下。

int bind(
SOCKET s,                    // 套接字句柄
const struct sockaddr* name, // 要关联的本地地址
int namelen                  // 地址的长度
);

bind 函数用在没有建立连接的套接字上,它的作用是绑定面向连接的或者无连接的套接
字。套接字被socket 函数创建以后,存在于指定的地址家族里,但它是未命名的。bind 函数
通过安排一个本地名称到未命名的socket 而建立此socket 的本地关联。

 

绑定套接字s 到本地地址:

// 填充sockaddr_in 结构
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(4567); //如果端口号等于0,程序执行时系统会为这个应用程序分配惟一的端口号,其值在1024~5000 之间
sin.sin_addr.S_un.S_addr = INADDR_ANY; //如果Internet 地址等于INADDR_ANY,系统会自动使用当前主机配置的所有IP 地址,简化了程序设计


// 绑定这个套接字到一个本地地址
if(::bind(sListen, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("Failed bind() \n");
return 0;
}
sockaddr_in 结构中的sin_family字段用来指定地址家族,该字段和socket函数中的sf参数的含义相同,所以唯一可以使用的值就是AF_INET。sin_port字段和sin_adder字段分
别制定套接字需要绑定的端口号和IP地址,放入这两个字段的数据的字节顺序必须是网络字节顺序。因为网络字节顺序和Intel CPU的字节顺序刚好相反,所以必须首先使用htons函数
进行转换。

3.设置套接字进入监听状态
listen 函数设置套接字进入监听状态。

int listen(
SOCKET s, // 套接字句柄
int backlog // 监听队列中允许保持的尚未处理的最大连接数量
);

 为了接受连接,首先使用socket函数创建套接字,然后使用bind函数将他绑定到本地地址,再用listen函数为到达的连接指定backlog,最后使用accept接受请求的连接。

listen函数仅应用在支持连接的套接字上,如SOCK_STREAM类型的套接字,函数执行成功后,套接字s进入了被动模式,到来的连接会被通知要排队等候接受处理。

在同一时间处理多个连接请求的服务器通常使用listen函数,如果一个连接请求到达,并且排队已满,客户端将接收到WSAECONNREFUSED错误。

4.接受连接请求

accept 函数用于接受到来的连接。

 

SOCKET accept(
SOCKET s, // 套接字句柄
struct sockaddr* addr, // 一个指向sockaddr_in 结构的指针,用于取得对方的地址信息
int* addrlen // 一个指向地址长度的指针
);

该函数在s 上取出未处理连接中的第一个连接,然后为这个连接创建新的套接字,返回
它的句柄。新创建的套接字是处理实际连接的套接字,它与s 有相同的属性。

程序默认工作在阻塞模式下,这种方式下如果没有未处理的连接存在,accept会一直等待下去,直到有新的连接发生才返回。

addrlen 用于指定addr所指空间大小,也用于返回地址的实际长度

客户端程序在创建套接字之后,要使用connect 函数请求与服务器连接,函数原型如下。

int connect(
SOCKET s, // 套接字句柄
const struct sockaddr FAR * name, // 一个指向 sockaddr_in 结构的指针,包含了要连接的服务器的地址信息。
int namelen // sockaddr_in 结构的长度
);

第一个参数s 是此连接使用的客户端套接字,另两个参数name 和namelen 用来寻址远程
套接字(正在监听的服务器套接字)。

5.收发数据
对流套接字来说,一般使用 send 和recv 函数来收发数据。

int send(
SOCKET s, // 套接字句柄
const char FAR * buf, // 要发送数据的缓冲区地址
int len, // 缓冲区长度
int flags // 指定了调用方式,通常设位0
);

int recv( SOCKET s, char FAR * buf, int len, int );
//该函数第一个参数是accept函数连接成功后创建的处理实际连接的套接字

send函数在一个连接的套接字上发送缓冲区数据,返回发送数据的实际字节数。recv函数从对方接收数据,并将其存储到指定的缓冲区。flags参数在这两个函数中通常设置为0。

二.UDP编程

TCP 由于可靠、稳定的特点而被用在大部分场合,但它对系统资源要求比较高。UDP 是
一个简单的面向数据报的传输层协议,又叫用户数据报协议。它提供了无连接的、不可靠的
数据传输服务。无连接是指它不像TCP 那样在通信前先与对方建立连接以确定对方的状态。
不可靠是指它直接按照指定IP 地址和端口号将数据包发出去,如果对方不在线的话数据就可
能丢失。

值得注意的是,创建套接字之后,如果首先调用的是sendto 函数,则可以不调用bind 函
数显式地绑定本地地址,系统会自动为程序绑定,因此今后即便是调用recvfrom 也不会失败
(因为套接字已经绑定了)。但是,如果创建套接字之后,直接调用recvfrom 就会失败,因
为套接字还没有绑定。

1.UDP 编程流程
(1)服务器端程序设计流程如下。
①创建套接字(socket)。
②绑定 IP 地址和端口(bind)。
③收发数据(sendto/recvfrom)。
④关闭连接(closesocket)。
(2)客户端程序设计流程如下。
①创建套接字(socket)。
②收发数据(sendto/recvfrom)。
③关闭连接(closesocket)。

UDP 用于发送数据的函数

int sendto (
SOCKET s, // 用来发送数据的套接字
const char FAR * buf, // 指向发送数据的缓冲区
int len, // 要发送数据的长度
int flags, // 一般指定为0
const struct sockaddr * to, // 指向一个包含目标地址和端口号的sockaddr_in 结构
int tolen // 为 sockaddr_in 结构的大小
);

 

UDP 用于接受数据的函数

int recvfrom (
SOCKET s, //监听套接字
char FAR* buf, 
int len, 
int flags, 
struct sockaddr FAR* from, //指向sockaddr_in 结构的指针,函数在这里返回数据发送方的地址
int FAR* fromlen);

实例:

 

//客户端
#include <winsock2.h> // initsock.h 文件 #include<Windows.h> #include<iostream> #pragma comment(lib, "WS2_32") // 链接到WS2_32.lib class CInitSock { public: CInitSock(BYTE minorVer = 2, BYTE majorVer = 2) { // 初始化WS2_32.dll WSADATA wsaData; WORD sockVersion = MAKEWORD(minorVer, majorVer); if (::WSAStartup(sockVersion, &wsaData) != 0) { exit(0); } } ~CInitSock() { ::WSACleanup(); } }; using namespace std; CInitSock winsock; int main() { //创建套接字 SOCKET s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (s == INVALID_SOCKET) { printf("Failed socket() \n"); return 0; } // 填写远程地址信息 sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(4567); addr.sin_addr.S_un.S_addr = inet_addr("192.168.0.110"); // 发送数据 char szText[] = " UDP Hello Server ! \r\n"; ::sendto(s, szText, strlen(szText), 0, (sockaddr*)&addr, sizeof(addr)); getchar(); return 0; }
//服务器
#include <winsock2.h> // initsock.h 文件 #include<Windows.h> #include<iostream> #pragma comment(lib, "WS2_32") // 链接到WS2_32.lib class CInitSock { public: CInitSock(BYTE minorVer = 2, BYTE majorVer = 2) { // 初始化WS2_32.dll WSADATA wsaData; WORD sockVersion = MAKEWORD(minorVer, majorVer); if (::WSAStartup(sockVersion, &wsaData) != 0) { exit(0); } } ~CInitSock() { ::WSACleanup(); } }; using namespace std; CInitSock winsock; int main() { SOCKET s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (s == INVALID_SOCKET) { int v1=WSAGetLastError(); printf("Failed socket() \n"); return 0; } // 填充sockaddr_in 结构 sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(4567); sin.sin_addr.S_un.S_addr = INADDR_ANY; // 绑定这个套接字到一个本地地址 if (::bind(s, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) { printf("Failed bind() \n"); return 0; } // 接收数据 char buff[1024]; sockaddr_in addr; int nLen = sizeof(addr); while (TRUE) { int nRecv = ::recvfrom(s, buff, 1024, 0, (sockaddr*)&addr, &nLen); if (nRecv > 0) { buff[nRecv] = '\0'; printf(" 接收到数据(%s):%s", ::inet_ntoa(addr.sin_addr), buff); } } ::closesocket(s); getchar(); return 0; }

 

posted @ 2020-03-27 17:47  坦坦荡荡  阅读(1038)  评论(0)    收藏  举报