Windows网络编程笔记之一:winsock简介

本文深入探讨了Winsock编程的核心概念,包括初始化、错误检查、协议寻址、创建套接字、面向连接通信和无连接通信等关键步骤。详细解释了如何在应用程序中使用Winsock接口,提供了创建、绑定、监听和接受连接的方法,以及在TCP/IP环境中进行数据传输的策略。此外,还介绍了Winsock的关闭过程和如何在无连接通信场景下接收和发送数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.winsock 的初始化

    int WASStartup(WORD wVersionRequested, LPWSADATA lpWSADATA);

    wVersionRequested 高字节制定所需winsock库的次版本,低字节是主版本。可以使用宏MAKEWORD(x, y), x为高字节,y为低字节。

    typdef strcut WSADATA

    {

        WORD wVersion; // 将要使用的winsock版本

        WORD wHightVersion;  //包含现有winsock库的最高版本

        char       szDescription[WSADESCRIPTION_LEN + 1]; 

        char       szSystemStatus[WDASYS_STATUS_LEN + 1];

        unsigned short iMaxSocket;  //同时打开的最大套接字数量

        unsigned shorot iMaxUdpDg; // 数据表的最大长度

        char FAR* lpVendorInfo;   

    }

 

    在使用winsock接口编写好应用程序之后,应该使用WSACleanup函数,这个函数能够使得winsock释放所有由winsock分配的资源,并取消这个应用程序挂起的winsock调用。

    int WSACleanup(void);

 

2. 错误检查和处理

    int WSAGetLastError(void);

    返回winsock的错误代码。

 

3. 协议寻址

  3.1 IPV4寻址

  ipv4中,计算机分配一个32位的地址,客户机要通过tcp或udp和服务器通信时,必须指定一个ip地址和一个端口号,服务器打算监听传入的客户端请求时,也必须指定一个ip地址和端口号。

  在winsock中,应用程序通过SOCKADDR_IN结构指定ip地址和服务器端口号。

  struct sockaddr_in

  {

    short sin_family; //字段必须为AF_INT,  以告知winsock此时正在使用ip地址簇。

    u_short sin_port; //标识服务器服务的TCP或UDP通信端口。

    struct in_addr sin_addr; //把ip地址作为一个4字节存储起来。

    char sin_zero[8];//字节填充,使得SOCKADDR_IN和SOCKADDR结构的长度一样。

  };

  网络字节顺序采用big-endian(从最有意义的字节到最无意义的字节,intel86处理器采用little-endian)。

  主机字节转换为网络字节顺序的四个API:

  u_long htonl(u_long hostlong); 

  int WSAHtonl(SOCKET s,  u_long hostlong, u_long FAR* lpnetlong);

  u_short htons(u_short hostshort);

  int WSAHtons(SOCKET s, u_short hostshort, u_short FAR* lpnetshort);

  网络字节顺序转换为主机字节顺序的四个API:

  u_long ntohl(u_long netlong);

  int WSANtohl(SOCKET s, u_long netlong, ulong FAR* lphostlong);

  u_short ntohs(u_short netshort);

  int WSANtohs(SOCKET s, u_short netshort, u_short FAR* lphostshort);

4. 创建套接字

 

  两个函数socket和WSASocket可以创建套接字,socket定义如下:

  SOCKET socket(

  int af, // ipv4应设为AF_INET

  int type, //协议的套接字类型 TCP/IP创建套接字,设为SOCK_STREAM, UDP/IP应设为SOCK_DGRAM

  int protocol //用于给定地址族和套接字类型具有多重入口时,对具体的传送做限定,TCP应设为IPPROTO_TCP, UPD应设为IPPPROTO_UDP

  );  

 

5. 面向连接的通信

  在IP中,面向连接的通信通过TCP/IP完成的,TCP提供两个计算机之间可靠无误的数据传输,TCP通信在源计算机和目标计算机之间建立一个虚拟连接,建立连接后,计算机之间便能以双向字节流的防暑进行数据交换。

  

 

  5.1服务器API函数

  服务器等待任意数量的客户机与之建立连接,必须在一个已知的名称上监听连接。在TCP/IP中,这个名称就是本地IP地址,再加上一个端口号。

  winsock第一步是通过bind API将一个给定的套接字绑定到已知的名称上,第二步用listen API函数将套接字设为监听模式,最后,若客户端试图建立连接,服务器必须通过accept或WSAAccept调用接受连接。

 

  5.1.1绑定

  bind函数可将指定的套接字同一个已知地址绑定到一起,函数如下:

  int bind(

  SOCKET   s, //等待客户端连接的套接字

  const struct sockaddr FAR* name, //根据使用的协议,填充一个地址缓冲区

  int namelen //name缓冲区的地址长度

  );

 

  5.1.2监听

  指示套接字等候连接传入的api是listen函数,定义如下:

  int listen(

  SOCKET s,  //被绑定的套接字

  int backlog  //被搁置的连接最大长度

  );

 

  5.1.3接受连接

  通过函数accept, WSAAccept, AcceptEx函数完成。accept函数如下:

  SOCKET accept(

  SOCKET s, // 绑定的套接字,处于监听模式

  struct sockaddr FAR* addr, //协议对应的SOCKADDR地址,函数返回后,这个结构会包含发出连接请求的客户端的ip地址信息,对于ipv4,返回ipv4的信息,对应的结构体为SOCKADDR_IN.

  int FAR* addrlen //返回addr的结构的长度

  );

  函数返回一个新的套接字描述符,对应已经接受的那个客户端连接。该客户端的所有后续操作都使用这个套接字。

 

  5.2客户端API函数

  客户端创建要简单的多,只许3个步骤

  1.创建一个套接字。

  2.建立一个SOCKADDR地址结构,结构名称为准备连接到的服务器名。对于TCP/IP, 为客户端所监听的服务器的IP地址和端口号。

  3.用connect或WSAConnect初始化客户端与服务器的连接。

  connect函数定义如下:

  int connect(

  SOCKET s, //建立连接的有效TCP套接字。

  const struct sockaddr FAR* name, //TCP套接字的地址结构,(SOCKADDR_IN),表示要连接的服务器。

  int namelen //name参数的长度

  );

 

  5.3数据传输

  要在已建立连接的套接字上发送数据,用send和WSASend,在已建立连接的套接字上接受数据recv和WSARecv。

  所有收发数据的缓冲区都属于简单的char类型,即面向字节。

 

  5.3.1 send和WSASend

  int send(

  SOCKET s, //已建立连接,用于发送数据的套接字

  const char FAR* buf, //即将发送的数据

  int len, //buf大小

  int flags // 可为0,MSG_DONTROUTE或MSG_OOB,MSG_DONTROUTE。要求传输层不要将发出的数据路由出去,是否由下一层的传输来决定。

  );

  成功的情况下,send返回发送的字节数,错误返回SOCKET_ERROR。

 

  int WSASend(

  SOCKET s, //连接会话的有效句柄

  LPWSABUF lpBuffers, //指向一组WSABUF结构的指针

  DWORD dwBufferCount, //WSABUF的数量

  LPDWORD lpNumberOfBytesSent, //写入包含已发送的字节数

  DWORD dwFlags,

  LPWSAOVERLAPPED lpOverlapped, //重叠IO

  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //重叠IO完成例程

  );

  如果成功,返回0,否则返回SOCKET_ERROR

 

  5.3.2 WSASendDisconnect

  这个函数非常特殊,一般不用,原型如下:

  int WSASendDisconnect(

  SOCKET s,

  LPWSABUF lpOutboundDisconnectData

  );

 

  5.3.3 recv和WSARecv

  int recv(

  SOCKET s, //准备接受数据的套接字

  char FAR* buf, //接受数据的缓冲区

  int len, //数据缓冲区的长度

  int flags //

  );

 

  int WSARecv(

  SOCKET s, //连接会话的有效句柄

  LPWSABUF lpBuffers, //指向一组WSABUF结构的指针

  DWORD dwBufferCount, //WSABUF的数量

  LPDWORD lpNumberOfBytesSent, //收到的字节数

  DWORD dwFlags,

  LPWSAOVERLAPPED lpOverlapped, //重叠IO

  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //重叠IO完成例程

  );

 

  5.3.4WSARecvDisconnect

  int WSARecvDisconnect(

  SOCKET s,

  LPWSABUF lpInboundDisconnectData

  );

  和WADSendDisconnect一样,接收到的数据时断开数据,时另一端的WSASendDisconnect调用发出的,不能用于接受普通数据。

 

  5.4流协议

  流协议中,发送者和接受者可以将数据分解成小数据块,或者将数据合并为大数据块。

  对于流套接字上收发数据所用的函数,它们不能保证需要读取或写入的数据的数量。

  

  散播-聚集IO:比如客户端发送到服务器的消息可能这样构成的,一个指定某种操作固定的32字节的头,一个64字节的数据块和一个16字节的尾。

  就可以用3个WSABUF结构组成的数字调用WSASend,这3个结构分别对应3种消息类型,在接收端,3个WSABUF调用WSARecv。

 

  5.5中断连接

  完成了套接字连接,就要关掉它,并释放关联到套接字的所有资源,真正的释放与套接字关联的资源,调用closesocket即可。

  但closesocket可能会带来负面影响,可能会导致数据的丢失。因此在调用closesocket之前,利用shutdown从容的终止连接。

 

  5.5.1shutdown

  为了保证通信双方能够收到应用程序发出的所有数据,对于一个好的应用程序来说,应该通知接收端“不再发送数据”。同样,通信对方也应如此。

  int shutdown(

  SOCKEt s,

  int how

  );

  how 参数可以是后面的任何一个值:SD_RECEIVE, SD_SEND, SD_BOTH。

  SD_RECEIVE表示不允许再调用接收函数。

  SD_SEND表示不允许再调用发送函数。

  SD_BOTH表示取消两端的收发操作。

  并非所有的面向连接协议都支持从容关闭。

 

  5.5.2 closesocket

  int closesocket(SOCKET s);

  该函数会释放套接字描述符,在利用套接字的调用就会失败,出现WSAENOTSOCK,如果没有对套接字的其他引用,那么与套接字关联的资源都被释放,

  包括丢弃队列中的数据。

 

6.无连接通信

  在IP中,无连接通信通过UDP/IP完成,UDP不能确保可靠的数据传输,但能将数据发送到多个目标,或者接受多个源数据。

  6.1接收端

  先用socket或WSASocket创建套接字,再把这个套接字和准备接受数据的借口绑定在一起,通过bind函数完成,但不调用listen和accept函数。

  只等待接受数据。

  int recvfrom(

  SOCKET s,

  char FAR* buf,

  int len,

  int flags,

  struct sockaddr FAR* from, //填入发送数据的工作站地址

  int FAR* fromlen  

  );

  返回读取的字节总数。

 

  Winsock2版本的WSARecvFrom

  int WSARecvFrom(

  SOCKET S,

  LPWSABUF lpBuffers,

  DWORD  dwBufferCount,

  LPDWORD lpNumberOfByteRecvd,

  LPDWORD lpFlags,

  struct sockadd FAR* lpFrom,

  LPINT lpFromlen,

  LPWSAOVERLAPPED lpOverlapped,

  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

  );

 

  无连接套接字上接受(发送)数据的另一种方法是建立连接,无连接的套接字一旦建立,便可以利用SOCKADDR参数(被设为与之通信的远程接收端的地址)

  调用connect或者WSAConnect。但事实上没有建立连接。

 

  6.2发送端

  先建立个套接字,然后调用sendto或者WSASendto

  int sendto(

  SOCKET s,

  const char FAR* buf,

  int len,

  int flags,

  const struct sockaddr FAR* to, //接收数据的工作站的目标地址

  int tolen 

  );

 

  int WSASendto(

  SOCKET s,

  LPWSABUF lpBuffers,

  DWORD dwBufferCount,

  LPDWORD lpNumberOfByteSent, //真正发送到接收端的字节数

  DWORD dwFlags,

  const struct sockaddr FAR* lpTo, //接收端的地址信息

  int iTolen, //sockaddr的结构长度

  LPWSAOVERLAPPED lpOverlapped,

  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpOverlappedRoutine

  );

 

  6.3基于消息的协议

  面向连接的通信同时也是流协议,无连接通信几乎都是基于消息的。

  首先,由于面向消息的协议保留了数据边界,所以提交给发送函数的数据在被发送完之前会形成阻塞,对非阻塞IO模式而言,如果数据未能完全发送,

  发送函数就会返回WSAEWOLUDBLOCK,这意味着下层的系统不能对不完整的数据进行处理,应该稍后再次调用发送函数。

  在连接的另一端,对接受函数必须提供一个足够大的缓冲区,如果提供的缓冲区不够大,接收调用就会失败,将出现WSAEMSGSIZE错误。

  发生这种情况时,缓冲区就会被填满,但未完全收完的数据会被抛弃。被截断的数据也无法恢复。

  唯一的例外是支持部分消息的协议,当接受调用仅收到部分消息时,在WSARecv,WSARecvFrom返回前,将flag设为MSG_PARTIAL。

 

  6.4释放套接字资源

  由于无连接协议没有连接,所以也不会有对连接的正式关闭和从容关闭,在接收端或发送端完成接收发送数据时,只需调用closesocket。

   

7. 其他API函数

  7.1 getpeername

  获得连接上来的客户端的套接字地址信息,该信息是关于已建立的那个套接字的。

  int getpeername(

  SOCKET s,//连接上来的套接字

  struct sockaddr FAR* name, //返回的是传递到连接调用的地址

  int FAR* namelen

  );

 

  7.2 getsockname

  返回的是给定套接字的本地接口的地址信息,就是本地sock的信息

  int getsockname(

  SOCKET s, 

  struct sockaddr FAR* name,

  int FAR* namelen

  );

 

  7.3WSADuplicateSocket

  可以用另一个进程打开指向同一个下层套接字句柄,另外的进程也能对该套接字进行操作。必须考虑进程间通信。

  int WSADuplicateSocket(

  SOCKET s, //准备复制的套接字句柄

  DWORD dwProcessId, //打算使用所复制的套接字的进程ID。

  LPWSAPROTOCOL_INFO lpProtocolInfo //指向目标进程打开复制句柄所需的信息。

  );

  此函数复制的是套接字的描述符,而不是实际套接字,如果一个进程在复制的套接字上调用closesocket,会导致进程中的描述符被释放;

  但在最后留下的那个描述符上调用closesocket之前,下层套接字会保持打开状态。

转载于:https://www.cnblogs.com/fatrony/archive/2012/05/16/2504433.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值