Socket编程UDP\TCP

目录

Socket

UDP编程

socket函数

bind函数

sockaddr_in

TCP编程

socket函数

bind函数

listen函数

accept函数

connect函数

UDP服务端和客户端通信

多进程TCP服务端和客户端通信

多线程TCP服务端和客户端通信


Socket

socket是计算机网络编程中的一个重要概念和函数,主要用于实现网络通信。是一种通信机制,它允许不同计算机上的进程之间进行通信。

它广泛应用于各种网络应用程序,如 Web 服务器与浏览器之间的通信、文件传输协议(FTP)、即时通讯软件等。例如,当你在浏览器中访问一个网站时,浏览器会通过socket与 Web 服务器建立连接,然后通过这个连接获取网页的内容。

UDP编程

UDP是网络层协议IP之上的一个无连接、不可靠的传输层协议。

特点:

  1. 无连接:
  • UDP通信不需要在发送数据之前建立连接,也不需要在数据传输完成后关闭连接。这意味着发送方可以随时发送数据,而接收方也可以随时接收数据,无需进行连接的建立和维护。

  • 优点:减少了建立和维护连接的开销,使得数据传输速度更快,适合对实时性要求较高的场景,如网络视频会议、在线游戏等。

  • 缺点:由于没有连接的概念,无法保证数据是否能够成功到达接收方。

  1. 不可靠
  • UDP不保证数据的顺序、完整性和可靠性。它不会对丢失的数据包进行重传,也不会对乱序的数据包进行排序。

  • 优点:减少了协议开销,提高了数据传输效率,适合对数据完整性要求不高的场景。

  • 缺点:数据传输的可靠性较差,可能会出现数据丢失、重复或乱序的情况。

  1. 面向数据报
  • UDP以数据报的形式进行数据传输,每个数据报是一个独立的单元,包含完整的数据信息和目标地址信息。发送方将数据封装成数据报后发送出去,接收方收到数据报后进行解封装并提取数据。

  • 优点:数据报的格式简单,易于实现和处理。

  • 缺点:每个数据报的大小有限制,通常不能超过64KB,否则可能会被网络分割成多个数据报,增加了数据丢失和乱序的风险。

  1. 轻量级
  • UDP的协议头只有8字节,相比TCP的20字节(不包括选项)协议头,UDP的协议头更小,数据传输效率更高。

  • 优点:适合传输小块数据,如DNS查询、SNMP等。

  • 缺点:对于大块数据的传输,UDP的效率优势可能会被数据丢失和乱序的问题所抵消。

socket函数

socket函数是网络编程中的一个基础函数,用于创建一个通信端点

cpp 复制代码
int socket(int domain, int type, int protocol);

domain(协议族):指定通信所使用的协议族。

  • AF_INET:用于IPv4地址
  • AF_INET6:用于IPv6地址
  • AF_UNIX或AF_LOCAL:用于本地进程间通信(IPC),数据不会经过网络层。

type(套接字类型):指定套接字的类型,决定了套接字的通信语义。

  • SOCK_STREAM:流式套接字,使用 TCP 协议。它提供可靠的、面向连接的字节流服务。
  • SOCK_DGRAM:数据报套接字,使用 UDP 协议。它提供无连接的、不可靠的数据报服务。
  • SOCK_RAW:原始套接字,允许直接访问低层协议,通常用于开发网络工具或协议分析。
  • SOCK_SEQPACKET:有序分组套接字,提供可靠、面向连接的分组服务,类似于SOCK_STREAM但以分组形式传输数据。

protocol(协议):指定具体的协议,通常情况下,这个参数可以设置为 0,表示使用默认协议(TCP、UDP)

在大多数情况下,协议可以通过套接字类型自动推断,因此设置为 0 是最常用的方式。

返回值:

调用成功,返回非负整数,称为**套接字文件描述符。**这个描述符类似于文件描述符,用于后续的网络操作,入如绑定地址、监听连接、发送和接收数据等。

调用失败,返回-1

bind函数

函数是网络编程中的一个基本函数,用于将一个套接字绑定到一个特定的网络接口和端口上。这样做是为了指定套接字应该在哪个地址和端口上监听传入的连接或数据。

cpp 复制代码
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:套接字文件描述符,由socket函数创建。

addr:指向sockaddr结构的指针,该结构包含了要绑定的地址信息。

addrlen:sockaddr结构的长度。

返回值:

成功返回0

失败返回-1

sockaddr_in

在给传递第二个参数sockaddr*类型时,我们往往会使用sockaddr_in类型的变量传递进来

sockaddr和sockaddr_in是继承关系,所以可以传递参数

cpp 复制代码
struct sockaddr {
    sa_family_t sa_family;  // 地址族,如AF_INET, AF_INET6等
    char        sa_data[14];  // 地址数据,长度足以容纳任何结构
};

struct sockaddr_in {
    sa_family_t sin_family;   // 地址族,通常是AF_INET
    in_port_t   sin_port;     // 端口号
    struct in_addr sin_addr;  // IPv4地址
};

为什么要使用sockaddr_in?

  1. 类型安全:使用struct sockaddr_in可以确保你传递给bind、connect等函数的地址信息是正确类型的
  2. 字段访问:提供了直接访问IPv4地址和端口号的字段,这使得设置和解析地址信息更加方便和直观。
  3. 通用接口:虽然bind、connect等函数的参数类型是struct sockaddr *,但通过传递特定类型的地址结构(如sockaddr_in),你可以利用这些函数的通用接口来处理不同类型的地址。

TCP编程

TCP编程指的是使用传输控制协议进行网络通信的编程。TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。

特点:

1.面向连接:

  • 在数据传输开始之前,必须建立一个连接。这通过三次握手过程完成,以确保双方都能发送和接收数据。

  • 连接建立后,数据传输开始;传输结束后,通过四次挥手过程断开连接。

2.可靠性:

  • TCP通过确认应答、超时重传以及数据排序等机制保证数据的可靠传输。

  • 它还使用校验和函数来检验数据的正确性,确保数据在传输过程中未被篡改。

3.流量控制:

  • TCP使用滑动窗口机制来控制发送方的数据流量,防止接收方被大量数据淹没。

4.拥塞控制:

  • TCP动态调整其发送速率以适应网络拥塞情况,避免网络过载。常见的拥塞控制算法包括慢启动、拥塞避免、快速重传和快速恢复。

5.面向字节流:

  • TCP提供面向流的接口,即数据被视为无结构的字节流,不保留边界。这使得TCP可以灵活处理各种类型的数据。

6.全双工通信:

  • TCP连接是全双工的,意味着数据可以在两个方向上同时传输。

7.有序性:

  • TCP保证数据的有序传输,即接收方接收到的数据顺序与发送方发送的顺序相同。

8.错误处理:

  • TCP能够处理网络传输中的错误,如丢包、重复包等,确保数据的正确传输。

socket函数

同UDP编程

bind函数

同UDP编程

listen函数

listen函数是网络编程中用于将一个套接字(socket)置于监听状态的函数,这样它就可以接受来自客户端的连接请求。当服务器调用listen函数后,它就开始等待客户端的连接请求(通常称为"被动套接字")。

cpp 复制代码
#include <sys/socket.h>

int listen(int sockfd, int backlog);

sockfd:套接字文件描述符,通常由socket函数创建,并已经通过bind函数绑定到一个地址和端口。

backlog:指定在套接字接受连接之前,操作系统可以排队的最大连接请求数。这个参数定义了accpet调用可以阻塞的最大连接数。

返回值:

成功返回0

失败返回-1

accept函数

accept函数在网络编程中用于接受一个已经建立的连接请求。当服务器端的套接字在调用listen函数后进入监听状态时,它会等待客户端的连接请求。一旦有客户端连接请求到达,服务器就可以使用accept函数接受这个连接,从而创建一个新的套接字用于与该客户端进行通信。

cpp 复制代码
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd:服务器监听的套接字文件描述符。

addr:用于存储客户端地址信息的缓冲区。如果需要获取客户端的地址信息,可以提供这个参数;如果不需要,可以设为NULL

addrlen:指向socklen_t变量的指针,该变量在调用前应该初始化为addr缓冲区的长度。调用成功后,accept函数会更新这个变量的值为实际存储的地址长度。如果addr参数为NULL,则此参数也被忽略。

返回值:

成功时,accept函数返回一个新的套接字文件描述符,用于与客户端进行通信。

失败则返回-1

connect函数

connect函数在网络编程中用于建立一个从客户端套接字到服务器套接字的连接。它是一个阻塞调用,意味着在连接建立之前,该函数会一直等待。一旦连接建立,客户端就可以通过这个套接字发送和接收数据。

cpp 复制代码
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:客户端套接字文件描述符

addr:指向sockaddr结构的指针,该结构包含了要连接的服务器的地址信息。

addrlen:sockaddr结构的长度。

返回值:

成功返回0

失败返回-1

UDP服务端和客户端通信

服务端:

cpp 复制代码
#include <iostream>
#include <sys/socket.h> // socket
#include <netinet/in.h> // local
#include <arpa/inet.h> // htons inet_addr
#include <cstring> // memset 
#include <unistd.h> // close

// ./TestUDPServer port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "Please Use:./TestUDPServer port" << std::endl;
        return 1;
    }

    uint16_t serverport = std::stoi(argv[1]);

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cout << "socket fail" << std::endl;
        exit(1);
    }

    sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = INADDR_ANY; // 绑定到本机的任意地址

    int n = bind(sockfd, (struct sockaddr*)&server, sizeof(server));
    if (n < 0)
    {
        std::cout << "bind fail" << std::endl;
        close(sockfd);
        return 1;
    }

    std::cout << "UDP Server is running..." << std::endl;

    while (true)
    {
        char buffer[1024];
        sockaddr_in client;
        socklen_t len = sizeof(client); // 需要初始化
        memset(&client, 0, sizeof(client));
        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&client, &len);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client say# " << buffer <<std::endl; 
            ssize_t m = sendto(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&client, len);
            if (m < 0) 
            {
                std::cout << "sendto fail" << std::endl;
                break;
            }
        }
        else
        {
            std::cout << "recvfrom fail" << std::endl;
            break;
        }
    }

    close(sockfd);

    return 0;
}

客户端:

cpp 复制代码
#include <iostream>
#include <sys/socket.h> // socket
#include <netinet/in.h> // local
#include <arpa/inet.h> // htons inet_addr
#include <cstring> // memset 
#include <unistd.h> // close

// ./TestUDPClient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cout << "Please Use:./TestUDPClient serverip serverport" << std::endl;
        return 1;
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cout << "socket fail" << std::endl;
        exit(1);
    }

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());

    while (true)
    {
        std::string inbuffer;
        std::cout << "Please Enter# ";
        std::getline(std::cin, inbuffer);

        ssize_t n = sendto(sockfd, inbuffer.c_str(), inbuffer.size(), 0, (struct sockaddr*)&server, sizeof(server));
        if (n > 0)
        {
            char buffer[1024];
            struct sockaddr_in tmp;
            socklen_t len = sizeof(tmp); // 需要初始化
            ssize_t m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&tmp, &len);
            if (m > 0)
            {
                buffer[m] = 0;
                std::cout << "Server echo# " << buffer << std::endl;
            }
            else
            {
                std::cout << "recvfrom fail" << std::endl;
                break;
            }
        }
        else
        {
            std::cout << "sendto fail" << std::endl;
            break;
        }
    }

    close(sockfd);

    return 0;
}

多进程TCP服务端和客户端通信

服务端:

cpp 复制代码
#include <iostream>
#include <sys/socket.h> // socket
#include <netinet/in.h> // local
#include <arpa/inet.h> // htons inet_addr
#include <cstring> // memset 
#include <unistd.h> // close fork

void handle_client(int socket)
{
    char buffer[1024];
    while (true)
    {
        int n = read(socket, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << getpid() << "client Say# " << buffer << std::endl;
            int m = write(socket, buffer, n);
            if (m < 0)
            {
                std::cout << "write fail" << std::endl;
                return;
            }
        }
        else
        {
            std::cout << "read fail" << std::endl;
            return;
        }
    }
    
}

int main(int argc, char *argv[])
{
    if (argc != 2) 
    {
        std::cout << "Usage: ./TcpServer port" << std::endl;
        return 1;
    }

    uint16_t serverport = std::stoi(argv[1]);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cout << "socket fail" << std::endl;
        return 1;
    }

    sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = INADDR_ANY;

    int n = bind(sockfd, (struct sockaddr*)&server, sizeof(server));
    if (n < 0)
    {
        std::cout << "bind fail" << std::endl;
        close(sockfd);
        return 1;
    }

    n = listen(sockfd, 5);
    if (n < 0)
    {
        std::cout << "listen fail" << std::endl;
        close(sockfd);
        return 1;
    }

    std::cout << "TCP Server is running on " << serverport << "..." << std::endl;

    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    while (true)
    {
        int clientsocket = accept(sockfd, (struct sockaddr*)&client, &len);
        if (clientsocket < 0)
        {
            std::cout << "accept fail" << std::endl;
            close(sockfd);
            return 1;
        }
        pid_t pid = fork();
        if (pid == 0)
        {
            // 子进程
            std::cout << "client connect, pid: " << getpid() << std::endl;
            close(sockfd);
            handle_client(clientsocket);
            exit(0);
        }
        else if (pid > 0)
        {
            // 父进程
            close(clientsocket);
        }
        else
        {
            perror("fork fail\n");
            break;
        }
    }
    
    close(sockfd);

    return 0;
}

客户端同上

多线程TCP服务端和客户端通信

服务端:

cpp 复制代码
#include <iostream>
#include <sys/socket.h> // socket
#include <netinet/in.h> // local
#include <arpa/inet.h> // htons inet_addr
#include <cstring> // memset 
#include <unistd.h> // close fork
#include <thread>

void handle_client(int socket)
{
    char buffer[1024];
    std::thread::id thread_id = std::this_thread::get_id();
    while (true)
    {
        int n = read(socket, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << thread_id << "client Say# " << buffer << std::endl;
            int m = write(socket, buffer, n);
            if (m < 0)
            {
                std::cout << "write fail" << std::endl;
                return;
            }
        }
        else
        {
            std::cout << "read fail" << std::endl;
            return;
        }
    }
    
}

int main(int argc, char *argv[])
{
    if (argc != 2) 
    {
        std::cout << "Usage: ./TcpServer port" << std::endl;
        return 1;
    }

    uint16_t serverport = std::stoi(argv[1]);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cout << "socket fail" << std::endl;
        return 1;
    }

    sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = INADDR_ANY;

    int n = bind(sockfd, (struct sockaddr*)&server, sizeof(server));
    if (n < 0)
    {
        std::cout << "bind fail" << std::endl;
        close(sockfd);
        return 1;
    }

    n = listen(sockfd, 5);
    if (n < 0)
    {
        std::cout << "listen fail" << std::endl;
        close(sockfd);
        return 1;
    }

    std::cout << "TCP Server is running on " << serverport << "..." << std::endl;

    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    while (true)
    {
        int clientsocket = accept(sockfd, (struct sockaddr*)&client, &len);
        if (clientsocket < 0)
        {
            std::cout << "accept fail" << std::endl;
            close(sockfd);
            return 1;
        }
        
        std::thread(handle_client, clientsocket).detach();
    }
    
    close(sockfd);

    return 0;
}

客户端同上


相关推荐
山登绝顶我为峰 3(^v^)32 小时前
如何录制带备注的演示文稿(LaTex Beamer + Pympress)
c++·线性代数·算法·计算机·密码学·音视频·latex
游戏开发爱好者84 小时前
iOS重构期调试实战:架构升级中的性能与数据保障策略
websocket·网络协议·tcp/ip·http·网络安全·https·udp
十五年专注C++开发5 小时前
CMake基础:条件判断详解
c++·跨平台·cmake·自动化编译
天水幼麟5 小时前
动手学深度学习-学习笔记(总)
笔记·深度学习·学习
QuantumStack7 小时前
【C++ 真题】P1104 生日
开发语言·c++·算法
天若有情6737 小时前
01_软件卓越之道:功能性与需求满足
c++·软件工程·软件
whoarethenext7 小时前
使用 C++/OpenCV 和 MFCC 构建双重认证智能门禁系统
开发语言·c++·opencv·mfcc
天水幼麟7 小时前
动手学深度学习-学习笔记【二】(基础知识)
笔记·深度学习·学习
(:满天星:)8 小时前
第31篇:块设备与字符设备管理深度解析(基于OpenEuler 24.03)
linux·运维·服务器·网络·centos
沧海一笑-dj8 小时前
【51单片机】51单片机学习笔记-课程简介
笔记·学习·51单片机·江科大·江科大学习笔记·江科大单片机·江科大51单片机