WinSock API网络编程——TCPIP协议详解

    xiaoxiao2021-04-13  23

    

    WinSock API网络编程——TCP/IP协议(http://www.impcas.ac.cn/usr/lujun/browse.asp?id=winsock_tcp)

                WinSock API网络编程——TCP/IP协议作者:陆军 Email:ldlujun@163.com 时间:2004-08-28

    80年代初,美国政府的高级研究工程机构(ARPA)给加利福尼亚大学Berkeley分校提供了资金,为实现UNIX操作系统下的TCP/IP协议而开发了一个API(Application Programming Interface),称为Socket接口(套接字)。Socket接口是TCP/IP网络最为通用的API。

    90年代初,由Microsoft联合了其他几家公司共同制定了一套Windows下的网络编程接口,即Windows Sockets规范。Windows Sockets规范是一套开放的、支持多种协议的Windows网络编程接口,并已成为Windows网络编程的事实上的标准。目前,在实际应用中的Windows Sockets规范主要有1.1版和2.0版。2.0版可以支持多协议,有良好的向后兼容性,任何使用1.1版的源代码,二进制文件,应用程序都可以不加修改在2.0规范下使用。

    Socket实际上在计算机中提供了一个通信端口,可以通过这个端口与任何一个具有Socket接口的计算机通信。应用程序在网络上传输/接收的信息都通过这个Socket接口来实现的。在应用开发中可以像使用文件句柄一样来对Socket句柄进行读/写操作。目前可以使用两种套接口,即流式套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)。流式套接字提供了一个面向连接的、可靠的、数据无错的、无重复发送的及按发送顺序接收数据的服务;数据报套接字提供不可靠的、无连接的数据报传输服务。

    套接字可分为阻塞套接字和非阻塞套接字。阻塞套接字是指执行此套接字的网络调用时,直到成功才返回,否则一直阻塞在此网络调用上;而非阻塞套接字是指执行此套接字的网络调用时,不管是否执行成功,都立即返回。实际上非阻塞套接字是用得最多的。

    C/S模型,即客户机/服务器模型,是一种非对称式编程模式。对于这种模式而言,其中一部分需要作为服务端,用来响应并为客户提供固定的服务;另一部分则作为客户端用来向服务端提出请求或要求某种服务。在实际应用中,程序可以同时包含客户端和服务端。

    Microsoft Visual C++提供了十分完整的Windows Sockets库函数,并且对这些库函数进行了一系列封装,继而产生了CAsynSocket、CSocket、CSocketFile等类,它们封装着有关Socket的各种功能,使网络编程变得更加简单。但是为了更好理解Winsock的通信原理,这里将介绍怎样使用底层的API函数来实现网络通讯。

    面向连接协议的通信过程如下:服务端和客户端都必须建立通信套接字,而服务端套接字应先进入监听状态,然后客户端套接字发出连接请求,服务端套接字收到连接请求后,建立一个新套接字与客户端套接字进行通信,原来负责监听的套接字仍进行监听,如果再收到其它客户端套接字的连接请求,则再建立一个新套接字与之通信。通信完毕后断开连接,关闭相应套接字。

    (1) 初始化通信端口。可以在程序向导中添加Windows Sockets支持,或者直接添加代码:

    #include <afxsock.h> if (!AfxSocketInit()) {     AfxMessageBox("Windows 通信端口初始化失败!"); }

    (2) 初始化Windows Sockets DLL。目前Winsock有两个版本,版本号分别为1.1和2.2,对应参数为0x101和0x202。

    WSADATA wsaData; if (WSAStartup(MAKEWORD(1,1), &wsaData) != 0) {     AfxMessageBox("加载Windows Sockets DLL失败!");     WSACleanup(); }

    (3) 创建流式套接字。

    套接字族: AF_UNIX:UNIX内部协议族AF_INET:Iternet协议AF_NS:XeroxNs协议AF_IMPLINK:IMP链接层 套接字类型: SOCK_STREAM:流式套接字SOCK_DGRAM:数据报套接字SOCK_RAW:原始套接字SOCK_SEQPACKET:定序分组套接字  

    SOCKET m_Socket; m_Socket = INVALID_SOCKET; if ((m_Socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {     AfxMessageBox("创建套接字失败!"); }

    (4) 服务端绑定端口。端口号范围:1024到65535,低于1024的端口对应着因特网上的一些常见服务。

    struct sockaddr {     u_short sa_family;               // 地址族地址族 address family     address family char sa_data[14]; // 14字节的协议地址 up to 14 bytes of direct address }; typedef struct sockaddr SOCKADDR; typedef struct sockaddr *PSOCKADDR; typedef struct sockaddr FAR *LPSOCKADDR;

    struct sockaddr_in {     short sin_family;        // 地址族     u_short sin_port;        // 端口号     struct in_addr sin_addr; // IP地址     char sin_zero[8];        // 填充0 }; typedef struct sockaddr_in SOCKADDR_IN; typedef struct sockaddr_in *PSOCKADDR_IN; typedef struct sockaddr_in FAR *LPSOCKADDR_IN;

    字节顺序转换函数:     htons():"Host to Network Short"     htonl():"Host to Network long"     ntohs():"Network to Host Short"     ntohl():"Network to Host Long"

    SOCKADDR_IN m_saAddr; u_short     m_nPort = 20048;                // 端口号 ZeroMemory(&m_saAddr, sizeof(m_saAddr)); m_saAddr.sin_family      = AF_INET; m_saAddr.sin_port        = htons(m_nPort);  // 如果此值为0,系统将随机选择一个未被使用的端口号 m_saAddr.sin_addr.s_addr = INADDR_ANY;      // 填入本机IP地址 if (bind(m_Socket, (LPSOCKADDR) &m_saAddr, sizeof(m_saAddr)) == SOCKET_ERROR) {     AfxMessageBox("绑定端口失败!"); }

    (5) 服务端监听端口。

    #define MAX_BACKLOG 5 if (listen(m_Socket, MAX_BACKLOG) == SOCKET_ERROR) {     AfxMessageBox("监听失败!"); }

    (6) 客户端请求连接。

    DWORD m_dwServerIP; char m_sServerIP[] = "127.0.0.1"; // 主机IP地址 u_short m_nServerPort = 20048;    // 主机端口号 if ((m_dwServerIP = inet_addr(m_sServerIP)) == INADDR_NONE) {     AfxMessageBox("无法获取主机IP!");     return; } m_saAddr.sin_family = AF_INET; m_saAddr.sin_port = htons(m_nServerPort); m_saAddr.sin_addr.s_addr = m_dwServerIP; if (connect(m_Socket, (LPSOCKADDR) &m_saAddr, sizeof(m_saAddr))) {     AfxMessageBox("连接服务器失败!"); }

    (7) 注册网络事件。

    网络事件定义: FD_READ:网络数据包到达FD_WRITE:发送网络数据FD_OOB:OOB数据到达FD_ACCEPT:收到连接请求FD_CONNECT:已建立连接FD_CLOSE:断开连接FD_QOS:服务质量(QoS)发生变化FD_GROUP_QOS:保留事件FD_ROUTING_INTERFACE_CHANGE:指定地址的路由接口发生变化FD_ADDRESS_LIST_CHANGE:本地地址变化  

    #define WM_NETWORK_EVENT WM_USER + 101 if (WSAAsyncSelect(m_Socket, m_hWnd, WM_NETWORK_EVENT, FD_ACCEPT | FD_READ | FD_CLOSE) == SOCKET_ERROR) {     AfxMessageBox("注册网络事件失败!"); }

    (8) 处理网络事件。

    afx_msg LRESULT OnNetworkEvent(WPARAM wParam, LPARAM lParam); ON_MESSAGE(WM_NETWORK_EVEN, OnNetworkEvent) LRESULT OnNetworkEvent(WPARAM wParam, LPARAM lParam) {     switch (WSAGETSELECTEVENT(lParam))     {     case FD_ACCEPT:         // 接受连接请求         break;     case FD_READ:         // 接收数据         break;     case FD_CLOSE:         // 断开连接         break;     }     return 0L; }

    (9) 服务端接受连接请求。(采用上叙方法处理这里的网络事件,只是不需要处理FD_ACCEPT事件。)

    #define WM_CLIENT_EVENT WM_USER + 102 SOCKET m_hClientSocket[MAX_BACKLOG]; SOCKADDR_IN m_saClientAddr[MAX_BACKLOG]; for (int i = 0; i < MAX_BACKLOG; i++) {     ZeroMemory(&m_saClientAddr[i], sizeof(m_saClientAddr[i]));     m_hClientSocket[i] = INVALID_SOCKET; } BOOL Accept(void) {     CString sClientIP;     int nLength = sizeof(SOCKADDR);     for (int i = 0; i < MAX_BACKLOG; i++)     {         if (m_hClientSocket[i] == INVALID_SOCKET)         {             m_hClientSocket[i] = socket(PF_INET, SOCK_STREAM, 0);             m_hClientSocket[i] = accept(m_Socket, (LPSOCKADDR) &m_saClientAddr[i], (LPINT) &nLength);             WSAAsyncSelect(m_hClientSocket[i], m_hWnd, WM_CLIENT_EVENT, FD_READ | FD_CLOSE);             // 获取客户端IP             sClientIP.Format("%d.%d.%d.%d",                 m_saClientAddr[i].sin_addr.S_un.S_un_b.s_b1,                 m_saClientAddr[i].sin_addr.S_un.S_un_b.s_b2,                 m_saClientAddr[i].sin_addr.S_un.S_un_b.s_b3,                 m_saClientAddr[i].sin_addr.S_un.S_un_b.s_b4);             AfxMessageBox(sClientIP);             return TRUE;         }     }     AfxMessageBox("连接资源不足!");     return FALSE; }

    (10) 客户端断开连接。

    void ClientClose(WPARAM wParam) {     for (int i = 0; i < MAX_BACKLOG; i++)     {         if (m_ClientSocket[i] == wParam)         {             closesocket(m_hClientSocket[i]);             m_hClientSocket[i] = INVALID_SOCKET;         }     } }

    (11) 读取客户端数据。

    BOOL ClientRead(WPARAM wParam) {     int nBytesRead;     int nBufferLength;     int nEnd;     int nSpaceRemaining;     char chIncomingDataBuffer[4096];     nEnd = 0;     nBufferLength = sizeof(chIncomingDataBuffer);     nSpaceRemaining = sizeof(chIncomingDataBuffer);     nSpaceRemaining -= nEnd;     for (int i = 0; i < MAX_BACKLOG; i++)     {         if (m_hClientSocket[i] == wParam)         {             nBytesRead = recv(m_hClientSocket[i], (LPSTR) (chIncomingDataBuffer + nEnd), nSpaceRemaining, 0);             nEnd += nBytesRead;             if (nBytesRead == SOCKET_ERROR)             {                 AfxMessageBox("读取数据出错!")                 return FALSE;             }             chIncomingDataBuffer[nEnd] = '/0';             if (lstrlen(chIncomingDataBuffer) != 0)             {                 AfxMessageBox(chIncomingDataBuffer);             }         }     }     return TRUE; }

    (12) 读取服务端数据。

    BOOL Read(void) {     int nBytesRead;     int nBufferLength;     int nEnd;     int nSpaceRemaining;     char chIncomingDataBuffer[4096];     nEnd = 0;     nBufferLength = sizeof(chIncomingDataBuffer);     nSpaceRemaining = sizeof(chIncomingDataBuffer);     nSpaceRemaining -= nEnd;     nBytesRead = recv(m_Socket, (LPSTR) (chIncomingDataBuffer + nEnd), nSpaceRemaining, 0);     nEnd += nBytesRead;     if (nBytesRead == SOCKET_ERROR)     {         AfxMessageBox("读取数据出错!")         return FALSE;     }     chIncomingDataBuffer[nEnd] = '/0';     if (lstrlen(chIncomingDataBuffer) != 0)     {         AfxMessageBox(chIncomingDataBuffer);     }     return TRUE; }

    (13) 发送数据。

    BOOL Send(SOCKET Socket, CString sSendData) {     if (Socket == INVALID_SOCKET)     {         AfxMessageBox("套接字不可用!");         return FALSE;     }     if (send(Socket, sSendData, sSendData.GetLength(), 0) == SOCKET_ERROR)     {         AfxMessageBox("发送数据失败!");         return FALSE;     }     return TRUE; }

    (14) 关闭套接字。

    if (m_Socket != INVALID_SOCKET) {     closesocket(m_Socket); } m_Socket = INVALID_SOCKET; WSACleanup();

    希望本文能够对网络编程的初学者有所帮助。文中没有把服务端和客户端彻底分开,有些代码是它们共有的部分,而有的代码则专属于服务端或客户端,阅读时请注意相应的文字说明。由于时间匆忙,文中给出的代码都没有经过调试,难免有错误和不足之处,敬请大家原谅,同时也真心希望您能指出和纠正。

    转载请注明原文地址: https://ju.6miu.com/read-668645.html

    最新回复(0)