当前位置:   article > 正文

Winsock编程二:简单的聊天程序_若程序中想要正确识别wsadata结构体、word类型、makeword宏、lobyte宏、hiby

若程序中想要正确识别wsadata结构体、word类型、makeword宏、lobyte宏、hibyte宏

客户端:

  1. #define _CRT_SECURE_NO_WARNINGS
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <winsock.h>
  6. #include <windows.h>
  7. #pragma comment(lib, "wsock32.lib")
  8. int main(void)
  9. {
  10. char Sendbuf[100]; //发送数据缓冲区
  11. char Receivebuf[100]; //接收数据缓冲区
  12. int SendLen; //发送数据的长度
  13. int ReceiveLen; //接收数据的长度
  14. SOCKET socket_send; //定义套接字
  15. SOCKADDR_IN Server_add; //服务器在址信息结构
  16. WORD wVersionRequested; //WORD: unsigned short*
  17. WSADATA wsaData; //库版本信息结构
  18. int error; //错误码
  19. /*------ 初始化套接字库 ------*/
  20. /* 定义版本类型。将两个字节组合成一个字,前面是低字节,后面是高字节*/
  21. wVersionRequested = MAKEWORD(2, 2);
  22. error = WSAStartup(wVersionRequested, &wsaData);
  23. if (error != 0)
  24. {
  25. printf("加载套接字失败!\n");
  26. return 0;
  27. }
  28. /* 判断请求加载的版本号是否符合要求 */
  29. if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
  30. {
  31. WSACleanup(); //不符合,关闭套接字库
  32. return 0;
  33. }
  34. /* 设置服务器地址 */
  35. Server_add.sin_family = AF_INET;
  36. Server_add.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
  37. Server_add.sin_port = htons(5000);
  38. /* 连接服务器 */
  39. socket_send = socket(AF_INET, SOCK_STREAM, 0);
  40. if (connect(socket_send, (SOCKADDR *)&Server_add, sizeof(SOCKADDR)) == SOCKET_ERROR)
  41. {
  42. printf("连接失败!\n");
  43. }
  44. /* 开始聊天 */
  45. while ( 1 )
  46. {
  47. //发送数据过程
  48. printf("你说:");
  49. scanf("%s", Sendbuf);
  50. SendLen = send(socket_send, Sendbuf, 100, 0);
  51. if (SendLen < 0)
  52. {
  53. printf("发送失败!\n");
  54. }
  55. //接收数据过程
  56. ReceiveLen = recv(socket_send, Receivebuf, 100, 0);
  57. if (ReceiveLen < 0)
  58. {
  59. printf("接收失败!\n");
  60. printf("程序退出\n");
  61. break;
  62. }
  63. else
  64. {
  65. printf("来自附近的人:%s\n", Receivebuf);
  66. }
  67. }
  68. /* 释放套接字, 关闭动态库 */
  69. closesocket(socket_send);
  70. WSACleanup();
  71. system("pause");
  72. return 0;
  73. }

服务端

  1. #define _CRT_SECURE_NO_WARNINGS
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <windows.h>
  6. #include <winsock.h>
  7. #pragma comment(lib, "wsock32.lib")
  8. int main(void)
  9. {
  10. char Sendbuf[100]; //发送数据缓冲区
  11. char Receivebuf[100];//接收数据缓冲区
  12. int SendLen; //发送数据的长度
  13. int ReceiveLen; //接收数据的长度
  14. int Length; //表示SOCKETADDR的大小
  15. SOCKET socket_server; //定义服务器套接字
  16. SOCKET socket_receive;//定义用于连接套接字
  17. SOCKADDR_IN Server_add; //服务器地址信息结构
  18. SOCKADDR_IN Client_add; //客户端地址信息结构
  19. WORD wVersionRequested;
  20. WSADATA wsaData;
  21. int error;
  22. //初始化套接字库
  23. wVersionRequested = MAKEWORD(2, 2);
  24. error = WSAStartup(wVersionRequested, &wsaData);
  25. if (error != 0)
  26. {
  27. printf("加载套接字失败!\n");
  28. return 0;
  29. }
  30. if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
  31. {
  32. WSACleanup();
  33. return 0;
  34. }
  35. //设置连接地址
  36. Server_add.sin_family = AF_INET;
  37. Server_add.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
  38. Server_add.sin_port = htons(5000);
  39. //创建套接字
  40. socket_server = socket(AF_INET, SOCK_STREAM, 0);
  41. //绑定套接字到本地的某个地址和端口上
  42. if (bind(socket_server, (SOCKADDR *)&Server_add, sizeof(SOCKADDR)) == SOCKET_ERROR)
  43. {
  44. printf("绑定失败\n");
  45. }
  46. //设置套接字为监听状态
  47. if (listen(socket_server, 5) < 0)
  48. {
  49. printf("监听失败\n");
  50. }
  51. //接受连接
  52. Length = sizeof(SOCKADDR);
  53. socket_receive = accept(socket_server, ( SOCKADDR * )&Client_add, &Length);
  54. if (socket_receive == SOCKET_ERROR)
  55. {
  56. printf("接收失败\n");
  57. }
  58. //开始聊天
  59. while ( 1 )
  60. {
  61. /* ----- 接收数据 -----*/
  62. ReceiveLen = recv(socket_receive, Receivebuf, 100, 0);
  63. if (ReceiveLen < 0)
  64. {
  65. printf("接收失败\n");
  66. printf("程序退出\n");
  67. break;
  68. }
  69. else
  70. {
  71. printf("来自附近的人:%s\n", Receivebuf);
  72. }
  73. /* ----- 发送数据 -----*/
  74. printf("你说:");
  75. scanf("%s", Sendbuf);
  76. SendLen = send(socket_receive, Sendbuf, 100, 0);
  77. if (SendLen < 0 )
  78. {
  79. printf("发送失败!\n");
  80. }
  81. }
  82. /* ----- 释放套接字 关闭动态库------*/
  83. closesocket(socket_receive);
  84. closesocket(socket_server);
  85. WSACleanup();
  86. system("pause");
  87. return 0;
  88. }

数据类型和相关函数说明:
1)SOCKET类型
SOCKET是socket套接字类型,在WINSOCK2.H中有如下定义:

typedef unsigned int    u_int;

typedef u_int           SOCKET;

可知套接字实际上就是一个无符号整型,它将被Socket环境管理和使用。套接字将被创建、设置、用来发送和接收数据,最后会被关闭。


2) WORD类型、MAKEWORD、LOBYTE和HIBYTE宏
WORD类型是一个16位的无符号整型,在WTYPES.H中被定义为:

typedef unsigned short WORD;

其目的是提供两个字节的存储,在Socket中这两个字节可以表示主版本号和副版本号。使用MAKEWORD宏可以给一个WORD类型赋值。例如要表示主版本号2,副版本号0,可以使用以下代码:

WORD wVersionRequested;

wVersionRequested = MAKEWORD( 2, 0 ); 

注意低位内存存储主版本号2,高位内存存储副版本号0,其值为0x0002。使用宏LOBYTE可以读取WORD的低位字节,HIBYTE可以读取高位字节。


3)WSADATA类型和LPWSADATA类型
WSADATA类型是一个结构,描述了Socket库的一些相关信息,其结构定义如下:

typedef struct WSAData {

        WORD                    wVersion;

        WORD                    wHighVersion;

        char                    szDescription[WSADESCRIPTION_LEN+1];

        char                    szSystemStatus[WSASYS_STATUS_LEN+1];

        unsigned short          iMaxSockets;

        unsigned short          iMaxUdpDg;

        char FAR *              lpVendorInfo;

} WSADATA;

typedef WSADATA FAR *LPWSADATA;

值得注意的就是wVersion字段,存储了Socket的版本类型。LPWSADATA是WSADATA的指针类型。它们不用程序员手动填写,而是通过Socket的初始化函数WSAStartup读取出来。

4)WSAStartup函数
WSAStartup函数被用来初始化Socket环境,它的定义如下:

int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData);

其返回值为整型,调用方式为PASCAL(即标准类型,PASCAL等于__stdcall),参数有两个,第一个参数为WORD类型,指明了Socket的版本号,第二个参数为WSADATA类型的指针。

若返回值为0,则初始化成功,若不为0则失败。

5)WSACleanup函数
这是Socket环境的退出函数。返回值为0表示成功,SOCKET_ERROR表示失败。

6)socket函数
socket的创建函数,其定义为:

SOCKET PASCAL FAR socket (int af, int type, int protocol);

第一个参数为int af,代表网络地址族,目前只有一种取值是有效的,即AF_INET,代表internet地址族;

第二个参数为int type,代表网络协议类型,SOCK_DGRAM代表UDP协议,SOCK_STREAM代表TCP协议;

第三个参数为int protocol,指定网络地址族的特殊协议,目前无用,赋值0即可。

返回值为SOCKET,若返回INVALID_SOCKET则失败。

7)setsockopt函数
这个函数用来设置Socket的属性,若不能正确设置socket属性,则数据的发送和接收会失败。定义如下:

int PASCAL FAR setsockopt (SOCKET s, int level, int optname, const char FAR * optval, int optlen);

其返回值为int类型,0代表成功,SOCKET_ERROR代表有错误发生。

第一个参数SOCKET s,代表要设置的套接字;

第二个参数int level,代表要设置的属性所处的层次,层次包含以下取值:SOL_SOCKET代表套接字层次;IPPROTO_TCP代表TCP协议层次,IPPROTO_IP代表IP协议层次(后面两个我都没有用过);

第三个参数int optname,代表设置参数的名称,SO_BROADCAST代表允许发送广播数据的属性,其它属性可参考MSDN;

第四个参数const char FAR * optval,代表指向存储参数数值的指针,注意这里可能要使用reinterpret_cast类型转换;

第五个参数int optlen,代表存储参数数值变量的长度。

8)sockaddr_in、in_addr类型,inet_addr、inet_ntoa函数
sockaddr_in定义了socket发送和接收数据包的地址,定义:

struct sockaddr_in {

        short   sin_family;

        u_short sin_port;

        struct in_addr sin_addr;

        char    sin_zero[8];

};

其中in_addr的定义如下:

struct in_addr {

        union {

                struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;

                struct { u_short s_w1,s_w2; } S_un_w;

                u_long S_addr;

        } S_un;

首先阐述in_addr的含义,很显然它是一个存储ip地址的联合体,有三种表达方式:

第一种用四个字节来表示IP地址的四个数字;

第二种用两个双字节来表示IP地址;

第三种用一个长整型来表示IP地址。

给in_addr赋值的一种最简单方法是使用inet_addr函数,它可以把一个代表IP地址的字符串赋值转换为in_addr类型,如

addrto.sin_addr.s_addr=inet_addr("192.168.0.2");

本例子中由于是广播地址,所以没有使用这个函数。其反函数是inet_ntoa,可以把一个in_addr类型转换为一个字符串。

sockaddr_in的含义比in_addr的含义要广泛,其各个字段的含义和取值如下:

第一个字段short   sin_family,代表网络地址族,如前所述,只能取值AF_INET;

第二个字段u_short sin_port,代表IP地址端口,由程序员指定;

第三个字段struct in_addr sin_addr,代表IP地址;

第四个字段char    sin_zero[8],很搞笑,是为了保证sockaddr_in与SOCKADDR类型的长度相等而填充进来的字段。

以下代表指明了广播地址,端口号为7861的一个地址:

    sockaddr_in addrto;            //发往的地址 

    memset(&addrto,0,sizeof(addrto));

    addrto.sin_family = AF_INET;               //地址类型为internetwork

    addrto.sin_addr.s_addr = INADDR_BROADCAST; //设置ip为广播地址

    addrto.sin_port = htons(7861);             //端口号为7861


9) sockaddr类型
sockaddr类型是用来表示Socket地址的类型,同上面的sockaddr_in类型相比,sockaddr的适用范围更广,因为sockaddr_in只适用于TCP/IP地址。Sockaddr的定义如下:

struct sockaddr {

u_short    sa_family;

char       sa_data[14];

};  

可知sockaddr有16个字节,而sockaddr_in也有16个字节,所以sockaddr_in是可以强制类型转换为sockaddr的。事实上也往往使用这种方法。


10)Sleep函数
线程挂起函数,表示线程挂起一段时间。Sleep(1000)表示挂起一秒。定义于WINBASE.H头文件中。WINBASE.H又被包含于WINDOWS.H中,然后WINDOWS.H被WINSOCK2.H包含。所以在本例中使用Sleep函数不需要包含其它头文件。


12)sendto函数
在Socket中有两套发送和接收函数,一是sendto和recvfrom;二是send和recv。前一套在函数参数中要指明地址;而后一套需要先将套接字和一个地址绑定,然后直接发送和接收,不需绑定地址。sendto的定义如下:

int PASCAL FAR sendto (SOCKET s, const char FAR * buf, int len, int flags, const struct sockaddr FAR *to, int tolen);

第一个参数就是套接字;

第二个参数是要传送的数据指针;

第三个参数是要传送的数据长度(字节数);

第四个参数是传送方式的标识,如果不需要特殊要求则可以设置为0,其它值请参考MSDN;

第14、 WSAGetLastError函数

该函数用来在Socket相关API失败后读取错误码,根据这些错误码可以对照查出错误原因。

13)closesocket函数
关闭套接字,其参数为SOCKET类型。成功返回0,失败返回SOCKET_ERROR。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/147177
推荐阅读
相关标签
  

闽ICP备14008679号