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();

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

WinSock API网络编程——TCP/IP协议详解相关推荐

  1. TCP/IP协议详解---概述

    工作之后,才发现以前在学校里学的东西忘得太快太干净了,现在需要一点点地捡起来了,要不然写几行程序会闹很多笑话会出现很多bug的.从今天开始,翻一翻<TCP/IP协议详解 卷1>这本书,希望 ...

  2. 《TCP IP协议 详解》思考总结 · 三

    前言 这一篇文章主要围绕了IP协议,ICMP协议和UDP协议展开,希望可以在这里大概做一个总结,将<TCP/IP协议详解 卷一>书中TCP相关章节前面的内容做一个结束,在下一篇文章专心的去 ...

  3. TCP /IP协议详解【转】

    转自:https://www.jianshu.com/p/0cf648510bce?utm_campaign=maleskine&utm_content=note&utm_medium ...

  4. TCP/IP协议详解、TCP三次握手

    TCP/IP协议详解:TCP/IP协议详解_王佳斌-CSDN博客_tcp/ip协议认识HTTP协议它是互联网协议(Internet Protocol Suite),一个网络通信模型,是互联网的一个基本 ...

  5. TCP/IP协议详解卷一:Chapter21 笔记

    TCP/IP协议详解卷一:Chapter21 笔记 Chapter 21 TCP的超时与重传 21.3 往返时间测量 21.5 拥塞举例 21.6 拥塞避免算法 21.7 快速重传与快速恢复算法 21 ...

  6. 网络编程基础一:TCP/IP 协议详解

    前言 我们知道两个进程如果需要进行通讯最基本的一个前提[能够唯一的标示一个进程],在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候 ...

  7. 编写tcp服务器发送hex格式_Android网络编程-TCP/IP协议

    在Android网络编程-计算机网络基础一文中得知,IP协议属于网络层,TCP.UDP协议属于传输层. IP协议是TCP/IP协议族的动力,它为上层协议提供无状态.无连接.不可靠的服务. TCP协议是 ...

  8. 【网络基础】TCP/IP协议详解

    TCP/IP协议定义 TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)是在多个不同网络间实现信息传输的通信协议.是 ...

  9. 网络编程--TCP/IP协议

    参考:https://lijie.blog.csdn.net/article/details/105297532 https://blog.csdn.net/qq_20785973/article/d ...

最新文章

  1. 计算机检索的优点,专利检索与分析系统拥有哪些优势?
  2. PTA团体程序设计天梯赛-L2-003 月饼
  3. 【iOS基础知识】const与宏的区别
  4. bootstrap-table操作之“删除”
  5. PHP源码分析-PHP的生命周期
  6. 为什么招聘单片机工程师的时候要求精通C/C++?
  7. socket编程学习笔记
  8. 解锁环境变量在云原生应用中的各种姿势
  9. 【专升本计算机】最新甘肃省专升本考试C语言部分复习题带答案
  10. 【CodeForces - 1150C】Prefix Sum Primes(思维)
  11. 2、Collections操作(自定义类)的各种实现
  12. (飞鸽传书绿色版)网站
  13. VS、C#配置R语言开发环境
  14. SmtpClient SSL 发送邮件异常排查
  15. Maya: Render Setup System Maya教程:渲染设置系统 Lynda课程中文字幕
  16. 511遇见易语言列表框去重复项目到列表框
  17. PHOTOSHOP中常用的四种抠图方法
  18. 小米蓝牙键盘怎么连接_小米2手机连接使用蓝牙键盘和蓝牙鼠标教程(原创)
  19. 潜入浅出,java多线程到底是个什么东东?面试中应该注意哪方面多线程的知识?
  20. Linux 与 Python编程2021 经典函数实例 educoder实训

热门文章

  1. java 字符串模糊匹配_Java实现伪查询(全匹配+模糊匹配)
  2. 用c51语言把连续字节做比较,C51优化设计之循环语句(转)
  3. java项目启动时登录,Java项目启动时报错解决方法
  4. 子组件调用父组件方法_Vuejs组件(一)组件的注册使用方法
  5. php 常用简单函数,PHP中常用函数简单介绍
  6. 华为鸿蒙os生态,华为鸿蒙系统终于来了! 首款方舟编译器应用正式上架: 鸿蒙OS可用...
  7. 现代计算机系统中运算器设计一般采用的是,全国2014年4月高等教育自学考试计算机原理试题课程代码:02384...
  8. html移动滚动彩字字幕特效,如何制作滚动字幕 旋转好莱坞字幕特效效果图(超多滚动效果)...
  9. python怎么赋值int_int对象不支持项赋值
  10. oracle的have,Does oracle have “auto number” data type [duplicate]