Linux —C 编程 搭载VS2022进行网络编程,实现客户端服务器通讯

Linux网络编程

基于流套接字的编程流程

流程图:

在这里插入图片描述

流程描述:

服务端: 建立socket,申明自身的port和IP,并绑定到socket,使用listen监听,然后不断用accept去查看是否有连接。如果有,捕获socket,并通过recv获取消息的内容,通信完成后调用closeSocket关闭这个对应accept到的socket。如果不需要等待任何客户端连接,那么用closeSocket直接关闭自身的socket。


客户端: 建立socket,通过端口号和地址确定目标服务器,使用Connect连接到服务器,send发送消息,等待处理,通信完成后调用closeSocket关闭socket。

在任何类型的通信开始之前,网络应用程序都必须创建套接字。


套接字最初是为同一主机上的应用程序所创建,使得主机上运行的一个程序(又名一个进程)与另一个运行的程序进行通信。这就是所谓的进程间通信(Inter Process Communication,IPC)

有两种类型的套接字:基于文件的和面向网络的

基于文件的
家族名:AF_UNIX
(又名AF_LOCAL,在POSIX1.g标准中指定),它代表地址家族(addressfamily):UNIX。其他比较旧的系统可能会将地址家族表示成域(domain)或协议家族(protocolfamily),并使用其缩写PF而非AF。类似地,AF_LOCAL(在2000~2001年标准化)将代替AF_UNIX

面向网络的

家族名:AF_INET
或者地址家族:因特网。另一个地址家族AF_INET6用于第6版因特网协议(IPv6)寻址。此外,还有其他的地址家族,这些要么是专业的、过时的、很少使用的,要么是仍未实现的。在所有的地址家族之中,目前AF_INET是使用得最广泛的

套接字地址:主机-端口对:

socket顾名思义就是套接字的意思,用于描述地址和端口,是一个通信链的句柄。应用程序通过socket向网络发出请求或者回应。

做个比喻,套接字就像一个电话插孔,主机名和端口号就像区号和号码。当程序之间需要通信时,需要知道对端的主机名(IP)和端口号。有效的端口号范围为0~65535(小于1024的端口号预留给了系统)

socket编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW),前两者较常用。基于TCP的socket编程是流式套接字。

面向连接的套接字与无连接的套接字:

面向连接的套接字

  • TCP套接字的名字SOCK_STREAM。
  • 特点:可靠,开销大。
    在进行通信之前必须先建立一个连接,该连接的通信提供序列化的、可靠的和不重复的数据交付,而没有记录边界。这种类型的通信也称为虚拟电路或流套接字
    实现这种连接类型的主要协议是传输控制协议(缩写 TCP)
    为了创建 TCP套接字,必须使用 SOCK_STREAM 作为套接字类型。

无连接的套接字

  • UDP套接字的名字SOCK_DGRAM
  • 特点:不可靠(局网内还是比较可靠的),开销小。
    与虚拟电路形成鲜明对比的是数据报类型的套接字,它是一种无连接的套接字。
    在通信开始之前并不需要建立连接。此时,在数据传输过程中并无法保证它的顺序性、可靠性或重复性。数据报确实保存了记录边界,这就意味着消息是以整体发送的,而并非首先分成多个片段。
    实现这种连接类型的主要协议是用户数据报协议(缩写 UDP)。为
    了创建UDP套接字,必须使用SOCK_DGRAM作为套接字类型
    UDP套接字的SOCK_DGRAM名字来自于单词“datagram”(数据报)。

函数描述:

socket(AF_INET, SOCK_STREAM, 0)

socket()函数类似于 open()函数,它用于创建一个网络通信端点(打开一个网络通信),如果成功则返回一个 网络文件描述符,通常把这个文件描述符称为 socket 描述符(socket descriptor)

函数定义: 在域中创建一个type类型的套接字,使用协议,如果PROTOCOL为0,则自动选择一个返回新套接字的文件描述符,返回 -1表示错误。

bind(server_socket, (struct sockaddr*) & server_addr, sizeof(server_addr))

bind() 函数用于将一个 IP 地址或端口号与一个套接字进行绑定(将套接字与地址进行关联),(注意这里说的地址包括 IP 地址和端口号),因为对于客户端来说,它与服务器进行通信,首先需要知道服务器的 IP 地址以及对应的端口号,所以通常服务器的 IP 地址以及端口号都是众所周知的

函数定义: 为套接字FD提供本地地址ADDR(长度为LEN字节)

struct sockaddr_in server_addr

结构体定义: 描述Internet套接字地址的结构。

一般我们在使用的时候都会使用 struct sockaddr_in 结构体,sockaddr_in 和 sockaddr 是并列的结构(占用的空间是一样的),指向 sockaddr_in 的结构体的指针也可以指向 sockadd 的结构体,并代替它.

# 示例代码  struct sockaddr 结构体
struct sockaddr {
     
     
sa_family_t sa_family;
char sa_data[14];
}
// 第二个参数sa_data :是一个 char 类型数组,一共 14 个字节,在这 14 个字节中就包括了 IP 地址、端口号等信息

listen(server_socket, 10)

listen()函数只能在服务器进程中使用,让服务器进程进入监听状态,等待客户端的连接请求,listen()函数在一般在 bind()函数之后调用,在 accept()函数之前调用

函数定义: 准备接受套接字 FD 上的连接。在拒绝进一步的请求之前,N个连接请求将被排队。成功时返回0,错误时返回-1。

accept(server_socket, NULL, NULL)

服务器调用 listen()函数之后,就会进入到监听状态,等待客户端的连接请求,使用 accept()函数获取客户端的连接请求并建立连接。

函数定义: 等待套接字FD(文件描述符file descriptor)上的连接。当连接到达时,打开一个新的套接字与它通信,设置ADDR(即ADDR_LEN字节长度)为连接对等体的地址,ADDR_LEN为地址的实际长度,并返回新的套接字描述符, -1表示错误。

read(accept_socket, buffer, sizeof(buffer))

函数定义: 从FD中读取N_BYTES到BUF。返回读取的数字,-1表示错误,0表示EOF。该函数是一个取消点,因此没有使用THROW标记。

write(accept_socket, buffer, res)

将N字节的BUF写入FD。返回写入的数字,返回-1表示错误。该函数是一个取消点,因此没有使用THROW标记。

connect()

该函数用于客户端应用程序中,客户端调用 connect()函数将套接字 sockfd 与远程服务器进行连接。

#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
int main()
{
    
    
    char buffer[50] = {
    
    0};
    int res = 0;
    int server_socket; //这个是socket网络描述符,也叫套接字描述符。
    int accept_socket;
    // 第一步:创建套接字描述符       --“买一部手机”
    printf("开始创建tcp服务器\n");
    server_socket = socket(AF_INET, SOCK_STREAM, 0);//我们想要向网络发送数据都使用
                                                    //server_socket这个套接字描述符
    
    if (server_socket < 0)
    {
    
    
        perror("socke create failed:");
        return 0;
    }

    // 第二步:要告诉这个服务器 我的ip地址和端口号 我们要有一个保存ip地址和端口的变量
                //          --“买电话卡”
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET; // ipv4
    server_addr.sin_addr.s_addr = INADDR_ANY;   //inaddr_any 告诉系统自动绑定网卡ip地址
    server_addr.sin_port = htons(6668);    //网络地址转换,把主机字节顺序转换成网络字节顺序

    // 第三步:把我们设定好的ip地址和端口号绑定到我们的server_socket描述符上。 --“把电话卡插入手机上”

    if (bind(server_socket, (struct sockaddr*) & server_addr, sizeof(server_addr)) < 0)
    {
    
    
        perror("server bind error:");
        return 0;
    }

    // 第四步:我们调用listen 开始监听程序    --“把电话放在身上,电话铃响听到声音”
    if (listen(server_socket, 10) < 0)
    {
    
    
        perror("server listen error:");
        return 0;
    }

    // 第五步:以上4个步骤都ok后,我们就可以等待客户端连接过来了。
    
    // accept 函数有一个特点,当我们程序调用这个函数的时候,如果没有客户端连接到我们的服务器,
    // 那么这个函数将堵塞(程序停下不走了),直到有客户端连接到服务器,这个函数将解开,并
    // 并且返回一个新的套接字描述符。那么后期和客户端的通讯都交给这个新的套接字描述符来负责。
    printf("TCP服务器准备完成,等待客户端连接!\n");
    accept_socket = accept(server_socket, NULL, NULL);     //如果有客户端连接,返回一个新的套接字变量。
    printf("有客户端连接到服务器!\n");
    while (1)
    {
    
    
        //read函数就是接受客户端发来的数据,返回实际从客户端那边收到的字节数。
        //buffer: 收到客户端数据后把数据存放的地址  sizeof(buffer) 就是希望读取的字节数
        res = read(accept_socket, buffer, sizeof(buffer));
        printf("client read %s\n", buffer);
        write(accept_socket, buffer, res);
        memset(buffer, 0, sizeof(buffer));

    }
    printf("%s 向你问好!\n", "LINUX_C_SAMPLE");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_63669388/article/details/132707158