摘要

在学习服务器的过程中需要使用到winsock以及Socket编程,这里记录一下学到的Socket基础


  1. Socket套接字

    Socket就是一种通过(IP + Port)并遵循TCP/UDP等协议的进程间通信的技术,类似于UNIX中的管道,详细的教程度娘上很多。

    Socket主要有两类:
    (1) 标准Socket,又称为Berkeley Socket,主要用于Linux/UNIX
    (2) Winsock,主要用于Win平台

  2. Socket函数

    以Winsock为例,有一系列基本的Socket函数用来实现Socket通信

    (1) Socket
    // Socket 函数用于创建一个Socket套接字,函数原型如下
    SOCKET socket(int af, //使用的协议族int type, //Socket类型int protocol //使用的协议地址
    );
    // Socket 协议族在计算机里表示为一个整数,可以取值AF_INET
    // Socket 类型有两种:SOCK_STREAM 和 SOCK_DGRAM,代表流Socket(Tcp)和数据报Socket(UDP)// 如果函数成功,返回一个Socket套接字,否则,返回INVALID_SOCKET,在建立Socket连接之前,必须先创建所需的Socket套接字
    SOCKET s;
    s = socket(AF_INET, SOCK_STREAM, 0);
    

    在QT使用socket函数的时候出现’undefined reference to `_imp__socket’ 错误,解决办法是在PRO文件中加入如下语句:

    CONFIG += c++11 // 以防万一
    LIBS += -lpthread libwsock32 libws2_32

    而且在头文件中需要引入<windows.h> 或者 <winsock2.h>


    让我们接着Socket往下说

    (2) Connect
    // Connect函数用于尝试与远端建立一个Socket连接,函数原型如下:
    int connect(SOCKET s,   //Socket描述字const struct sockaddr* name,  //远端的地址int namelen   //远端地址的长度
    );
    // 在进行连接时,远端地址是一个SOCKADDR的结构,定义为:
    struct sockaddr_in {short sin_family,  //Socket组u_short sin_port, // 端口struct in_addr sin_addr, //IP地址char sin_zero[8]  //结构的长度
    }
    // 如果连接成功,返回0,否则返回SOCKET_ERROR。
    // 对于非阻塞模式的Socket连接,返回结果通常都是SOCKET_ERROR,
    //    并且错误代码为WSAEWOULDBLOCK,表示连接正在进行,而不是一个真正的错误//建立连接通常都是由客户端发出连接请求
    SOCKET s;
    SOCKADDR_IN ServerAddr;
    ServerAddr.sin_falimy = AF_INET;
    ServerAddr.sin_port = htons(80);  //也可以自定义其他Port
    ServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    connect(s, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));

    在QT使用connect函数的时候,会与QT本身的connect函数冲突,解决办法是加::域分隔符,如下:

    ::connect(s,(SOCKADDR*)&ServerAddr, sizeof(ServerAddr));

    让我们接着Connect往下说

    (3) Send
    // Send函数用于在某个Socket上向远端发送数据,原型为:
    int send(SOCKET s,const char* buf,   //存放发送数据的缓冲区int len,  //将要发送的数据长度int flags  //发送时使用的附加参数
    )
    // 如果发送成功,返回成功发送的字节数,否则返回SOCKET_ERROR(4) Recv
    int recv(SOCKET s,char* buf,  //存放接受数据的缓冲区int len,  //接受缓冲区的大小int flags  //接受时使用的附加标志
    )(5) Closesocket
    // 用于关闭不在需要的Socket
    int closesocket(SOCKET s  //Socket套接字
    )
    // 成功返回0,否则返回SOCKET_ERROR(6) Listen//Listen用于在某个Socket上建立监听,函数原型为:int listen(SOCKET s,int backlog   //缓存队列的长度)// 成功返回0,否则返回SOCKET_ERROR// 关于backlog参数,一般理解为协议层已经开始或已经完成连接的建立,但是应用程序还没有开始接受数据时,连接的数量未知。// 以前通常都是设为5,因为BSD4.2最大支持5,但是现在都是取SOMAXCONN,表示最大(7) Accept// 用于接受一条新的连接,注意:是接受连接而不是接受数据,原型为SOCKET accept(SOCKET s,  //监听中的Socketstruct sockaddr* addr,  //表示地址结构体的指针int* addrlen //地址结构体的长度)// 成功返回一个新的Socket套接字,否则返回SOCKET_ERROR(8) Bind//用于给一个Socket套接字分配一个本地协议地址,原型为int bind(SOCKET s,const struct sockaddr* name,  //表示地址结构体的指针in namelen  //地址结构体的程度)// 成功返回0,否则返回SOCKET_ERROR(9) Select// 用于检测Socket状态,主要用于高级的网络通信模型:int select(int nfds, //Winsock中此函数无意义fd_set* readfds, //进行可读检测的Socketfd_set* writefds,//进行可写检测的Socketfd_set* exceptfds, //进行异常检测的Socketconst struct timeval* timeout // 非阻塞模式中设置最大等待时间)// 成功返回0,否则返回SOCKET_ERROR
  3. IP地址转换

    主要有三种IP地址
    (1) 无符号整数地址:127.0.0.1
    (2) ASCII地址:“127.0.0.1”
    (3) 域名地址:“localhost”

    转换方法:

    (1) 整数地址到ASCII地址的转换#include <arpa/inet.h>// ASCII => INT
    int inet_aton(const char *straddr, struct in_addr* adrp); // 0表示失败,1表示成功// INT => ASCII
    char *inet_ntoa( struct in_addr inaddr); //NULL表示失败,其他任何值表示成功(2) 域名地址与整数地址的相互转换# include <netdb.h>// DOMAIN => INT
    struct hostent *gethostbyname( const char * name);// INT => DOMAIN
    struct hostent *gethostbyaddr( const char *addr, int len, int family);
  4. 字节转换

    由于大端对齐和小端对齐的原因,不同的计算机系统在进行网络通信时需要一个统一的格式,也就是Big Endian格式,称之为“网络字节顺序”(network byte order),相对的本地计算机使用的数据格式为“本机字节顺序”(host byte order)

    关于大小端对齐的问题,计算机专业的童鞋应该不会陌生,不清楚的可以看看这篇blog:
    http://blog.csdn.net/yangcs2009/article/details/39698997

    在发送到网络之前,必须先转化为网络字节顺序,系统提供了一些相应的函数,用于字节顺序的转换,这些函数命名有一定的规律:h代表字节顺序(host或理解为本机顺序);n代表网络顺序(network)

    u_long PASCAL FAR  hton1 ( IN u_long hostlong);
    //本地字节转化为网络字节顺序(长整数)
    u_short PASCAL FAR htons(IN u_short hostshort);
    //本地字节转化为网络字节顺序(短整数)
    u_long PASCAL FAR ntoh1 (IN u_long netlong);
    //网络字节转化为本地字节顺序(长整数)
    u_short PASCAL FAR ntohs ( IN u_short netshort);
    //网络字节转化为本地字节顺序(短整数)

    Q: 为什么在数据结构sockaddr_in中的sin_addr和sin_port要转换成网络字节顺序,而sin_family不需要呢?
    A: 答案是:sin_addr和sin_port分别封装在包的IP 和 UDP层,但是sin_family只是被本机使用来决定数据结构中包含什么类型的地址,没有被发送到网络上

  5. 基本Socket通信

    服务器端基本流程:[初始化监听Socket]->[接受新的客户端连接]->[收发数据]->[关闭连接]

    //!!注意:在Windows下必须包含头文件ws2tcpip.h ,否则会报错:socklen_t was not declared in this scope//!!注意:在Windows下执行socket相关操作前,必须初始化WSADATA!!!!
    WSADATA wsaData;
    int nRet;
    if((nRet = WSAStartup(MAKEWORD(2,2),&wsaData)) != 0){exit(0);
    }

    (一) 初始化监听Socket

    // (1) 初始化Socket
    SOCKET s;
    s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP) // 使用流Socket,基于TCP协议// (2)绑定Socket
    SOCKADDR_IN ServerAddr;
    ServerAddr.sin_family = AF_INET;
    ServerAddr.sin_port = htons(9000);
    ServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1");bind(s, (LPSOCKADDR)&ServerAddr, sizeof(ServerAddr));// (3)开始监听
    listen(s, SOMAXCONN);

    (二) 建立连接

    // (1) 检测Socket状态:函数select可以检测相应的Socket状态,从而决定是否需要建立新的连接
    fd_set readset;
    timeval timeout;
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    FD_ZERO(&readset);
    FD_SET(s, &readset);
    int ret = select(FD_SETSIZE, &readset, NULL, NULL, &timeout);
    if(ret>0 && FD_ISSET(s, &readset))
    {//新连接
    }// (2) 建立新的连接:如果客户端有新的连接请求介入,则建立一个新的连接
    // 注意:使用accept建立新的连接时,需要使用新的socket进行通信(temp)
    SOCKADDR_IN ServerAddr;
    int len = sizeof(ServerAddr);
    SOCKET temp;
    temp = accept(s, (SOCKADDR*) &ServerAddr, (socklen_t*)&len);
    if(temp == INVALID_SOCKET){//链接失败
    }

    (三) 收发数据

    // (1) 检测读入数据
    fd_set readset;
    timeval timeout;
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    FD_ZERO(&readset);
    FD_SET(s, &readset);
    int ret = select(FD_SETSIZE, &readset, NULL, NULL, &timeout);
    if(ret>0 && FD_ISSET(s, &readset))
    {//有新数据
    } // (2) 接收数据
    char buf[1024];
    in ret;
    ret = recv(s, buf, 1024, 0);// (3) 检测发送数据
    fd_set sendset;
    timeval timeout;
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    FD_ZERO(&sendset);
    FD_SET(s, &sendset);
    int ret = select(FD_SETSIZE, &sendset, NULL, NULL, &timeout);
    if(ret>0 && FD_ISSET(s, &sendset))
    {//可以发送新数据
    }  // (4) 发送数据
    char buf[1024];
    int ret;
    ret = send(s, buf, 1024, 0);

    (四) 关闭连接

    closesocket(s)

    客户端基本流程:[初始化监听Socket]->[建立连接]->[收发数据]->[关闭连接]

    (一) 初始化监听Socket

    // (1) 初始化Socket
    SOCKET s;
    s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP) // 使用流Socket,基于TCP协议// (2)绑定端口
    SOCKADDR_IN ServerAddr;
    ServerAddr.sin_family = AF_INET;
    ServerAddr.sin_addr_s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(0);
    bind(s, (LPSOCKADDR)&ServerAddr, sizeof(ServerAddr));
    // 客户端的IP地址和端口号都不需要固定,可以由系统分配,因此可以不适用bind函数绑定端口和协议// (3) 建立连接
    SOCKADDR_IN ServerAddr;
    ServerAddr.sin_family = AF_INET;
    ServerAddr.sin_port = htons(9000);
    ServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    connect(s, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));

Windows Socket编程基础相关推荐

  1. Windows黑客编程基础

    俗话说:"万事开头难",编程也不例外,初学者如何入门关键要有一份正确的理论作指 导,下面的这篇文章虽不能说是至理名言,但我相信通过作者细腻的分析.讲解和引导, 定能给初学者起到启蒙 ...

  2. Windows Socket编程笔记之最简单的小Demo

    Windows Socket编程的大致过程: 服务器端: ----过程-------------对应的API-------  0.初始化         |  WSAStartup()  1.创建So ...

  3. Windows驱动编程基础教程

    前言     本书非常适合熟悉Windows应用编程的读者转向驱动开发.所有的内容都从最基础的编程方法入手.介绍相关的内核API,然后举出示范的例子.这本书只有不到70页,是一本非常精简的小册子.所以 ...

  4. Windows驱动编程基础教程 (转)

     Windows驱动编程基础教程(转) 我经常在网上遇到心如火燎的提问者.他们碰到很多工作中的技术问题,是关于驱动开发的.其实绝大部分他们碰到的"巨大困难"是被老牛们看成初级得 ...

  5. 计算机网络原理 实验1 《Windows Socket编程》

    计算机网络原理 实验1 <Windows Socket编程> 一.实验目的 通过实验,熟悉并掌握计算机Windows 编程的基本知识,进一步加深对课堂所学基本内容的理解,掌握基本的Wind ...

  6. 楚狂人Windows驱动编程基础教程

    版权声明     本书是免费电子书.作者保留一切权利.但在保证本书完整性(包括版权声明.前言.正文内容.后记.以及作者的信息),并不增删.改变其中任何文字内容的前提下,欢迎任何读者以任何形式(包括各种 ...

  7. 计算机图形编程基础,Windows图形编程基础.ppt

    Windows图形编程基础 软件教研室 计算机图形学 第三章 Window图形编程基础 一.设备描述表DC及相关的MFC类 二.图形设备接口(GDI : Graphics device Interfa ...

  8. windows图形编程基础

    windows图形编程基础 2010年08月30日 图形设备接口(GDI,Graphics Device Interface)的主要目标之一是支持在输出设备(如视频显示器.打印机和绘图仪)上的与设备无 ...

  9. Java从零开始学四十五(Socket编程基础)

    一.网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可 ...

  10. 你得学会并且学得会的Socket编程基础知识

    这一篇文章,我将图文并茂地介绍Socket编程的基础知识,我相信,如果你按照步骤做完实验,一定可以对Socket编程有更好地理解. 本文源代码,可以通过这里下载 http://files.cnblog ...

最新文章

  1. 完全基于Transformer的目标检测器,ICLR匿名论文实现视觉、检测统一
  2. 如何下载指定版本的torchtext
  3. css3 transform rotate-2D旋转
  4. ofstream、ifstream、fstream
  5. python 计量_距离度量以及python实现(二)
  6. 文本文件的读写(字符流)
  7. python笔记之function函数
  8. vue中根据搜索内容跳转到页面指定位置
  9. 【Elasticsearch】es 提高 搜索速度
  10. linux计划任务一小时,linux,计划任务,每小时执行一次(共7篇).docx
  11. Infosys:印度信息技术巨头公司
  12. 设置背景图片自动适应屏幕
  13. pythonnumpy矩阵详解_基于Python Numpy的数组array和矩阵matrix详解_python
  14. 思维导图使用技巧:手把手教你怎么画思维导图 #CSDN博文精选# #系统化学习# #IT技术# #知识图谱#
  15. java jnlp_Java Web Start实践:动态生成JNLP
  16. 真_u3d程序员,基本脚本语法篇
  17. 十五数码难题 A*算法及深度优先算法实现
  18. 【华为OD机试真题 JAVA】找城市
  19. 华为面试题c/c++
  20. android程序包不存在,Android应用开发Android studio 错误: 程序包 不存在

热门文章

  1. MOSFET | 如何看懂MOSFET手册?①
  2. 中新赛克数据可视化_大道至简,OceanEye大数据可视化工具的高效之道
  3. Laravel渴求式加载
  4. Vue中点击复制文本功能
  5. linux更新字体库失败,wps for linux 字体库缺失问题的解决办法
  6. 逢看必会的三子棋小游戏:原来可以这么简单
  7. 【人月神话】浅谈人月神话0.2什么是“人月”,为什么是“神话”?
  8. CentOS7+华为交换机+Winserver2012配置KMS服务器开机自启和自动激活
  9. 吊打本地搜索神器everthing,最快 最强的电脑本地搜索神器!
  10. 《暗时间》读书笔记及读后感