winsock实现TCP通信

对于多字节数据在内存中有两种存储方式:

Little-endian:低字节在前,高字节在后;

Big-endian:高字节在前,低字节在后

网络协议在处理多字节整数时,采用的是高端字节序,在编程时一定要考虑主机字节顺序与网络字节顺序的相互转换。

1      WinSock基本
1.1     加载/卸载WinSock库
如果装载Winsock1,那么必须include<Winsock1.h>,同时建立与Wsock32.lib的链接关系。

如果装载Winsock2,那么必须include<Winsock2.h>,同时建立与Ws_32.lib的链接关系。

在调用一个Winsock函数之前,必须先加载一个Winsock的动态链接库:

intWSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData )

参数wVersionRequested是Winsock的版本号,最新版本是2.2。高位字节指定副版本,低位字节指定主版本,可用宏MAKEWORD(x,y)(其中,x是高位字节,y是低位字节)。如果需要Winsock2.2,指定0x0202或使用宏MAKEWORD(2,2)。

WSAStartup加载的Winsock库的信息存放到参数lpWSAData指向的结构体WSADATA中,可以设置为NULL。

卸载Winsock库:

intWSACleanup(void);

注意:每次调用WSAStartup( ),都需要调用相应的WSACleanup()。

1.2     SOCKET介绍
Win32中套接字不同于文件描述符,是一个独立的类型SOCKET,由两个函数建立的:

SOCKETWSASocket( int af,    int type,       intprotocol,

LPWSAPROTOCOL_INFOlpProtocolInfo,

GROUP g,   DWORD dwFlags    );

SOCKETsocket(    int af,    int type,       int protocol  );

参数af是协议的地址家族;参数type是协议的套接字类型;参数protocol指定了协议字段。

协议

地址家族

套接字类型

协议字段

IP

AF_INET

TCP

SOCK_STREAM

IPPROTO_IP

UDP

SOCK_DGRAM

IPPROTO_UDP

Raw sockets

SOCK_RAW

IPPROTO_RAW

IPPROTO_ICMP

参数g始终为0,因为目前尚无支持套接字组的Winsock版本。

参数dwFlags可以指定标志:WSA_FLAG_OVERLAPPED(默认设置)

参数lpProtocolInfo用来指定创建套接字的类型,可以设置为NULL。

1.3     WinSock的地址信息
Winsock通过sockaddr_in结构来指定IP地址和服务端口信息:

structsockaddr

{

unsigned short sa_family;      /* 地址协议族,一般为AF_INET*/

char sa_data[14];                    /* 14字节的协议地址,包含该socket的IP地址和端口号 */

};

另外还有一种结构类型sockaddr_in:

structsockaddr_in

{

short int sin_family;              /* 地址族 */

unsigned short int sin_port;  /* 网络字节顺序的端口号*/

struct in_addr sin_addr;       /* IP地址 */

unsigned char sin_zero[8];   /* 填充0 以保持与结构sockaddr同样大小 */

};

sockaddr_in结构使用更为方便,sockaddr_in.sin_zero应该用bzero( )或memset( )将其置为0。sockaddr_in的指针和sockaddr的指针可以通用。

structin_addr

{

unsigned long s_addr;            /* 网络字节顺序的IP地址*/

};

可以将s_addr指定为htonl( INADDR_ANY ),表示的是由系统自己指定IP地址。

在选择端口sin_port时,必须特别小心:

1.0~1023由IANA控制,是为固定服务保留的;

2.1024~49151是IANA列出来的、已注册的端口,供普通用户的普通用户进程或程序使用;

3.49152~65535是动态或私用端口,IANA这些端口上没有注册服务,可自由使用。

1.4     地址格式转换函数
inet_addr( )可以把一个点式IP地址转换成一个网络字节顺序的IP地址:

unsignedlong inet_addr( const char FAR *cp );

inet_nota( )可以把一个网络字节顺序的IP地址转换成一个点式IP地址:

char*FAR inet_ntoa( struct in_addr in );

1.5     网络字节顺序转换函数
主机字节顺序---->网络字节顺序:

u_longhtonl(     u_long hostlong );

intWSAHtonl(   SOCKET s, u_long hostlong,    u_long*lpnetlong );

u_shorthtons(   u_short hostshort );

intWSAHtons( SOCKET s,  u_short hostshort, u_short* lpnetshort );

网络字节顺序---->主机字节顺序:

u_longntohl(      u_long netlong   );

intWSANtohl(  SOCKET s,  u_long netlong,      u_long* lphostlong   );

u_shortntohs(   u_short netshort );

intWSANtohs( SOCKET s,  u_short netshort,   u_short* lphostshort );

1.6     名字解析
structhostent

{

char FAR* h_name;                            // 主机名

char FAR FAR ** h_aliases;        // 主机别名数组

short h_addrtype;                         // 主机地址类型

short h_length;                              // 主机地址长度(字节)

char FAR FAR ** h_addr_list;   // 主机地址数组,其中的地址是网络字节顺序。

}

1.6.1   主机名---->主机地址
structhostent * FAR gethostbyname( const char* name );

HANDLEWSAAsyncGetHostByName( HWND hWnd,  unsigned int wMsg,

const char* name,    char* buf,   int buflen );

WSAAsyncGetHostByNameAPI( )是gethostbyname( )的异步版,在结束时利用Windows消息向应用程序发出通知:

参数name可以是主机名,也可以是点式IP地址。

参数bWnd指定异步请求结束时,收到消息的窗口句柄。

参数wMsg是异步请求结束时,收到的窗口消息。

参数buf指向接收hostent数据的存储区。

参数buflen指定buf存储区的大小,推荐使用MAXGETHOSTSTRUCT。

1.6.2   主机地址---->主机名
structHOSTENT* FAR gethostbyaddr( const char* addr, int len, int type );

HANDLEWSAAsyncGetHostByAddr(  HWND hWnd, unsignedint wMsg, const char* addr,

int len,  int type,  char* buf,  int buflen );

WSAAsyncGetHostByAddrAPI( )是gethostbyaddr( )的异步版,在结束时利用Windows消息向应用程序发出通知:

参数addr是指向主机地址的指针,该主机地址按照网络字节顺序。

参数len指定参数addr的字节长度。

参数type为AF_INET。

1.7     获取Winsock函数的错误代码
intWSAGetLastError(void);

发生错误之后调用WSAGetLastError( ),就会返回发生错误的完整代码。

1.8     套接字的状态


套接字的初始状态都是CLOSED

套接字可以说是Winsock通讯的核心.Winsock通讯的所有数据传输,都是通过套接字来完成的,套接字包含了两个信息,一个是IP地址,一个是Port端口号,使用这两个信息,我们就可以确定网络中的任何一个通讯节点.

Client

#define _WINSOCK_DEPRECATED_NO_WARNINGS
 
#include <iostream>
#include <string>
 
#include <winsock2.h>  // 包含网络通信头文件
 
#pragma comment(lib, "ws2_32.lib")
 
#define PORT 8888
#define SERVER_IP "127.0.0.1"
#define  BUFFER_SIZE 256
 
static const std::string kExitFlag = "-1";
 
int main1() {
  // 初始化socket dll。
  WORD winsock_version = MAKEWORD(2,2);
  WSADATA wsa_data;
  if (WSAStartup(winsock_version, &wsa_data) != 0) {
    std::cout << "Failed to init socket dll!" << std::endl;
    return 1;
  }
 
  SOCKET client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (client_socket == INVALID_SOCKET) {
    std::cout << "Failed to create server socket!" << std::endl;
    return 2;
  }
 
  // 绑定IP和端口  及需要连接的目的IP,和端口
  sockaddr_in server_addr;
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(PORT);
  server_addr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);
  
// connect() 向服务器发起请求,服务器的IP地址和端口号保存在 sockaddr_in 结构体中。直到服务器传回数据后,connect() 才运行结束。
  if (connect(client_socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
    std::cout << "Failed to connect server!" << std::endl;
    return 3;
  }
 
  char recv_data[BUFFER_SIZE] = {};
 
  while (true) {
    std::string data;
    std::cout << "Input data: ";
    std::cin >> data;
 
    if (send(client_socket, data.c_str(), data.size(), 0) < 0) {
      std::cout << "Failed to send data!" << std::endl;
      break;
    }
   //接受目的主机 client
    int ret = recv(client_socket, recv_data, BUFFER_SIZE, 0);
    if (ret < 0) {
      std::cout << "Failed to receive data!" << std::endl;
      break;
    }
 
    std::cout << "Receive data from server: " << recv_data << std::endl;
 
    if (data == kExitFlag) {
      std::cout << "Exit!" << std::endl;
      break;
    }
  }
 
  closesocket(client_socket);
  WSACleanup();
 
  return 0;
}
 


Server

#define _WINSOCK_DEPRECATED_NO_WARNINGS
 
#include <iostream>
#include <string>
 
#include <winsock2.h>  // 包含网络通信头文件
 
#pragma comment(lib, "ws2_32.lib")  // 程序在链接的时候将该库链接进去。隐式加载动态库。加载 ws2_32.dll。
 
// 动态链接库有两种加载方式:隐式加载和显示加载。
// 1.隐式加载又叫载入时加载,指在主程序载入内存时搜索DLL,并将DLL载入内存。隐式加载也会有静态链接库的问题,如果程序稍大,加载时间就会过长,用户不能接受。
// .lib 文件包含DLL导出的函数和变量的符号名,只是用来为链接程序提供必要的信息,以便在链接时找到函数或变量的入口地址;
// .dll 文件才包含实际的函数和数据。所以首先需要将 dllDemo.lib 引入到当前项目.
// 2.显式加载又叫运行时加载,指主程序在运行过程中需要DLL中的函数时再加载。显式加载是将较大的程序分开加载的,程序运行时只需要将主程序载入内存,软件打开速度快,用户体验好。
 
// 动态链接库的好处:在需要的时候加载动态链接库某个函数。
// 隐式链接的缺点:使用比较简单,在程序的其他部分可以任意使用函数,但是当程序访问十来个dll动态链接库的时候,此时如果都使用隐式链接的时候,
// 启动此程序的时候,这十来个动态链接库都需要加载到内存,映射到内存的地址空间,这就会加大进程的启动时间,而且程序运行过程中,只是在某个条件下使用某个函数,
// 如果使用隐式链接会造成资源的浪费。这样需要采用动态加载的方式。
 
#define PORT 8888
#define  BUFFER_SIZE 256
 
static const std::string kExitFlag = "-1";
 
// 参考:http://c.biancheng.net/cpp/html/3030.html
 
int mains1() {
  // 初始化socket dll。
  // WinSock 规范的最新版本号为 2.2。
  // //主版本号为2,副版本号为2,返回 0x0202。
  // WSADATA保存Dll的信息。
  WORD winsock_version = MAKEWORD(2,2);
  WSADATA wsa_data;
  if (WSAStartup(winsock_version, &wsa_data) != 0) {
    std::cout << "Failed to init socket dll!" << std::endl;
    return 1;
  }
 
  // 参数 AF_INET 表示使用 IPv4 地址,SOCK_STREAM 表示使用面向连接的数据传输方式,IPPROTO_TCP 表示使用 TCP 协议。
  // AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。
  // 据传输方式,常用的有两种:SOCK_STREAM 和 SOCK_DGRAM。 SOCK_STREAM 表示面向连接的数据传输方式。 SOCK_DGRAM 表示无连接的数据传输方式。
  //  protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。
  SOCKET server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (server_socket == INVALID_SOCKET) {
    std::cout << "Failed to create server socket!" << std::endl;
    return 2;
  }
 
  // 绑定IP和端口。
  // IP地址和端口都保存在 sockaddr_in 结构体中。
  //struct sockaddr_in {
  //  sa_family_t     sin_family;   //地址族(Address Family),也就是地址类型
  //  uint16_t        sin_port;     //16位的端口号
  //  struct in_addr  sin_addr;     //32位IP地址
  //  char            sin_zero[8];  //不使用,一般用0填充
  //};
 
  //struct in_addr {
  //  in_addr_t  s_addr;  //32位的IP地址
  //};
 
  //struct sockaddr {
  //  sa_family_t  sin_family;   //地址族(Address Family),也就是地址类型
  //  char         sa_data[14];  //IP地址和端口号
  //};
 
  sockaddr_in server_addr;
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(PORT);
  server_addr.sin_addr.S_un.S_addr = INADDR_ANY;
 
  // 为什么使用 sockaddr_in 而不使用 sockaddr.
  // sockaddr 是一种通用的结构体,可以用来保存多种类型的IP地址和端口号,而 sockaddr_in 是专门用来保存 IPv4 地址的结构体。
  // 正是由于通用结构体 sockaddr 使用不便,才针对不同的地址类型定义了不同的结构体。
//bind 绑定值本地某个地址端口
  if (bind(server_socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
    std::cout << "Failed to bind port!" << std::endl;
    return 3;
  }
 
  // 监听。
  // 第二个参数是 请求队列的长度。
  // listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到 accept()。
  // accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。
  if (listen(server_socket, 10)) {
    std::cout << "Failed to listen!" << std::endl;
    return 4;
  }
 
  // 循环接收数据。
  sockaddr_in client_addr;
  int client_addr_len = sizeof(client_addr);
  std::cout << "Wait for connecting..." << std::endl;
 
  // 程序一旦执行到 accept() 就会被阻塞(暂停运行),直到客户端发起请求。
  SOCKET client_socket = accept(server_socket, (SOCKADDR*)&client_addr, &client_addr_len);
  if (client_socket == INVALID_SOCKET) {
    std::cout << "Failed to accept!" << std::endl;
    return 5;
  }
 
  std::cout << "Succeed to receive a connection: " << inet_ntoa(client_addr.sin_addr) << std::endl;
 
  char recv_buf[BUFFER_SIZE] = {};
  while (true) {
    // 接收数据。
    // 返回值是读取的字节数。没有内容时,等待请求。
    int ret = recv(client_socket, recv_buf, BUFFER_SIZE, 0);
    if (ret < 0) {
      std::cout << "Failed to receive data!" << std::endl;
      break;
    }
 
    std::cout << "Receive from Client: " << recv_buf << std::endl;
    if (kExitFlag == recv_buf) {
      std::cout << "Exit!" << std::endl;
      break;
    }
 
    // 发送数据给客户端。
    char* send_data = "Hello, Tcp Client!\n";
    send(client_socket, send_data, strlen(send_data), 0);
  }
 
  // 关闭套接字。
  closesocket(client_socket);
  closesocket(server_socket);
 
  // 释放dll。
  WSACleanup();
 
  return 0;
}

--------------------- 
转载:https://blog.csdn.net/u011726005/article/details/76944620 
 

猜你喜欢

转载自blog.csdn.net/liudongdong19/article/details/83992438