zoukankan      html  css  js  c++  java
  • tcp 双方通信

    思路

    主线程负责发送消息,另一线程负责接收消息。服务端和客户端均是如此。

    注意

    当A方close掉用于通信的socket端口后,该端口是不会立即关闭的。因为此时可能B方的信息还没send完。因此,此时A方的recv仍旧处于阻塞状态,会最后再等待收一次信息。此时,当B方send一个信息给A后,A方recv到后,A的socket端口就正式关闭了,A的recv返回-1。

    此时由于A的socket端口已关闭,因此B得recv返回0。

    注意区分,如果是这样的代码:如果A方close掉socket端口后,A方程序中并没有处于阻塞状态的recv,那么这个socket端口一close就是真正关闭了。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <netdb.h>
    #define ACTIVE 1 
    #define PASSIVE 0
    #define SERVER_PORT 1314
    #define CLIENT_PORT 1350
    /* 接收消息通过线程实现 */
    void* chat_handler(void* arg)
    {    
        int fd_client = (int)arg;
        char buf[1024];
        int test;
        while(memset(buf, 0, 1024), (test=recv(fd_client, buf, 1024, 0)) > 0)
        {
            write(1, buf, strlen(buf));
        }
        //用于测试recv返回值
        printf("break child while!
    ");
        printf("test = %d 
    ",test);
        return (void*)0;
        //close(fd_client);
    }
    
    int main(int argc,char* argv[])// exe peer_ip
    {
        int flag;
        int fd_peer;        /* 对方socket描述符,由acccept返回 */
        if(argc == 1)
        {
            flag = PASSIVE ;
        }else if(argc == 2)
        {
            flag = ACTIVE ;
        }
        
        /*---------------------------------------socket----------------------------------------*/
        int sfd ; /* sfd为本地socket描述符 */
        sfd = socket(AF_INET, SOCK_STREAM, 0) ;
        if(sfd == -1)
        {
            perror("socket");
            exit(1);
        }
        
        /*------------ ---------------------------bind -----------------------------------------*/
        char my_hostname[1024]="";
        struct hostent * p ;
        gethostname(my_hostname, 1024); /* 获取本地hostname,以便根据hostname查出ip */
        p = gethostbyname(my_hostname) ;/* 根据hostname,获取指向struct hostent结构体的指针 */ 
        
        struct sockaddr_in local_addr ; /* 绑定socket端口以及IP的结构体 */
        memset(&local_addr, 0, sizeof(local_addr));
        local_addr.sin_family = AF_INET ;
        if(flag == ACTIVE)
        {
            local_addr.sin_port = htons(CLIENT_PORT); /* 作为主动方,需要绑定的端口是client_port */
        }else 
        {
            local_addr.sin_port = htons(SERVER_PORT); /* 作为服务器,需要绑定的端口server_port */ /*两者均为本机端口*/
        }
        local_addr.sin_addr = *(struct in_addr *)(p ->h_addr_list)[0]; /* 绑定IP地址 */ /* 本地IP */
        if(-1 == bind(sfd, (struct sockaddr *)&local_addr, sizeof(local_addr)))
        {
            perror("bind");
            close(sfd);
            exit(1);
        }
        
        /*----------------------------------如果是服务器,listen+accept---------------------------------------------- */
        if(flag == PASSIVE)
        {
            if(-1 == listen(sfd, 10))
            {
                perror("listen");
                close(sfd);
                exit(1);
            }    
            struct sockaddr_in peer_addr ; /* 存放返回socket描述符方的联系方式的结构体,是传出参数 */
            int len ; 
            memset(&peer_addr, 0, sizeof(peer_addr));
            len = sizeof(peer_addr);
            //accept返回对方socket描述符,并且peer_addr与len均为传出参数
            fd_peer = accept(sfd,(struct sockaddr*)&peer_addr,&len);
            printf("%s:%d begin to talk!
    ",inet_ntoa(peer_addr.sin_addr),ntohs(peer_addr.sin_port));
            close(sfd);//kris
        }else if(flag == ACTIVE) /*-------------如果是主动方,connect -------------------------------------------------*/
        {
            fd_peer = sfd ; //如果是主动方实际上本方就是从sfd通信的。
            /* 主动方要去连对方,需要知道对方IP,通过命令行参数传;也需要知道服务器端口号,程序已经写死 */ 
            struct sockaddr_in server_addr ;
            memset(&server_addr, 0, sizeof(server_addr));
            server_addr.sin_family = AF_INET ;
            server_addr.sin_port = htons(SERVER_PORT);/*加了htons,如果右边是12345678,传到左边还是12345678;如果不加htons如果右边是12345678,传到左边就变成78563412了 */
            server_addr.sin_addr.s_addr = inet_addr(argv[1]);
            /* connect连不上对方就返回-1,睡一秒后继续连 */
            while( connect(sfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1)
            {
                sleep(1);
                printf("connecting....
    ");
            }
            printf("success ! 
    ");
        }
            
            pthread_t thd ;
            pthread_create(&thd, NULL,chat_handler,(void*)fd_peer);
            char msg[1024];
            while(memset(msg, 0, 1024), fgets(msg, 1024, stdin) != NULL)
            {
                send(fd_peer, msg, strlen(msg), 0);
            }
            close(fd_peer);
            //用于测试
            printf("close close close 
    ");
           pthread_join(thd, NULL);    
    }
    
    /* 本代码通信是在fd_peer这个socket端口上 */

    1. 如果A按ctrl+D(主线程退出while循环,close端口,阻塞在pthread_join上),那么B此时再发送一个消息,在线程中A recv 到并将其打印,此时A的socket端口真正关闭,recv返回-1,退出循环,退出线程,A结束。B由于A的socket端口关闭,线程中的recv返回0,退出循环,退出线程。此时B如果按下ctrl+D,也会退出程序。

    2. 一方按下ctrl+D后,另一方马上也按下ctrl+D。则双方陷入僵局。如果一方按下ctrl+c,强行退出程序后,另一方也会跟着退。原因:一方强退,导致socket端口真正关闭,另一方子线程中的recv返回0,退出循环,退出线程。

    3. 一方上来就ctrl+c强退。则对方的子线程会退掉,原因同上。此时另一方按ctrl+D就退出程序了。

    小结

    recv返回值分以下3种情况

    1. 大于0。接收到的数据大小。

    2. 等于0。对方的socket端口真正关闭。

    3. 小于0。出错。其中一种情况是,本方的socket端口已经真正关闭后,还试图从该端口中获取数据。

    recv返回值实际上和read是类似的。

    发现

    send一个空的消息,如下:

    send(fd_peer,"",0,0);

    对方的recv不会返回0,仍旧阻塞着。

    这点和UDP有所不同,在UDP中,一方sendto一个空消息给另一方,对方的recvfrom是返回0的。

  • 相关阅读:
    mysql各个版本下载地址
    hadoop-0.20.2完全分布式集群
    04_Spring中使用Quartz
    03_CronTrigger
    02_SimpleTrigger
    02_ActiveMQ入门
    01_JMS概述
    01_Quartz基础结构
    Java开发高性能网站需要关注的事
    06_Jedis完成MySQL的条件查询案例
  • 原文地址:https://www.cnblogs.com/hxjbc/p/3963995.html
Copyright © 2011-2022 走看看