• Linux 系统编程 学习:8-基于socket的网络编程3:基于 TCP 的通信


    知识

    TCP(Transmission Control Protoco 传输控制协议)。

    TCP是一种面向广域网的通信协议,目的是在跨越多个网络通信时,为两个通信端点之间提供一条具有下列特点的通信方式:

    • 基于流的方式;

    • 面向连接;

    • 可靠通信方式;

    • 在网络状况不佳的时候尽量降低系统由于重传带来的带宽开销;

    • 通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点。

    为满足TCP协议的这些特点,TCP协议做了如下的规定:

    • 数据分片:在发送端对用户数据进行分片,在接收端进行重组,由TCP确定分片的大小并控制分片和重组;
    • 到达确认:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认;
    • 超时重发:发送方在发送分片时启动超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片;
    • 滑动窗口:TCP连接每一方的接收缓冲空间大小都固定,接收端只允许另一端发送接收端缓冲区所能接纳的数据,TCP在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出;
    • 失序处理:作为IP数据报来传输的TCP分片到达时可能会失序,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层;
    • 重复处理:作为IP数据报来传输的TCP分片会发生重复,TCP的接收端必须丢弃重复的数据;
    • 数据校验:TCP将保持它首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到分片的检验和有差错,TCP将丢弃这个分片,并不确认收到此报文段导致对端超时并重发。
    ServerClient双方都创建socket对象socketsocket服务器一般绑定端口号bind服务器监听是否有连接请求listen客户端请求链接connectaccept收发消息send/recvsend/recv关闭连接closecloseServerClient

    有关函数介绍

    根据流程图,我们知道,在UDP通信中,使用到了这些函数:socket()bind()sendto()recvfrom()

    上面的函数我们在《基于UDP 的通信》 中已经讲过,这里不再重复了。

    在TCP中,多了这几个函数:listen()connect()accept()

    服务器调用listen 监听 客户端的 connectlisten成功时,服务器使用由accept获取到的新的套接字进行通信。

    当客户端调用connect函数时,将引发三次握手过程:客户端首先发送SYN请求分组,此时服务端会将请求放入SYN队列,同时向客户端发送ACK确认报文,然后客户端向服务端再次发送ACK报文。服务端收到ACK确认报文后,将SYN里的连接请求移入ACCEPT队列。此时三次握手结束,即TCP连接成功建立。然后内核通知用户空间的阻塞的服务进程,服务进程调用accept仅仅是从ACCEPT队列里取出一个连接而已。也就是说客户端调用connect连接服务器,与服务器调用accept“接受”连接是两个独立的过程。

    参考:《服务端不调用accept,客户端connect能否成功?》

    listen

    c
    #include<sys/types.h>          /* See NOTES */
    #include<sys/socket.h>
    
    intlisten(int sockfd, int backlog);
    

    描述: 将尚未建立连接的socket转换为被动socket,并监听发给这个被动socket的connect请求。

    参数解析:

    sockfd:由socket函数成功返回的值

    backlog :内核应该为相应套接口排队的最大连接个数(不是用来限制socket的最大连接数),一般为以下两个队列的大小之和,即未完成三次握手队列 + 已经完成三次握手队列。即:TCP模块允许的已完成三次握手过程(TCP模块完成)但还没来得及被应用程序accept的最大链接数。

    内核为任何一个给定的监听套接口维护两个队列:

    1、未完成连接队列(incomplete connection queue),每个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程。这些套接口处于SYN_RCVD状态。

    2、已完成连接队列(completed connection queue),每个已完成TCP三次握手过程的客户对应其中一项。这些套接口处于ESTABLISHED状态。

    当来自客户的SYN到达时,TCP在 未完成连接队列 中创建一个新项,然后响应以三次握手的第二个分节:服务器的SYN响应,其中稍带对客户SYN的ACK(即SYN+ACK)。这一项一直保留在未完成连接队列中,直到三路握手的第三个分节(客户对服务器SYN的ACK)到达或者该项超时为止(曾经源自Berkeley的实现为这些未完成连接的项设置的超时值为75秒)。如果三路握手正常完成,该项就从未完成连接队列移到已完成连接队列的队尾。当进程调用accept时,已完成连接队列中的队头项将返回给进程,或者如果该队列为空,那么进程将被投入睡眠,直到TCP在该队列中放入一项才唤醒它。

    返回值: 成功返回0,失败返回-1,置errno:

    • EADDRINUSE:另一个套接字已在同一端口上侦听。
    • EADDRINUSE:(Internet域套接字)sockfd引用的套接字以前没有绑定到地址,在尝试将其绑定到临时端口时,确定临时端口范围中的所有端口号当前都在使用中。
    • EBADF:参数sockfd不是有效的描述符。
    • ENOTSOCK:文件描述符sockfd没有引用套接字。
    • EOPNOTSUPP:套接字的类型不支持listen()操作。

    主动socket和被动socket

    一般来说,使用socket函数创建的socket默认是主动socket,这意味着一个主动的socket可以调用connect跟一个被动socket建立一个连接,对主动socket来说,这叫主动打开。

    被动socket是一个通过调用listen函数监听要发起连接的socket,当被动socket接受一个连接通常称为被动打开。

    在大多数网络程序中,服务端会作为被动socket被动接受连接,而客户端会作为主动socket主动发起连接。

    服务端通过socket函数创建的socket是主动socket,而listen函数就是把这个还未接受连接的主动socket转换为被动socket,因为服务端只需要被动接受客户端的连接请求。

    Linux系统设置未连接队列最大数限制

    linux系统tcp/ip协议栈有个选项可以设置未连接队列大小限制tcp_max_syn_backlog

    可以通过命令:cat /proc/sys/net/ipv4/tcp_max_syn_backlog 查看

    Linux 系统中提供somaxconn这个参数,它定义了系统中每一个端口最大的监听队列的长度,这是个全局的参数,默认值为128

    可以通过命令: cat /proc/sys/net/core/somaxconn 查看

    connect

    c
    #include<sys/types.h>          /* See NOTES */
    #include<sys/socket.h>
    
    intconnect(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);
    

    描述: 连接一个被动socket

    参数解析:

    sockfd:主动socket

    addr:目的地址

    addrlen:地址属性的长度(addr的大小)

    返回值:成功返回0,失败返回-1,置errno:

    • EAFNOSUPPORT:传递的地址在其sau family字段中没有正确的地址系列。
    • EAGAIN :路由缓存中的条目不足。
    • EALREADY:套接字未阻塞,上一次连接尝试尚未完成。
      EBADF:文件描述符不是描述符表中的有效索引。
    • ECONNREFUSED:没有人监听远程地址。
    • EFAULT :套接字结构地址在用户的地址空间之外。
    • EINPROGRESS:套接字未阻塞,无法立即完成连接。可以通过选择要写入的套接字来选择(2)或轮询(2)以完成。
    • EINTR :系统调用被捕获的信号中断。
    • EISCONN:套接字已连接。
    • ENETUNREACH:无法访问网络。
    • ENOTSOCK:sockfd不是套接字。
    • EPROTOTYPE:套接字类型不支持请求的通信协议。例如,在尝试将UNIX域数据报套接字连接到流套接字时,可能会发生此错误。
    • ETIMEDOUT:尝试连接时超时。服务器可能太忙,无法接受新连接。请注意,对于IP套接字,当服务器上启用Syncookie时,超时可能非常长。

    accept

    c
    #include<sys/types.h>          /* See NOTES */
    #include<sys/socket.h>
    
    intaccept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    
    #define _GNU_SOURCE             /* See feature_test_macros(7) */
    #include<sys/socket.h>
    
    intaccept4(int sockfd, struct sockaddr *addr,
                socklen_t *addrlen, int flags);
    
    

    描述: 从内核的ACCEPT队列中取出对应被动socket的连接,关于连接的有关属性填入addr中。

    参数解析:

    sockfd:对应的被动socket

    addr:保存连接方的addr属性的容器

    len:addr属性的长度

    返回值: 成功返回可用于连接的新socket,失败返回-1,置errno:

    此外,可能会返回新套接字的网络错误以及为协议定义的网络错误。各种Linux内核可以返回其他错误,例如ENOSR、ESOCKTNOSUPPORT、EPROTONOSUPPORT、ETIMEDOUT。在跟踪期间可以看到值ERESTARTSYS。

    • EMFILE :已达到打开的文件描述符数的每个进程限制

    • ENFILE :已达到系统范围内打开文件总数的限制

    • ENOBUFS, ENOMEM:没有足够的可用内存。这通常意味着内存分配受到套接字缓冲区限制,而不是系统内存的限制

    • ENOTSOCK sockfd不是套接字

    • EOPNOTSUPP 引用的套接字不是SOCK_STREAM类型

    • EPROTO :协议错误

    • EPERM (Linux) :防火墙规则禁止连接

    例程

    我们简单地进行一次TCP对答通信的实现

    server.c

    c
    /*
    #    Copyright By Schips, All Rights Reserved
    #    https://gitee.com/schips/
    #
    #    File Name:  server.c
    #    Created  :  Sat 21 Mar 2020 04:43:39 PM CST
    */
    
    #include<stdio.h>
    #include<unistd.h>
    #include<sys/types.h>          /* See NOTES */
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    
    typedef struct _info {
        char name[10];
        char text[54];
    }info;
    
    intmain(int argc, char *argv[]){
        int my_socket; 
        unsigned int len;
        int ret;
    
        // 创建套接字
        my_socket = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP socket
        if(my_socket == -1) { perror("Socket"); }
        printf("Creat a socket :[%d]
    ", my_socket);
    
        // 用于接收消息
        info buf ={0};
    
        // 指定地址
        structsockaddr_inaddr = {0};
        addr.sin_family = AF_INET;  // 地址协议族
            addr.sin_addr.s_addr = inet_addr("127.0.0.1");   //指定 IP地址
            addr.sin_port = htons(12345); //指定端口号
    
        // 服务器 绑定
        bind(my_socket, (struct sockaddr *)&addr, sizeof(addr));
    
        // my_socket 只用于监听
        ret = listen(my_socket, 10);
        if(-1 == ret) { perror("listen"); }
        printf("Listening
    ");
    
        int new_socket;
        structsockaddr_innew = {0};
        int new_addr_size;
        // accept以后会返回一个新的套接字,用于与客户端通信
        new_socket = accept(my_socket, (struct sockaddr*)&new, &new_addr_size);
        printf("New socket is %d
    ", new_socket);
        perror("accept");
    
        // 接收并打印消息
        //recvfrom(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
        recv(new_socket, &buf, sizeof(buf), 0);
            perror("recvfrom");
    
        printf("%s: %s
    ", buf.name, buf.text);
      
        // 回复消息
        sprintf(buf.name, "Server");
        sprintf(buf.text, "Had recvied your message");
        //sendto(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
        send(new_socket, &buf, sizeof(buf), 0);
        perror("sendto");
    
        // 关闭连接
        //shutdown(my_socket, SHUT_RDWR); perror("shutdown");
        close(new_socket); perror("close");
        return close(my_socket); perror("close");
        printf("%d
    ", errno);
        return errno;
    }
    

    client.c

    c
    /*
    #    Copyright By Schips, All Rights Reserved
    #    https://gitee.com/schips/
    #
    #    File Name:  client.c
    #    Created  :  Sat 21 Mar 2020 04:43:39 PM CST
    */
    
    #include<stdio.h>
    #include<unistd.h>
    #include<sys/types.h>          /* See NOTES */
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    
    typedef struct _info {
        char name[10];
        char text[54];
    }info;
    
    intmain(int argc, char *argv[]){
        int my_socket;
        unsigned int len;
        int ret;
    
        // 创建套接字
        my_socket = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP socket
        if(my_socket == -1) { perror("Socket"); }
        printf("Creat a socket :[%d]
    ", my_socket);
    
        // 用于接收消息
        info buf ={0};
    
        // 指定地址
        structsockaddr_inaddr = {0};
        addr.sin_family = AF_INET;  // 地址协议族
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");   //指定 IP地址
        addr.sin_port = htons(12345); //指定端口号
        
        // 用于连接服务器
        connect(my_socket, (struct sockaddr *)(&addr), sizeof(struct sockaddr_in));
        if(-1 == ret) { perror("connect"); }
        printf("connected
    ");
    
        // 回复消息
        sprintf(buf.name, "Client");
        sprintf(buf.text, "Hello tcp text.");
        //sendto(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
        send(my_socket, &buf, sizeof(buf), 0);
        perror("sendto");
    
        // 接收并打印消息
        //recvfrom(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
        recv(my_socket, &buf, sizeof(buf), 0);
        perror("recvfrom");
    
        printf("%s: %s
    ", buf.name, buf.text);
    
        // 关闭连接
        //shutdown(my_socket, SHUT_RDWR); perror("shutdown");
    
        return close(my_socket); perror("close");
        printf("%d
    ", errno);
        return errno;
    }
    

    知识

    TCP(Transmission Control Protoco 传输控制协议)。

    TCP是一种面向广域网的通信协议,目的是在跨越多个网络通信时,为两个通信端点之间提供一条具有下列特点的通信方式:

    • 基于流的方式;

    • 面向连接;

    • 可靠通信方式;

    • 在网络状况不佳的时候尽量降低系统由于重传带来的带宽开销;

    • 通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点。

    为满足TCP协议的这些特点,TCP协议做了如下的规定:

    • 数据分片:在发送端对用户数据进行分片,在接收端进行重组,由TCP确定分片的大小并控制分片和重组;
    • 到达确认:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认;
    • 超时重发:发送方在发送分片时启动超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片;
    • 滑动窗口:TCP连接每一方的接收缓冲空间大小都固定,接收端只允许另一端发送接收端缓冲区所能接纳的数据,TCP在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出;
    • 失序处理:作为IP数据报来传输的TCP分片到达时可能会失序,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层;
    • 重复处理:作为IP数据报来传输的TCP分片会发生重复,TCP的接收端必须丢弃重复的数据;
    • 数据校验:TCP将保持它首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到分片的检验和有差错,TCP将丢弃这个分片,并不确认收到此报文段导致对端超时并重发。
    ServerClient双方都创建socket对象socketsocket服务器一般绑定端口号bind服务器监听是否有连接请求listen客户端请求链接connectaccept收发消息send/recvsend/recv关闭连接closecloseServerClient

    有关函数介绍

    根据流程图,我们知道,在UDP通信中,使用到了这些函数:socket()bind()sendto()recvfrom()

    上面的函数我们在《基于UDP 的通信》 中已经讲过,这里不再重复了。

    在TCP中,多了这几个函数:listen()connect()accept()

    服务器调用listen 监听 客户端的 connectlisten成功时,服务器使用由accept获取到的新的套接字进行通信。

    当客户端调用connect函数时,将引发三次握手过程:客户端首先发送SYN请求分组,此时服务端会将请求放入SYN队列,同时向客户端发送ACK确认报文,然后客户端向服务端再次发送ACK报文。服务端收到ACK确认报文后,将SYN里的连接请求移入ACCEPT队列。此时三次握手结束,即TCP连接成功建立。然后内核通知用户空间的阻塞的服务进程,服务进程调用accept仅仅是从ACCEPT队列里取出一个连接而已。也就是说客户端调用connect连接服务器,与服务器调用accept“接受”连接是两个独立的过程。

    参考:《服务端不调用accept,客户端connect能否成功?》

    listen

    c
    #include<sys/types.h>          /* See NOTES */
    #include<sys/socket.h>
    
    intlisten(int sockfd, int backlog);
    

    描述: 将尚未建立连接的socket转换为被动socket,并监听发给这个被动socket的connect请求。

    参数解析:

    sockfd:由socket函数成功返回的值

    backlog :内核应该为相应套接口排队的最大连接个数(不是用来限制socket的最大连接数),一般为以下两个队列的大小之和,即未完成三次握手队列 + 已经完成三次握手队列。即:TCP模块允许的已完成三次握手过程(TCP模块完成)但还没来得及被应用程序accept的最大链接数。

    内核为任何一个给定的监听套接口维护两个队列:

    1、未完成连接队列(incomplete connection queue),每个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程。这些套接口处于SYN_RCVD状态。

    2、已完成连接队列(completed connection queue),每个已完成TCP三次握手过程的客户对应其中一项。这些套接口处于ESTABLISHED状态。

    当来自客户的SYN到达时,TCP在 未完成连接队列 中创建一个新项,然后响应以三次握手的第二个分节:服务器的SYN响应,其中稍带对客户SYN的ACK(即SYN+ACK)。这一项一直保留在未完成连接队列中,直到三路握手的第三个分节(客户对服务器SYN的ACK)到达或者该项超时为止(曾经源自Berkeley的实现为这些未完成连接的项设置的超时值为75秒)。如果三路握手正常完成,该项就从未完成连接队列移到已完成连接队列的队尾。当进程调用accept时,已完成连接队列中的队头项将返回给进程,或者如果该队列为空,那么进程将被投入睡眠,直到TCP在该队列中放入一项才唤醒它。

    返回值: 成功返回0,失败返回-1,置errno:

    • EADDRINUSE:另一个套接字已在同一端口上侦听。
    • EADDRINUSE:(Internet域套接字)sockfd引用的套接字以前没有绑定到地址,在尝试将其绑定到临时端口时,确定临时端口范围中的所有端口号当前都在使用中。
    • EBADF:参数sockfd不是有效的描述符。
    • ENOTSOCK:文件描述符sockfd没有引用套接字。
    • EOPNOTSUPP:套接字的类型不支持listen()操作。

    主动socket和被动socket

    一般来说,使用socket函数创建的socket默认是主动socket,这意味着一个主动的socket可以调用connect跟一个被动socket建立一个连接,对主动socket来说,这叫主动打开。

    被动socket是一个通过调用listen函数监听要发起连接的socket,当被动socket接受一个连接通常称为被动打开。

    在大多数网络程序中,服务端会作为被动socket被动接受连接,而客户端会作为主动socket主动发起连接。

    服务端通过socket函数创建的socket是主动socket,而listen函数就是把这个还未接受连接的主动socket转换为被动socket,因为服务端只需要被动接受客户端的连接请求。

    Linux系统设置未连接队列最大数限制

    linux系统tcp/ip协议栈有个选项可以设置未连接队列大小限制tcp_max_syn_backlog

    可以通过命令:cat /proc/sys/net/ipv4/tcp_max_syn_backlog 查看

    Linux 系统中提供somaxconn这个参数,它定义了系统中每一个端口最大的监听队列的长度,这是个全局的参数,默认值为128

    可以通过命令: cat /proc/sys/net/core/somaxconn 查看

    connect

    c
    #include<sys/types.h>          /* See NOTES */
    #include<sys/socket.h>
    
    intconnect(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);
    

    描述: 连接一个被动socket

    参数解析:

    sockfd:主动socket

    addr:目的地址

    addrlen:地址属性的长度(addr的大小)

    返回值:成功返回0,失败返回-1,置errno:

    • EAFNOSUPPORT:传递的地址在其sau family字段中没有正确的地址系列。
    • EAGAIN :路由缓存中的条目不足。
    • EALREADY:套接字未阻塞,上一次连接尝试尚未完成。
      EBADF:文件描述符不是描述符表中的有效索引。
    • ECONNREFUSED:没有人监听远程地址。
    • EFAULT :套接字结构地址在用户的地址空间之外。
    • EINPROGRESS:套接字未阻塞,无法立即完成连接。可以通过选择要写入的套接字来选择(2)或轮询(2)以完成。
    • EINTR :系统调用被捕获的信号中断。
    • EISCONN:套接字已连接。
    • ENETUNREACH:无法访问网络。
    • ENOTSOCK:sockfd不是套接字。
    • EPROTOTYPE:套接字类型不支持请求的通信协议。例如,在尝试将UNIX域数据报套接字连接到流套接字时,可能会发生此错误。
    • ETIMEDOUT:尝试连接时超时。服务器可能太忙,无法接受新连接。请注意,对于IP套接字,当服务器上启用Syncookie时,超时可能非常长。

    accept

    c
    #include<sys/types.h>          /* See NOTES */
    #include<sys/socket.h>
    
    intaccept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    
    #define _GNU_SOURCE             /* See feature_test_macros(7) */
    #include<sys/socket.h>
    
    intaccept4(int sockfd, struct sockaddr *addr,
                socklen_t *addrlen, int flags);
    
    

    描述: 从内核的ACCEPT队列中取出对应被动socket的连接,关于连接的有关属性填入addr中。

    参数解析:

    sockfd:对应的被动socket

    addr:保存连接方的addr属性的容器

    len:addr属性的长度

    返回值: 成功返回可用于连接的新socket,失败返回-1,置errno:

    此外,可能会返回新套接字的网络错误以及为协议定义的网络错误。各种Linux内核可以返回其他错误,例如ENOSR、ESOCKTNOSUPPORT、EPROTONOSUPPORT、ETIMEDOUT。在跟踪期间可以看到值ERESTARTSYS。

    • EMFILE :已达到打开的文件描述符数的每个进程限制

    • ENFILE :已达到系统范围内打开文件总数的限制

    • ENOBUFS, ENOMEM:没有足够的可用内存。这通常意味着内存分配受到套接字缓冲区限制,而不是系统内存的限制

    • ENOTSOCK sockfd不是套接字

    • EOPNOTSUPP 引用的套接字不是SOCK_STREAM类型

    • EPROTO :协议错误

    • EPERM (Linux) :防火墙规则禁止连接

    例程

    我们简单地进行一次TCP对答通信的实现

    server.c

    c
    /*
    #    Copyright By Schips, All Rights Reserved
    #    https://gitee.com/schips/
    #
    #    File Name:  server.c
    #    Created  :  Sat 21 Mar 2020 04:43:39 PM CST
    */
    
    #include<stdio.h>
    #include<unistd.h>
    #include<sys/types.h>          /* See NOTES */
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    
    typedef struct _info {
        char name[10];
        char text[54];
    }info;
    
    intmain(int argc, char *argv[]){
        int my_socket; 
        unsigned int len;
        int ret;
    
        // 创建套接字
        my_socket = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP socket
        if(my_socket == -1) { perror("Socket"); }
        printf("Creat a socket :[%d]
    ", my_socket);
    
        // 用于接收消息
        info buf ={0};
    
        // 指定地址
        structsockaddr_inaddr = {0};
        addr.sin_family = AF_INET;  // 地址协议族
            addr.sin_addr.s_addr = inet_addr("127.0.0.1");   //指定 IP地址
            addr.sin_port = htons(12345); //指定端口号
    
        // 服务器 绑定
        bind(my_socket, (struct sockaddr *)&addr, sizeof(addr));
    
        // my_socket 只用于监听
        ret = listen(my_socket, 10);
        if(-1 == ret) { perror("listen"); }
        printf("Listening
    ");
    
        int new_socket;
        structsockaddr_innew = {0};
        int new_addr_size;
        // accept以后会返回一个新的套接字,用于与客户端通信
        new_socket = accept(my_socket, (struct sockaddr*)&new, &new_addr_size);
        printf("New socket is %d
    ", new_socket);
        perror("accept");
    
        // 接收并打印消息
        //recvfrom(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
        recv(new_socket, &buf, sizeof(buf), 0);
            perror("recvfrom");
    
        printf("%s: %s
    ", buf.name, buf.text);
      
        // 回复消息
        sprintf(buf.name, "Server");
        sprintf(buf.text, "Had recvied your message");
        //sendto(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
        send(new_socket, &buf, sizeof(buf), 0);
        perror("sendto");
    
        // 关闭连接
        //shutdown(my_socket, SHUT_RDWR); perror("shutdown");
        close(new_socket); perror("close");
        return close(my_socket); perror("close");
        printf("%d
    ", errno);
        return errno;
    }
    

    client.c

    c
    /*
    #    Copyright By Schips, All Rights Reserved
    #    https://gitee.com/schips/
    #
    #    File Name:  client.c
    #    Created  :  Sat 21 Mar 2020 04:43:39 PM CST
    */
    
    #include<stdio.h>
    #include<unistd.h>
    #include<sys/types.h>          /* See NOTES */
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    
    typedef struct _info {
        char name[10];
        char text[54];
    }info;
    
    intmain(int argc, char *argv[]){
        int my_socket;
        unsigned int len;
        int ret;
    
        // 创建套接字
        my_socket = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP socket
        if(my_socket == -1) { perror("Socket"); }
        printf("Creat a socket :[%d]
    ", my_socket);
    
        // 用于接收消息
        info buf ={0};
    
        // 指定地址
        structsockaddr_inaddr = {0};
        addr.sin_family = AF_INET;  // 地址协议族
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");   //指定 IP地址
        addr.sin_port = htons(12345); //指定端口号
        
        // 用于连接服务器
        connect(my_socket, (struct sockaddr *)(&addr), sizeof(struct sockaddr_in));
        if(-1 == ret) { perror("connect"); }
        printf("connected
    ");
    
        // 回复消息
        sprintf(buf.name, "Client");
        sprintf(buf.text, "Hello tcp text.");
        //sendto(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
        send(my_socket, &buf, sizeof(buf), 0);
        perror("sendto");
    
        // 接收并打印消息
        //recvfrom(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
        recv(my_socket, &buf, sizeof(buf), 0);
        perror("recvfrom");
    
        printf("%s: %s
    ", buf.name, buf.text);
    
        // 关闭连接
        //shutdown(my_socket, SHUT_RDWR); perror("shutdown");
    
        return close(my_socket); perror("close");
        printf("%d
    ", errno);
        return errno;
    }
    
  • 相关阅读:
    基于NS2的差分服务网络测试(含awk分析代码)
    ubuntu14下NSG2的安装和使用
    (转)QOS入门详解
    gnuplot安装的小问题
    中介者模式(Mediator)_java实现
    命令模式(Command)_java实现
    观察者模式(Observer)_java实现
    策略模式(Strategy)_java实现
    状态模式(State)_java实现
    备忘录模式(Memento)_java实现
  • 原文地址:https://www.cnblogs.com/wt88/p/15100299.html
Copyright © 2020-2023  润新知