• [linux-socket]UNIX网络编程之TCP连接


    1、常用函数介绍

    int socket(int domain,int type,int protocol);
    /*
    domain:AF_INET设为IPV4
    type:SOCK_STREAM对应TCP,SOCK_DGRAM对应UDP
    protocol:设0
    返回值:返回一个套接字,失败返回-1
    */
    int bind(int sockfd,struct sockaddr *my_addr,int addrlen);
    /*
    sockfd:由socket()调用返回的需要绑定的套接字
    my_addr:sockaddr类型的地址
    addrlen:sizeof(sockaddr)。
    返回值:成功返回0;失败返回-1
    */
    struct sockaddr_in {
    short sin_family; /* 地址类型,TCPIP协议只能填AF_INET */
    unsigned short sin_port; /*使用端口号 */
    struct in_addr sin_addr; /* 网络地址,如需绑定所有地址,填INADDR_ANY */
    unsigned char sin_zero[8]; /* 填0即可 */
    };//一般将其强制转换成sockaddr来使用
    uint16_t htons(uint16_t hostshort);
    /*
    把系统的16位整数调整为“大端模式”
    */
    int inet_aton(const char *string, struct in_addr *addr);
    /*
    把字符串的IP地址转化为in_addr结构体
    */
    int connect(int sockfd,struct sockaddr* serv_addr,int addrlen);
    /*
    sockfd:连接到的套接字
    serv_addr:连接到的服务器地址
    addrlen:sizeof(serv_addr)
    返回值:失败返回-1
    */
    int listen(int sockfd,int backlog);//设置服务器监听模式
    /*
    sockfd:需要设置监听的服务器套接字
    backlog:进入队列中允许的连接的个数。
    返回值:出错返回-1
    */
    int accept(int sockfd,void *addr,int* addrlen);//接受已经connect并在款冲队列中等待的套接字,队列为空时默认进入阻塞状态,直到有客户端进行connect()
    /*
    sockfd:正在监听端口的套接字
    addr:用于存储客户的地址结构体
    addrlen:sizeof(struct sockaddr_in)
    返回值:失败-1
    */
    int send(int sockfd,const void* msg,int len,int flags);//TCP发送数据
    /*
    sockfd:发送目标的套接字
    msg:需要发送数据的头指针
    len:数据的字节长度
    flags:设为0
    返回值:返回实际发送的字节数。注意,返回值可能比需要发送的字节数要少,此时需要再次发送剩下的字节。如失败返回-1
    */
    int recv(int sockfd,void* buf,int len,unsigned int flags);//接受TCP数据
    /*
    sockfd:是要读取的套接口字
    buf:保存数据的内存入口。
    len:缓冲区的最大长度。注意,缓冲区不需用完
    flags:设为0
    返回值:返回实际读取到缓冲区的字节数,如果出错则返回-1。
    */

    2、服务器端流程

    设置服务器地址

     //设置一个socket地址结构server_addr,代表服务器internet地址, 端口
        struct sockaddr_in server_addr;
        bzero(&server_addr,sizeof(server_addr)); //把一段内存区的内容全部设置为0
        server_addr.sin_family = AF_INET;//
        server_addr.sin_addr.s_addr = htons(INADDR_ANY);//INADDR_ANY是全0特殊地址,用于含有多IP地址的服务器,表示同时绑定自己的所有地址
        server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);//小端数转大端数函数

    创建服务器套接字

    //创建用于internet的流协议(TCP)socket,用server_socket代表服务器socket
        int server_socket = socket(AF_INET,SOCK_STREAM,0);
        if( server_socket < 0){
            printf("Create Socket Failed!");
            exit(1);
        }

    绑定地址与套接字

    //把socket和socket地址结构联系起来
        if( bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr))){
            printf("Server Bind Port : %d Failed!", HELLO_WORLD_SERVER_PORT); 
            exit(1);
        }

    设置服务器为监听状态

    //server_socket用于监听
        if ( listen(server_socket, LENGTH_OF_LISTEN_QUEUE) ){
            printf("Server Listen Failed!"); 
            exit(1);
        }

    接受客户端的连接

    int new_server_socket = accept(server_socket,(struct sockaddr*)&client_addr,&length);
            if ( new_server_socket < 0) {
                printf("Server Accept Failed!
    ");
                break;
            }

    从客户端接收数据

    //接收客户端发送来的信息到buffer中
            length = recv(new_server_socket,buffer,BUFFER_SIZE,0);//如果对方在一次连接里发送了两次呢?
            if (length < 0){
                printf("Server Recieve Data Failed!
    ");
                exit(1);
            }
            printf("
    %s",buffer);

    发送数据到客户端

    char buffer[BUFFER_SIZE];
            bzero(buffer, BUFFER_SIZE);
            strcpy(buffer,"Hello,World! 从服务器来!");
            strcat(buffer,"
    "); //C语言字符串连接
            //发送buffer中的字符串到new_server_socket,实际是给客户端
            send(new_server_socket,buffer,BUFFER_SIZE,0);

    关闭连接,空出端口

        //关闭与客户端的连接
        close(new_server_socket);
        //关闭监听用的socket
        close(server_socket);

    3、客户端流程

    设置客户端地址

    //设置一个socket地址结构client_addr,代表客户机internet地址, 端口
        struct sockaddr_in client_addr;
        bzero(&client_addr,sizeof(client_addr)); //把一段内存区的内容全部设置为0
        client_addr.sin_family = AF_INET;    //internet协议族
        client_addr.sin_addr.s_addr = htons(INADDR_ANY);//INADDR_ANY表示自动获取本机地址
        client_addr.sin_port = htons(0);    //0表示让系统自动分配一个空闲端口

    建立客户端的套接字

    //创建用于internet的流协议(TCP)socket,用client_socket代表客户机socket
        int client_socket = socket(AF_INET,SOCK_STREAM,0);
        if( client_socket < 0) {
            printf("Create Socket Failed!
    ");
            exit(1);
        }

    绑定客户端地址与套接字

    //把客户机的socket和客户机的socket地址结构联系起来
        if( bind(client_socket,(struct sockaddr*)&client_addr,sizeof(client_addr))){
            printf("Client Bind Port Failed!
    "); 
            exit(1);
        }

    设置服务器地址结构体

    //设置一个socket地址结构server_addr,代表服务器的internet地址, 端口
        struct sockaddr_in server_addr;
        bzero(&server_addr,sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        if(inet_aton(argv[1],&server_addr.sin_addr) == 0){ //服务器的IP地址来自程序的参数,aton字符串IP地址转化为网络地址格式
            printf("Server IP Address Error!
    ");
            exit(1);
        }
        server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);

    建立于服务器的连接

    socklen_t server_addr_length = sizeof(server_addr);
        //向服务器发起连接,连接成功后client_socket代表了客户机和服务器的一个socket连接
        if(connect(client_socket,(struct sockaddr*)&server_addr, server_addr_length) < 0)
        {
            printf("Can Not Connect To %s!
    ",argv[1]);
            exit(1);
        }

    接收、发送数据

    关闭连接

     完整代码:https://github.com/iyjhabc/study_examples/blob/master/server.c

    编译c程序 gcc client.c -o client

    运行客户端 ./client 127.0.0.1

    127.0.0.1为保留IP地址,固定指向本地主机

    ----------------------------------------------------------------------------------------------------

    虽然知道各个函数怎么用,但这几个函数都是要组合起来使用,组合得不对使用就会出错,下面介绍一下如何才是正确组合TCP函数。

      两个计算机通过网络连接,说白了通过两样东西来定位,IP和端口。套接字其实就是一个已经连接到某IP和某端口的一条抽象通道。知道这两个概念,下面就容易理解了。

    1、服务器

      作为服务器,一般来说不会首先连接别人,而是等别人主动连接它,他被动等待别人的连接。并且作为公共使用的服务器,必须有固定的端口,否则别人怎么知道怎么找到你?

      (1)bind().服务器的第一步是把新建的服务器socket套接字bind一个端口。此时此套接字已经跟服务器的IP和端口紧紧联系在一起了(但还没连接)。

      (2)listen()与accept().此二函数是一起使用的,首先把服务器的套接字设为监听状态,然后在循环里面调用accept。它被调用后会阻塞自己所在的线程,直到有客户端connect为止。

      (3)recv().当有客户端连接服务器,accept的阻塞被释放,并返回一个已经连向该客户端的套接字。此时服务器就可以通过此套接字,使用recv函数接受来自客户端的数据,并使用send给客户端发送数据。

      (4)close().accept返回的套接字使用完成后,必须使用close把它关闭。

      总结来说,服务器只需自己绑定一个端口,等待客户端的连接。它完全不用管客户端的ip和端口,因为accept返回的套接字已经包含了连接向客户端IP和端口的连接通路。

    2、客户端

      作为客户端,一般是主动连接服务器,等待服务器的回应。客户端必须知道想要连接的服务器的IP和端口。

      (1)connect().首先客户端新建一个套接字,并根据服务器的IP和端口把套接字connect到服务器,形成连接通路。

      (2)send().连接成功后,就可以利用该套接字向服务器发送数据。

      (3)recvfrom().因为刚才的套接字已经与服务器形成连接,因此也可以用来接收服务器返回的数据。

      这里再说明一下,connect形成与服务器连接这个套接字,其实就是服务器端accept返回那个套接字,正因为如此,客户端才能用这个套接字recv服务器返回的消息。至于为什么用recvfrom而不是recv,接下来说。

      服务器与客户端握手对话(客发-服收-服发-客收)代码可参考以下:

    客户端:https://github.com/iyjhabc/study_examples/blob/master/tcp_data_transport_client.c

    服务器:https://github.com/iyjhabc/study_examples/blob/master/tcp_data_transport.c

    3、recv与recvfrom

      众所周知recv主要用在TCP中,recvfrom主要用在UDP中,但如客户端需要等待服务器返回的消息,再作下一步运算的话,就应使用recvfrom,如上面的例子,客户端等待服务器放置数据完成,再重新发送下一轮数据。原因是,recv默认是非阻塞的,当客户端发送完后调用recv,服务器还没发送数据过来recv就已经执行完毕,因此接受不到服务器数据。recvfrom默认是阻塞的,它会等待服务器返回的数据再往下运行。

      并且recv只能用于TCP,而recvfrom同时用于TCP和UDP。recvfrom参数的地址指针将会记录接到的数据的来源地址。再有一点,recv和recvfrom参数里面的套接字,必须是已经建立连接的,没连接,怎么知道接受谁呢?要监听任意的客户端,应先使用listen和accept。

    4、send

      一般来说,使用一次send,便connect一次。如连续使用send,后面send的数据会丢失。为何?其实联系服务器的原理便知道。客户端connect后,服务器accept并返回一个连接双方的套接字。此时双方用此套接字发送接收数据。但一般来说服务器recv一次后便会close该套接字。如果此时客户端继续试用send,自然就发送不成功了。

      总结就是,必须在套接字连同的条件下才能使用send(也是recv也是),很多情况是你自己没有断开连接,但对方其实已经close了该套接字了,便造成了发送失败。所以,连接一次发送一次数据send一次,是比较安全的做法。

  • 相关阅读:
    RandomAccessFile类
    IO_ObjectOutputStream(对象的序列化)
    IO流_文件切割与合并(带配置信息)
    IO流_文件切割与合并
    IO流_SequenceInputStream(序列流)
    10-stack
    9-queue
    16-两种方法全排列
    8-全排列next_permutation
    15-Call to your teacher(有向图的连通判断)
  • 原文地址:https://www.cnblogs.com/iyjhabc/p/3155141.html
Copyright © 2020-2023  润新知