• Windows 环境下的 Socket 编程 3 - 基于 TCP 的服务器/客户端


    基于 TCP 的服务器端/客户端

    绝大多数 TCP 服务器端都按照如下顺序调用:
    在这里插入图片描述
    在 Windows 环境下,代码表示为:

    	WSADATA wsaData;
        SOCKET hServSock, hClntSock;
        SOCKADDR_IN servAddr, clntAddr;
        int szClntAddr;
    	
    	/*Windows 环境 Socket 编程必须*/
        if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
            //错误处理
    	
    	/*创建套接字,使用 IPv4 协议族、面向连接的 TCP 数据传输方式,对于 IPv4 协议族,第三个参数总是为 0 */
        hServSock = socket(PF_INET, SOCK_STREAM, 0);
        if(hServSock == INVALID_SOCKET)
            //错误处理
        
        /*设置协议族的地址信息*/
        memset(&servAddr, 0, sizeof(servAddr));
        servAddr.sin_family = AF_INET;					//地址族设置为 IPv4
        servAddr.sin_addr.s_addr = htonl(INADDR_ANY);	//设置 IP 地址
        servAddr.sin_port = htons(atoi(argv[1]));		//设置端口号
    	
    	/*分配套接字地址*/
        if(bind(hServSock, (SOCKADDR *)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
            //错误处理
    	
    	/*等待连接请求*/
        if(listen(hServSock, 5) == SOCKET_ERROR)
            //错误处理
        
        /*受理客户端连接请求*/
        szClntAddr = sizeof(clntAddr);
        hClntSock = accept(hServSock, (SOCKADDR *)&clntAddr, &szClntAddr);
        if(hClntSock == INVALID_SOCKET)
            //错误处理
    	
    	/*接收数据*/
        strLen = recv(hClntSock, message, BUF_SIZE,0);
    	
    	/*发送数据*/
        send(hClntSock, (const char *)&calculation_result, sizeof(int), 0);
        
        closesocket(hClntSock);
        closesocket(hServSock);
        WSACleanup();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    绝大多数 TCP 客户端都按照如下顺序调用:
    在这里插入图片描述
    在 Windows 环境下,代码表示为:

        WSADATA wsaData;
        SOCKET hClntSock;
        SOCKADDR_IN servAddr;
    	
    	/*Windows 环境 Socket 编程必须*/
        if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
            //错误处理
    	
    	/*创建套接字,使用 IPv4 协议族、面向连接的 TCP 数据传输方式,对于 IPv4 协议族,第三个参数总是为 0 */
        hClntSock = socket(PF_INET, SOCK_STREAM, 0);
        if(hClntSock == INVALID_SOCKET)
            //错误处理
        
        /*设置协议族的地址信息*/
        memset(&servAddr, 0, sizeof(servAddr));
        servAddr.sin_family = AF_INET;
        servAddr.sin_addr.s_addr = inet_addr(argv[1]);
        servAddr.sin_port = htons(atoi(argv[2]));
    	
    	/*连接到服务器*/
        if(connect(hClntSock, (SOCKADDR *)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
            //错误处理
    	
    	/*发送数据*/
        send(hClntSock, message, strLen, 0);
        
        /*接收数据*/
        recv(hClntSock, &result, sizeof(int), 0);
        
        closesocket(hClntSock);
        WSACleanup();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    基于 TCP 的服务器端-客户端函数调用关系
    在这里插入图片描述
    客户端只能等到服务器端调用 listen 函数后才能调用 connect 函数。

    TCP 细节

    TCP 套接字的数据收发无边界。服务器端即使调用 1 次 send 函数传输 40 字节的数据,客户端有可能通过 4 次 recv 函数调用每次读取 10 字节。也就是说,客户端可以缓慢的分批接收。
    TCP 套接字的 I/O 是有缓冲的。send 函数调用后,数据将移至输出缓冲区;在适当的时候(不管是分别传送还是一次性传送)传向对方的输入缓冲区。 recv 函数调用后,从输入缓冲区读取数据。

    • I/O 缓冲区在每个 TCP 套接字中单独存在。
    • I/O 缓冲区在创建套接字时(调用 socket 函数)自动生成。
    • 即使关闭套接字也会继续传递输出缓冲中遗留的数据
    • 关闭套接字将丢失输入缓冲区中的数据
      TCP 不会因为缓冲溢出而丢失数据。这是因为 TCP 会控制数据流(滑动窗口)。

    断开连接

    TCP 中的断开连接过程比建立连接过程更重要,因为连接过程中一般不会出现大的变数,但断开过程有可能发生预想不到的情况。
    使用 close 或者 closesocket 函数意味着完全断开连接。完全断开连接不仅指无法传输数据,而且也不能接收数据。

    还有一种断开连接的方法是只断开一部分连接,成为半关闭。半关闭是指可以传输数据但无法接收,或者可以接收数据但无法传输。
    每一个套接字建立后,都会存在 2 个流:
    在这里插入图片描述
    建立套接字的两台主机,都会拥有单独的输入流和输出流。使用 close 或者 closesocket 函数将同时断开这 2 个流,而半关闭只关闭其中 1 个流。

    1. shutdown 函数:关闭其中 1 个流
    // 成功返回 0,失败返回 -1
    int shutdown(SOCKET s,int how);
    
    • 1
    • 2

    s:套接字
    how:断开方式

    • SD_RECEIVE:断开输入流,套接字无法接收数据
    • SD_SEND:断开输出流,套接字无法传输数据
    • SD_BOTH:同时断开 I/O 流
  • 相关阅读:
    Spring Boot+RabbitMQ 通过fanout模式实现消息接收(支持消费者多实例部署)
    redis过期key的清理/删除策略
    java毕业设计基于javaweb+mysql数据库实现的校园迎新(新生报道)网站含论文+开题报告
    随笔2022.12.6
    Redis命令行使用Lua脚本
    网络编程基础
    Selector的使用
    SpringCloud中服务熔断组件Hystrix和网关组件Gateway的使用
    基于SSM的校园二手交易网站设计与实现
    图像的频域--学习笔记
  • 原文地址:https://blog.csdn.net/zhzht19861011/article/details/126570089