zoukankan      html  css  js  c++  java
  • TCP之简单回传(一)

    本文介绍Tcp的简单应用:简单的 回传(即客户端发送什么,服务器就转发给客户端什么)。

    主要包含以下几个函数原型:

    服务器端:

    //服务器端主要函数原型:
    int
    socket(int domain, int type, int protocol); int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen); int listen( int fd, int backlog); SOCKET PASCAL accept( SOCKET s, struct sockaddr * addr,int * addrlen);

    客户端:

    int socket(int domain, int type, int protocol);
    int PASCAL FAR connect( SOCKET s, const struct sockaddr FAR* name, int namelen);
    服务器端和客户端:以上函数错误的情况下都是 -1

    服务器与客户端工作流程如下:

    服务器端流程:              客户端流程:
     socket                    socket
       ↓                          ↓
      bind                     connect
       ↓                          ↓
     listen                   write/send
       ↓                          ↓
     accept                    read/recv
       ↓                          ↓
    read/recv                   close
       ↓
    write/send
       ↓
    close    


    服务器端:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    //错误处理,推荐做法
    #define ERR_EXIT(m) 
        do { 
            perror(m);
            exit(EXIT_FAILURE);
        }while(0)
    
    void do_service(int sockfd);
    
    int main(int argc, const char *argv[])
    {
    //socket 返回一个监听的文件描述符 int listenfd = socket(PF_INET, SOCK_STREAM, 0); if(listenfd == -1) ERR_EXIT("socket");
    //
    地址复用--->记住即可 int on = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) ERR_EXIT("setsockopt"); //bind 实现将服务器的地址IP,端口号PORT绑定 struct sockaddr_in addr; memset(&addr, 0, sizeof addr); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("127.0.0.1");//将IP转化为网络字节序 addr.sin_port = htons(8976);//主机字节序转化为网络字节序 if(bind(listenfd, (struct sockaddr*)&addr, sizeof addr) == -1) ERR_EXIT("bind"); //listen 监听集合(监听客户端是否发送给服务器消息) if(listen(listenfd, SOMAXCONN) == -1) ERR_EXIT("listen"); //accept 接受客户端请求,并返回另外一个文件描述符单独处理客户的请求 int peerfd = accept(listenfd, NULL, NULL); //read&write do_service(peerfd); //close close(peerfd); close(listenfd); return 0; } //read&write void do_service(int sockfd) { char recvbuf[1024] = {0}; while(1) { //read 读取客户端发送来的数据 int nread = read(sockfd, recvbuf, sizeof recvbuf); if(nread == -1)//出错情况以及处理 { if(errno == EINTR) //读取时由中断信号产生的错误 continue; ERR_EXIT("read");//error } else if(nread == 0)//客户端已发送完毕,即客户端的写端关闭 EOF { printf("close ... "); exit(EXIT_SUCCESS); } //回传给客户端 write(sockfd, recvbuf, strlen(recvbuf)); //清空缓冲区,以免发送覆盖不完全情况 memset(recvbuf, 0, sizeof recvbuf); } }


    客户端:

    //client.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #define ERR_EXIT(m) 
        do { 
            perror(m);
            exit(EXIT_FAILURE);
        }while(0)
    
    void do_service(int sockfd);
    
    int main(int argc, const char *argv[])
    {
     //socket
        int peerfd = socket(PF_INET, SOCK_STREAM, 0);
        if(peerfd == -1)
            ERR_EXIT("socket");
    
    //connect请求与服务器连接
        struct sockaddr_in addr;
        memset(&addr, 0, sizeof addr);
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //localhost
        addr.sin_port = htons(8976);
        socklen_t len = sizeof addr;
        if(connect(peerfd, (struct sockaddr*)&addr, len) == -1)
            ERR_EXIT("Connect");
    
    //send&recv
        do_service(peerfd);
    
    
        return 0;
    }
    
    //send&recv
    void do_service(int sockfd)
    {
        char recvbuf[1024] = {0}; //接收缓冲区
        char sendbuf[1024] = {0};//发送缓冲区
        while(1)
        {
            fgets(sendbuf, sizeof sendbuf, stdin);//从键盘中输入进发送缓冲区数据
            write(sockfd, sendbuf, strlen(sendbuf)); //向服务器发送数据
    
            //read
            int nread = read(sockfd, recvbuf, sizeof recvbuf); //读取服务器发来的数据
            if(nread == -1)//err
            {
                if(errno == EINTR)
                    continue;
                ERR_EXIT("read");
            }
            else if(nread == 0)//EOF
            {
                printf("server close!
    ");
                close(sockfd);
                exit(EXIT_SUCCESS);
            }
    
            printf("receive msg : %s", recvbuf); //打印所接收的数据
    
            memset(recvbuf, 0, sizeof recvbuf);
            memset(sendbuf, 0, sizeof sendbuf);
        }
    }

    这样我们就简单实现了tcp通信。

    注意:本程序不能解决 字节流的 粘包问题

    如下程序:服务器与客户端发生改变的代码仅仅是 do_serve 程序;

    服务器端改动的部分如下:

    void do_service(int sockfd)
    {
        int cnt = 0;
        char recvbuf[1024000] = {0}; //从缓冲区一次读取的数据远远大于客户端一次发送的数据(1024)
        while(1)
        {
            int nread = read(sockfd, recvbuf, sizeof recvbuf); //从缓冲区读数据
            if(nread == -1)
            {
                if(errno == EINTR)
                    continue;
                ERR_EXIT("read");
            }
            else if(nread == 0)
            {
                printf("close ...
    ");
                exit(EXIT_SUCCESS);
            }
    
            printf("count = %d, receive size = %d
    ", ++cnt, nread);
            memset(recvbuf, 0, sizeof recvbuf);
        }
    }

    客户端所改动的数据如下:

    void do_service(int sockfd)
    {
        #define SIZE 1024
        char sendbuf[SIZE + 1] = {0};
        int i;
        for(i = 0; i < SIZE; ++i) //缓冲区中的每个字符的值
            sendbuf[i] = 'a';
    
        int cnt = 0; //次数
        while(1)
        {
            int i;
            for(i = 0; i < 10; ++i) //每次发送SIZE个字符,总共发送十次
            {
                write(sockfd, sendbuf, SIZE);
                printf("count = %d, write %d bytes
    ", ++cnt, SIZE);
            }
            nano_sleep(4); //暂停客户端4s钟
    
            memset(sendbuf, 0, sizeof sendbuf);
        }
    }
    
    void nano_sleep(double val)
    {
        struct timespec tv;
        tv.tv_sec = val; //取整
        tv.tv_nsec = (val - tv.tv_sec) * 1000 * 1000 * 1000;
    
        int ret;
        do
        {
            ret = nanosleep(&tv, &tv);
        }while(ret == -1 && errno == EINTR); //只要是由中断信号所产生的错误,就继续执行该循环体,直至睡眠所要求的时间
    }

     该程序会发生粘包问题
    问题根源在于,客户端没有与服务器协商一次发送多少信息,
    若服务器端缓冲区足够大或者很小,很可能会发生接收不足或两次所发送的信息粘在一起。
    解决方案:
    1)每次发送固定数据,服务器接收固定数据;
    2)事先发送一个int32_t数据,告诉服务器,客户端要发送数据的长度;解决方案见:TCP之简单回传(二)
    3)采用特定标示,如遇到 ,则默认为一次接收完成。 解决方案见:TCP之简单回传(三)  和  TCP之简单回传(四)

  • 相关阅读:
    openwrt 汉化
    错误: libstdc++.so.6: cannot open shared object file: No such file or directory
    openwrt uci
    openwrt makefile选项
    Ubuntu服务器断网问题解决
    lldpcli 常用命令
    openwrt ramips随记
    shell脚本学习(二)
    完成响应式的方式
    盒子模型 W3C中和IE中盒子的总宽度分别是什么
  • 原文地址:https://www.cnblogs.com/xfxu/p/4025514.html
Copyright © 2011-2022 走看看